| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | |
| 30 | #include <QtTest/QtTest> |
| 31 | #include <qsslkey.h> |
| 32 | #include <qsslsocket.h> |
| 33 | #include <QScopeGuard> |
| 34 | #include <qsslconfiguration.h> |
| 35 | #include <qsslellipticcurve.h> |
| 36 | |
| 37 | #include <QtNetwork/qhostaddress.h> |
| 38 | #include <QtNetwork/qnetworkproxy.h> |
| 39 | |
| 40 | #include <QtCore/qstring.h> |
| 41 | #include <QtCore/qdebug.h> |
| 42 | #include <QtCore/qlist.h> |
| 43 | |
| 44 | #ifdef QT_BUILD_INTERNAL |
| 45 | #ifndef QT_NO_SSL |
| 46 | #include "private/qsslkey_p.h" |
| 47 | #define TEST_CRYPTO |
| 48 | #endif |
| 49 | #ifndef QT_NO_OPENSSL |
| 50 | #include "private/qsslsocket_openssl_symbols_p.h" |
| 51 | #endif |
| 52 | #endif |
| 53 | |
| 54 | #include <algorithm> |
| 55 | |
| 56 | class tst_QSslKey : public QObject |
| 57 | { |
| 58 | Q_OBJECT |
| 59 | |
| 60 | struct KeyInfo { |
| 61 | QFileInfo fileInfo; |
| 62 | QSsl::KeyAlgorithm algorithm; |
| 63 | QSsl::KeyType type; |
| 64 | int length; |
| 65 | QSsl::EncodingFormat format; |
| 66 | KeyInfo( |
| 67 | const QFileInfo &fileInfo, QSsl::KeyAlgorithm algorithm, QSsl::KeyType type, |
| 68 | int length, QSsl::EncodingFormat format) |
| 69 | : fileInfo(fileInfo), algorithm(algorithm), type(type), length(length) |
| 70 | , format(format) {} |
| 71 | }; |
| 72 | |
| 73 | QList<KeyInfo> keyInfoList; |
| 74 | |
| 75 | void createPlainTestRows(bool pemOnly = false); |
| 76 | public: |
| 77 | tst_QSslKey(); |
| 78 | |
| 79 | public slots: |
| 80 | void initTestCase(); |
| 81 | |
| 82 | #ifndef QT_NO_SSL |
| 83 | |
| 84 | private slots: |
| 85 | void emptyConstructor(); |
| 86 | void constructor_data(); |
| 87 | void constructor(); |
| 88 | #ifndef QT_NO_OPENSSL |
| 89 | void constructorHandle_data(); |
| 90 | void constructorHandle(); |
| 91 | #endif |
| 92 | void copyAndAssign_data(); |
| 93 | void copyAndAssign(); |
| 94 | void equalsOperator(); |
| 95 | void length_data(); |
| 96 | void length(); |
| 97 | void toPemOrDer_data(); |
| 98 | void toPemOrDer(); |
| 99 | void toEncryptedPemOrDer_data(); |
| 100 | void toEncryptedPemOrDer(); |
| 101 | |
| 102 | void passphraseChecks_data(); |
| 103 | void passphraseChecks(); |
| 104 | void noPassphraseChecks(); |
| 105 | #ifdef TEST_CRYPTO |
| 106 | void encrypt_data(); |
| 107 | void encrypt(); |
| 108 | #endif |
| 109 | |
| 110 | #endif |
| 111 | private: |
| 112 | QString testDataDir; |
| 113 | |
| 114 | bool fileContainsUnsupportedEllipticCurve(const QString &fileName) const; |
| 115 | QVector<QString> unsupportedCurves; |
| 116 | }; |
| 117 | |
| 118 | tst_QSslKey::tst_QSslKey() |
| 119 | { |
| 120 | #ifndef QT_NO_SSL |
| 121 | const QString expectedCurves[] = { |
| 122 | // See how we generate them in keys/genkey.sh. |
| 123 | QStringLiteral("secp224r1" ), |
| 124 | QStringLiteral("prime256v1" ), |
| 125 | QStringLiteral("secp384r1" ), |
| 126 | QStringLiteral("brainpoolP256r1" ), |
| 127 | QStringLiteral("brainpoolP384r1" ), |
| 128 | QStringLiteral("brainpoolP512r1" ) |
| 129 | }; |
| 130 | const auto supportedCurves = QSslConfiguration::supportedEllipticCurves(); |
| 131 | |
| 132 | for (const auto &requestedEc : expectedCurves) { |
| 133 | auto pos = std::find_if(first: supportedCurves.begin(), last: supportedCurves.end(), |
| 134 | pred: [&requestedEc](const QSslEllipticCurve &supported) { |
| 135 | return requestedEc == supported.shortName(); |
| 136 | }); |
| 137 | if (pos == supportedCurves.end()) { |
| 138 | qWarning() << "EC with the name:" << requestedEc |
| 139 | << "is not supported by your build of OpenSSL and will not be tested." ; |
| 140 | unsupportedCurves.push_back(t: requestedEc); |
| 141 | } |
| 142 | } |
| 143 | #else |
| 144 | unsupportedCurves = {}; // not unsued anymore. |
| 145 | #endif |
| 146 | } |
| 147 | |
| 148 | bool tst_QSslKey::fileContainsUnsupportedEllipticCurve(const QString &fileName) const |
| 149 | { |
| 150 | for (const auto &name : unsupportedCurves) { |
| 151 | if (fileName.contains(s: name)) |
| 152 | return true; |
| 153 | } |
| 154 | return false; |
| 155 | } |
| 156 | |
| 157 | void tst_QSslKey::initTestCase() |
| 158 | { |
| 159 | testDataDir = QFileInfo(QFINDTESTDATA("rsa-without-passphrase.pem" )).absolutePath(); |
| 160 | if (testDataDir.isEmpty()) |
| 161 | testDataDir = QCoreApplication::applicationDirPath(); |
| 162 | if (!testDataDir.endsWith(s: QLatin1String("/" ))) |
| 163 | testDataDir += QLatin1String("/" ); |
| 164 | |
| 165 | QDir dir(testDataDir + "keys" ); |
| 166 | const QFileInfoList fileInfoList = dir.entryInfoList(filters: QDir::Files | QDir::Readable); |
| 167 | QRegExp rx(QLatin1String("^(rsa|dsa|dh|ec)-(pub|pri)-(\\d+)-?[\\w-]*\\.(pem|der)$" )); |
| 168 | for (const QFileInfo &fileInfo : fileInfoList) { |
| 169 | if (fileContainsUnsupportedEllipticCurve(fileName: fileInfo.fileName())) |
| 170 | continue; |
| 171 | |
| 172 | if (rx.indexIn(str: fileInfo.fileName()) >= 0) { |
| 173 | keyInfoList << KeyInfo( |
| 174 | fileInfo, |
| 175 | rx.cap(nth: 1) == QLatin1String("rsa" ) ? QSsl::Rsa : |
| 176 | rx.cap(nth: 1) == QLatin1String("dsa" ) ? QSsl::Dsa : |
| 177 | rx.cap(nth: 1) == QLatin1String("dh" ) ? QSsl::Dh : QSsl::Ec, |
| 178 | rx.cap(nth: 2) == QLatin1String("pub" ) ? QSsl::PublicKey : QSsl::PrivateKey, |
| 179 | rx.cap(nth: 3).toInt(), |
| 180 | rx.cap(nth: 4) == QLatin1String("pem" ) ? QSsl::Pem : QSsl::Der); |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | #ifndef QT_NO_SSL |
| 186 | |
| 187 | static QByteArray readFile(const QString &absFilePath) |
| 188 | { |
| 189 | QFile file(absFilePath); |
| 190 | if (!file.open(flags: QIODevice::ReadOnly)) { |
| 191 | QWARN("failed to open file" ); |
| 192 | return QByteArray(); |
| 193 | } |
| 194 | return file.readAll(); |
| 195 | } |
| 196 | |
| 197 | void tst_QSslKey::emptyConstructor() |
| 198 | { |
| 199 | if (!QSslSocket::supportsSsl()) |
| 200 | return; |
| 201 | |
| 202 | QSslKey key; |
| 203 | QVERIFY(key.isNull()); |
| 204 | QVERIFY(key.length() < 0); |
| 205 | |
| 206 | QSslKey key2; |
| 207 | QCOMPARE(key, key2); |
| 208 | } |
| 209 | |
| 210 | Q_DECLARE_METATYPE(QSsl::KeyAlgorithm) |
| 211 | Q_DECLARE_METATYPE(QSsl::KeyType) |
| 212 | Q_DECLARE_METATYPE(QSsl::EncodingFormat) |
| 213 | |
| 214 | void tst_QSslKey::createPlainTestRows(bool pemOnly) |
| 215 | { |
| 216 | QTest::addColumn<QString>(name: "absFilePath" ); |
| 217 | QTest::addColumn<QSsl::KeyAlgorithm>(name: "algorithm" ); |
| 218 | QTest::addColumn<QSsl::KeyType>(name: "type" ); |
| 219 | QTest::addColumn<int>(name: "length" ); |
| 220 | QTest::addColumn<QSsl::EncodingFormat>(name: "format" ); |
| 221 | foreach (KeyInfo keyInfo, keyInfoList) { |
| 222 | if (pemOnly && keyInfo.format != QSsl::EncodingFormat::Pem) |
| 223 | continue; |
| 224 | #if defined(Q_OS_WINRT) || QT_CONFIG(schannel) |
| 225 | if (keyInfo.fileInfo.fileName().contains("RC2-64" )) |
| 226 | continue; // WinRT/Schannel treats RC2 as 128 bit |
| 227 | #endif |
| 228 | #if !defined(QT_NO_SSL) && defined(QT_NO_OPENSSL) // generic backend |
| 229 | if (keyInfo.fileInfo.fileName().contains(QRegularExpression("-aes\\d\\d\\d-" ))) |
| 230 | continue; // No AES support in the generic back-end |
| 231 | if (keyInfo.fileInfo.fileName().contains("pkcs8-pkcs12" )) |
| 232 | continue; // The generic back-end doesn't support PKCS#12 algorithms |
| 233 | #endif |
| 234 | |
| 235 | QTest::newRow(dataTag: keyInfo.fileInfo.fileName().toLatin1()) |
| 236 | << keyInfo.fileInfo.absoluteFilePath() << keyInfo.algorithm << keyInfo.type |
| 237 | << keyInfo.length << keyInfo.format; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | void tst_QSslKey::constructor_data() |
| 242 | { |
| 243 | createPlainTestRows(); |
| 244 | } |
| 245 | |
| 246 | void tst_QSslKey::constructor() |
| 247 | { |
| 248 | if (!QSslSocket::supportsSsl()) |
| 249 | return; |
| 250 | |
| 251 | QFETCH(QString, absFilePath); |
| 252 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 253 | QFETCH(QSsl::KeyType, type); |
| 254 | QFETCH(QSsl::EncodingFormat, format); |
| 255 | |
| 256 | QByteArray encoded = readFile(absFilePath); |
| 257 | QByteArray passphrase; |
| 258 | if (QByteArray(QTest::currentDataTag()).contains(c: "-pkcs8-" )) |
| 259 | passphrase = QByteArray("1234" ); |
| 260 | QSslKey key(encoded, algorithm, format, type, passphrase); |
| 261 | QVERIFY(!key.isNull()); |
| 262 | } |
| 263 | |
| 264 | #ifndef QT_NO_OPENSSL |
| 265 | |
| 266 | void tst_QSslKey::constructorHandle_data() |
| 267 | { |
| 268 | createPlainTestRows(pemOnly: true); |
| 269 | } |
| 270 | |
| 271 | void tst_QSslKey::constructorHandle() |
| 272 | { |
| 273 | #ifndef QT_BUILD_INTERNAL |
| 274 | QSKIP("This test requires -developer-build." ); |
| 275 | #else |
| 276 | if (!QSslSocket::supportsSsl()) |
| 277 | return; |
| 278 | |
| 279 | QFETCH(QString, absFilePath); |
| 280 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 281 | QFETCH(QSsl::KeyType, type); |
| 282 | QFETCH(int, length); |
| 283 | |
| 284 | QByteArray pem = readFile(absFilePath); |
| 285 | auto func = (type == QSsl::KeyType::PublicKey |
| 286 | ? q_PEM_read_bio_PUBKEY |
| 287 | : q_PEM_read_bio_PrivateKey); |
| 288 | |
| 289 | QByteArray passphrase; |
| 290 | if (QByteArray(QTest::currentDataTag()).contains(c: "-pkcs8-" )) |
| 291 | passphrase = "1234" ; |
| 292 | |
| 293 | BIO* bio = q_BIO_new(a: q_BIO_s_mem()); |
| 294 | q_BIO_write(a: bio, b: pem.constData(), c: pem.length()); |
| 295 | EVP_PKEY *origin = func(bio, nullptr, nullptr, static_cast<void *>(passphrase.data())); |
| 296 | Q_ASSERT(origin); |
| 297 | q_EVP_PKEY_up_ref(a: origin); |
| 298 | QSslKey key(origin, type); |
| 299 | q_BIO_free(a: bio); |
| 300 | |
| 301 | EVP_PKEY *handle = q_EVP_PKEY_new(); |
| 302 | switch (algorithm) { |
| 303 | case QSsl::Rsa: |
| 304 | q_EVP_PKEY_set1_RSA(a: handle, b: static_cast<RSA *>(key.handle())); |
| 305 | break; |
| 306 | case QSsl::Dsa: |
| 307 | q_EVP_PKEY_set1_DSA(a: handle, b: static_cast<DSA *>(key.handle())); |
| 308 | break; |
| 309 | case QSsl::Dh: |
| 310 | q_EVP_PKEY_set1_DH(a: handle, b: static_cast<DH *>(key.handle())); |
| 311 | break; |
| 312 | #ifndef OPENSSL_NO_EC |
| 313 | case QSsl::Ec: |
| 314 | q_EVP_PKEY_set1_EC_KEY(a: handle, b: static_cast<EC_KEY *>(key.handle())); |
| 315 | break; |
| 316 | #endif |
| 317 | default: |
| 318 | break; |
| 319 | } |
| 320 | |
| 321 | auto cleanup = qScopeGuard(f: [origin, handle] { |
| 322 | q_EVP_PKEY_free(a: origin); |
| 323 | q_EVP_PKEY_free(a: handle); |
| 324 | }); |
| 325 | |
| 326 | QVERIFY(!key.isNull()); |
| 327 | QCOMPARE(key.algorithm(), algorithm); |
| 328 | QCOMPARE(key.type(), type); |
| 329 | QCOMPARE(key.length(), length); |
| 330 | QCOMPARE(q_EVP_PKEY_cmp(origin, handle), 1); |
| 331 | #endif |
| 332 | } |
| 333 | |
| 334 | #endif |
| 335 | |
| 336 | void tst_QSslKey::copyAndAssign_data() |
| 337 | { |
| 338 | createPlainTestRows(); |
| 339 | } |
| 340 | |
| 341 | void tst_QSslKey::copyAndAssign() |
| 342 | { |
| 343 | if (!QSslSocket::supportsSsl()) |
| 344 | return; |
| 345 | |
| 346 | QFETCH(QString, absFilePath); |
| 347 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 348 | QFETCH(QSsl::KeyType, type); |
| 349 | QFETCH(QSsl::EncodingFormat, format); |
| 350 | |
| 351 | QByteArray encoded = readFile(absFilePath); |
| 352 | QByteArray passphrase; |
| 353 | if (QByteArray(QTest::currentDataTag()).contains(c: "-pkcs8-" )) |
| 354 | passphrase = QByteArray("1234" ); |
| 355 | QSslKey key(encoded, algorithm, format, type, passphrase); |
| 356 | |
| 357 | QSslKey copied(key); |
| 358 | QCOMPARE(key, copied); |
| 359 | QCOMPARE(key.algorithm(), copied.algorithm()); |
| 360 | QCOMPARE(key.type(), copied.type()); |
| 361 | QCOMPARE(key.length(), copied.length()); |
| 362 | QCOMPARE(key.toPem(), copied.toPem()); |
| 363 | QCOMPARE(key.toDer(), copied.toDer()); |
| 364 | |
| 365 | QSslKey assigned = key; |
| 366 | QCOMPARE(key, assigned); |
| 367 | QCOMPARE(key.algorithm(), assigned.algorithm()); |
| 368 | QCOMPARE(key.type(), assigned.type()); |
| 369 | QCOMPARE(key.length(), assigned.length()); |
| 370 | QCOMPARE(key.toPem(), assigned.toPem()); |
| 371 | QCOMPARE(key.toDer(), assigned.toDer()); |
| 372 | } |
| 373 | |
| 374 | void tst_QSslKey::equalsOperator() |
| 375 | { |
| 376 | // ### unimplemented |
| 377 | } |
| 378 | |
| 379 | void tst_QSslKey::length_data() |
| 380 | { |
| 381 | createPlainTestRows(); |
| 382 | } |
| 383 | |
| 384 | void tst_QSslKey::length() |
| 385 | { |
| 386 | if (!QSslSocket::supportsSsl()) |
| 387 | return; |
| 388 | |
| 389 | QFETCH(QString, absFilePath); |
| 390 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 391 | QFETCH(QSsl::KeyType, type); |
| 392 | QFETCH(int, length); |
| 393 | QFETCH(QSsl::EncodingFormat, format); |
| 394 | |
| 395 | QByteArray encoded = readFile(absFilePath); |
| 396 | QByteArray passphrase; |
| 397 | if (QByteArray(QTest::currentDataTag()).contains(c: "-pkcs8-" )) |
| 398 | passphrase = QByteArray("1234" ); |
| 399 | QSslKey key(encoded, algorithm, format, type, passphrase); |
| 400 | QVERIFY(!key.isNull()); |
| 401 | QCOMPARE(key.length(), length); |
| 402 | } |
| 403 | |
| 404 | void tst_QSslKey::toPemOrDer_data() |
| 405 | { |
| 406 | createPlainTestRows(); |
| 407 | } |
| 408 | |
| 409 | void tst_QSslKey::toPemOrDer() |
| 410 | { |
| 411 | if (!QSslSocket::supportsSsl()) |
| 412 | return; |
| 413 | |
| 414 | QFETCH(QString, absFilePath); |
| 415 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 416 | QFETCH(QSsl::KeyType, type); |
| 417 | QFETCH(QSsl::EncodingFormat, format); |
| 418 | |
| 419 | QByteArray dataTag = QByteArray(QTest::currentDataTag()); |
| 420 | if (dataTag.contains(c: "-pkcs8-" )) // these are encrypted |
| 421 | QSKIP("Encrypted PKCS#8 keys gets decrypted when loaded. So we can't compare it to the encrypted version." ); |
| 422 | #ifndef QT_NO_OPENSSL |
| 423 | if (dataTag.contains(c: "pkcs8" )) |
| 424 | QSKIP("OpenSSL converts PKCS#8 keys to other formats, invalidating comparisons." ); |
| 425 | #else // !openssl |
| 426 | if (dataTag.contains("pkcs8" ) && dataTag.contains("rsa" )) |
| 427 | QSKIP("PKCS#8 RSA keys are changed into a different format in the generic back-end, meaning the comparison fails." ); |
| 428 | #endif // openssl |
| 429 | |
| 430 | QByteArray encoded = readFile(absFilePath); |
| 431 | QSslKey key(encoded, algorithm, format, type); |
| 432 | QVERIFY(!key.isNull()); |
| 433 | if (format == QSsl::Pem) |
| 434 | encoded.replace(before: '\r', c: "" ); |
| 435 | QCOMPARE(format == QSsl::Pem ? key.toPem() : key.toDer(), encoded); |
| 436 | } |
| 437 | |
| 438 | void tst_QSslKey::toEncryptedPemOrDer_data() |
| 439 | { |
| 440 | QTest::addColumn<QString>(name: "absFilePath" ); |
| 441 | QTest::addColumn<QSsl::KeyAlgorithm>(name: "algorithm" ); |
| 442 | QTest::addColumn<QSsl::KeyType>(name: "type" ); |
| 443 | QTest::addColumn<QSsl::EncodingFormat>(name: "format" ); |
| 444 | QTest::addColumn<QString>(name: "password" ); |
| 445 | |
| 446 | QStringList passwords; |
| 447 | passwords << " " << "foobar" << "foo bar" |
| 448 | << "aAzZ`1234567890-=~!@#$%^&*()_+[]{}\\|;:'\",.<>/?" ; // ### add more (?) |
| 449 | foreach (KeyInfo keyInfo, keyInfoList) { |
| 450 | if (keyInfo.fileInfo.fileName().contains(s: "pkcs8" )) |
| 451 | continue; // pkcs8 keys are encrypted in a different way than the other keys |
| 452 | foreach (QString password, passwords) { |
| 453 | const QByteArray testName = keyInfo.fileInfo.fileName().toLatin1() |
| 454 | + '-' + (keyInfo.algorithm == QSsl::Rsa ? "RSA" : |
| 455 | (keyInfo.algorithm == QSsl::Dsa ? "DSA" : "EC" )) |
| 456 | + '-' + (keyInfo.type == QSsl::PrivateKey ? "PrivateKey" : "PublicKey" ) |
| 457 | + '-' + (keyInfo.format == QSsl::Pem ? "PEM" : "DER" ) |
| 458 | + password.toLatin1(); |
| 459 | QTest::newRow(dataTag: testName.constData()) |
| 460 | << keyInfo.fileInfo.absoluteFilePath() << keyInfo.algorithm << keyInfo.type |
| 461 | << keyInfo.format << password; |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | void tst_QSslKey::toEncryptedPemOrDer() |
| 467 | { |
| 468 | if (!QSslSocket::supportsSsl()) |
| 469 | return; |
| 470 | |
| 471 | QFETCH(QString, absFilePath); |
| 472 | QFETCH(QSsl::KeyAlgorithm, algorithm); |
| 473 | QFETCH(QSsl::KeyType, type); |
| 474 | QFETCH(QSsl::EncodingFormat, format); |
| 475 | QFETCH(QString, password); |
| 476 | |
| 477 | QByteArray plain = readFile(absFilePath); |
| 478 | QSslKey key(plain, algorithm, format, type); |
| 479 | QVERIFY(!key.isNull()); |
| 480 | |
| 481 | QByteArray pwBytes(password.toLatin1()); |
| 482 | |
| 483 | if (type == QSsl::PrivateKey) { |
| 484 | QByteArray encryptedPem = key.toPem(passPhrase: pwBytes); |
| 485 | QVERIFY(!encryptedPem.isEmpty()); |
| 486 | QSslKey keyPem(encryptedPem, algorithm, QSsl::Pem, type, pwBytes); |
| 487 | QVERIFY(!keyPem.isNull()); |
| 488 | QCOMPARE(keyPem, key); |
| 489 | QCOMPARE(keyPem.toPem(), key.toPem()); |
| 490 | } else { |
| 491 | // verify that public keys are never encrypted by toPem() |
| 492 | QByteArray encryptedPem = key.toPem(passPhrase: pwBytes); |
| 493 | QVERIFY(!encryptedPem.isEmpty()); |
| 494 | QByteArray plainPem = key.toPem(); |
| 495 | QVERIFY(!plainPem.isEmpty()); |
| 496 | QCOMPARE(encryptedPem, plainPem); |
| 497 | } |
| 498 | |
| 499 | if (type == QSsl::PrivateKey) { |
| 500 | // verify that private keys are never "encrypted" by toDer() and |
| 501 | // instead an empty string is returned, see QTBUG-41038. |
| 502 | QByteArray encryptedDer = key.toDer(passPhrase: pwBytes); |
| 503 | QVERIFY(encryptedDer.isEmpty()); |
| 504 | } else { |
| 505 | // verify that public keys are never encrypted by toDer() |
| 506 | QByteArray encryptedDer = key.toDer(passPhrase: pwBytes); |
| 507 | QVERIFY(!encryptedDer.isEmpty()); |
| 508 | QByteArray plainDer = key.toDer(); |
| 509 | QVERIFY(!plainDer.isEmpty()); |
| 510 | QCOMPARE(encryptedDer, plainDer); |
| 511 | } |
| 512 | |
| 513 | // ### add a test to verify that public keys are _decrypted_ correctly (by the ctor) |
| 514 | } |
| 515 | |
| 516 | void tst_QSslKey::passphraseChecks_data() |
| 517 | { |
| 518 | QTest::addColumn<QString>(name: "fileName" ); |
| 519 | QTest::addColumn<QByteArray>(name: "passphrase" ); |
| 520 | |
| 521 | const QByteArray pass("123" ); |
| 522 | const QByteArray aesPass("1234" ); |
| 523 | |
| 524 | QTest::newRow(dataTag: "DES" ) << QString(testDataDir + "rsa-with-passphrase-des.pem" ) << pass; |
| 525 | QTest::newRow(dataTag: "3DES" ) << QString(testDataDir + "rsa-with-passphrase-3des.pem" ) << pass; |
| 526 | QTest::newRow(dataTag: "RC2" ) << QString(testDataDir + "rsa-with-passphrase-rc2.pem" ) << pass; |
| 527 | #if (!defined(QT_NO_OPENSSL) && !defined(OPENSSL_NO_AES)) || (defined(QT_NO_OPENSSL) && QT_CONFIG(ssl)) |
| 528 | QTest::newRow(dataTag: "AES128" ) << QString(testDataDir + "rsa-with-passphrase-aes128.pem" ) << aesPass; |
| 529 | QTest::newRow(dataTag: "AES192" ) << QString(testDataDir + "rsa-with-passphrase-aes192.pem" ) << aesPass; |
| 530 | QTest::newRow(dataTag: "AES256" ) << QString(testDataDir + "rsa-with-passphrase-aes256.pem" ) << aesPass; |
| 531 | #endif // (OpenSSL && AES) || generic backend |
| 532 | } |
| 533 | |
| 534 | void tst_QSslKey::passphraseChecks() |
| 535 | { |
| 536 | QFETCH(QString, fileName); |
| 537 | QFETCH(QByteArray, passphrase); |
| 538 | |
| 539 | QFile keyFile(fileName); |
| 540 | QVERIFY(keyFile.exists()); |
| 541 | { |
| 542 | if (!keyFile.isOpen()) |
| 543 | keyFile.open(flags: QIODevice::ReadOnly); |
| 544 | else |
| 545 | keyFile.reset(); |
| 546 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey); |
| 547 | QVERIFY(key.isNull()); // null passphrase => should not be able to decode key |
| 548 | } |
| 549 | { |
| 550 | if (!keyFile.isOpen()) |
| 551 | keyFile.open(flags: QIODevice::ReadOnly); |
| 552 | else |
| 553 | keyFile.reset(); |
| 554 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "" ); |
| 555 | QVERIFY(key.isNull()); // empty passphrase => should not be able to decode key |
| 556 | } |
| 557 | { |
| 558 | if (!keyFile.isOpen()) |
| 559 | keyFile.open(flags: QIODevice::ReadOnly); |
| 560 | else |
| 561 | keyFile.reset(); |
| 562 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "WRONG!" ); |
| 563 | QVERIFY(key.isNull()); // wrong passphrase => should not be able to decode key |
| 564 | } |
| 565 | { |
| 566 | if (!keyFile.isOpen()) |
| 567 | keyFile.open(flags: QIODevice::ReadOnly); |
| 568 | else |
| 569 | keyFile.reset(); |
| 570 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, passphrase); |
| 571 | QVERIFY(!key.isNull()); // correct passphrase |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | void tst_QSslKey::noPassphraseChecks() |
| 576 | { |
| 577 | // be sure and check a key without passphrase too |
| 578 | QString fileName(testDataDir + "rsa-without-passphrase.pem" ); |
| 579 | QFile keyFile(fileName); |
| 580 | { |
| 581 | if (!keyFile.isOpen()) |
| 582 | keyFile.open(flags: QIODevice::ReadOnly); |
| 583 | else |
| 584 | keyFile.reset(); |
| 585 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey); |
| 586 | QVERIFY(!key.isNull()); // null passphrase => should be able to decode key |
| 587 | } |
| 588 | { |
| 589 | if (!keyFile.isOpen()) |
| 590 | keyFile.open(flags: QIODevice::ReadOnly); |
| 591 | else |
| 592 | keyFile.reset(); |
| 593 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "" ); |
| 594 | QVERIFY(!key.isNull()); // empty passphrase => should be able to decode key |
| 595 | } |
| 596 | { |
| 597 | if (!keyFile.isOpen()) |
| 598 | keyFile.open(flags: QIODevice::ReadOnly); |
| 599 | else |
| 600 | keyFile.reset(); |
| 601 | QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "xxx" ); |
| 602 | QVERIFY(!key.isNull()); // passphrase given but key is not encrypted anyway => should work |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | #ifdef TEST_CRYPTO |
| 607 | Q_DECLARE_METATYPE(QSslKeyPrivate::Cipher) |
| 608 | |
| 609 | void tst_QSslKey::encrypt_data() |
| 610 | { |
| 611 | QTest::addColumn<QSslKeyPrivate::Cipher>(name: "cipher" ); |
| 612 | QTest::addColumn<QByteArray>(name: "key" ); |
| 613 | QTest::addColumn<QByteArray>(name: "plainText" ); |
| 614 | QTest::addColumn<QByteArray>(name: "cipherText" ); |
| 615 | QTest::addColumn<QByteArray>(name: "iv" ); |
| 616 | |
| 617 | QByteArray iv("abcdefgh" ); |
| 618 | QTest::newRow(dataTag: "DES-CBC, length 0" ) |
| 619 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 620 | << QByteArray() |
| 621 | << QByteArray::fromHex(hexEncoded: "956585228BAF9B1F" ) |
| 622 | << iv; |
| 623 | QTest::newRow(dataTag: "DES-CBC, length 1" ) |
| 624 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 625 | << QByteArray(1, 'a') |
| 626 | << QByteArray::fromHex(hexEncoded: "E6880AF202BA3C12" ) |
| 627 | << iv; |
| 628 | QTest::newRow(dataTag: "DES-CBC, length 2" ) |
| 629 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 630 | << QByteArray(2, 'a') |
| 631 | << QByteArray::fromHex(hexEncoded: "A82492386EED6026" ) |
| 632 | << iv; |
| 633 | QTest::newRow(dataTag: "DES-CBC, length 3" ) |
| 634 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 635 | << QByteArray(3, 'a') |
| 636 | << QByteArray::fromHex(hexEncoded: "90B76D5B79519CBA" ) |
| 637 | << iv; |
| 638 | QTest::newRow(dataTag: "DES-CBC, length 4" ) |
| 639 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 640 | << QByteArray(4, 'a') |
| 641 | << QByteArray::fromHex(hexEncoded: "63E3DD6FED87052A" ) |
| 642 | << iv; |
| 643 | QTest::newRow(dataTag: "DES-CBC, length 5" ) |
| 644 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 645 | << QByteArray(5, 'a') |
| 646 | << QByteArray::fromHex(hexEncoded: "03ACDB0EACBDFA94" ) |
| 647 | << iv; |
| 648 | QTest::newRow(dataTag: "DES-CBC, length 6" ) |
| 649 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 650 | << QByteArray(6, 'a') |
| 651 | << QByteArray::fromHex(hexEncoded: "7D95024E42A3A88A" ) |
| 652 | << iv; |
| 653 | QTest::newRow(dataTag: "DES-CBC, length 7" ) |
| 654 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 655 | << QByteArray(7, 'a') |
| 656 | << QByteArray::fromHex(hexEncoded: "5003436B8A8E42E9" ) |
| 657 | << iv; |
| 658 | QTest::newRow(dataTag: "DES-CBC, length 8" ) |
| 659 | << QSslKeyPrivate::DesCbc << QByteArray("01234567" ) |
| 660 | << QByteArray(8, 'a') |
| 661 | << QByteArray::fromHex(hexEncoded: "E4C1F054BF5521C0A4A0FD4A2BC6C1B1" ) |
| 662 | << iv; |
| 663 | |
| 664 | QTest::newRow(dataTag: "DES-EDE3-CBC, length 0" ) |
| 665 | << QSslKeyPrivate::DesEde3Cbc << QByteArray("0123456789abcdefghijklmn" ) |
| 666 | << QByteArray() |
| 667 | << QByteArray::fromHex(hexEncoded: "3B2B4CD0B0FD495F" ) |
| 668 | << iv; |
| 669 | QTest::newRow(dataTag: "DES-EDE3-CBC, length 8" ) |
| 670 | << QSslKeyPrivate::DesEde3Cbc << QByteArray("0123456789abcdefghijklmn" ) |
| 671 | << QByteArray(8, 'a') |
| 672 | << QByteArray::fromHex(hexEncoded: "F2A5A87763C54A72A3224103D90CDB03" ) |
| 673 | << iv; |
| 674 | |
| 675 | QTest::newRow(dataTag: "RC2-40-CBC, length 0" ) |
| 676 | << QSslKeyPrivate::Rc2Cbc << QByteArray("01234" ) |
| 677 | << QByteArray() |
| 678 | << QByteArray::fromHex(hexEncoded: "6D05D52392FF6E7A" ) |
| 679 | << iv; |
| 680 | QTest::newRow(dataTag: "RC2-40-CBC, length 8" ) |
| 681 | << QSslKeyPrivate::Rc2Cbc << QByteArray("01234" ) |
| 682 | << QByteArray(8, 'a') |
| 683 | << QByteArray::fromHex(hexEncoded: "75768E64C5749072A5D168F3AFEB0005" ) |
| 684 | << iv; |
| 685 | |
| 686 | QTest::newRow(dataTag: "RC2-64-CBC, length 0" ) |
| 687 | << QSslKeyPrivate::Rc2Cbc << QByteArray("01234567" ) |
| 688 | << QByteArray() |
| 689 | << QByteArray::fromHex(hexEncoded: "ADAE6BF70F420130" ) |
| 690 | << iv; |
| 691 | QTest::newRow(dataTag: "RC2-64-CBC, length 8" ) |
| 692 | << QSslKeyPrivate::Rc2Cbc << QByteArray("01234567" ) |
| 693 | << QByteArray(8, 'a') |
| 694 | << QByteArray::fromHex(hexEncoded: "C7BF5C80AFBE9FBEFBBB9FD935F6D0DF" ) |
| 695 | << iv; |
| 696 | |
| 697 | QTest::newRow(dataTag: "RC2-128-CBC, length 0" ) |
| 698 | << QSslKeyPrivate::Rc2Cbc << QByteArray("012345679abcdefg" ) |
| 699 | << QByteArray() |
| 700 | << QByteArray::fromHex(hexEncoded: "1E965D483A13C8FB" ) |
| 701 | << iv; |
| 702 | QTest::newRow(dataTag: "RC2-128-CBC, length 8" ) |
| 703 | << QSslKeyPrivate::Rc2Cbc << QByteArray("012345679abcdefg" ) |
| 704 | << QByteArray(8, 'a') |
| 705 | << QByteArray::fromHex(hexEncoded: "5AEC1A5B295660B02613454232F7DECE" ) |
| 706 | << iv; |
| 707 | |
| 708 | #if (!defined(QT_NO_OPENSSL) && !defined(OPENSSL_NO_AES)) || (defined(QT_NO_OPENSSL) && QT_CONFIG(ssl)) |
| 709 | // AES needs a longer IV |
| 710 | iv = QByteArray("abcdefghijklmnop" ); |
| 711 | QTest::newRow(dataTag: "AES-128-CBC, length 0" ) |
| 712 | << QSslKeyPrivate::Aes128Cbc << QByteArray("012345679abcdefg" ) |
| 713 | << QByteArray() |
| 714 | << QByteArray::fromHex(hexEncoded: "28DE1A9AA26601C30DD2527407121D1A" ) |
| 715 | << iv; |
| 716 | QTest::newRow(dataTag: "AES-128-CBC, length 8" ) |
| 717 | << QSslKeyPrivate::Aes128Cbc << QByteArray("012345679abcdefg" ) |
| 718 | << QByteArray(8, 'a') |
| 719 | << QByteArray::fromHex(hexEncoded: "08E880B1BA916F061C1E801D7F44D0EC" ) |
| 720 | << iv; |
| 721 | |
| 722 | QTest::newRow(dataTag: "AES-192-CBC, length 0" ) |
| 723 | << QSslKeyPrivate::Aes192Cbc << QByteArray("0123456789abcdefghijklmn" ) |
| 724 | << QByteArray() |
| 725 | << QByteArray::fromHex(hexEncoded: "E169E0E205CDC2BA895B7CF6097673B1" ) |
| 726 | << iv; |
| 727 | QTest::newRow(dataTag: "AES-192-CBC, length 8" ) |
| 728 | << QSslKeyPrivate::Aes192Cbc << QByteArray("0123456789abcdefghijklmn" ) |
| 729 | << QByteArray(8, 'a') |
| 730 | << QByteArray::fromHex(hexEncoded: "3A227D6A3A13237316D30AA17FF9B0A7" ) |
| 731 | << iv; |
| 732 | |
| 733 | QTest::newRow(dataTag: "AES-256-CBC, length 0" ) |
| 734 | << QSslKeyPrivate::Aes256Cbc << QByteArray("0123456789abcdefghijklmnopqrstuv" ) |
| 735 | << QByteArray() |
| 736 | << QByteArray::fromHex(hexEncoded: "4BAACAA0D22199C97DE206C465B7B14A" ) |
| 737 | << iv; |
| 738 | QTest::newRow(dataTag: "AES-256-CBC, length 8" ) |
| 739 | << QSslKeyPrivate::Aes256Cbc << QByteArray("0123456789abcdefghijklmnopqrstuv" ) |
| 740 | << QByteArray(8, 'a') |
| 741 | << QByteArray::fromHex(hexEncoded: "879C8C25EC135CDF0B14490A0A7C2F67" ) |
| 742 | << iv; |
| 743 | #endif // (OpenSSL && AES) || generic backend |
| 744 | } |
| 745 | |
| 746 | void tst_QSslKey::encrypt() |
| 747 | { |
| 748 | QFETCH(QSslKeyPrivate::Cipher, cipher); |
| 749 | QFETCH(QByteArray, key); |
| 750 | QFETCH(QByteArray, plainText); |
| 751 | QFETCH(QByteArray, cipherText); |
| 752 | QFETCH(QByteArray, iv); |
| 753 | |
| 754 | #if defined(Q_OS_WINRT) || QT_CONFIG(schannel) |
| 755 | QEXPECT_FAIL("RC2-40-CBC, length 0" , "WinRT/Schannel treats RC2 as 128-bit" , Abort); |
| 756 | QEXPECT_FAIL("RC2-40-CBC, length 8" , "WinRT/Schannel treats RC2 as 128-bit" , Abort); |
| 757 | QEXPECT_FAIL("RC2-64-CBC, length 0" , "WinRT/Schannel treats RC2 as 128-bit" , Abort); |
| 758 | QEXPECT_FAIL("RC2-64-CBC, length 8" , "WinRT/Schannel treats RC2 as 128-bit" , Abort); |
| 759 | #endif |
| 760 | QByteArray encrypted = QSslKeyPrivate::encrypt(cipher, data: plainText, key, iv); |
| 761 | QCOMPARE(encrypted, cipherText); |
| 762 | |
| 763 | QByteArray decrypted = QSslKeyPrivate::decrypt(cipher, data: cipherText, key, iv); |
| 764 | QCOMPARE(decrypted, plainText); |
| 765 | } |
| 766 | #endif |
| 767 | |
| 768 | #endif |
| 769 | |
| 770 | QTEST_MAIN(tst_QSslKey) |
| 771 | #include "tst_qsslkey.moc" |
| 772 | |