From 943595c937420acf375aa4c44f1a3df6f655698c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Obiltschnig?= Date: Tue, 6 Mar 2018 22:53:27 +0100 Subject: [PATCH] GH #2129: Add support for AES-GCM ciphers --- Crypto/include/Poco/Crypto/CipherKey.h | 10 ++++- Crypto/include/Poco/Crypto/CipherKeyImpl.h | 7 --- Crypto/include/Poco/Crypto/CryptoTransform.h | 13 +++++- Crypto/src/CipherImpl.cpp | 46 +++++++++++++++++++- Crypto/src/CipherKeyImpl.cpp | 11 ++++- Crypto/src/RSACipherImpl.cpp | 26 +++++++++++ Crypto/testsuite/src/CryptoTest.cpp | 36 ++++++++++++++- Crypto/testsuite/src/CryptoTest.h | 1 + 8 files changed, 138 insertions(+), 12 deletions(-) diff --git a/Crypto/include/Poco/Crypto/CipherKey.h b/Crypto/include/Poco/Crypto/CipherKey.h index e3d7c70e5..6b1ad36bc 100644 --- a/Crypto/include/Poco/Crypto/CipherKey.h +++ b/Crypto/include/Poco/Crypto/CipherKey.h @@ -70,7 +70,11 @@ public: const ByteVec& key, const ByteVec& iv); /// Creates a new CipherKeyImpl object using the given cipher - /// name, key and initialization vector. + /// name, key and initialization vector (IV). + /// + /// The size of the IV must match the cipher's expected + /// IV size (see ivSize()), except for GCM mode, which allows + /// a custom IV size. CipherKey(const std::string& name); /// Creates a new CipherKeyImpl object. Autoinitializes key and @@ -105,6 +109,10 @@ public: void setIV(const ByteVec& iv); /// Sets the initialization vector (IV) for the Cipher. + /// + /// The size of the vector must match the cipher's expected + /// IV size (see ivSize()), except for GCM mode, which allows + /// a custom IV size. CipherKeyImpl::Ptr impl(); /// Returns the impl object diff --git a/Crypto/include/Poco/Crypto/CipherKeyImpl.h b/Crypto/include/Poco/Crypto/CipherKeyImpl.h index e35d6b343..f7807aad9 100644 --- a/Crypto/include/Poco/Crypto/CipherKeyImpl.h +++ b/Crypto/include/Poco/Crypto/CipherKeyImpl.h @@ -156,13 +156,6 @@ inline const CipherKeyImpl::ByteVec& CipherKeyImpl::getIV() const } -inline void CipherKeyImpl::setIV(const ByteVec& iv) -{ - poco_assert(iv.size() == static_cast(ivSize())); - _iv = iv; -} - - inline const EVP_CIPHER* CipherKeyImpl::cipher() { return _pCipher; diff --git a/Crypto/include/Poco/Crypto/CryptoTransform.h b/Crypto/include/Poco/Crypto/CryptoTransform.h index d1612a78d..e2046bff3 100644 --- a/Crypto/include/Poco/Crypto/CryptoTransform.h +++ b/Crypto/include/Poco/Crypto/CryptoTransform.h @@ -49,7 +49,18 @@ public: /// padding and the padding is checked and removed when decrypting. If the padding parameter is zero then /// no padding is performed, the total amount of data encrypted or decrypted must then be a multiple of /// the block size or an error will occur. - + + virtual std::string getTag(std::size_t tagSize = 16) const = 0; + /// Returns the GCM tag after encrypting using GCM mode. + /// + /// Must be called after finalize(). + + virtual void setTag(const std::string& tag) = 0; + /// Sets the GCM tag for authenticated decryption using GCM mode. + /// + /// Must be set before finalize() is called, otherwise + /// decryption will fail. + virtual std::streamsize transform( const unsigned char* input, std::streamsize inputLength, diff --git a/Crypto/src/CipherImpl.cpp b/Crypto/src/CipherImpl.cpp index c554c49dd..af48dc595 100644 --- a/Crypto/src/CipherImpl.cpp +++ b/Crypto/src/CipherImpl.cpp @@ -15,6 +15,7 @@ #include "Poco/Crypto/CipherImpl.h" #include "Poco/Crypto/CryptoTransform.h" #include "Poco/Exception.h" +#include "Poco/Buffer.h" #include @@ -60,8 +61,9 @@ namespace ~CryptoTransformImpl(); std::size_t blockSize() const; - int setPadding(int padding); + std::string getTag(std::size_t tagSize) const; + void setTag(const std::string& tag); std::streamsize transform( const unsigned char* input, @@ -110,6 +112,18 @@ namespace _iv.empty() ? 0 : &_iv[0], (dir == DIR_ENCRYPT) ? 1 : 0); #endif + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + if (_iv.size() != EVP_CIPHER_iv_length(_pCipher) && EVP_CIPHER_mode(_pCipher) == EVP_CIPH_GCM_MODE) + { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + int rc = EVP_CIPHER_CTX_ctrl(_pContext, EVP_CTRL_GCM_SET_IVLEN, _iv.size(), NULL); +#else + int rc = EVP_CIPHER_CTX_ctrl(&_context, EVP_CTRL_GCM_SET_IVLEN, _iv.size(), NULL); +#endif + if (rc == 0) throwError(); + } +#endif } @@ -144,6 +158,36 @@ namespace } + std::string CryptoTransformImpl::getTag(std::size_t tagSize) const + { + std::string tag; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + Poco::Buffer buffer(tagSize); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + int rc = EVP_CIPHER_CTX_ctrl(_pContext, EVP_CTRL_GCM_GET_TAG, tagSize, buffer.begin()); +#else + int rc = EVP_CIPHER_CTX_ctrl(&_context, EVP_CTRL_GCM_GET_TAG, tagSize, buffer.begin()); +#endif + if (rc == 0) throwError(); + tag.assign(buffer.begin(), tagSize); +#endif + return tag; + } + + + void CryptoTransformImpl::setTag(const std::string& tag) + { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + int rc = EVP_CIPHER_CTX_ctrl(_pContext, EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())); +#elif OPENSSL_VERSION_NUMBER >= 0x10000000L + int rc = EVP_CIPHER_CTX_ctrl(&_context, EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast(tag.data())); +#else + int rc = 0; +#endif + if (rc == 0) throwError(); + } + + std::streamsize CryptoTransformImpl::transform( const unsigned char* input, std::streamsize inputLength, diff --git a/Crypto/src/CipherKeyImpl.cpp b/Crypto/src/CipherKeyImpl.cpp index b30cbd5e7..0bd8c2c32 100644 --- a/Crypto/src/CipherKeyImpl.cpp +++ b/Crypto/src/CipherKeyImpl.cpp @@ -63,7 +63,7 @@ CipherKeyImpl::CipherKeyImpl(const std::string& name, _key(key), _iv(iv) { - // dummy access to Cipherfactory so that the EVP lib is initilaized + // dummy access to Cipherfactory so that the EVP lib is initialized CipherFactory::defaultFactory(); _pCipher = EVP_get_cipherbyname(name.c_str()); @@ -115,6 +115,7 @@ CipherKeyImpl::Mode CipherKeyImpl::mode() const case EVP_CIPH_OFB_MODE: return MODE_OFB; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L case EVP_CIPH_CTR_MODE: return MODE_CTR; @@ -123,6 +124,7 @@ CipherKeyImpl::Mode CipherKeyImpl::mode() const case EVP_CIPH_CCM_MODE: return MODE_CCM; +#endif } throw Poco::IllegalStateException("Unexpected value of EVP_CIPHER_mode()"); } @@ -212,4 +214,11 @@ int CipherKeyImpl::ivSize() const } +void CipherKeyImpl::setIV(const ByteVec& iv) +{ + poco_assert(mode() == MODE_GCM || iv.size() == static_cast(ivSize())); + _iv = iv; +} + + } } // namespace Poco::Crypto diff --git a/Crypto/src/RSACipherImpl.cpp b/Crypto/src/RSACipherImpl.cpp index 9ba36b5b4..34a04ba83 100644 --- a/Crypto/src/RSACipherImpl.cpp +++ b/Crypto/src/RSACipherImpl.cpp @@ -69,6 +69,8 @@ namespace std::size_t blockSize() const; std::size_t maxDataSize() const; + std::string getTag(std::size_t) const; + void setTag(const std::string&); std::streamsize transform( const unsigned char* input, @@ -127,6 +129,17 @@ namespace } + std::string RSAEncryptImpl::getTag(std::size_t) const + { + return std::string(); + } + + + void RSAEncryptImpl::setTag(const std::string&) + { + } + + std::streamsize RSAEncryptImpl::transform( const unsigned char* input, std::streamsize inputLength, @@ -192,6 +205,8 @@ namespace ~RSADecryptImpl(); std::size_t blockSize() const; + std::string getTag(std::size_t) const; + void setTag(const std::string&); std::streamsize transform( const unsigned char* input, @@ -233,6 +248,17 @@ namespace } + std::string RSADecryptImpl::getTag(std::size_t) const + { + return std::string(); + } + + + void RSADecryptImpl::setTag(const std::string&) + { + } + + std::streamsize RSADecryptImpl::transform( const unsigned char* input, std::streamsize inputLength, diff --git a/Crypto/testsuite/src/CryptoTest.cpp b/Crypto/testsuite/src/CryptoTest.cpp index 65bea53f0..89d92b1fa 100644 --- a/Crypto/testsuite/src/CryptoTest.cpp +++ b/Crypto/testsuite/src/CryptoTest.cpp @@ -16,6 +16,7 @@ #include "Poco/Crypto/CipherKey.h" #include "Poco/Crypto/X509Certificate.h" #include "Poco/Crypto/CryptoStream.h" +#include "Poco/Crypto/CryptoTransform.h" #include "Poco/StreamCopier.h" #include "Poco/Base64Encoder.h" #include "Poco/HexBinaryEncoder.h" @@ -188,6 +189,38 @@ void CryptoTest::testEncryptDecryptDESECB() } +void CryptoTest::testEncryptDecryptGCM() +{ + CipherKey key("aes-256-gcm"); + + CipherKey::ByteVec iv(20, 213); + key.setIV(iv); + + Cipher::Ptr pCipher = CipherFactory::defaultFactory().createCipher(key); + + for (std::size_t n = 1; n < MAX_DATA_SIZE; n++) + { + std::stringstream str; + CryptoTransform* pEncryptor = pCipher->createEncryptor(); + CryptoOutputStream encryptorStream(str, pEncryptor); + std::string in(n, 'x'); + encryptorStream << in; + encryptorStream.close(); + assert (encryptorStream.good()); + + std::string tag = pEncryptor->getTag(); + + CryptoTransform* pDecryptor = pCipher->createDecryptor(); + pDecryptor->setTag(tag); + CryptoInputStream decryptorStream(str, pDecryptor); + std::string out; + decryptorStream >> out; + + assert (in == out); + } +} + + void CryptoTest::testPassword() { CipherKey key("aes256", "password", "salt"); @@ -331,8 +364,9 @@ CppUnit::Test* CryptoTest::suite() CppUnit_addTest(pSuite, CryptoTest, testEncryptDecryptWithSalt); CppUnit_addTest(pSuite, CryptoTest, testEncryptDecryptWithSaltSha1); CppUnit_addTest(pSuite, CryptoTest, testEncryptDecryptDESECB); + CppUnit_addTest(pSuite, CryptoTest, testEncryptDecryptGCM); CppUnit_addTest(pSuite, CryptoTest, testPassword); - CppUnit_addTest(pSuite, CryptoTest,testPasswordSha1); + CppUnit_addTest(pSuite, CryptoTest, testPasswordSha1); CppUnit_addTest(pSuite, CryptoTest, testEncryptInterop); CppUnit_addTest(pSuite, CryptoTest, testDecryptInterop); CppUnit_addTest(pSuite, CryptoTest, testStreams); diff --git a/Crypto/testsuite/src/CryptoTest.h b/Crypto/testsuite/src/CryptoTest.h index 0fdca3841..a4d6dd1cf 100644 --- a/Crypto/testsuite/src/CryptoTest.h +++ b/Crypto/testsuite/src/CryptoTest.h @@ -33,6 +33,7 @@ public: void testEncryptDecryptWithSalt(); void testEncryptDecryptWithSaltSha1(); void testEncryptDecryptDESECB(); + void testEncryptDecryptGCM(); void testStreams(); void testPassword(); void testPasswordSha1();