From cd3dece750aa30b15091f211a72b6fcf67d49853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Sun, 31 Aug 2014 14:55:06 +0300 Subject: ssl: common key parser support for encrypted keys This adds the infrastructure for reading and writing encrypted private keys when using non-OpenSSL backends. Each platform must provide its cryptographic encrypt / decrypt functions. As WinRT already uses the common parser, this commit includes an implementation for that platform. Done-with: Andrew Knight Task-number: QTBUG-40688 Change-Id: I0d153425ce63601ff03b784a111e13962061025f Reviewed-by: Richard J. Moore --- src/network/ssl/qsslkey_openssl.cpp | 3 +- src/network/ssl/qsslkey_p.cpp | 52 ++++++++++++++++++-- src/network/ssl/qsslkey_p.h | 13 ++++- src/network/ssl/qsslkey_qt.cpp | 78 ++++++++++++++++++++++++++--- src/network/ssl/qsslkey_winrt.cpp | 98 +++++++++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 15 deletions(-) (limited to 'src/network') diff --git a/src/network/ssl/qsslkey_openssl.cpp b/src/network/ssl/qsslkey_openssl.cpp index 7e78ac0fee..6b0fa954eb 100644 --- a/src/network/ssl/qsslkey_openssl.cpp +++ b/src/network/ssl/qsslkey_openssl.cpp @@ -111,7 +111,8 @@ bool QSslKeyPrivate::fromEVP_PKEY(EVP_PKEY *pkey) void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) { - decodePem(pemFromDer(der), QByteArray(), deepClear); + QMap headers; + decodePem(pemFromDer(der, headers), QByteArray(), deepClear); } void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, diff --git a/src/network/ssl/qsslkey_p.cpp b/src/network/ssl/qsslkey_p.cpp index 2b0dab9933..b051ec6874 100644 --- a/src/network/ssl/qsslkey_p.cpp +++ b/src/network/ssl/qsslkey_p.cpp @@ -63,6 +63,7 @@ #include #include +#include #include #ifndef QT_NO_DEBUG_STREAM #include @@ -130,7 +131,7 @@ QByteArray QSslKeyPrivate::pemFooter() const Returns a DER key formatted as PEM. */ -QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const +QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der, const QMap &headers) const { QByteArray pem(der.toBase64()); @@ -144,7 +145,16 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const if (rem) pem.append('\n'); // ### - pem.prepend(pemHeader() + '\n'); + QByteArray extra; + if (!headers.isEmpty()) { + QMap::const_iterator it = headers.constEnd(); + do { + it--; + extra += it.key() + ": " + it.value() + '\n'; + } while (it != headers.constBegin()); + extra += '\n'; + } + pem.prepend(pemHeader() + '\n' + extra); pem.append(pemFooter() + '\n'); return pem; @@ -155,7 +165,7 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const Returns a PEM key formatted as DER. */ -QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const +QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem, QMap *headers) const { const QByteArray header = pemHeader(); const QByteArray footer = pemFooter(); @@ -169,6 +179,39 @@ QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + const QByteArrayMatcher lf("\n"); + const QByteArrayMatcher colon(":"); + int i = 0; + while (i < der.count()) { + int j = colon.indexIn(der, i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = lf.indexIn(der, j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + return QByteArray::fromBase64(der); // ignores newlines } @@ -337,7 +380,8 @@ QByteArray QSslKey::toDer(const QByteArray &passPhrase) const return QByteArray(); #ifndef QT_NO_OPENSSL - return d->derFromPem(toPem(passPhrase)); + QMap headers; + return d->derFromPem(toPem(passPhrase), &headers); #else return d->derData; #endif diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h index 9c1476038a..d24606e6a6 100644 --- a/src/network/ssl/qsslkey_p.h +++ b/src/network/ssl/qsslkey_p.h @@ -91,8 +91,8 @@ public: bool deepClear = true); QByteArray pemHeader() const; QByteArray pemFooter() const; - QByteArray pemFromDer(const QByteArray &der) const; - QByteArray derFromPem(const QByteArray &pem) const; + QByteArray pemFromDer(const QByteArray &der, const QMap &headers) const; + QByteArray derFromPem(const QByteArray &pem, QMap *headers) const; int length() const; QByteArray toPem(const QByteArray &passPhrase) const; @@ -106,6 +106,15 @@ public: RSA *rsa; DSA *dsa; #else + enum Cipher { + DesCbc, + DesEde3Cbc, + Rc2Cbc + }; + + Q_AUTOTEST_EXPORT static QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Q_AUTOTEST_EXPORT static QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Qt::HANDLE opaque; QByteArray derData; int keyLength; diff --git a/src/network/ssl/qsslkey_qt.cpp b/src/network/ssl/qsslkey_qt.cpp index feeb7d6f87..c14cf0250c 100644 --- a/src/network/ssl/qsslkey_qt.cpp +++ b/src/network/ssl/qsslkey_qt.cpp @@ -43,6 +43,8 @@ #include "qsslkey_p.h" #include "qasn1element_p.h" +#include + QT_USE_NAMESPACE static const quint8 bits_table[256] = { @@ -78,6 +80,31 @@ static int numberOfBits(const QByteArray &modulus) return bits; } +static QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, const QByteArray &iv) +{ + QByteArray key; + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(passPhrase); + hash.addData(iv); + switch (cipher) { + case QSslKeyPrivate::DesCbc: + key = hash.result().left(8); + break; + case QSslKeyPrivate::DesEde3Cbc: + key = hash.result(); + hash.reset(); + hash.addData(key); + hash.addData(passPhrase); + hash.addData(iv); + key += hash.result().left(8); + break; + case QSslKeyPrivate::Rc2Cbc: + key = hash.result(); + break; + } + return key; +} + void QSslKeyPrivate::clear(bool deep) { Q_UNUSED(deep); @@ -155,12 +182,32 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, bool deepClear) { - if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) { - Q_UNIMPLEMENTED(); - return; - } + QMap headers; + QByteArray data = derFromPem(pem, &headers); + if (headers.value("Proc-Type") == "4,ENCRYPTED") { + QList dekInfo = headers.value("DEK-Info").split(','); + if (dekInfo.size() != 2) { + clear(deepClear); + return; + } + + Cipher cipher; + if (dekInfo.first() == "DES-CBC") { + cipher = DesCbc; + } else if (dekInfo.first() == "DES-EDE3-CBC") { + cipher = DesEde3Cbc; + } else if (dekInfo.first() == "RC2-CBC") { + cipher = Rc2Cbc; + } else { + clear(deepClear); + return; + } - decodeDer(derFromPem(pem), deepClear); + const QByteArray iv = QByteArray::fromHex(dekInfo.last()); + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = decrypt(cipher, data, key, iv); + } + decodeDer(data, deepClear); } int QSslKeyPrivate::length() const @@ -170,12 +217,27 @@ int QSslKeyPrivate::length() const QByteArray QSslKeyPrivate::toPem(const QByteArray &passPhrase) const { + QByteArray data; + QMap headers; + if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) { - Q_UNIMPLEMENTED(); - return QByteArray(); + // ### use a cryptographically secure random number generator + QByteArray iv; + iv.resize(8); + for (int i = 0; i < iv.size(); ++i) + iv[i] = (qrand() & 0xff); + + Cipher cipher = DesEde3Cbc; + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = encrypt(cipher, derData, key, iv); + + headers.insert("Proc-Type", "4,ENCRYPTED"); + headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex()); + } else { + data = derData; } - return pemFromDer(derData); + return pemFromDer(data, headers); } Qt::HANDLE QSslKeyPrivate::handle() const diff --git a/src/network/ssl/qsslkey_winrt.cpp b/src/network/ssl/qsslkey_winrt.cpp index 2c83069694..c5b4146ee9 100644 --- a/src/network/ssl/qsslkey_winrt.cpp +++ b/src/network/ssl/qsslkey_winrt.cpp @@ -61,3 +61,101 @@ using namespace ABI::Windows::Security::Cryptography::Core; using namespace ABI::Windows::Storage::Streams; QT_USE_NAMESPACE + +struct SslKeyGlobal +{ + SslKeyGlobal() + { + HRESULT hr; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_CryptographicEngine).Get(), + &engine); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr keyProviderFactory; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_SymmetricKeyAlgorithmProvider).Get(), + &keyProviderFactory); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"DES_CBC").Get(), + &keyProviders[QSslKeyPrivate::DesCbc]); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"3DES_CBC").Get(), + &keyProviders[QSslKeyPrivate::DesEde3Cbc]); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"RC2_CBC").Get(), + &keyProviders[QSslKeyPrivate::Rc2Cbc]); + Q_ASSERT_SUCCEEDED(hr); + + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_CryptographicBuffer).Get(), + &bufferFactory); + Q_ASSERT_SUCCEEDED(hr); + } + + ComPtr engine; + QHash> keyProviders; + ComPtr bufferFactory; +}; +Q_GLOBAL_STATIC(SslKeyGlobal, g) + +static QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, QByteArray data, const QByteArray &key, const QByteArray &iv, bool encrypt) +{ + HRESULT hr; + + ISymmetricKeyAlgorithmProvider *keyProvider = g->keyProviders[cipher].Get(); + Q_ASSERT(keyProvider); + + ComPtr keyBuffer; + hr = g->bufferFactory->CreateFromByteArray(key.length(), (BYTE *)key.data(), &keyBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr cryptographicKey; + hr = keyProvider->CreateSymmetricKey(keyBuffer.Get(), &cryptographicKey); + Q_ASSERT_SUCCEEDED(hr); + + UINT32 blockLength; + hr = keyProvider->get_BlockLength(&blockLength); + Q_ASSERT_SUCCEEDED(hr); + if (encrypt) { // Add padding + const char padding = blockLength - data.length() % blockLength; + data += QByteArray(padding, padding); + } + + ComPtr dataBuffer; + hr = g->bufferFactory->CreateFromByteArray(data.length(), (BYTE *)data.data(), &dataBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr ivBuffer; + hr = g->bufferFactory->CreateFromByteArray(iv.length(), (BYTE *)iv.data(), &ivBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr resultBuffer; + hr = encrypt ? g->engine->Encrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer) + : g->engine->Decrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer); + Q_ASSERT_SUCCEEDED(hr); + + UINT32 resultLength; + hr = resultBuffer->get_Length(&resultLength); + Q_ASSERT_SUCCEEDED(hr); + ComPtr bufferAccess; + hr = resultBuffer.As(&bufferAccess); + Q_ASSERT_SUCCEEDED(hr); + byte *resultData; + hr = bufferAccess->Buffer(&resultData); + Q_ASSERT_SUCCEEDED(hr); + + if (!encrypt) { // Remove padding + const uchar padding = resultData[resultLength - 1]; + if (padding > 0 && padding <= blockLength) + resultLength -= padding; + else + qWarning("Invalid padding length of %u; decryption likely failed.", padding); + } + + return QByteArray(reinterpret_cast(resultData), resultLength); +} + +QByteArray QSslKeyPrivate::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return doCrypt(cipher, data, key, iv, false); +} + +QByteArray QSslKeyPrivate::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return doCrypt(cipher, data, key, iv, true); +} -- cgit v1.2.3