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 | |