diff options
-rw-r--r-- | src/network/ssl/qasn1element_p.h | 33 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_p.cpp | 5 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_p.h | 4 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_qt.cpp | 407 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp | 19 |
5 files changed, 457 insertions, 11 deletions
diff --git a/src/network/ssl/qasn1element_p.h b/src/network/ssl/qasn1element_p.h index c706c1f321..2068254a95 100644 --- a/src/network/ssl/qasn1element_p.h +++ b/src/network/ssl/qasn1element_p.h @@ -72,12 +72,18 @@ QT_BEGIN_NAMESPACE #define PKCS12_OID RSADSI_OID "1.12." // -PBES1 -#define PKCS5_MD2_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "1") -#define PKCS5_MD2_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "4") +#define PKCS5_MD2_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "1") // Not (yet) implemented +#define PKCS5_MD2_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "4") // Not (yet) implemented #define PKCS5_MD5_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "3") #define PKCS5_MD5_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "6") #define PKCS5_SHA1_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "10") #define PKCS5_SHA1_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "11") +#define PKCS12_SHA1_RC4_128_OID QByteArrayLiteral(PKCS12_OID "1.1") // Not (yet) implemented +#define PKCS12_SHA1_RC4_40_OID QByteArrayLiteral(PKCS12_OID "1.2") // Not (yet) implemented +#define PKCS12_SHA1_3KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.3") +#define PKCS12_SHA1_2KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.4") +#define PKCS12_SHA1_RC2_128_CBC_OID QByteArrayLiteral(PKCS12_OID "1.5") +#define PKCS12_SHA1_RC2_40_CBC_OID QByteArrayLiteral(PKCS12_OID "1.6") // -PBKDF2 #define PKCS5_PBKDF2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "12") @@ -85,6 +91,29 @@ QT_BEGIN_NAMESPACE // -PBES2 #define PKCS5_PBES2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "13") +// Digest +#define DIGEST_ALGORITHM_OID RSADSI_OID "2." +// -HMAC-SHA-1 +#define HMAC_WITH_SHA1 QByteArrayLiteral(DIGEST_ALGORITHM_OID "7") +// -HMAC-SHA-2 +#define HMAC_WITH_SHA224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "8") +#define HMAC_WITH_SHA256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "9") +#define HMAC_WITH_SHA384 QByteArrayLiteral(DIGEST_ALGORITHM_OID "10") +#define HMAC_WITH_SHA512 QByteArrayLiteral(DIGEST_ALGORITHM_OID "11") +#define HMAC_WITH_SHA512_224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "12") +#define HMAC_WITH_SHA512_256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "13") + +// Encryption algorithms +#define ENCRYPTION_ALGORITHM_OID RSADSI_OID "3." +#define DES_CBC_ENCRYPTION_OID QByteArrayLiteral("1.3.14.3.2.7") +#define DES_EDE3_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "7") +#define RC2_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "2") +#define RC5_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "9") // Not (yet) implemented +#define AES_OID "2.16.840.1.101.3.4.1." +#define AES128_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "2") +#define AES192_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "22") // Not (yet) implemented +#define AES256_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "42") // Not (yet) implemented + class Q_AUTOTEST_EXPORT QAsn1Element { public: diff --git a/src/network/ssl/qsslkey_p.cpp b/src/network/ssl/qsslkey_p.cpp index 2957633348..28e3e2efd8 100644 --- a/src/network/ssl/qsslkey_p.cpp +++ b/src/network/ssl/qsslkey_p.cpp @@ -185,6 +185,11 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der, const QMap<QByteArr if (isEncryptedPkcs8(der)) { pem.prepend(pkcs8Header(true) + '\n' + extra); pem.append(pkcs8Footer(true) + '\n'); +#if !QT_CONFIG(openssl) + } else if (isPkcs8) { + pem.prepend(pkcs8Header(false) + '\n' + extra); + pem.append(pkcs8Footer(false) + '\n'); +#endif } else { pem.prepend(pemHeader() + '\n' + extra); pem.append(pemFooter() + '\n'); diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h index d6c5af9d47..7ae2cc740b 100644 --- a/src/network/ssl/qsslkey_p.h +++ b/src/network/ssl/qsslkey_p.h @@ -93,6 +93,10 @@ public: Qt::HANDLE handle() const; bool isEncryptedPkcs8(const QByteArray &der) const; +#if !QT_CONFIG(openssl) + QByteArray decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase); + bool isPkcs8 = false; +#endif bool isNull; QSsl::KeyType type; diff --git a/src/network/ssl/qsslkey_qt.cpp b/src/network/ssl/qsslkey_qt.cpp index 0e7702bbeb..a13275f3bb 100644 --- a/src/network/ssl/qsslkey_qt.cpp +++ b/src/network/ssl/qsslkey_qt.cpp @@ -43,8 +43,11 @@ #include <QtCore/qdatastream.h> #include <QtCore/qcryptographichash.h> +#include <QtCore/QMessageAuthenticationCode> #include <QtCore/qrandom.h> +#include <QtNetwork/qpassworddigestor.h> + QT_USE_NAMESPACE static const quint8 bits_table[256] = { @@ -154,16 +157,86 @@ void QSslKeyPrivate::clear(bool deep) keyLength = -1; } +static int extractPkcs8KeyLength(const QVector<QAsn1Element> &items, QSslKeyPrivate *that) { + Q_ASSERT(items.size() == 3); + int keyLength; + + auto getName = [](QSsl::KeyAlgorithm algorithm) { + switch (algorithm){ + case QSsl::Rsa: return "RSA"; + case QSsl::Dsa: return "DSA"; + case QSsl::Ec: return "EC"; + case QSsl::Opaque: return "Opaque"; + } + Q_UNREACHABLE(); + }; + + const QVector<QAsn1Element> pkcs8Info = items[1].toVector(); + if (pkcs8Info.size() != 2 || pkcs8Info[0].type() != QAsn1Element::ObjectIdentifierType) + return -1; + const QByteArray value = pkcs8Info[0].toObjectId(); + if (value == RSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm != QSsl::Rsa)) { + // We could change the 'algorithm' of QSslKey here and continue loading, but + // this is not supported in the openssl back-end, so we'll fail here and give + // the user some feedback. + qWarning() << "QSslKey: Found RSA key when asked to use" << getName(that->algorithm) + << "\nLoading will fail."; + return -1; + } + // Luckily it contains the 'normal' RSA-key format inside, so we can just recurse + // and read the key's info. + that->decodeDer(items[2].value()); + // The real info has been filled out in the call above, so return as if it was invalid + // to avoid overwriting the data. + return -1; + } else if (value == EC_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm != QSsl::Ec)) { + // As above for RSA. + qWarning() << "QSslKey: Found EC key when asked to use" << getName(that->algorithm) + << "\nLoading will fail."; + return -1; + } + // I don't know where this is documented, but the elliptic-curve identifier has been + // moved into the "pkcs#8 wrapper", which is what we're interested in. + if (pkcs8Info[1].type() != QAsn1Element::ObjectIdentifierType) + return -1; + keyLength = curveBits(pkcs8Info[1].toObjectId()); + } else if (value == DSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm != QSsl::Dsa)) { + // As above for RSA. + qWarning() << "QSslKey: Found DSA when asked to use" << getName(that->algorithm) + << "\nLoading will fail."; + return -1; + } + // DSA's structure is documented here: + // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. + if (pkcs8Info[1].type() != QAsn1Element::SequenceType) + return -1; + const QVector<QAsn1Element> dsaInfo = pkcs8Info[1].toVector(); + if (dsaInfo.size() != 3 || dsaInfo[0].type() != QAsn1Element::IntegerType) + return -1; + keyLength = numberOfBits(dsaInfo[0].value()); + } else { + // in case of unexpected formats: + qWarning() << "QSslKey: Unsupported PKCS#8 key algorithm:" << value + << "\nFile a bugreport to Qt (include the line above)."; + return -1; + } + return keyLength; +} + void QSslKeyPrivate::decodeDer(const QByteArray &der, const QByteArray &passPhrase, bool deepClear) { - Q_UNUSED(passPhrase); clear(deepClear); if (der.isEmpty()) return; + // decryptPkcs8 decrypts if necessary or returns 'der' unaltered + QByteArray decryptedDer = decryptPkcs8(der, passPhrase); QAsn1Element elem; - if (!elem.read(der) || elem.type() != QAsn1Element::SequenceType) + if (!elem.read(decryptedDer) || elem.type() != QAsn1Element::SequenceType) return; if (type == QSsl::PublicKey) { @@ -213,7 +286,16 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, const QByteArray &passPhra return; const QByteArray versionHex = items[0].value().toHex(); - if (algorithm == QSsl::Rsa) { + if (items.size() == 3 && items[1].type() == QAsn1Element::SequenceType + && items[2].type() == QAsn1Element::OctetStringType) { + if (versionHex != "00" && versionHex != "01") + return; + int pkcs8KeyLength = extractPkcs8KeyLength(items, this); + if (pkcs8KeyLength == -1) + return; + isPkcs8 = true; + keyLength = pkcs8KeyLength; + } else if (algorithm == QSsl::Rsa) { if (versionHex != "00") return; if (items.size() != 9 || items[1].type() != QAsn1Element::IntegerType) @@ -241,7 +323,7 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, const QByteArray &passPhra } } - derData = der; + derData = decryptedDer; isNull = false; } @@ -308,3 +390,320 @@ Qt::HANDLE QSslKeyPrivate::handle() const { return opaque; } + +// Maps OIDs to the encryption cipher they specify +static const QMap<QByteArray, QSslKeyPrivate::Cipher> oidCipherMap { + {DES_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesCbc}, + {DES_EDE3_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesEde3Cbc}, + // {PKCS5_MD2_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, // No MD2 + {PKCS5_MD5_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + {PKCS5_SHA1_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + // {PKCS5_MD2_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, // No MD2 + {PKCS5_MD5_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {PKCS5_SHA1_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {RC2_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc2Cbc} + // {RC5_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc5Cbc}, // No RC5 + // {AES128_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes128}, // no AES + // {AES192_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes192}, + // {AES256_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes256} +}; + +struct EncryptionData +{ + EncryptionData() : initialized(false) + {} + EncryptionData(QSslKeyPrivate::Cipher cipher, QByteArray key, QByteArray iv) + : initialized(true), cipher(cipher), key(key), iv(iv) + {} + bool initialized; + QSslKeyPrivate::Cipher cipher; + QByteArray key; + QByteArray iv; +}; + +static EncryptionData readPbes2(const QVector<QAsn1Element> &element, const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.2 + /*** Scheme: *** + * Sequence (scheme-specific info..) + * Sequence (key derivation info) + * Object Identifier (Key derivation algorithm (e.g. PBKDF2)) + * Sequence (salt) + * CHOICE (this entry can be either of the types it contains) + * Octet string (actual salt) + * Object identifier (Anything using this is deferred to a later version of PKCS #5) + * Integer (iteration count) + * Sequence (encryption algorithm info) + * Object identifier (identifier for the algorithm) + * Algorithm dependent, is covered in the switch further down + */ + + static const QMap<QByteArray, QCryptographicHash::Algorithm> pbes2OidHashFunctionMap { + // PBES2/PBKDF2 + {HMAC_WITH_SHA1, QCryptographicHash::Sha1}, + {HMAC_WITH_SHA224, QCryptographicHash::Sha224}, + {HMAC_WITH_SHA256, QCryptographicHash::Sha256}, + {HMAC_WITH_SHA512, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_224, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_256, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA384, QCryptographicHash::Sha384} + }; + + // Values from their respective sections here: https://tools.ietf.org/html/rfc8018#appendix-B.2 + static const QMap<QSslKeyPrivate::Cipher, int> cipherKeyLengthMap { + {QSslKeyPrivate::Cipher::DesCbc, 8}, + {QSslKeyPrivate::Cipher::DesEde3Cbc, 24}, + // @note: variable key-length (https://tools.ietf.org/html/rfc8018#appendix-B.2.3) + {QSslKeyPrivate::Cipher::Rc2Cbc, 4} + // @todo: AES(, rc5?) + }; + + const QVector<QAsn1Element> keyDerivationContainer = element[0].toVector(); + if (keyDerivationContainer.size() != 2 + || keyDerivationContainer[0].type() != QAsn1Element::ObjectIdentifierType + || keyDerivationContainer[1].type() != QAsn1Element::SequenceType) { + return {}; + } + + const QByteArray keyDerivationAlgorithm = keyDerivationContainer[0].toObjectId(); + const QVector<QAsn1Element> keyDerivationParams = keyDerivationContainer[1].toVector(); + + const QVector<QAsn1Element> encryptionAlgorithmContainer = element[1].toVector(); + if (encryptionAlgorithmContainer.size() != 2 + || encryptionAlgorithmContainer[0].type() != QAsn1Element::ObjectIdentifierType) { + return {}; + } + + auto iterator = oidCipherMap.constFind(encryptionAlgorithmContainer[0].toObjectId()); + if (iterator == oidCipherMap.cend()) { + qWarning() + << "QSslKey: Unsupported encryption cipher OID:" << encryptionAlgorithmContainer[0].toObjectId() + << "\nFile a bugreport to Qt (include the line above)."; + return {}; + } + + QSslKeyPrivate::Cipher cipher = *iterator; + QByteArray key; + QByteArray iv; + switch (cipher) { + case QSslKeyPrivate::Cipher::DesCbc: + case QSslKeyPrivate::Cipher::DesEde3Cbc: + // https://tools.ietf.org/html/rfc8018#appendix-B.2.1 (DES-CBC-PAD) + // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 (DES-EDE3-CBC-PAD) + // @todo https://tools.ietf.org/html/rfc8018#appendix-B.2.5 (AES-CBC-PAD) + /*** Scheme: *** + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::OctetStringType) + return {}; + + // @note: All AES identifiers should be able to use this branch!! + iv = encryptionAlgorithmContainer[1].value(); + + if (iv.size() != 8) // @note: AES needs 16 bytes + return {}; + break; + case QSslKeyPrivate::Cipher::Rc2Cbc: { + // https://tools.ietf.org/html/rfc8018#appendix-B.2.3 + /*** Scheme: *** + * Sequence (rc2 parameters) + * Integer (rc2 parameter version) + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::SequenceType) + return {}; + const QVector<QAsn1Element> rc2ParametersContainer = encryptionAlgorithmContainer[1].toVector(); + if ((rc2ParametersContainer.size() != 1 && rc2ParametersContainer.size() != 2) + || rc2ParametersContainer.back().type() != QAsn1Element::OctetStringType) { + return {}; + } + iv = rc2ParametersContainer.back().value(); + if (iv.size() != 8) + return {}; + break; + } // @todo(?): case (RC5 , AES) + } + + if (Q_LIKELY(keyDerivationAlgorithm == PKCS5_PBKDF2_ENCRYPTION_OID)) { + // Definition: https://tools.ietf.org/html/rfc8018#appendix-A.2 + QByteArray salt; + if (keyDerivationParams[0].type() == QAsn1Element::OctetStringType) { + salt = keyDerivationParams[0].value(); + } else if (keyDerivationParams[0].type() == QAsn1Element::ObjectIdentifierType) { + Q_UNIMPLEMENTED(); + /* See paragraph from https://tools.ietf.org/html/rfc8018#appendix-A.2 + which ends with: "such facilities are deferred to a future version of PKCS #5" + */ + return {}; + } else { + return {}; + } + + // Iterations needed to derive the key + int iterationCount = keyDerivationParams[1].toInteger(); + // Optional integer + int keyLength = -1; + int vectorPos = 2; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::IntegerType) { + keyLength = keyDerivationParams[vectorPos].toInteger(nullptr); + ++vectorPos; + } else { + keyLength = cipherKeyLengthMap[cipher]; + } + + // Optional algorithm identifier (default: HMAC-SHA-1) + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::SequenceType) { + QVector<QAsn1Element> hashAlgorithmContainer = keyDerivationParams[vectorPos].toVector(); + hashAlgorithm = pbes2OidHashFunctionMap[hashAlgorithmContainer.front().toObjectId()]; + Q_ASSERT(hashAlgorithmContainer[1].type() == QAsn1Element::NullType); + ++vectorPos; + } + Q_ASSERT(keyDerivationParams.size() == vectorPos); + + key = QPasswordDigestor::deriveKeyPbkdf2(hashAlgorithm, passPhrase, salt, iterationCount, keyLength); + } else { + qWarning() + << "QSslKey: Unsupported key derivation algorithm OID:" << keyDerivationAlgorithm + << "\nFile a bugreport to Qt (include the line above)."; + return {}; + } + return {cipher, key, iv}; +} + +// Maps OIDs to the hash function it specifies +static const QMap<QByteArray, QCryptographicHash::Algorithm> pbes1OidHashFunctionMap { +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + // PKCS5 + //{PKCS5_MD2_DES_CBC_OID, QCryptographicHash::Md2}, No MD2 + //{PKCS5_MD2_RC2_CBC_OID, QCryptographicHash::Md2}, + {PKCS5_MD5_DES_CBC_OID, QCryptographicHash::Md5}, + {PKCS5_MD5_RC2_CBC_OID, QCryptographicHash::Md5}, +#endif + {PKCS5_SHA1_DES_CBC_OID, QCryptographicHash::Sha1}, + {PKCS5_SHA1_RC2_CBC_OID, QCryptographicHash::Sha1}, + // PKCS12 (unimplemented) + // {PKCS12_SHA1_RC4_128_OID, QCryptographicHash::Sha1}, // No RC4 + // {PKCS12_SHA1_RC4_40_OID, QCryptographicHash::Sha1}, + // @todo: lacking support. @note: there might be code to do this inside qsslsocket_mac... + // further note that more work may be required for the 3DES variations listed to be available. + // {PKCS12_SHA1_3KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_2KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_128_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_40_CBC_OID, QCryptographicHash::Sha1} +}; + + +static EncryptionData readPbes1(const QVector<QAsn1Element> &element, const QByteArray &encryptionScheme, const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.1 + // Steps refer to this section: https://tools.ietf.org/html/rfc8018#section-6.1.2 + /*** Scheme: *** + * Sequence (PBE Parameter) + * Octet string (salt) + * Integer (iteration counter) + */ + // Step 1 + if (element.size() != 2 + || element[0].type() != QAsn1Element::ElementType::OctetStringType + || element[1].type() != QAsn1Element::ElementType::IntegerType) { + return {}; + } + QByteArray salt = element[0].value(); + if (salt.size() != 8) + return {}; + + int iterationCount = element[1].toInteger(); + if (iterationCount < 0) + return {}; + + // Step 2 + auto iterator = pbes1OidHashFunctionMap.constFind(encryptionScheme); + if (iterator == pbes1OidHashFunctionMap.cend()) { + // Qt was compiled with ONLY_SHA1 (or it's MD2) + return {}; + } + QCryptographicHash::Algorithm hashAlgorithm = *iterator; + QByteArray key = QPasswordDigestor::deriveKeyPbkdf1(hashAlgorithm, passPhrase, salt, iterationCount, 16); + if (key.size() != 16) + return {}; + + // Step 3 + QByteArray iv = key.right(8); // last 8 bytes are used as IV + key.truncate(8); // first 8 bytes are used for the key + + QSslKeyPrivate::Cipher cipher = oidCipherMap[encryptionScheme]; +#ifdef Q_OS_WINRT + // @todo: document this instead? find some other solution? + if (cipher == QSslKeyPrivate::Cipher::Rc2Cbc) + qWarning("PBES1 with RC2_CBC doesn't work properly on WinRT."); +#endif + // Steps 4-6 are done after returning + return {cipher, key, iv}; +} + +QByteArray QSslKeyPrivate::decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase) +{ + // RFC 5958: https://tools.ietf.org/html/rfc5958 + /*** Scheme: *** + * Sequence + * Sequence + * Object Identifier (encryption scheme (currently PBES2, PBES1, @todo PKCS12)) + * Sequence (scheme parameters) + * Octet String (the encrypted data) + */ + QAsn1Element elem; + if (!elem.read(encrypted) || elem.type() != QAsn1Element::SequenceType) + return encrypted; + + const QVector<QAsn1Element> items = elem.toVector(); + if (items.size() != 2 + || items[0].type() != QAsn1Element::SequenceType + || items[1].type() != QAsn1Element::OctetStringType) { + return encrypted; + } + + const QVector<QAsn1Element> encryptionSchemeContainer = items[0].toVector(); + + if (encryptionSchemeContainer.size() != 2 + || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType + || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); + const QVector<QAsn1Element> schemeParameterContainer = encryptionSchemeContainer[1].toVector(); + + if (schemeParameterContainer.size() != 2 + && schemeParameterContainer[0].type() != QAsn1Element::SequenceType + && schemeParameterContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + EncryptionData data; + if (encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID) { + data = readPbes2(schemeParameterContainer, passPhrase); + } else if (pbes1OidHashFunctionMap.contains(encryptionScheme)) { + data = readPbes1(schemeParameterContainer, encryptionScheme, passPhrase); + } else if (encryptionScheme.startsWith(PKCS12_OID)) { + Q_UNIMPLEMENTED(); // this isn't some 'unknown', I know these aren't implemented + return encrypted; + } else { + qWarning() + << "QSslKey: Unsupported encryption scheme OID:" << encryptionScheme + << "\nFile a bugreport to Qt (include the line above)."; + return encrypted; + } + + if (!data.initialized) { + // something went wrong, return + return encrypted; + } + + QByteArray decryptedKey = decrypt(data.cipher, items[1].value(), data.key, data.iv); + // The data is still wrapped in a octet string, so let's unwrap it + QAsn1Element decryptedKeyElement(QAsn1Element::ElementType::OctetStringType, decryptedKey); + return decryptedKeyElement.value(); +} diff --git a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp b/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp index e39bcd30e5..ddfe52c5e4 100644 --- a/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp +++ b/tests/auto/network/ssl/qsslkey/tst_qsslkey.cpp @@ -164,10 +164,15 @@ void tst_QSslKey::createPlainTestRows(bool filter, QSsl::EncodingFormat format) foreach (KeyInfo keyInfo, keyInfoList) { if (filter && keyInfo.format != format) continue; - +#ifdef Q_OS_WINRT + if (keyInfo.fileInfo.fileName().contains("RC2-64")) + continue; // WinRT treats RC2 as 128 bit +#endif #if !defined(QT_NO_SSL) && defined(QT_NO_OPENSSL) // generic backend - if (keyInfo.fileInfo.fileName().contains("pkcs8")) - continue; // The generic backend does not support pkcs8 (yet) + if (keyInfo.fileInfo.fileName().contains(QRegularExpression("-aes\\d\\d\\d-"))) + continue; // No AES support in the generic back-end + if (keyInfo.fileInfo.fileName().contains("pkcs8-pkcs12")) + continue; // The generic back-end doesn't support PKCS#12 algorithms #endif QTest::newRow(keyInfo.fileInfo.fileName().toLatin1()) @@ -324,11 +329,15 @@ void tst_QSslKey::toPemOrDer() QFETCH(QSsl::KeyType, type); QFETCH(QSsl::EncodingFormat, format); - if (QByteArray(QTest::currentDataTag()).contains("-pkcs8-")) // these are encrypted + QByteArray dataTag = QByteArray(QTest::currentDataTag()); + if (dataTag.contains("-pkcs8-")) // these are encrypted QSKIP("Encrypted PKCS#8 keys gets decrypted when loaded. So we can't compare it to the encrypted version."); #ifndef QT_NO_OPENSSL - if (QByteArray(QTest::currentDataTag()).contains("pkcs8")) + if (dataTag.contains("pkcs8")) QSKIP("OpenSSL converts PKCS#8 keys to other formats, invalidating comparisons."); +#else // !openssl + if (dataTag.contains("pkcs8") && dataTag.contains("rsa")) + QSKIP("PKCS#8 RSA keys are changed into a different format in the generic back-end, meaning the comparison fails."); #endif // openssl QByteArray encoded = readFile(absFilePath); |