From 2f512aaa6c39390a5a0eb1d1e37f070127d068a4 Mon Sep 17 00:00:00 2001 From: Michael Stahl Date: Tue, 19 Dec 2023 19:13:00 +0100 Subject: tdf#105844 offapi,package,sfx2: use Argon2 for wholesome ODF encryption https://www.rfc-editor.org/rfc/rfc9106.html * add css::xml::crypto::KDFID constant group * add "KeyDerivationFunction" to setEncryptionAlgorithms sequence * Argon2 is used by default for wholesome ODF encryption, but $LO_ARGON2_DISABLE can be set to use PBKDF2 * extend various structs in package * use 3 new ODF attributes "loext:argon2-iterations" "loext:argon2-memory" "loext:argon2-lanes" to store the arguments * use this URL for now: "urn:org:documentfoundation:names:experimental:office:manifest:argon2id" * use default arguments according to second recommendation from "7.4. Recommendations" of RFC9106; 64 MiB RAM should hopefully not be too much even for 32 bit builds Change-Id: I683118cc5e0706bd6544db6fb909096768ac9920 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161009 Tested-by: Jenkins Reviewed-by: Michael Stahl --- package/source/zipapi/XUnbufferedStream.cxx | 2 +- package/source/zipapi/ZipFile.cxx | 94 +++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 6 deletions(-) (limited to 'package/source/zipapi') diff --git a/package/source/zipapi/XUnbufferedStream.cxx b/package/source/zipapi/XUnbufferedStream.cxx index 350f142c25ba..8b628b14ddfe 100644 --- a/package/source/zipapi/XUnbufferedStream.cxx +++ b/package/source/zipapi/XUnbufferedStream.cxx @@ -85,7 +85,7 @@ XUnbufferedStream::XUnbufferedStream( throw ZipIOException("Integer-overflow"); bool bHaveEncryptData = rData.is() && rData->m_aInitVector.hasElements() && - ((rData->m_aSalt.hasElements() && rData->m_nIterationCount != 0) + ((rData->m_aSalt.hasElements() && (rData->m_oPBKDFIterationCount || rData->m_oArgon2Args)) || rData->m_aKey.hasElements()); bool bMustDecrypt = nStreamMode == UNBUFF_STREAM_DATA && bHaveEncryptData && bIsEncrypted; diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx index bdcd8610be60..6137e3a0bb0a 100644 --- a/package/source/zipapi/ZipFile.cxx +++ b/package/source/zipapi/ZipFile.cxx @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,8 @@ #include #include +#include + #include "blowfishcontext.hxx" #include "sha1context.hxx" #include @@ -173,20 +176,52 @@ uno::Reference< xml::crypto::XCipherContext > ZipFile::StaticGetCipher( const un } uno::Sequence< sal_Int8 > aDerivedKey( xEncryptionData->m_nDerivedKeySize ); - if ( !xEncryptionData->m_nIterationCount && - xEncryptionData->m_nDerivedKeySize == xEncryptionData->m_aKey.getLength() ) + if (!xEncryptionData->m_oPBKDFIterationCount && !xEncryptionData->m_oArgon2Args + && xEncryptionData->m_nDerivedKeySize == xEncryptionData->m_aKey.getLength()) { // gpg4libre: no need to derive key, m_aKey is already // usable as symmetric session key aDerivedKey = xEncryptionData->m_aKey; } + else if (xEncryptionData->m_oArgon2Args) + { + // apparently multiple lanes cannot be processed in parallel (the + // implementation will clamp), but it doesn't make sense to have more + // threads than CPUs + uint32_t const threads(::comphelper::ThreadPool::getPreferredConcurrency()); + // need to use context to set a fixed version + argon2_context context = { + .out = reinterpret_cast(aDerivedKey.getArray()), + .outlen = ::sal::static_int_cast(aDerivedKey.getLength()), + .pwd = reinterpret_cast(xEncryptionData->m_aKey.getArray()), + .pwdlen = ::sal::static_int_cast(xEncryptionData->m_aKey.getLength()), + .salt = reinterpret_cast(xEncryptionData->m_aSalt.getArray()), + .saltlen = ::sal::static_int_cast(xEncryptionData->m_aSalt.getLength()), + .secret = nullptr, .secretlen = 0, + .ad = nullptr, .adlen = 0, + .t_cost = ::sal::static_int_cast(::std::get<0>(*xEncryptionData->m_oArgon2Args)), + .m_cost = ::sal::static_int_cast(::std::get<1>(*xEncryptionData->m_oArgon2Args)), + .lanes = ::sal::static_int_cast(::std::get<2>(*xEncryptionData->m_oArgon2Args)), + .threads = threads, + .version = ARGON2_VERSION_13, + .allocate_cbk = nullptr, .free_cbk = nullptr, + .flags = ARGON2_DEFAULT_FLAGS + }; + // libargon2 validates all the arguments so don't need to do it here + int const rc = argon2id_ctx(&context); + if (rc != ARGON2_OK) + { + SAL_WARN("package", "argon2id_ctx failed to derive key: " << argon2_error_message(rc)); + throw ZipIOException("argon2id_ctx failed to derive key"); + } + } else if ( rtl_Digest_E_None != rtl_digest_PBKDF2( reinterpret_cast< sal_uInt8* >( aDerivedKey.getArray() ), aDerivedKey.getLength(), reinterpret_cast< const sal_uInt8 * > (xEncryptionData->m_aKey.getConstArray() ), xEncryptionData->m_aKey.getLength(), reinterpret_cast< const sal_uInt8 * > ( xEncryptionData->m_aSalt.getConstArray() ), xEncryptionData->m_aSalt.getLength(), - xEncryptionData->m_nIterationCount ) ) + *xEncryptionData->m_oPBKDFIterationCount) ) { throw ZipIOException("Can not create derived key!" ); } @@ -236,12 +271,30 @@ void ZipFile::StaticFillHeader( const ::rtl::Reference< EncryptionData >& rData, *(pHeader++) = ( n_ConstCurrentVersion >> 8 ) & 0xFF; // Then the iteration Count - sal_Int32 nIterationCount = rData->m_nIterationCount; + sal_Int32 const nIterationCount = rData->m_oPBKDFIterationCount ? *rData->m_oPBKDFIterationCount : 0; *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 0 ) & 0xFF); *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 8 ) & 0xFF); *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 16 ) & 0xFF); *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 24 ) & 0xFF); + sal_Int32 const nArgon2t = rData->m_oArgon2Args ? ::std::get<0>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast((nArgon2t >> 0) & 0xFF); + *(pHeader++) = static_cast((nArgon2t >> 8) & 0xFF); + *(pHeader++) = static_cast((nArgon2t >> 16) & 0xFF); + *(pHeader++) = static_cast((nArgon2t >> 24) & 0xFF); + + sal_Int32 const nArgon2m = rData->m_oArgon2Args ? ::std::get<1>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast((nArgon2m >> 0) & 0xFF); + *(pHeader++) = static_cast((nArgon2m >> 8) & 0xFF); + *(pHeader++) = static_cast((nArgon2m >> 16) & 0xFF); + *(pHeader++) = static_cast((nArgon2m >> 24) & 0xFF); + + sal_Int32 const nArgon2p = rData->m_oArgon2Args ? ::std::get<2>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast((nArgon2p >> 0) & 0xFF); + *(pHeader++) = static_cast((nArgon2p >> 8) & 0xFF); + *(pHeader++) = static_cast((nArgon2p >> 16) & 0xFF); + *(pHeader++) = static_cast((nArgon2p >> 24) & 0xFF); + // FIXME64: need to handle larger sizes // Then the size: *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 0 ) & 0xFF); @@ -334,7 +387,38 @@ bool ZipFile::StaticFillData ( ::rtl::Reference< BaseEncryptionData > const & r nCount |= ( pBuffer[nPos++] & 0xFF ) << 8; nCount |= ( pBuffer[nPos++] & 0xFF ) << 16; nCount |= ( pBuffer[nPos++] & 0xFF ) << 24; - rData->m_nIterationCount = nCount; + if (nCount != 0) + { + rData->m_oPBKDFIterationCount.emplace(nCount); + } + else + { + rData->m_oPBKDFIterationCount.reset(); + } + + sal_Int32 nArgon2t = pBuffer[nPos++] & 0xFF; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2m = pBuffer[nPos++] & 0xFF; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2p = pBuffer[nPos++] & 0xFF; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 24; + + if (nArgon2t != 0 && nArgon2m != 0 && nArgon2p != 0) + { + rData->m_oArgon2Args.emplace(nArgon2t, nArgon2m, nArgon2p); + } + else + { + rData->m_oArgon2Args.reset(); + } rSize = pBuffer[nPos++] & 0xFF; rSize |= ( pBuffer[nPos++] & 0xFF ) << 8; -- cgit