From 6db3aeb6e698b07d2fb4985a0c529358b7323f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Wed, 4 Jul 2018 16:25:37 +0200 Subject: [PATCH] CryptoTools: add HMAC, move crypto impl. details to CryptoImpl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I8edb24ee5d9595ef54bd49526b631baf8a7415b1 Reviewed-on: https://gerrit.libreoffice.org/56970 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl --- include/oox/crypto/CryptTools.hxx | 90 ++++--- oox/CppunitTest_oox_crypto.mk | 15 ++ oox/qa/unit/CryptoTest.cxx | 65 +++++ oox/source/crypto/CryptTools.cxx | 426 +++++++++++++++++++++--------- 4 files changed, 425 insertions(+), 171 deletions(-) diff --git a/include/oox/crypto/CryptTools.hxx b/include/oox/crypto/CryptTools.hxx index d5bc5b95bda3..90c60b2a6313 100644 --- a/include/oox/crypto/CryptTools.hxx +++ b/include/oox/crypto/CryptTools.hxx @@ -21,26 +21,41 @@ #define INCLUDED_OOX_CRYPTO_CRYPTTOOLS_HXX #include - -#if USE_TLS_OPENSSL -#include -#include -#endif // USE_TLS_OPENSSL - -#if USE_TLS_NSS -#include -#include -#include -#endif // USE_TLS_NSS +#include +#include #include - -#include +#include namespace oox { namespace core { -class Crypto +/** Rounds up the input to the nearest multiple + * + * For example: + * input 1, multiple 16 = 16 + * input 16, multiple 16 = 16 + * input 17, multiple 16 = 32 + * input 31, multiple 16 = 32 + */ +template +T roundUp(T input, T multiple) +{ + if (input % multiple == 0) + return input; + return ((input / multiple) * multiple) + multiple; +} + +enum class CryptoHashType +{ + SHA1, + SHA256, + SHA512 +}; + +struct CryptoImpl; + +class OOX_DLLPUBLIC Crypto { public: enum CryptoType @@ -52,47 +67,24 @@ public: }; protected: -#if USE_TLS_OPENSSL - EVP_CIPHER_CTX mContext; -#endif -#if USE_TLS_NSS - PK11Context* mContext; - SECItem* mSecParam; - PK11SymKey* mSymKey; -#endif - -#if USE_TLS_OPENSSL - const EVP_CIPHER* getCipher(CryptoType type); -#endif -#if USE_TLS_NSS - void setupContext( - std::vector& key, - std::vector& iv, - CryptoType type, - CK_ATTRIBUTE_TYPE operation); -#endif + std::unique_ptr mpImpl; protected: Crypto(); public: virtual ~Crypto(); - - virtual sal_uInt32 update( - std::vector& output, - std::vector& input, - sal_uInt32 inputLength = 0) = 0; }; -class Decrypt : public Crypto +class OOX_DLLPUBLIC Decrypt : public Crypto { public: Decrypt(std::vector& key, std::vector& iv, CryptoType type); - virtual sal_uInt32 update( + sal_uInt32 update( std::vector& output, std::vector& input, - sal_uInt32 inputLength = 0) override; + sal_uInt32 inputLength = 0); static sal_uInt32 aes128ecb( @@ -102,17 +94,27 @@ public: }; -class Encrypt : public Crypto +class OOX_DLLPUBLIC Encrypt : public Crypto { public: Encrypt(std::vector& key, std::vector& iv, CryptoType type); - virtual sal_uInt32 update( + sal_uInt32 update( std::vector& output, std::vector& input, - sal_uInt32 inputLength = 0) override; + sal_uInt32 inputLength = 0); }; +class OOX_DLLPUBLIC CryptoHash : public Crypto +{ + sal_Int32 mnHashSize; +public: + CryptoHash(std::vector& rKey, CryptoHashType eType); + bool update(std::vector& rInput, sal_uInt32 nInputLength = 0); + std::vector finalize(); +}; + + } // namespace core } // namespace oox diff --git a/oox/CppunitTest_oox_crypto.mk b/oox/CppunitTest_oox_crypto.mk index 64fd4df490b5..f4a641283152 100644 --- a/oox/CppunitTest_oox_crypto.mk +++ b/oox/CppunitTest_oox_crypto.mk @@ -16,6 +16,20 @@ $(eval $(call gb_CppunitTest_add_exception_objects,oox_crypto,\ $(eval $(call gb_CppunitTest_use_sdk_api,oox_crypto)) +ifeq ($(TLS),OPENSSL) +$(eval $(call gb_CppunitTest_externals,oox_crypto,\ + openssl \ + openssl_headers \ +)) +else +ifeq ($(TLS),NSS) +$(eval $(call gb_CppunitTest_use_externals,oox_crypto,\ + plc4 \ + nss3 \ +)) +endif +endif + $(eval $(call gb_CppunitTest_use_libraries,oox_crypto,\ basegfx \ comphelper \ @@ -68,6 +82,7 @@ $(eval $(call gb_CppunitTest_use_components,oox_crypto,\ unotools/util/utl \ uui/util/uui \ vcl/vcl.common \ + sax/source/expatwrap/expwrap \ )) diff --git a/oox/qa/unit/CryptoTest.cxx b/oox/qa/unit/CryptoTest.cxx index efe3e0cb5d6e..c35fa2f7d9a0 100644 --- a/oox/qa/unit/CryptoTest.cxx +++ b/oox/qa/unit/CryptoTest.cxx @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -24,13 +25,77 @@ using namespace css; class CryptoTest : public CppUnit::TestFixture { public: + void testCryptoHash(); + void testRoundUp(); void testStandard2007(); CPPUNIT_TEST_SUITE(CryptoTest); + CPPUNIT_TEST(testCryptoHash); + CPPUNIT_TEST(testRoundUp); CPPUNIT_TEST(testStandard2007); CPPUNIT_TEST_SUITE_END(); }; +namespace +{ +std::string toString(std::vector const& aInput) +{ + std::stringstream aStream; + for (auto const& aValue : aInput) + { + aStream << std::setw(2) << std::setfill('0') << std::hex << static_cast(aValue); + } + + return aStream.str(); +} +} + +void CryptoTest::testCryptoHash() +{ + // Check examples from Wikipedia (https://en.wikipedia.org/wiki/HMAC) + OString aContentString("The quick brown fox jumps over the lazy dog"); + std::vector aContent(aContentString.getStr(), + aContentString.getStr() + aContentString.getLength()); + std::vector aKey = { 'k', 'e', 'y' }; + { + oox::core::CryptoHash aCryptoHash(aKey, oox::core::CryptoHashType::SHA1); + aCryptoHash.update(aContent); + std::vector aHash = aCryptoHash.finalize(); + CPPUNIT_ASSERT_EQUAL(std::string("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"), + toString(aHash)); + } + + { + oox::core::CryptoHash aCryptoHash(aKey, oox::core::CryptoHashType::SHA256); + aCryptoHash.update(aContent); + std::vector aHash = aCryptoHash.finalize(); + CPPUNIT_ASSERT_EQUAL( + std::string("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"), + toString(aHash)); + } + + { + oox::core::CryptoHash aCryptoHash(aKey, oox::core::CryptoHashType::SHA512); + aCryptoHash.update(aContent); + std::vector aHash = aCryptoHash.finalize(); + CPPUNIT_ASSERT_EQUAL( + std::string("b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb82f948a549" + "f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a"), + toString(aHash)); + } +} + +void CryptoTest::testRoundUp() +{ + CPPUNIT_ASSERT_EQUAL(16, oox::core::roundUp(16, 16)); + CPPUNIT_ASSERT_EQUAL(32, oox::core::roundUp(32, 16)); + CPPUNIT_ASSERT_EQUAL(64, oox::core::roundUp(64, 16)); + + CPPUNIT_ASSERT_EQUAL(16, oox::core::roundUp(01, 16)); + CPPUNIT_ASSERT_EQUAL(32, oox::core::roundUp(17, 16)); + CPPUNIT_ASSERT_EQUAL(32, oox::core::roundUp(31, 16)); +} + void CryptoTest::testStandard2007() { oox::core::Standard2007Engine aEngine; diff --git a/oox/source/crypto/CryptTools.cxx b/oox/source/crypto/CryptTools.cxx index e0b39f67cca4..1f280b1c1196 100644 --- a/oox/source/crypto/CryptTools.cxx +++ b/oox/source/crypto/CryptTools.cxx @@ -12,106 +12,244 @@ #include #include +#include + +#if USE_TLS_OPENSSL +#include +#include +#include +#endif // USE_TLS_OPENSSL + +#if USE_TLS_NSS +#include +#include +#include +#endif // USE_TLS_NSS + namespace oox { namespace core { -Crypto::Crypto() -#if USE_TLS_NSS - : mContext(nullptr) - , mSecParam(nullptr) - , mSymKey(nullptr) -#endif +#if USE_TLS_OPENSSL +struct CryptoImpl +{ + std::unique_ptr mpContext; + std::unique_ptr mpHmacContext; + + CryptoImpl() = default; + + void setupEncryptContext(std::vector& key, std::vector& iv, Crypto::CryptoType eType) + { + mpContext.reset(new EVP_CIPHER_CTX); + EVP_CIPHER_CTX_init(mpContext.get()); + + const EVP_CIPHER* cipher = getCipher(eType); + if (cipher == nullptr) + return; + + if (iv.empty()) + EVP_EncryptInit_ex(mpContext.get(), cipher, nullptr, key.data(), 0); + else + EVP_EncryptInit_ex(mpContext.get(), cipher, nullptr, key.data(), iv.data()); + EVP_CIPHER_CTX_set_padding(mpContext.get(), 0); + } + + void setupDecryptContext(std::vector& key, std::vector& iv, Crypto::CryptoType eType) + { + mpContext.reset(new EVP_CIPHER_CTX); + EVP_CIPHER_CTX_init(mpContext.get()); + + const EVP_CIPHER* pCipher = getCipher(eType); + if (pCipher == nullptr) + return; + + const size_t nMinKeySize = EVP_CIPHER_key_length(pCipher); + if (key.size() < nMinKeySize) + key.resize(nMinKeySize, 0); + + if (iv.empty()) + EVP_DecryptInit_ex(mpContext.get(), pCipher, nullptr, key.data(), 0); + else + { + const size_t nMinIVSize = EVP_CIPHER_iv_length(pCipher); + if (iv.size() < nMinIVSize) + iv.resize(nMinIVSize, 0); + + EVP_DecryptInit_ex(mpContext.get(), pCipher, nullptr, key.data(), iv.data()); + } + EVP_CIPHER_CTX_set_padding(mpContext.get(), 0); + } + + void setupCryptoHashContext(std::vector& rKey, CryptoHashType eType) + { + mpHmacContext.reset(new HMAC_CTX); + HMAC_CTX_init(mpHmacContext.get()); + const EVP_MD* aEvpMd; + switch (eType) + { + case CryptoHashType::SHA1: + aEvpMd = EVP_sha1(); break; + case CryptoHashType::SHA256: + aEvpMd = EVP_sha256(); break; + case CryptoHashType::SHA512: + aEvpMd = EVP_sha512(); break; + } + HMAC_Init(mpHmacContext.get(), rKey.data(), rKey.size(), aEvpMd); + } + + ~CryptoImpl() + { + if (mpContext) + EVP_CIPHER_CTX_cleanup(mpContext.get()); + if (mpHmacContext) + HMAC_CTX_cleanup(mpHmacContext.get()); + } + + const EVP_CIPHER* getCipher(Crypto::CryptoType type) + { + switch(type) + { + case Crypto::CryptoType::AES_128_ECB: + return EVP_aes_128_ecb(); + case Crypto::CryptoType::AES_128_CBC: + return EVP_aes_128_cbc(); + case Crypto::CryptoType::AES_256_CBC: + return EVP_aes_256_cbc(); + default: + break; + } + return nullptr; + } +}; + +#elif USE_TLS_NSS + +struct CryptoImpl +{ + PK11Context* mContext; + SECItem* mSecParam; + PK11SymKey* mSymKey; + PK11SlotInfo* mpSlot; + + CryptoImpl() + : mContext(nullptr) + , mSecParam(nullptr) + , mSymKey(nullptr) + { + // Initialize NSS, database functions are not needed + NSS_NoDB_Init(nullptr); + } + + ~CryptoImpl() + { + if (mContext) + PK11_DestroyContext(mContext, PR_TRUE); + if (mSymKey) + PK11_FreeSymKey(mSymKey); + if (mSecParam) + SECITEM_FreeItem(mSecParam, PR_TRUE); + if (mpSlot) + PK11_FreeSlot(mpSlot); + } + + void setupCryptoContext(std::vector& key, std::vector& iv, Crypto::CryptoType type, CK_ATTRIBUTE_TYPE operation) + { + CK_MECHANISM_TYPE mechanism = static_cast(-1); + + SECItem ivItem; + ivItem.type = siBuffer; + if(iv.empty()) + ivItem.data = nullptr; + else + ivItem.data = iv.data(); + ivItem.len = iv.size(); + + SECItem* pIvItem = nullptr; + + switch(type) + { + case Crypto::CryptoType::AES_128_ECB: + mechanism = CKM_AES_ECB; + break; + case Crypto::CryptoType::AES_128_CBC: + mechanism = CKM_AES_CBC; + pIvItem = &ivItem; + break; + case Crypto::CryptoType::AES_256_CBC: + mechanism = CKM_AES_CBC; + pIvItem = &ivItem; + break; + default: + break; + } + + mpSlot = PK11_GetBestSlot(mechanism, nullptr); + + if (!mpSlot) + throw css::uno::RuntimeException("NSS Slot failure", css::uno::Reference()); + + SECItem keyItem; + keyItem.type = siBuffer; + keyItem.data = key.data(); + keyItem.len = key.size(); + + mSymKey = PK11_ImportSymKey(mpSlot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, nullptr); + if (!mSymKey) + throw css::uno::RuntimeException("NSS SymKey failure", css::uno::Reference()); + + mSecParam = PK11_ParamFromIV(mechanism, pIvItem); + mContext = PK11_CreateContextBySymKey(mechanism, operation, mSymKey, mSecParam); + } + + void setupCryptoHashContext(std::vector& rKey, CryptoHashType eType) + { + CK_MECHANISM_TYPE aMechanism = static_cast(-1); + + switch(eType) + { + case CryptoHashType::SHA1: + aMechanism = CKM_SHA_1_HMAC; + break; + case CryptoHashType::SHA256: + aMechanism = CKM_SHA256_HMAC; + break; + case CryptoHashType::SHA512: + aMechanism = CKM_SHA512_HMAC; + break; + } + + mpSlot = PK11_GetBestSlot(aMechanism, nullptr); + + if (!mpSlot) + throw css::uno::RuntimeException("NSS Slot failure", css::uno::Reference()); + + SECItem aKeyItem; + aKeyItem.data = rKey.data(); + aKeyItem.len = rKey.size(); + + mSymKey = PK11_ImportSymKey(mpSlot, aMechanism, PK11_OriginUnwrap, CKA_SIGN, &aKeyItem, nullptr); + if (!mSymKey) + throw css::uno::RuntimeException("NSS SymKey failure", css::uno::Reference()); + + SECItem param; + param.data = nullptr; + param.len = 0; + mContext = PK11_CreateContextBySymKey(aMechanism, CKA_SIGN, mSymKey, ¶m); + } +}; +#else +struct CryptoImpl +{}; +#endif + +Crypto::Crypto() + : mpImpl(o3tl::make_unique()) { -#if USE_TLS_NSS - // Initialize NSS, database functions are not needed - NSS_NoDB_Init(nullptr); -#endif // USE_TLS_NSS } Crypto::~Crypto() { -#if USE_TLS_OPENSSL - EVP_CIPHER_CTX_cleanup( &mContext ); -#endif -#if USE_TLS_NSS - if (mContext) - PK11_DestroyContext(mContext, PR_TRUE); - if (mSymKey) - PK11_FreeSymKey(mSymKey); - if (mSecParam) - SECITEM_FreeItem(mSecParam, PR_TRUE); -#endif } -#if USE_TLS_OPENSSL -const EVP_CIPHER* Crypto::getCipher(CryptoType type) -{ - switch(type) - { - case AES_128_ECB: - return EVP_aes_128_ecb(); - case AES_128_CBC: - return EVP_aes_128_cbc(); - case AES_256_CBC: - return EVP_aes_256_cbc(); - default: - break; - } - return NULL; -} -#endif - -#if USE_TLS_NSS -void Crypto::setupContext(std::vector& key, std::vector& iv, CryptoType type, CK_ATTRIBUTE_TYPE operation) -{ - CK_MECHANISM_TYPE mechanism = static_cast(-1); - - SECItem ivItem; - ivItem.type = siBuffer; - if(iv.empty()) - ivItem.data = nullptr; - else - ivItem.data = iv.data(); - ivItem.len = iv.size(); - - SECItem* pIvItem = nullptr; - - switch(type) - { - case AES_128_ECB: - mechanism = CKM_AES_ECB; - break; - case AES_128_CBC: - mechanism = CKM_AES_CBC; - pIvItem = &ivItem; - break; - case AES_256_CBC: - mechanism = CKM_AES_CBC; - pIvItem = &ivItem; - break; - default: - break; - } - - PK11SlotInfo* pSlot(PK11_GetBestSlot(mechanism, nullptr)); - - if (!pSlot) - throw css::uno::RuntimeException("NSS Slot failure", css::uno::Reference()); - - SECItem keyItem; - keyItem.type = siBuffer; - keyItem.data = key.data(); - keyItem.len = key.size(); - - mSymKey = PK11_ImportSymKey(pSlot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, nullptr); - if (!mSymKey) - throw css::uno::RuntimeException("NSS SymKey failure", css::uno::Reference()); - - mSecParam = PK11_ParamFromIV(mechanism, pIvItem); - mContext = PK11_CreateContextBySymKey(mechanism, operation, mSymKey, mSecParam); -} -#endif // USE_TLS_NSS - // DECRYPT Decrypt::Decrypt(std::vector& key, std::vector& iv, CryptoType type) @@ -124,29 +262,11 @@ Decrypt::Decrypt(std::vector& key, std::vector& iv, Crypto #endif #if USE_TLS_OPENSSL - EVP_CIPHER_CTX_init(&mContext); - - const EVP_CIPHER* cipher = getCipher(type); - - const size_t nMinKeySize = EVP_CIPHER_key_length(cipher); - if (key.size() < nMinKeySize) - key.resize(nMinKeySize, 0); - - if (iv.empty()) - EVP_DecryptInit_ex(&mContext, cipher, nullptr, key.data(), 0); - else - { - const size_t nMinIVSize = EVP_CIPHER_iv_length(cipher); - if (iv.size() < nMinIVSize) - iv.resize(nMinIVSize, 0); - - EVP_DecryptInit_ex(&mContext, cipher, nullptr, key.data(), iv.data()); - } - EVP_CIPHER_CTX_set_padding(&mContext, 0); + mpImpl->setupDecryptContext(key, iv, type); #endif #if USE_TLS_NSS - setupContext(key, iv, type, CKA_DECRYPT); + mpImpl->setupCryptoContext(key, iv, type, CKA_DECRYPT); #endif // USE_TLS_NSS } @@ -163,11 +283,11 @@ sal_uInt32 Decrypt::update(std::vector& output, std::vectormpContext.get(), output.data(), &outputLength, input.data(), actualInputLength); #endif // USE_TLS_OPENSSL #if USE_TLS_NSS - (void)PK11_CipherOp( mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength ); + (void)PK11_CipherOp(mpImpl->mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength); #endif // USE_TLS_NSS return static_cast(outputLength); @@ -194,19 +314,9 @@ Encrypt::Encrypt(std::vector& key, std::vector& iv, Crypto #endif #if USE_TLS_OPENSSL - EVP_CIPHER_CTX_init(&mContext); - - const EVP_CIPHER* cipher = getCipher(type); - - if (iv.empty()) - EVP_EncryptInit_ex(&mContext, cipher, nullptr, key.data(), 0); - else - EVP_EncryptInit_ex(&mContext, cipher, nullptr, key.data(), iv.data()); - EVP_CIPHER_CTX_set_padding(&mContext, 0); -#endif - -#if USE_TLS_NSS - setupContext(key, iv, type, CKA_ENCRYPT); + mpImpl->setupEncryptContext(key, iv, type); +#elif USE_TLS_NSS + mpImpl->setupCryptoContext(key, iv, type, CKA_ENCRYPT); #endif // USE_TLS_NSS } @@ -223,16 +333,78 @@ sal_uInt32 Encrypt::update(std::vector& output, std::vectormpContext.get(), output.data(), &outputLength, input.data(), actualInputLength); #endif // USE_TLS_OPENSSL #if USE_TLS_NSS - (void)PK11_CipherOp(mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength); + (void)PK11_CipherOp(mpImpl->mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength); #endif // USE_TLS_NSS return static_cast(outputLength); } +// CryptoHash - HMAC + +namespace +{ + +sal_Int32 getSizeForHashType(CryptoHashType eType) +{ + switch (eType) + { + case CryptoHashType::SHA1: return 20; + case CryptoHashType::SHA256: return 32; + case CryptoHashType::SHA512: return 64; + } + return 0; +} + +} // end anonymous namespace + +CryptoHash::CryptoHash(std::vector& rKey, CryptoHashType eType) + : Crypto() + , mnHashSize(getSizeForHashType(eType)) +{ +#if USE_TLS_OPENSSL + mpImpl->setupCryptoHashContext(rKey, eType); +#elif USE_TLS_NSS + mpImpl->setupCryptoHashContext(rKey, eType); + PK11_DigestBegin(mpImpl->mContext); +#else + (void)rKey; +#endif +} + +bool CryptoHash::update(std::vector& rInput, sal_uInt32 nInputLength) +{ +#if USE_TLS_OPENSSL + USE_TLS_NSS > 0 + sal_uInt32 nActualInputLength = (nInputLength == 0 || nInputLength > rInput.size()) ? rInput.size() : nInputLength; +#else + (void)input; + (void)inputLength; +#endif + +#if USE_TLS_OPENSSL + return HMAC_Update(mpImpl->mpHmacContext.get(), rInput.data(), nActualInputLength) != 0; +#elif USE_TLS_NSS + return PK11_DigestOp(mpImpl->mContext, rInput.data(), nActualInputLength) == SECSuccess; +#endif +} + +std::vector CryptoHash::finalize() +{ + std::vector aHash(mnHashSize, 0); + unsigned int nSizeWritten; +#if USE_TLS_OPENSSL + (void) HMAC_Final(mpImpl->mpHmacContext.get(), aHash.data(), &nSizeWritten) != 0; +#elif USE_TLS_NSS + PK11_DigestFinal(mpImpl->mContext, aHash.data(), &nSizeWritten, aHash.size()); +#endif + (void)nSizeWritten; + + return aHash; +} + } // namespace core } // namespace oox