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 <qsslcertificate.h>
32#include <qsslkey.h>
33#include <qsslsocket.h>
34#include <qsslcertificateextension.h>
35
36#ifndef QT_NO_OPENSSL
37#include <openssl/obj_mac.h>
38#endif
39
40class tst_QSslCertificate : public QObject
41{
42 Q_OBJECT
43
44 struct CertInfo {
45 QFileInfo fileInfo;
46 QFileInfo fileInfo_digest_md5;
47 QFileInfo fileInfo_digest_sha1;
48 QSsl::EncodingFormat format;
49 CertInfo(const QFileInfo &fileInfo, QSsl::EncodingFormat format)
50 : fileInfo(fileInfo), format(format) {}
51 };
52
53 QList<CertInfo> certInfoList;
54 QMap<QString, QString> subjAltNameMap;
55 QMap<QString, QString> pubkeyMap;
56 QMap<QString, QString> md5Map;
57 QMap<QString, QString> sha1Map;
58
59 void createTestRows();
60#ifndef QT_NO_SSL
61 void compareCertificates(const QSslCertificate & cert1, const QSslCertificate & cert2);
62#endif
63
64public slots:
65 void initTestCase();
66
67#ifndef QT_NO_SSL
68private slots:
69 void hash();
70 void emptyConstructor();
71 void constructor_data();
72 void constructor();
73 void constructor_device();
74 void constructingGarbage();
75 void copyAndAssign_data();
76 void copyAndAssign();
77 void digest_data();
78 void digest();
79 void subjectAlternativeNames_data();
80 void utf8SubjectNames();
81 void subjectAlternativeNames();
82 void subjectInfoToString();
83 void subjectIssuerDisplayName_data();
84 void subjectIssuerDisplayName();
85 void publicKey_data();
86 void publicKey();
87 void toPemOrDer_data();
88 void toPemOrDer();
89 void fromDevice();
90 void fromPath_data();
91 void fromPath();
92 void fromPath_qregularexpression_data();
93 void fromPath_qregularexpression();
94 void certInfo();
95 void certInfoQByteArray();
96 void task256066toPem();
97 void nulInCN();
98 void nulInSan();
99 void largeSerialNumber();
100 void largeExpirationDate();
101 void blacklistedCertificates();
102 void selfsignedCertificates();
103 void toText();
104 void multipleCommonNames();
105 void subjectAndIssuerAttributes();
106 void verify();
107 void extensions();
108 void extensionsCritical();
109 void threadSafeConstMethods();
110 void version_data();
111 void version();
112 void pkcs12();
113
114 // helper for verbose test failure messages
115 QString toString(const QList<QSslError>&);
116
117// ### add tests for certificate bundles (multiple certificates concatenated into a single
118// structure); both PEM and DER formatted
119#endif
120private:
121 QString testDataDir;
122};
123
124void tst_QSslCertificate::initTestCase()
125{
126 testDataDir = QFileInfo(QFINDTESTDATA("certificates")).absolutePath();
127 if (testDataDir.isEmpty())
128 testDataDir = QCoreApplication::applicationDirPath();
129 if (!testDataDir.endsWith(s: QLatin1String("/")))
130 testDataDir += QLatin1String("/");
131
132 QDir dir(testDataDir + "certificates");
133 QFileInfoList fileInfoList = dir.entryInfoList(filters: QDir::Files | QDir::Readable);
134 QRegExp rxCert(QLatin1String("^.+\\.(pem|der)$"));
135 QRegExp rxSan(QLatin1String("^(.+\\.(?:pem|der))\\.san$"));
136 QRegExp rxPubKey(QLatin1String("^(.+\\.(?:pem|der))\\.pubkey$"));
137 QRegExp rxDigest(QLatin1String("^(.+\\.(?:pem|der))\\.digest-(md5|sha1)$"));
138 foreach (QFileInfo fileInfo, fileInfoList) {
139 if (rxCert.indexIn(str: fileInfo.fileName()) >= 0)
140 certInfoList <<
141 CertInfo(fileInfo,
142 rxCert.cap(nth: 1) == QLatin1String("pem") ? QSsl::Pem : QSsl::Der);
143 if (rxSan.indexIn(str: fileInfo.fileName()) >= 0)
144 subjAltNameMap.insert(key: rxSan.cap(nth: 1), value: fileInfo.absoluteFilePath());
145 if (rxPubKey.indexIn(str: fileInfo.fileName()) >= 0)
146 pubkeyMap.insert(key: rxPubKey.cap(nth: 1), value: fileInfo.absoluteFilePath());
147 if (rxDigest.indexIn(str: fileInfo.fileName()) >= 0) {
148 if (rxDigest.cap(nth: 2) == QLatin1String("md5"))
149 md5Map.insert(key: rxDigest.cap(nth: 1), value: fileInfo.absoluteFilePath());
150 else
151 sha1Map.insert(key: rxDigest.cap(nth: 1), value: fileInfo.absoluteFilePath());
152 }
153 }
154}
155
156#ifndef QT_NO_SSL
157
158void tst_QSslCertificate::hash()
159{
160 // mostly a compile-only test, to check that qHash(QSslCertificate) is found.
161 QSet<QSslCertificate> certs;
162 certs << QSslCertificate();
163 QCOMPARE(certs.size(), 1);
164}
165
166static QByteArray readFile(const QString &absFilePath)
167{
168 QFile file(absFilePath);
169 if (!file.open(flags: QIODevice::ReadOnly)) {
170 QWARN("failed to open file");
171 return QByteArray();
172 }
173 return file.readAll();
174}
175
176void tst_QSslCertificate::emptyConstructor()
177{
178 if (!QSslSocket::supportsSsl())
179 return;
180
181 QSslCertificate certificate;
182 QVERIFY(certificate.isNull());
183 //make sure none of the functions crash (task 203035)
184 QVERIFY(!certificate.isBlacklisted());
185 QCOMPARE(certificate.version() , QByteArray());
186 QCOMPARE(certificate.serialNumber(), QByteArray());
187 QCOMPARE(certificate.digest(), QCryptographicHash::hash(QByteArray(), QCryptographicHash::Md5));
188 QCOMPARE(certificate.issuerInfo(QSslCertificate::Organization), QStringList());
189 QCOMPARE(certificate.subjectInfo(QSslCertificate::Organization), QStringList());
190 QCOMPARE(certificate.subjectAlternativeNames(),(QMultiMap<QSsl::AlternativeNameEntryType, QString>()));
191 QCOMPARE(certificate.effectiveDate(), QDateTime());
192 QCOMPARE(certificate.expiryDate(), QDateTime());
193}
194
195Q_DECLARE_METATYPE(QSsl::EncodingFormat);
196
197void tst_QSslCertificate::createTestRows()
198{
199 QTest::addColumn<QString>(name: "absFilePath");
200 QTest::addColumn<QSsl::EncodingFormat>(name: "format");
201 foreach (CertInfo certInfo, certInfoList) {
202 QTest::newRow(dataTag: certInfo.fileInfo.fileName().toLatin1())
203 << certInfo.fileInfo.absoluteFilePath() << certInfo.format;
204 }
205}
206
207void tst_QSslCertificate::constructor_data()
208{
209 createTestRows();
210}
211
212void tst_QSslCertificate::constructor()
213{
214 if (!QSslSocket::supportsSsl())
215 return;
216
217 QFETCH(QString, absFilePath);
218 QFETCH(QSsl::EncodingFormat, format);
219
220 QByteArray encoded = readFile(absFilePath);
221 QSslCertificate certificate(encoded, format);
222 QVERIFY(!certificate.isNull());
223}
224
225void tst_QSslCertificate::constructor_device()
226{
227 if (!QSslSocket::supportsSsl())
228 return;
229
230 QFile f(testDataDir + "verify-certs/test-ocsp-good-cert.pem");
231 bool ok = f.open(flags: QIODevice::ReadOnly);
232 QVERIFY(ok);
233
234 QSslCertificate cert(&f);
235 QVERIFY(!cert.isNull());
236 f.close();
237
238 // Check opening a DER as a PEM fails
239 QFile f2(testDataDir + "certificates/cert.der");
240 ok = f2.open(flags: QIODevice::ReadOnly);
241 QVERIFY(ok);
242
243 QSslCertificate cert2(&f2);
244 QVERIFY(cert2.isNull());
245 f2.close();
246
247 // Check opening a DER as a DER works
248 QFile f3(testDataDir + "certificates/cert.der");
249 ok = f3.open(flags: QIODevice::ReadOnly);
250 QVERIFY(ok);
251
252 QSslCertificate cert3(&f3, QSsl::Der);
253 QVERIFY(!cert3.isNull());
254 f3.close();
255
256 // Check opening a PEM as a DER fails
257 QFile f4(testDataDir + "verify-certs/test-ocsp-good-cert.pem");
258 ok = f4.open(flags: QIODevice::ReadOnly);
259 QVERIFY(ok);
260
261 QSslCertificate cert4(&f4, QSsl::Der);
262 QVERIFY(cert4.isNull());
263 f4.close();
264}
265
266void tst_QSslCertificate::constructingGarbage()
267{
268 if (!QSslSocket::supportsSsl())
269 return;
270
271 QByteArray garbage("garbage");
272 QSslCertificate certificate(garbage);
273 QVERIFY(certificate.isNull());
274}
275
276void tst_QSslCertificate::copyAndAssign_data()
277{
278 createTestRows();
279}
280
281void tst_QSslCertificate::compareCertificates(
282 const QSslCertificate & cert1, const QSslCertificate & cert2)
283{
284 QCOMPARE(cert1.isNull(), cert2.isNull());
285 // Note: in theory, the next line could fail even if the certificates are identical!
286 QCOMPARE(cert1.isBlacklisted(), cert2.isBlacklisted());
287 QCOMPARE(cert1.version(), cert2.version());
288 QCOMPARE(cert1.serialNumber(), cert2.serialNumber());
289 QCOMPARE(cert1.digest(), cert2.digest());
290 QCOMPARE(cert1.toPem(), cert2.toPem());
291 QCOMPARE(cert1.toDer(), cert2.toDer());
292 for (int info = QSslCertificate::Organization;
293 info <= QSslCertificate::StateOrProvinceName; info++) {
294 const QSslCertificate::SubjectInfo subjectInfo = (QSslCertificate::SubjectInfo)info;
295 QCOMPARE(cert1.issuerInfo(subjectInfo), cert2.issuerInfo(subjectInfo));
296 QCOMPARE(cert1.subjectInfo(subjectInfo), cert2.subjectInfo(subjectInfo));
297 }
298 QCOMPARE(cert1.subjectAlternativeNames(), cert2.subjectAlternativeNames());
299 QCOMPARE(cert1.effectiveDate(), cert2.effectiveDate());
300 QCOMPARE(cert1.expiryDate(), cert2.expiryDate());
301 QCOMPARE(cert1.version(), cert2.version());
302 QCOMPARE(cert1.serialNumber(), cert2.serialNumber());
303 // ### add more functions here ...
304}
305
306void tst_QSslCertificate::copyAndAssign()
307{
308 if (!QSslSocket::supportsSsl())
309 return;
310
311 QFETCH(QString, absFilePath);
312 QFETCH(QSsl::EncodingFormat, format);
313
314 QByteArray encoded = readFile(absFilePath);
315 QSslCertificate certificate(encoded, format);
316
317 QVERIFY(!certificate.isNull());
318
319 QSslCertificate copied(certificate);
320 compareCertificates(cert1: certificate, cert2: copied);
321
322 QSslCertificate assigned = certificate;
323 compareCertificates(cert1: certificate, cert2: assigned);
324}
325
326void tst_QSslCertificate::digest_data()
327{
328 QTest::addColumn<QString>(name: "absFilePath");
329 QTest::addColumn<QSsl::EncodingFormat>(name: "format");
330 QTest::addColumn<QString>(name: "absFilePath_digest_md5");
331 QTest::addColumn<QString>(name: "absFilePath_digest_sha1");
332 foreach (CertInfo certInfo, certInfoList) {
333 QString certName = certInfo.fileInfo.fileName();
334 QTest::newRow(dataTag: certName.toLatin1())
335 << certInfo.fileInfo.absoluteFilePath()
336 << certInfo.format
337 << md5Map.value(akey: certName)
338 << sha1Map.value(akey: certName);
339 }
340}
341
342// Converts a digest of the form '{MD5|SHA1} Fingerprint=AB:B8:32...' to binary format.
343static QByteArray convertDigest(const QByteArray &input)
344{
345 QByteArray result;
346 QRegExp rx(QLatin1String("(?:=|:)([0-9A-Fa-f]{2})"));
347 int pos = 0;
348 while ((pos = rx.indexIn(str: input, offset: pos)) != -1) {
349 result.append(a: rx.cap(nth: 1).toLatin1());
350 pos += rx.matchedLength();
351 }
352 return QByteArray::fromHex(hexEncoded: result);
353}
354
355void tst_QSslCertificate::digest()
356{
357 if (!QSslSocket::supportsSsl())
358 return;
359
360 QFETCH(QString, absFilePath);
361 QFETCH(QSsl::EncodingFormat, format);
362 QFETCH(QString, absFilePath_digest_md5);
363 QFETCH(QString, absFilePath_digest_sha1);
364
365 QByteArray encoded = readFile(absFilePath);
366 QSslCertificate certificate(encoded, format);
367 QVERIFY(!certificate.isNull());
368
369 if (!absFilePath_digest_md5.isEmpty())
370 QCOMPARE(convertDigest(readFile(absFilePath_digest_md5)),
371 certificate.digest(QCryptographicHash::Md5));
372
373 if (!absFilePath_digest_sha1.isEmpty())
374 QCOMPARE(convertDigest(readFile(absFilePath_digest_sha1)),
375 certificate.digest(QCryptographicHash::Sha1));
376}
377
378void tst_QSslCertificate::subjectAlternativeNames_data()
379{
380 QTest::addColumn<QString>(name: "certFilePath");
381 QTest::addColumn<QSsl::EncodingFormat>(name: "format");
382 QTest::addColumn<QString>(name: "subjAltNameFilePath");
383
384 foreach (CertInfo certInfo, certInfoList) {
385 QString certName = certInfo.fileInfo.fileName();
386 if (subjAltNameMap.contains(key: certName))
387 QTest::newRow(dataTag: certName.toLatin1())
388 << certInfo.fileInfo.absoluteFilePath()
389 << certInfo.format
390 << subjAltNameMap.value(akey: certName);
391 }
392}
393
394void tst_QSslCertificate::subjectAlternativeNames()
395{
396 if (!QSslSocket::supportsSsl())
397 return;
398
399 QFETCH(QString, certFilePath);
400 QFETCH(QSsl::EncodingFormat, format);
401 QFETCH(QString, subjAltNameFilePath);
402
403 QByteArray encodedCert = readFile(absFilePath: certFilePath);
404 QSslCertificate certificate(encodedCert, format);
405 QVERIFY(!certificate.isNull());
406
407 QByteArray fileContents = readFile(absFilePath: subjAltNameFilePath);
408
409 const QMultiMap<QSsl::AlternativeNameEntryType, QString> altSubjectNames =
410 certificate.subjectAlternativeNames();
411
412 // verify that each entry in subjAltNames is present in fileContents
413 for (auto it = altSubjectNames.cbegin(), end = altSubjectNames.cend(); it != end; ++it) {
414 QByteArray type;
415 if (it.key() == QSsl::EmailEntry)
416 type = "email";
417 else if (it.key() == QSsl::DnsEntry)
418 type = "DNS";
419 else
420 QFAIL("unsupported alternative name type");
421 const QByteArray entry = type + ':' + it.value().toLatin1();
422 QVERIFY(fileContents.contains(entry));
423 }
424
425 // verify that each entry in fileContents is present in subjAltNames
426 QRegExp rx(QLatin1String("(email|DNS):([^,\\r\\n]+)"));
427 for (int pos = 0; (pos = rx.indexIn(str: fileContents, offset: pos)) != -1; pos += rx.matchedLength()) {
428 QSsl::AlternativeNameEntryType key;
429 if (rx.cap(nth: 1) == QLatin1String("email"))
430 key = QSsl::EmailEntry;
431 else if (rx.cap(nth: 1) == QLatin1String("DNS"))
432 key = QSsl::DnsEntry;
433 else
434 QFAIL("unsupported alternative name type");
435 QVERIFY(altSubjectNames.contains(key, rx.cap(2)));
436 }
437}
438
439void tst_QSslCertificate::subjectInfoToString()
440{
441 QFile certFile(testDataDir + "more-certificates/aspiriniks.ca.crt");
442 const bool ok = certFile.open(flags: QIODevice::ReadOnly);
443 QVERIFY(ok);
444 const auto chain = QSslCertificate::fromDevice(device: &certFile, format: QSsl::Pem);
445 QCOMPARE(chain.size(), 1);
446 const auto cert = chain.at(i: 0);
447 QVERIFY(!cert.isNull());
448
449 const auto testInfo = [&cert](QSslCertificate::SubjectInfo info, const QString &expected) {
450 const auto infoAsList = cert.subjectInfo(info);
451 if (infoAsList.size())
452 return expected == infoAsList.at(i: 0);
453 return expected == QString();
454 };
455
456 QVERIFY(testInfo(QSslCertificate::Organization, QStringLiteral("TT ASA")));
457 QVERIFY(testInfo(QSslCertificate::CommonName, QStringLiteral("aspiriniks.troll.no")));
458 QVERIFY(testInfo(QSslCertificate::LocalityName, QStringLiteral("Oslo")));
459 QVERIFY(testInfo(QSslCertificate::OrganizationalUnitName, QStringLiteral("QT SW")));
460 QVERIFY(testInfo(QSslCertificate::CountryName, QStringLiteral("NO")));
461 QVERIFY(testInfo(QSslCertificate::StateOrProvinceName, QStringLiteral("Oslo")));
462 QVERIFY(testInfo(QSslCertificate::DistinguishedNameQualifier, QString()));
463 QVERIFY(testInfo(QSslCertificate::SerialNumber, QString()));
464#ifndef QT_NO_OPENSSL
465 // TODO: check why generic code does not handle this!
466 QVERIFY(testInfo(QSslCertificate::EmailAddress, QStringLiteral("ababic@trolltech.com")));
467#endif
468}
469
470void tst_QSslCertificate::subjectIssuerDisplayName_data()
471{
472 QTest::addColumn<QString>(name: "certName");
473 QTest::addColumn<QString>(name: "expectedName");
474
475 QTest::addRow(format: "CommonName") << QStringLiteral("more-certificates/cert-cn.pem") << QStringLiteral("YOUR name");
476 QTest::addRow(format: "OrganizationName") << QStringLiteral("more-certificates/cert-on.pem") << QStringLiteral("R&D");
477 QTest::addRow(format: "OrganizationUnitName") << QStringLiteral("more-certificates/cert-oun.pem") << QStringLiteral("Foundations");
478#ifndef QT_NO_OPENSSL
479 QTest::addRow(format: "NoSubjectName") << QStringLiteral("more-certificates/cert-noname.pem") << QString();
480#endif
481}
482
483void tst_QSslCertificate::subjectIssuerDisplayName()
484{
485 QFETCH(const QString, certName);
486 QFETCH(const QString, expectedName);
487
488 const auto chain = QSslCertificate::fromPath(path: testDataDir + certName);
489 QCOMPARE(chain.size(), 1);
490 const auto cert = chain.at(i: 0);
491 QVERIFY(!cert.isNull());
492 QCOMPARE(cert.subjectDisplayName(), expectedName);
493 QCOMPARE(cert.issuerDisplayName(), expectedName);
494}
495
496void tst_QSslCertificate::utf8SubjectNames()
497{
498 QSslCertificate cert = QSslCertificate::fromPath(path: testDataDir + "certificates/cert-ss-san-utf8.pem", format: QSsl::Pem,
499 syntax: QSslCertificate::PatternSyntax::FixedString).first();
500 QVERIFY(!cert.isNull());
501
502 // O is "Heavy Metal Records" with heavy use of "decorations" like accents, umlauts etc.,
503 // OU uses arabian / asian script letters near codepoint 64K.
504 // strings split where the compiler would otherwise find three-digit hex numbers
505 static const char *o = "H\xc4\x95\xc4\x82\xc6\xb2\xc3\xbf \xca\x8d\xe1\xba\xbf\xca\x88\xe1\xba"
506 "\xb7\xe1\xb8\xbb R\xc3\xa9" "c" "\xc3\xb6rd\xc5\x9d";
507 static const char *ou = "\xe3\x88\xa7" "A" "\xe3\x89\x81\xef\xbd\xab" "BC";
508
509 // the following two tests should help find "\x"-literal encoding bugs in the test itself
510 QCOMPARE(cert.subjectInfo("O")[0].length(), QString::fromUtf8(o).length());
511 QCOMPARE (cert.subjectInfo("O")[0].toUtf8().toHex(), QByteArray(o).toHex());
512
513 QCOMPARE(cert.subjectInfo("O")[0], QString::fromUtf8(o));
514 QCOMPARE(cert.subjectInfo("OU")[0], QString::fromUtf8(ou));
515}
516
517void tst_QSslCertificate::publicKey_data()
518{
519 QTest::addColumn<QString>(name: "certFilePath");
520 QTest::addColumn<QSsl::EncodingFormat>(name: "format");
521 QTest::addColumn<QString>(name: "pubkeyFilePath");
522
523 foreach (CertInfo certInfo, certInfoList) {
524 QString certName = certInfo.fileInfo.fileName();
525 if (pubkeyMap.contains(key: certName))
526 QTest::newRow(dataTag: certName.toLatin1())
527 << certInfo.fileInfo.absoluteFilePath()
528 << certInfo.format
529 << pubkeyMap.value(akey: certName);
530 }
531}
532
533void tst_QSslCertificate::publicKey()
534{
535 if (!QSslSocket::supportsSsl())
536 return;
537
538 QFETCH(QString, certFilePath);
539 QFETCH(QSsl::EncodingFormat, format);
540 QFETCH(QString, pubkeyFilePath);
541
542 QSsl::KeyAlgorithm algorithm;
543 if (QFileInfo(pubkeyFilePath).fileName().startsWith(s: "dsa-"))
544 algorithm = QSsl::Dsa;
545 else if (QFileInfo(pubkeyFilePath).fileName().startsWith(s: "ec-"))
546 algorithm = QSsl::Ec;
547 else
548 algorithm = QSsl::Rsa;
549
550 QByteArray encodedCert = readFile(absFilePath: certFilePath);
551 QSslCertificate certificate(encodedCert, format);
552 QVERIFY(!certificate.isNull());
553
554 QByteArray encodedPubkey = readFile(absFilePath: pubkeyFilePath);
555 QSslKey pubkey(encodedPubkey, algorithm, format, QSsl::PublicKey);
556 QVERIFY(!pubkey.isNull());
557
558 QCOMPARE(certificate.publicKey(), pubkey);
559}
560
561void tst_QSslCertificate::toPemOrDer_data()
562{
563 createTestRows();
564}
565
566static const char BeginCertString[] = "-----BEGIN CERTIFICATE-----";
567static const char EndCertString[] = "-----END CERTIFICATE-----";
568
569// Returns, in Pem-format, the first certificate found in a Pem-formatted block
570// (Note that such a block may contain e.g. a private key at the end).
571static QByteArray firstPemCertificateFromPem(const QByteArray &pem)
572{
573 int startPos = pem.indexOf(c: BeginCertString);
574 int endPos = pem.indexOf(c: EndCertString);
575 if (startPos == -1 || endPos == -1)
576 return QByteArray();
577 return pem.mid(index: startPos, len: endPos + sizeof(EndCertString) - startPos);
578}
579
580void tst_QSslCertificate::toPemOrDer()
581{
582 if (!QSslSocket::supportsSsl())
583 return;
584
585 QFETCH(QString, absFilePath);
586 QFETCH(QSsl::EncodingFormat, format);
587
588 QByteArray encoded = readFile(absFilePath);
589 QSslCertificate certificate(encoded, format);
590 QVERIFY(!certificate.isNull());
591 if (format == QSsl::Pem) {
592 encoded.replace(before: '\r',c: "");
593 QByteArray firstPem = firstPemCertificateFromPem(pem: encoded);
594 QCOMPARE(certificate.toPem(), firstPem);
595 } else {
596 // ### for now, we assume that DER-encoded certificates don't contain bundled stuff
597 QCOMPARE(certificate.toDer(), encoded);
598 }
599}
600
601void tst_QSslCertificate::fromDevice()
602{
603 QTest::ignoreMessage(type: QtWarningMsg, message: "QSslCertificate::fromDevice: cannot read from a null device");
604 QList<QSslCertificate> certs = QSslCertificate::fromDevice(device: nullptr); // don't crash
605 QVERIFY(certs.isEmpty());
606
607 QFile certFile(testDataDir + "certificates/cert.der");
608 const bool ok = certFile.open(flags: QIODevice::ReadOnly);
609 QVERIFY(ok);
610 const auto chain = QSslCertificate::fromDevice(device: &certFile, format: QSsl::Der);
611 QCOMPARE(chain.size(), 1);
612 QVERIFY(!chain.at(0).isNull());
613}
614
615void tst_QSslCertificate::fromPath_data()
616{
617 QTest::addColumn<QString>(name: "path");
618 QTest::addColumn<int>(name: "syntax");
619 QTest::addColumn<bool>(name: "pemencoding");
620 QTest::addColumn<int>(name: "numCerts");
621
622 QTest::newRow(dataTag: "empty fixed pem") << QString() << int(QRegExp::FixedString) << true << 0;
623 QTest::newRow(dataTag: "empty fixed der") << QString() << int(QRegExp::FixedString) << false << 0;
624 QTest::newRow(dataTag: "empty regexp pem") << QString() << int(QRegExp::RegExp) << true << 0;
625 QTest::newRow(dataTag: "empty regexp der") << QString() << int(QRegExp::RegExp) << false << 0;
626 QTest::newRow(dataTag: "empty wildcard pem") << QString() << int(QRegExp::Wildcard) << true << 0;
627 QTest::newRow(dataTag: "empty wildcard der") << QString() << int(QRegExp::Wildcard) << false << 0;
628 QTest::newRow(dataTag: "\"certificates\" fixed pem") << (testDataDir + "certificates") << int(QRegExp::FixedString) << true << 0;
629 QTest::newRow(dataTag: "\"certificates\" fixed der") << (testDataDir + "certificates") << int(QRegExp::FixedString) << false << 0;
630 QTest::newRow(dataTag: "\"certificates\" regexp pem") << (testDataDir + "certificates") << int(QRegExp::RegExp) << true << 0;
631 QTest::newRow(dataTag: "\"certificates\" regexp der") << (testDataDir + "certificates") << int(QRegExp::RegExp) << false << 0;
632 QTest::newRow(dataTag: "\"certificates\" wildcard pem") << (testDataDir + "certificates") << int(QRegExp::Wildcard) << true << 0;
633 QTest::newRow(dataTag: "\"certificates\" wildcard der") << (testDataDir + "certificates") << int(QRegExp::Wildcard) << false << 0;
634 QTest::newRow(dataTag: "\"certificates/cert.pem\" fixed pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::FixedString) << true << 1;
635 QTest::newRow(dataTag: "\"certificates/cert.pem\" fixed der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::FixedString) << false << 0;
636 QTest::newRow(dataTag: "\"certificates/cert.pem\" regexp pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::RegExp) << true << 1;
637 QTest::newRow(dataTag: "\"certificates/cert.pem\" regexp der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::RegExp) << false << 0;
638 QTest::newRow(dataTag: "\"certificates/cert.pem\" wildcard pem") << (testDataDir + "certificates/cert.pem") << int(QRegExp::Wildcard) << true << 1;
639 QTest::newRow(dataTag: "\"certificates/cert.pem\" wildcard der") << (testDataDir + "certificates/cert.pem") << int(QRegExp::Wildcard) << false << 0;
640 QTest::newRow(dataTag: "\"certificates/*\" fixed pem") << (testDataDir + "certificates/*") << int(QRegExp::FixedString) << true << 0;
641 QTest::newRow(dataTag: "\"certificates/*\" fixed der") << (testDataDir + "certificates/*") << int(QRegExp::FixedString) << false << 0;
642 QTest::newRow(dataTag: "\"certificates/*\" regexp pem") << (testDataDir + "certificates/*") << int(QRegExp::RegExp) << true << 0;
643 QTest::newRow(dataTag: "\"certificates/*\" regexp der") << (testDataDir + "certificates/*") << int(QRegExp::RegExp) << false << 0;
644 QTest::newRow(dataTag: "\"certificates/*\" wildcard pem") << (testDataDir + "certificates/*") << int(QRegExp::Wildcard) << true << 7;
645 QTest::newRow(dataTag: "\"certificates/ca*\" wildcard pem") << (testDataDir + "certificates/ca*") << int(QRegExp::Wildcard) << true << 1;
646 QTest::newRow(dataTag: "\"certificates/cert*\" wildcard pem") << (testDataDir + "certificates/cert*") << int(QRegExp::Wildcard) << true << 4;
647 QTest::newRow(dataTag: "\"certificates/cert-[sure]*\" wildcard pem") << (testDataDir + "certificates/cert-[sure]*") << int(QRegExp::Wildcard) << true << 3;
648 QTest::newRow(dataTag: "\"certificates/cert-[not]*\" wildcard pem") << (testDataDir + "certificates/cert-[not]*") << int(QRegExp::Wildcard) << true << 0;
649 QTest::newRow(dataTag: "\"certificates/*\" wildcard der") << (testDataDir + "certificates/*") << int(QRegExp::Wildcard) << false << 2;
650 QTest::newRow(dataTag: "\"c*/c*.pem\" fixed pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::FixedString) << true << 0;
651 QTest::newRow(dataTag: "\"c*/c*.pem\" fixed der") << (testDataDir + "c*/c*.pem") << int(QRegExp::FixedString) << false << 0;
652 QTest::newRow(dataTag: "\"c*/c*.pem\" regexp pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::RegExp) << true << 0;
653 QTest::newRow(dataTag: "\"c*/c*.pem\" regexp der") << (testDataDir + "c*/c*.pem") << int(QRegExp::RegExp) << false << 0;
654 QTest::newRow(dataTag: "\"c*/c*.pem\" wildcard pem") << (testDataDir + "c*/c*.pem") << int(QRegExp::Wildcard) << true << 5;
655 QTest::newRow(dataTag: "\"c*/c*.pem\" wildcard der") << (testDataDir + "c*/c*.pem") << int(QRegExp::Wildcard) << false << 0;
656 QTest::newRow(dataTag: "\"d*/c*.pem\" fixed pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::FixedString) << true << 0;
657 QTest::newRow(dataTag: "\"d*/c*.pem\" fixed der") << (testDataDir + "d*/c*.pem") << int(QRegExp::FixedString) << false << 0;
658 QTest::newRow(dataTag: "\"d*/c*.pem\" regexp pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::RegExp) << true << 0;
659 QTest::newRow(dataTag: "\"d*/c*.pem\" regexp der") << (testDataDir + "d*/c*.pem") << int(QRegExp::RegExp) << false << 0;
660 QTest::newRow(dataTag: "\"d*/c*.pem\" wildcard pem") << (testDataDir + "d*/c*.pem") << int(QRegExp::Wildcard) << true << 0;
661 QTest::newRow(dataTag: "\"d*/c*.pem\" wildcard der") << (testDataDir + "d*/c*.pem") << int(QRegExp::Wildcard) << false << 0;
662 QTest::newRow(dataTag: "\"c.*/c.*.pem\" fixed pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::FixedString) << true << 0;
663 QTest::newRow(dataTag: "\"c.*/c.*.pem\" fixed der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::FixedString) << false << 0;
664 QTest::newRow(dataTag: "\"c.*/c.*.pem\" regexp pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::RegExp) << true << 5;
665 QTest::newRow(dataTag: "\"c.*/c.*.pem\" regexp der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::RegExp) << false << 0;
666 QTest::newRow(dataTag: "\"c.*/c.*.pem\" wildcard pem") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::Wildcard) << true << 0;
667 QTest::newRow(dataTag: "\"c.*/c.*.pem\" wildcard der") << (testDataDir + "c.*/c.*.pem") << int(QRegExp::Wildcard) << false << 0;
668 QTest::newRow(dataTag: "\"d.*/c.*.pem\" fixed pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::FixedString) << true << 0;
669 QTest::newRow(dataTag: "\"d.*/c.*.pem\" fixed der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::FixedString) << false << 0;
670 QTest::newRow(dataTag: "\"d.*/c.*.pem\" regexp pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::RegExp) << true << 0;
671 QTest::newRow(dataTag: "\"d.*/c.*.pem\" regexp der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::RegExp) << false << 0;
672 QTest::newRow(dataTag: "\"d.*/c.*.pem\" wildcard pem") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::Wildcard) << true << 0;
673 QTest::newRow(dataTag: "\"d.*/c.*.pem\" wildcard der") << (testDataDir + "d.*/c.*.pem") << int(QRegExp::Wildcard) << false << 0;
674#ifdef Q_OS_LINUX
675 QTest::newRow(dataTag: "absolute path wildcard pem") << (testDataDir + "certificates/*.pem") << int(QRegExp::Wildcard) << true << 7;
676#endif
677
678 QTest::newRow(dataTag: "trailing-whitespace") << (testDataDir + "more-certificates/trailing-whitespace.pem") << int(QRegExp::FixedString) << true << 1;
679 QTest::newRow(dataTag: "no-ending-newline") << (testDataDir + "more-certificates/no-ending-newline.pem") << int(QRegExp::FixedString) << true << 1;
680 QTest::newRow(dataTag: "malformed-just-begin") << (testDataDir + "more-certificates/malformed-just-begin.pem") << int(QRegExp::FixedString) << true << 0;
681 QTest::newRow(dataTag: "malformed-just-begin-no-newline") << (testDataDir + "more-certificates/malformed-just-begin-no-newline.pem") << int(QRegExp::FixedString) << true << 0;
682}
683
684void tst_QSslCertificate::fromPath()
685{
686 QFETCH(QString, path);
687 QFETCH(int, syntax);
688 QFETCH(bool, pemencoding);
689 QFETCH(int, numCerts);
690
691 QCOMPARE(QSslCertificate::fromPath(path,
692 pemencoding ? QSsl::Pem : QSsl::Der,
693 QRegExp::PatternSyntax(syntax)).size(),
694 numCerts);
695}
696
697void tst_QSslCertificate::fromPath_qregularexpression_data()
698{
699 QTest::addColumn<QString>(name: "path");
700 QTest::addColumn<int>(name: "syntax");
701 QTest::addColumn<bool>(name: "pemencoding");
702 QTest::addColumn<int>(name: "numCerts");
703
704 QTest::newRow(dataTag: "empty fixed pem") << QString() << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
705 QTest::newRow(dataTag: "empty fixed der") << QString() << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
706 QTest::newRow(dataTag: "empty regexp pem") << QString() << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
707 QTest::newRow(dataTag: "empty regexp der") << QString() << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
708 QTest::newRow(dataTag: "empty wildcard pem") << QString() << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
709 QTest::newRow(dataTag: "empty wildcard der") << QString() << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
710 QTest::newRow(dataTag: "\"certificates\" fixed pem") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
711 QTest::newRow(dataTag: "\"certificates\" fixed der") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
712 QTest::newRow(dataTag: "\"certificates\" regexp pem") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
713 QTest::newRow(dataTag: "\"certificates\" regexp der") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
714 QTest::newRow(dataTag: "\"certificates\" wildcard pem") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
715 QTest::newRow(dataTag: "\"certificates\" wildcard der") << (testDataDir + "certificates") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
716 QTest::newRow(dataTag: "\"certificates/cert.pem\" fixed pem") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 1;
717 QTest::newRow(dataTag: "\"certificates/cert.pem\" fixed der") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
718 QTest::newRow(dataTag: "\"certificates/cert.pem\" regexp pem") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 1;
719 QTest::newRow(dataTag: "\"certificates/cert.pem\" regexp der") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
720 QTest::newRow(dataTag: "\"certificates/cert.pem\" wildcard pem") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 1;
721 QTest::newRow(dataTag: "\"certificates/cert.pem\" wildcard der") << (testDataDir + "certificates/cert.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
722 QTest::newRow(dataTag: "\"certificates/*\" fixed pem") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
723 QTest::newRow(dataTag: "\"certificates/*\" fixed der") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
724 QTest::newRow(dataTag: "\"certificates/*\" regexp pem") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
725 QTest::newRow(dataTag: "\"certificates/*\" regexp der") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
726 QTest::newRow(dataTag: "\"certificates/*\" wildcard pem") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 7;
727 QTest::newRow(dataTag: "\"certificates/ca*\" wildcard pem") << (testDataDir + "certificates/ca*") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 1;
728 QTest::newRow(dataTag: "\"certificates/cert*\" wildcard pem") << (testDataDir + "certificates/cert*") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 4;
729 QTest::newRow(dataTag: "\"certificates/cert-[sure]*\" wildcard pem") << (testDataDir + "certificates/cert-[sure]*") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 3;
730 QTest::newRow(dataTag: "\"certificates/cert-[not]*\" wildcard pem") << (testDataDir + "certificates/cert-[not]*") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
731 QTest::newRow(dataTag: "\"certificates/*\" wildcard der") << (testDataDir + "certificates/*") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 2;
732 QTest::newRow(dataTag: "\"c*/c*.pem\" fixed pem") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
733 QTest::newRow(dataTag: "\"c*/c*.pem\" fixed der") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
734 QTest::newRow(dataTag: "\"c*/c*.pem\" regexp pem") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
735 QTest::newRow(dataTag: "\"c*/c*.pem\" regexp der") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
736 QTest::newRow(dataTag: "\"c*/c*.pem\" wildcard pem") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 5;
737 QTest::newRow(dataTag: "\"c*/c*.pem\" wildcard der") << (testDataDir + "c*/c*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
738 QTest::newRow(dataTag: "\"d*/c*.pem\" fixed pem") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
739 QTest::newRow(dataTag: "\"d*/c*.pem\" fixed der") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
740 QTest::newRow(dataTag: "\"d*/c*.pem\" regexp pem") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
741 QTest::newRow(dataTag: "\"d*/c*.pem\" regexp der") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
742 QTest::newRow(dataTag: "\"d*/c*.pem\" wildcard pem") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
743 QTest::newRow(dataTag: "\"d*/c*.pem\" wildcard der") << (testDataDir + "d*/c*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
744 QTest::newRow(dataTag: "\"c.*/c.*.pem\" fixed pem") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
745 QTest::newRow(dataTag: "\"c.*/c.*.pem\" fixed der") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
746 QTest::newRow(dataTag: "\"c.*/c.*.pem\" regexp pem") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 5;
747 QTest::newRow(dataTag: "\"c.*/c.*.pem\" regexp der") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
748 QTest::newRow(dataTag: "\"c.*/c.*.pem\" wildcard pem") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
749 QTest::newRow(dataTag: "\"c.*/c.*.pem\" wildcard der") << (testDataDir + "c.*/c.*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
750 QTest::newRow(dataTag: "\"d.*/c.*.pem\" fixed pem") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
751 QTest::newRow(dataTag: "\"d.*/c.*.pem\" fixed der") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::FixedString) << false << 0;
752 QTest::newRow(dataTag: "\"d.*/c.*.pem\" regexp pem") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << true << 0;
753 QTest::newRow(dataTag: "\"d.*/c.*.pem\" regexp der") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::RegularExpression) << false << 0;
754 QTest::newRow(dataTag: "\"d.*/c.*.pem\" wildcard pem") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 0;
755 QTest::newRow(dataTag: "\"d.*/c.*.pem\" wildcard der") << (testDataDir + "d.*/c.*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << false << 0;
756#ifdef Q_OS_LINUX
757 QTest::newRow(dataTag: "absolute path wildcard pem") << (testDataDir + "certificates/*.pem") << int(QSslCertificate::PatternSyntax::Wildcard) << true << 7;
758#endif
759
760 QTest::newRow(dataTag: "trailing-whitespace") << (testDataDir + "more-certificates/trailing-whitespace.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 1;
761 QTest::newRow(dataTag: "no-ending-newline") << (testDataDir + "more-certificates/no-ending-newline.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 1;
762 QTest::newRow(dataTag: "malformed-just-begin") << (testDataDir + "more-certificates/malformed-just-begin.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
763 QTest::newRow(dataTag: "malformed-just-begin-no-newline") << (testDataDir + "more-certificates/malformed-just-begin-no-newline.pem") << int(QSslCertificate::PatternSyntax::FixedString) << true << 0;
764}
765
766void tst_QSslCertificate::fromPath_qregularexpression()
767{
768 QFETCH(QString, path);
769 QFETCH(int, syntax);
770 QFETCH(bool, pemencoding);
771 QFETCH(int, numCerts);
772
773 QCOMPARE(QSslCertificate::fromPath(path,
774 pemencoding ? QSsl::Pem : QSsl::Der,
775 QSslCertificate::PatternSyntax(syntax)).size(),
776 numCerts);
777}
778
779void tst_QSslCertificate::certInfo()
780{
781// MD5 Fingerprint=B6:CF:57:34:DA:A9:73:21:82:F7:CF:4D:3D:85:31:88
782// SHA1 Fingerprint=B6:D1:51:82:E0:29:CA:59:96:38:BD:B6:F9:40:05:91:6D:49:09:60
783// Certificate:
784// Data:
785// Version: 1 (0x0)
786// Serial Number: 17 (0x11)
787// Signature Algorithm: sha1WithRSAEncryption
788// Issuer: C=AU, ST=Queensland, O=CryptSoft Pty Ltd, CN=Test CA (1024 bit)
789// Validity
790// Not Before: Apr 17 07:40:26 2007 GMT
791// Not After : May 17 07:40:26 2007 GMT
792// Subject: CN=name/with/slashes, C=NO
793// Subject Public Key Info:
794// Public Key Algorithm: rsaEncryption
795// RSA Public Key: (1024 bit)
796// Modulus (1024 bit):
797// 00:eb:9d:e9:03:ac:30:4f:a9:58:03:44:c7:18:26:
798// 2f:48:93:d5:ac:a0:fb:e8:53:c4:7b:2a:01:89:e6:
799// fc:5a:0c:c5:f5:21:f8:d7:4a:92:02:67:db:f1:9f:
800// 36:9a:62:9d:f3:ce:48:8e:ba:ed:5a:a8:9d:4f:bb:
801// 24:16:43:4c:b5:79:08:f6:d9:22:8f:5f:15:0a:43:
802// 25:03:7a:9d:a7:af:e3:26:b1:53:55:5e:60:57:c8:
803// ed:2f:1c:f3:36:0a:78:64:91:f9:17:a7:34:d7:8b:
804// bd:f1:fc:d1:8c:4f:a5:96:75:b2:7b:fc:21:f0:c7:
805// d9:5f:0c:57:18:b2:af:b9:4b
806// Exponent: 65537 (0x10001)
807// Signature Algorithm: sha1WithRSAEncryption
808// 95:e6:94:e2:98:33:57:a2:98:fa:af:50:b9:76:a9:51:83:2c:
809// 0b:61:a2:36:d0:e6:90:6d:e4:f8:c4:c7:50:ef:17:94:4e:21:
810// a8:fa:c8:33:aa:d1:7f:bc:ca:41:d6:7d:e7:44:76:c0:bf:45:
811// 4a:76:25:42:6d:53:76:fd:fc:74:29:1a:ea:2b:cc:06:ab:d1:
812// b8:eb:7d:6b:11:f7:9b:41:bb:9f:31:cb:ed:4d:f3:68:26:ed:
813// 13:1d:f2:56:59:fe:6f:7c:98:b6:25:69:4e:ea:b4:dc:c2:eb:
814// b7:bb:50:18:05:ba:ad:af:08:49:fe:98:63:55:ba:e7:fb:95:
815// 5d:91
816 static const char pem[] =
817 "-----BEGIN CERTIFICATE-----\n"
818 "MIIB8zCCAVwCAREwDQYJKoZIhvcNAQEFBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n"
819 "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n"
820 "VQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDcwNDE3MDc0MDI2WhcNMDcwNTE3\n"
821 "MDc0MDI2WjApMRowGAYDVQQDExFuYW1lL3dpdGgvc2xhc2hlczELMAkGA1UEBhMC\n"
822 "Tk8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOud6QOsME+pWANExxgmL0iT\n"
823 "1ayg++hTxHsqAYnm/FoMxfUh+NdKkgJn2/GfNppinfPOSI667VqonU+7JBZDTLV5\n"
824 "CPbZIo9fFQpDJQN6naev4yaxU1VeYFfI7S8c8zYKeGSR+RenNNeLvfH80YxPpZZ1\n"
825 "snv8IfDH2V8MVxiyr7lLAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAleaU4pgzV6KY\n"
826 "+q9QuXapUYMsC2GiNtDmkG3k+MTHUO8XlE4hqPrIM6rRf7zKQdZ950R2wL9FSnYl\n"
827 "Qm1Tdv38dCka6ivMBqvRuOt9axH3m0G7nzHL7U3zaCbtEx3yVln+b3yYtiVpTuq0\n"
828 "3MLrt7tQGAW6ra8ISf6YY1W65/uVXZE=\n"
829 "-----END CERTIFICATE-----\n";
830 static const char der[] = // hex encoded
831 "30:82:01:f3:30:82:01:5c:02:01:11:30:0d:06:09:2a"
832 "86:48:86:f7:0d:01:01:05:05:00:30:5b:31:0b:30:09"
833 "06:03:55:04:06:13:02:41:55:31:13:30:11:06:03:55"
834 "04:08:13:0a:51:75:65:65:6e:73:6c:61:6e:64:31:1a"
835 "30:18:06:03:55:04:0a:13:11:43:72:79:70:74:53:6f"
836 "66:74:20:50:74:79:20:4c:74:64:31:1b:30:19:06:03"
837 "55:04:03:13:12:54:65:73:74:20:43:41:20:28:31:30"
838 "32:34:20:62:69:74:29:30:1e:17:0d:30:37:30:34:31"
839 "37:30:37:34:30:32:36:5a:17:0d:30:37:30:35:31:37"
840 "30:37:34:30:32:36:5a:30:29:31:1a:30:18:06:03:55"
841 "04:03:13:11:6e:61:6d:65:2f:77:69:74:68:2f:73:6c"
842 "61:73:68:65:73:31:0b:30:09:06:03:55:04:06:13:02"
843 "4e:4f:30:81:9f:30:0d:06:09:2a:86:48:86:f7:0d:01"
844 "01:01:05:00:03:81:8d:00:30:81:89:02:81:81:00:eb"
845 "9d:e9:03:ac:30:4f:a9:58:03:44:c7:18:26:2f:48:93"
846 "d5:ac:a0:fb:e8:53:c4:7b:2a:01:89:e6:fc:5a:0c:c5"
847 "f5:21:f8:d7:4a:92:02:67:db:f1:9f:36:9a:62:9d:f3"
848 "ce:48:8e:ba:ed:5a:a8:9d:4f:bb:24:16:43:4c:b5:79"
849 "08:f6:d9:22:8f:5f:15:0a:43:25:03:7a:9d:a7:af:e3"
850 "26:b1:53:55:5e:60:57:c8:ed:2f:1c:f3:36:0a:78:64"
851 "91:f9:17:a7:34:d7:8b:bd:f1:fc:d1:8c:4f:a5:96:75"
852 "b2:7b:fc:21:f0:c7:d9:5f:0c:57:18:b2:af:b9:4b:02"
853 "03:01:00:01:30:0d:06:09:2a:86:48:86:f7:0d:01:01"
854 "05:05:00:03:81:81:00:95:e6:94:e2:98:33:57:a2:98"
855 "fa:af:50:b9:76:a9:51:83:2c:0b:61:a2:36:d0:e6:90"
856 "6d:e4:f8:c4:c7:50:ef:17:94:4e:21:a8:fa:c8:33:aa"
857 "d1:7f:bc:ca:41:d6:7d:e7:44:76:c0:bf:45:4a:76:25"
858 "42:6d:53:76:fd:fc:74:29:1a:ea:2b:cc:06:ab:d1:b8"
859 "eb:7d:6b:11:f7:9b:41:bb:9f:31:cb:ed:4d:f3:68:26"
860 "ed:13:1d:f2:56:59:fe:6f:7c:98:b6:25:69:4e:ea:b4"
861 "dc:c2:eb:b7:bb:50:18:05:ba:ad:af:08:49:fe:98:63"
862 "55:ba:e7:fb:95:5d:91";
863
864 QSslCertificate cert = QSslCertificate::fromPath(path: testDataDir + "certificates/cert.pem", format: QSsl::Pem,
865 syntax: QSslCertificate::PatternSyntax::FixedString).first();
866 QVERIFY(!cert.isNull());
867
868 QCOMPARE(cert.issuerInfo(QSslCertificate::Organization)[0], QString("CryptSoft Pty Ltd"));
869 QCOMPARE(cert.issuerInfo(QSslCertificate::CommonName)[0], QString("Test CA (1024 bit)"));
870 QCOMPARE(cert.issuerInfo(QSslCertificate::LocalityName), QStringList());
871 QCOMPARE(cert.issuerInfo(QSslCertificate::OrganizationalUnitName), QStringList());
872 QCOMPARE(cert.issuerInfo(QSslCertificate::CountryName)[0], QString("AU"));
873 QCOMPARE(cert.issuerInfo(QSslCertificate::StateOrProvinceName)[0], QString("Queensland"));
874
875 QCOMPARE(cert.issuerInfo("O")[0], QString("CryptSoft Pty Ltd"));
876 QCOMPARE(cert.issuerInfo("CN")[0], QString("Test CA (1024 bit)"));
877 QCOMPARE(cert.issuerInfo("L"), QStringList());
878 QCOMPARE(cert.issuerInfo("OU"), QStringList());
879 QCOMPARE(cert.issuerInfo("C")[0], QString("AU"));
880 QCOMPARE(cert.issuerInfo("ST")[0], QString("Queensland"));
881
882 QCOMPARE(cert.subjectInfo(QSslCertificate::Organization), QStringList());
883 QCOMPARE(cert.subjectInfo(QSslCertificate::CommonName)[0], QString("name/with/slashes"));
884 QCOMPARE(cert.subjectInfo(QSslCertificate::LocalityName), QStringList());
885 QCOMPARE(cert.subjectInfo(QSslCertificate::OrganizationalUnitName), QStringList());
886 QCOMPARE(cert.subjectInfo(QSslCertificate::CountryName)[0], QString("NO"));
887 QCOMPARE(cert.subjectInfo(QSslCertificate::StateOrProvinceName), QStringList());
888
889 QCOMPARE(cert.subjectInfo("O"), QStringList());
890 QCOMPARE(cert.subjectInfo("CN")[0], QString("name/with/slashes"));
891 QCOMPARE(cert.subjectInfo("L"), QStringList());
892 QCOMPARE(cert.subjectInfo("OU"), QStringList());
893 QCOMPARE(cert.subjectInfo("C")[0], QString("NO"));
894 QCOMPARE(cert.subjectInfo("ST"), QStringList());
895
896 QCOMPARE(cert.version(), QByteArray::number(1));
897 QCOMPARE(cert.serialNumber(), QByteArray("11"));
898
899 QCOMPARE(cert.toPem().constData(), (const char*)pem);
900 QCOMPARE(cert.toDer(), QByteArray::fromHex(der));
901
902 QCOMPARE(cert.digest(QCryptographicHash::Md5),
903 QByteArray::fromHex("B6:CF:57:34:DA:A9:73:21:82:F7:CF:4D:3D:85:31:88"));
904 QCOMPARE(cert.digest(QCryptographicHash::Sha1),
905 QByteArray::fromHex("B6:D1:51:82:E0:29:CA:59:96:38:BD:B6:F9:40:05:91:6D:49:09:60"));
906
907 QCOMPARE(cert.effectiveDate().toUTC(), QDateTime(QDate(2007, 4, 17), QTime(7,40,26), Qt::UTC));
908 QCOMPARE(cert.expiryDate().toUTC(), QDateTime(QDate(2007, 5, 17), QTime(7,40,26), Qt::UTC));
909 QVERIFY(cert.expiryDate() < QDateTime::currentDateTime()); // cert has expired
910
911 QSslCertificate copy = cert;
912 QCOMPARE(cert, copy);
913 QVERIFY(!(cert != copy));
914
915 QCOMPARE(cert, QSslCertificate(pem, QSsl::Pem));
916 QCOMPARE(cert, QSslCertificate(QByteArray::fromHex(der), QSsl::Der));
917}
918
919void tst_QSslCertificate::certInfoQByteArray()
920{
921 QSslCertificate cert = QSslCertificate::fromPath(path: testDataDir + "certificates/cert.pem", format: QSsl::Pem,
922 syntax: QSslCertificate::PatternSyntax::FixedString).first();
923 QVERIFY(!cert.isNull());
924
925 // in this test, check the bytearray variants before the enum variants to see if
926 // we fixed a bug we had with lazy initialization of the values.
927 QCOMPARE(cert.issuerInfo("CN")[0], QString("Test CA (1024 bit)"));
928 QCOMPARE(cert.subjectInfo("CN")[0], QString("name/with/slashes"));
929}
930
931void tst_QSslCertificate::task256066toPem()
932{
933 // a certificate whose PEM encoding's length is a multiple of 64
934 const char *mycert = "-----BEGIN CERTIFICATE-----\n" \
935 "MIIEGjCCAwKgAwIBAgIESikYSjANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQGEwJF\n" \
936 "RTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEPMA0GA1UECxMG\n" \
937 "RVNURUlEMRcwFQYDVQQDEw5FU1RFSUQtU0sgMjAwNzAeFw0wOTA2MDUxMzA2MTha\n" \
938 "Fw0xNDA2MDkyMTAwMDBaMIGRMQswCQYDVQQGEwJFRTEPMA0GA1UEChMGRVNURUlE\n" \
939 "MRcwFQYDVQQLEw5hdXRoZW50aWNhdGlvbjEhMB8GA1UEAxMYSEVJQkVSRyxTVkVO\n" \
940 "LDM3NzA5MjcwMjg1MRAwDgYDVQQEEwdIRUlCRVJHMQ0wCwYDVQQqEwRTVkVOMRQw\n" \
941 "EgYDVQQFEwszNzcwOTI3MDI4NTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" \
942 "k2Euwhm34vu1jOFp02J5fQRx9LW2C7x78CbJ7yInoAKn7QR8UdxTU7mJk90Opejo\n" \
943 "71RUi2/aYl4jCr9gr99v2YoLufMRwAuqdmwmwqH1WAHRUtIcD0oPdKyelmmn9ig0\n" \
944 "RV+yJLNT3dnyrwPw+uuzDe3DeKepGKE4lxexliCaAx0CAyCMW6OCATEwggEtMA4G\n" \
945 "A1UdDwEB/wQEAwIEsDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwPAYD\n" \
946 "VR0fBDUwMzAxoC+gLYYraHR0cDovL3d3dy5zay5lZS9jcmxzL2VzdGVpZC9lc3Rl\n" \
947 "aWQyMDA3LmNybDAgBgNVHREEGTAXgRVzdmVuLmhlaWJlcmdAZWVzdGkuZWUwUQYD\n" \
948 "VR0gBEowSDBGBgsrBgEEAc4fAQEBATA3MBIGCCsGAQUFBwICMAYaBG5vbmUwIQYI\n" \
949 "KwYBBQUHAgEWFWh0dHA6Ly93d3cuc2suZWUvY3BzLzAfBgNVHSMEGDAWgBRIBt6+\n" \
950 "jIdXlYB4Y/qcIysroDoYdTAdBgNVHQ4EFgQUKCjpDf+LcvL6AH0QOiW6rMTtB/0w\n" \
951 "CQYDVR0TBAIwADANBgkqhkiG9w0BAQUFAAOCAQEABRyRuUm9zt8V27WuNeXtCDmU\n" \
952 "MGzA6g4QXNAd2nxFzT3k+kNzzQTOcgRdmjiEPuK49On+GWnBr/5MSBNhbCJVPWr/\n" \
953 "yym1UYTBisaqhRt/N/kwZqd0bHeLJk+ZxSePXRyqkp9H8KPWqz7H+O/FxRS4ffxo\n" \
954 "Q9Clem+e0bcjNlL5xXiRGycBeZq8cKj+0+A/UuattznQlvHdlCEsSeu1fPOORqFV\n" \
955 "fZur4HC31lQD7xVvETLiL83CtOQC78+29XPD6Zlrrc5OF2yibSVParY19b8Zh6yu\n" \
956 "p1dNvN8pBgXGrsyxRonwHooV2ghGNmGILkpdvlQfnxeCUg4erfHjDdSY9vmT7w==\n" \
957 "-----END CERTIFICATE-----\n";
958
959 QByteArray pem1(mycert);
960 QSslCertificate cert1(pem1);
961 QVERIFY(!cert1.isNull());
962 QByteArray pem2(cert1.toPem());
963 QSslCertificate cert2(pem2);
964 QVERIFY(!cert2.isNull());
965 QCOMPARE(pem1, pem2);
966}
967
968void tst_QSslCertificate::nulInCN()
969{
970#if QT_CONFIG(securetransport) || defined(Q_OS_WINRT) || QT_CONFIG(schannel)
971 QSKIP("Generic QSslCertificatePrivate fails this test");
972#endif
973 QList<QSslCertificate> certList =
974 QSslCertificate::fromPath(path: testDataDir + "more-certificates/badguy-nul-cn.crt", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
975 QCOMPARE(certList.size(), 1);
976
977 const QSslCertificate &cert = certList.at(i: 0);
978 QVERIFY(!cert.isNull());
979
980 QString cn = cert.subjectInfo(info: QSslCertificate::CommonName)[0];
981 QVERIFY(cn != QLatin1String("www.bank.com"));
982
983 static const char realCN[] = "www.bank.com\0.badguy.com";
984 QCOMPARE(cn, QString::fromLatin1(realCN, sizeof realCN - 1));
985}
986
987void tst_QSslCertificate::nulInSan()
988{
989#if QT_CONFIG(securetransport) || defined(Q_OS_WINRT) || QT_CONFIG(schannel)
990 QSKIP("Generic QSslCertificatePrivate fails this test");
991#endif
992 QList<QSslCertificate> certList =
993 QSslCertificate::fromPath(path: testDataDir + "more-certificates/badguy-nul-san.crt", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
994 QCOMPARE(certList.size(), 1);
995
996 const QSslCertificate &cert = certList.at(i: 0);
997 QVERIFY(!cert.isNull());
998
999 QMultiMap<QSsl::AlternativeNameEntryType, QString> san = cert.subjectAlternativeNames();
1000 QVERIFY(!san.isEmpty());
1001
1002 QString dnssan = san.value(akey: QSsl::DnsEntry);
1003 QVERIFY(!dnssan.isEmpty());
1004 QVERIFY(dnssan != "www.bank.com");
1005
1006 static const char realSAN[] = "www.bank.com\0www.badguy.com";
1007 QCOMPARE(dnssan, QString::fromLatin1(realSAN, sizeof realSAN - 1));
1008}
1009
1010void tst_QSslCertificate::largeSerialNumber()
1011{
1012 QList<QSslCertificate> certList =
1013 QSslCertificate::fromPath(path: testDataDir + "more-certificates/cert-large-serial-number.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1014
1015 QCOMPARE(certList.size(), 1);
1016
1017 const QSslCertificate &cert = certList.at(i: 0);
1018 QVERIFY(!cert.isNull());
1019 QCOMPARE(cert.serialNumber(), QByteArray("01:02:03:04:05:06:07:08:09:10:aa:bb:cc:dd:ee:ff:17:18:19:20"));
1020}
1021
1022void tst_QSslCertificate::largeExpirationDate() // QTBUG-12489
1023{
1024 QList<QSslCertificate> certList =
1025 QSslCertificate::fromPath(path: testDataDir + "more-certificates/cert-large-expiration-date.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1026
1027 QCOMPARE(certList.size(), 1);
1028
1029 const QSslCertificate &cert = certList.at(i: 0);
1030 QVERIFY(!cert.isNull());
1031 QCOMPARE(cert.effectiveDate().toUTC(), QDateTime(QDate(2010, 8, 4), QTime(9, 53, 41), Qt::UTC));
1032 // if the date is larger than 2049, then the generalized time format is used
1033 QCOMPARE(cert.expiryDate().toUTC(), QDateTime(QDate(2051, 8, 29), QTime(9, 53, 41), Qt::UTC));
1034}
1035
1036void tst_QSslCertificate::blacklistedCertificates()
1037{
1038 QList<QSslCertificate> blacklistedCerts = QSslCertificate::fromPath(path: testDataDir + "more-certificates/blacklisted*.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::Wildcard);
1039 QVERIFY(blacklistedCerts.count() > 0);
1040 for (int a = 0; a < blacklistedCerts.count(); a++) {
1041 QVERIFY(blacklistedCerts.at(a).isBlacklisted());
1042 }
1043}
1044
1045void tst_QSslCertificate::selfsignedCertificates()
1046{
1047 QVERIFY(QSslCertificate::fromPath(testDataDir + "certificates/cert-ss.pem", QSsl::Pem, QSslCertificate::PatternSyntax::FixedString).first().isSelfSigned());
1048 QVERIFY(!QSslCertificate::fromPath(testDataDir + "certificates/cert.pem", QSsl::Pem, QSslCertificate::PatternSyntax::FixedString).first().isSelfSigned());
1049 QVERIFY(!QSslCertificate().isSelfSigned());
1050}
1051
1052void tst_QSslCertificate::toText()
1053{
1054 QList<QSslCertificate> certList =
1055 QSslCertificate::fromPath(path: testDataDir + "more-certificates/cert-large-expiration-date.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1056
1057 QCOMPARE(certList.size(), 1);
1058 const QSslCertificate &cert = certList.at(i: 0);
1059
1060 // Openssl's cert dump method changed slightly between 0.9.8, 1.0.0 and 1.01 versions, so we want it to match any output
1061
1062 QFile f098(testDataDir + "more-certificates/cert-large-expiration-date.txt.0.9.8");
1063 QVERIFY(f098.open(QIODevice::ReadOnly | QFile::Text));
1064 QByteArray txt098 = f098.readAll();
1065
1066 QFile f100(testDataDir + "more-certificates/cert-large-expiration-date.txt.1.0.0");
1067 QVERIFY(f100.open(QIODevice::ReadOnly | QFile::Text));
1068 QByteArray txt100 = f100.readAll();
1069
1070 QFile f101(testDataDir + "more-certificates/cert-large-expiration-date.txt.1.0.1");
1071 QVERIFY(f101.open(QIODevice::ReadOnly | QFile::Text));
1072 QByteArray txt101 = f101.readAll();
1073
1074 QFile f101c(testDataDir + "more-certificates/cert-large-expiration-date.txt.1.0.1c");
1075 QVERIFY(f101c.open(QIODevice::ReadOnly | QFile::Text));
1076 QByteArray txt101c = f101c.readAll();
1077
1078 QFile f111(testDataDir + "more-certificates/cert-large-expiration-date.txt.1.1.1");
1079 QVERIFY(f111.open(QIODevice::ReadOnly | QFile::Text));
1080 QByteArray txt111 = f111.readAll();
1081
1082 QString txtcert = cert.toText();
1083
1084#ifdef QT_NO_OPENSSL
1085 QEXPECT_FAIL("", "QTBUG-40884: QSslCertificate::toText is not implemented on WinRT", Continue);
1086#endif
1087 QVERIFY(QString::fromLatin1(txt098) == txtcert ||
1088 QString::fromLatin1(txt100) == txtcert ||
1089 QString::fromLatin1(txt101) == txtcert ||
1090 QString::fromLatin1(txt101c) == txtcert ||
1091 QString::fromLatin1(txt111) == txtcert );
1092}
1093
1094void tst_QSslCertificate::multipleCommonNames()
1095{
1096 QList<QSslCertificate> certList =
1097 QSslCertificate::fromPath(path: testDataDir + "more-certificates/test-cn-two-cns-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1098 QVERIFY(certList.count() > 0);
1099
1100 QStringList commonNames = certList[0].subjectInfo(info: QSslCertificate::CommonName);
1101 QVERIFY(commonNames.contains(QString("www.example.com")));
1102 QVERIFY(commonNames.contains(QString("www2.example.com")));
1103}
1104
1105void tst_QSslCertificate::subjectAndIssuerAttributes()
1106{
1107 QList<QSslCertificate> certList =
1108 QSslCertificate::fromPath(path: testDataDir + "more-certificates/test-cn-with-drink-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1109 QVERIFY(certList.count() > 0);
1110
1111 QList<QByteArray> attributes = certList[0].subjectInfoAttributes();
1112 QVERIFY(attributes.contains(QByteArray("favouriteDrink")));
1113 attributes.clear();
1114
1115 certList = QSslCertificate::fromPath(path: testDataDir + "more-certificates/natwest-banking.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1116 QVERIFY(certList.count() > 0);
1117
1118 QByteArray shortName("1.3.6.1.4.1.311.60.2.1.3");
1119#if !defined(QT_NO_OPENSSL) && defined(SN_jurisdictionCountryName)
1120 shortName = SN_jurisdictionCountryName;
1121#endif
1122 attributes = certList[0].subjectInfoAttributes();
1123 QVERIFY(attributes.contains(shortName));
1124}
1125
1126void tst_QSslCertificate::verify()
1127{
1128#if QT_CONFIG(securetransport)
1129 QSKIP("Not implemented in SecureTransport");
1130#endif
1131
1132 QList<QSslError> errors;
1133 QList<QSslCertificate> toVerify;
1134
1135 // Like QVERIFY, but be verbose about the content of `errors' when failing
1136#define VERIFY_VERBOSE(A) \
1137 QVERIFY2((A), \
1138 qPrintable(QString("errors: %1").arg(toString(errors))) \
1139 )
1140
1141#ifdef QT_NO_OPENSSL
1142 QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not yet support verifying a chain", Abort);
1143#endif
1144 // Empty chain is unspecified error
1145 errors = QSslCertificate::verify(certificateChain: toVerify);
1146 VERIFY_VERBOSE(errors.count() == 1);
1147 VERIFY_VERBOSE(errors[0] == QSslError(QSslError::UnspecifiedError));
1148 errors.clear();
1149
1150 // Verify a valid cert signed by a CA
1151 QList<QSslCertificate> caCerts = QSslCertificate::fromPath(path: testDataDir + "verify-certs/cacert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1152
1153QT_WARNING_PUSH
1154QT_WARNING_DISABLE_DEPRECATED
1155 QSslSocket::addDefaultCaCertificate(certificate: caCerts.first());
1156QT_WARNING_POP
1157
1158 toVerify = QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-ocsp-good-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1159
1160 errors = QSslCertificate::verify(certificateChain: toVerify);
1161 VERIFY_VERBOSE(errors.count() == 0);
1162 errors.clear();
1163
1164 // Test a blacklisted certificate
1165 toVerify = QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-addons-mozilla-org-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1166 errors = QSslCertificate::verify(certificateChain: toVerify);
1167 bool foundBlack = false;
1168 foreach (const QSslError &error, errors) {
1169 if (error.error() == QSslError::CertificateBlacklisted) {
1170 foundBlack = true;
1171 break;
1172 }
1173 }
1174 QVERIFY(foundBlack);
1175 errors.clear();
1176
1177 // This one is expired and untrusted
1178 toVerify = QSslCertificate::fromPath(path: testDataDir + "more-certificates/cert-large-serial-number.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1179 errors = QSslCertificate::verify(certificateChain: toVerify);
1180 VERIFY_VERBOSE(errors.contains(QSslError(QSslError::SelfSignedCertificate, toVerify[0])));
1181 VERIFY_VERBOSE(errors.contains(QSslError(QSslError::CertificateExpired, toVerify[0])));
1182 errors.clear();
1183 toVerify.clear();
1184
1185 // This one is signed by a valid cert, but the signer is not a valid CA
1186 toVerify << QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-intermediate-not-ca-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString).first();
1187 toVerify << QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-ocsp-good-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString).first();
1188 errors = QSslCertificate::verify(certificateChain: toVerify);
1189 VERIFY_VERBOSE(errors.contains(QSslError(QSslError::InvalidCaCertificate, toVerify[1])));
1190 toVerify.clear();
1191
1192 // This one is signed by a valid cert, and the signer is a valid CA
1193 toVerify << QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-intermediate-is-ca-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString).first();
1194 toVerify << QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-intermediate-ca-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString).first();
1195 errors = QSslCertificate::verify(certificateChain: toVerify);
1196 VERIFY_VERBOSE(errors.count() == 0);
1197
1198 // Recheck the above with hostname validation
1199 errors = QSslCertificate::verify(certificateChain: toVerify, hostName: QLatin1String("example.com"));
1200 VERIFY_VERBOSE(errors.count() == 0);
1201
1202 // Recheck the above with a bad hostname
1203 errors = QSslCertificate::verify(certificateChain: toVerify, hostName: QLatin1String("fail.example.com"));
1204 VERIFY_VERBOSE(errors.contains(QSslError(QSslError::HostNameMismatch, toVerify[0])));
1205 toVerify.clear();
1206
1207#undef VERIFY_VERBOSE
1208}
1209
1210QString tst_QSslCertificate::toString(const QList<QSslError>& errors)
1211{
1212 QStringList errorStrings;
1213
1214 foreach (const QSslError& error, errors) {
1215 errorStrings.append(t: QLatin1Char('"') + error.errorString() + QLatin1Char('"'));
1216 }
1217
1218 return QLatin1String("[ ") + errorStrings.join(sep: QLatin1String(", ")) + QLatin1String(" ]");
1219}
1220
1221void tst_QSslCertificate::extensions()
1222{
1223 QList<QSslCertificate> certList =
1224 QSslCertificate::fromPath(path: testDataDir + "more-certificates/natwest-banking.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1225 QVERIFY(certList.count() > 0);
1226
1227 QSslCertificate cert = certList[0];
1228 QList<QSslCertificateExtension> extensions = cert.extensions();
1229 QCOMPARE(extensions.count(), 9);
1230
1231 int unknown_idx = -1;
1232 int authority_info_idx = -1;
1233 int basic_constraints_idx = -1;
1234 int subject_key_idx = -1;
1235 int auth_key_idx = -1;
1236
1237 for (int i=0; i < extensions.length(); ++i) {
1238 QSslCertificateExtension ext = extensions[i];
1239
1240 //qDebug() << i << ":" << ext.name() << ext.oid();
1241 if (ext.oid() == QStringLiteral("1.3.6.1.5.5.7.1.12"))
1242 unknown_idx = i;
1243 if (ext.name() == QStringLiteral("authorityInfoAccess"))
1244 authority_info_idx = i;
1245 if (ext.name() == QStringLiteral("basicConstraints"))
1246 basic_constraints_idx = i;
1247 if (ext.name() == QStringLiteral("subjectKeyIdentifier"))
1248 subject_key_idx = i;
1249 if (ext.name() == QStringLiteral("authorityKeyIdentifier"))
1250 auth_key_idx = i;
1251 }
1252
1253 QVERIFY(unknown_idx != -1);
1254 QVERIFY(authority_info_idx != -1);
1255 QVERIFY(basic_constraints_idx != -1);
1256 QVERIFY(subject_key_idx != -1);
1257 QVERIFY(auth_key_idx != -1);
1258
1259 // Unknown
1260 QSslCertificateExtension unknown = extensions[unknown_idx];
1261 QCOMPARE(unknown.oid(), QStringLiteral("1.3.6.1.5.5.7.1.12"));
1262 QCOMPARE(unknown.name(), QStringLiteral("1.3.6.1.5.5.7.1.12"));
1263 QVERIFY(!unknown.isCritical());
1264 QVERIFY(!unknown.isSupported());
1265
1266 QByteArray unknownValue = QByteArray::fromHex(
1267 hexEncoded: "3060A15EA05C305A305830561609696D6167652F6769663021301F300706052B0E03021A0414" \
1268 "4B6BB92896060CBBD052389B29AC4B078B21051830261624687474703A2F2F6C6F676F2E7665" \
1269 "72697369676E2E636F6D2F76736C6F676F312E676966");
1270 QCOMPARE(unknown.value().toByteArray(), unknownValue);
1271
1272 // Authority Info Access
1273 QSslCertificateExtension aia = extensions[authority_info_idx];
1274 QCOMPARE(aia.oid(), QStringLiteral("1.3.6.1.5.5.7.1.1"));
1275 QCOMPARE(aia.name(), QStringLiteral("authorityInfoAccess"));
1276 QVERIFY(!aia.isCritical());
1277 QVERIFY(aia.isSupported());
1278
1279 QVariantMap aiaValue = aia.value().toMap();
1280 QCOMPARE(aiaValue.keys(), QList<QString>() << QStringLiteral("OCSP") << QStringLiteral("caIssuers"));
1281 QString ocsp = aiaValue[QStringLiteral("OCSP")].toString();
1282 QString caIssuers = aiaValue[QStringLiteral("caIssuers")].toString();
1283
1284 QCOMPARE(ocsp, QStringLiteral("http://EVIntl-ocsp.verisign.com"));
1285 QCOMPARE(caIssuers, QStringLiteral("http://EVIntl-aia.verisign.com/EVIntl2006.cer"));
1286
1287 // Basic constraints
1288 QSslCertificateExtension basic = extensions[basic_constraints_idx];
1289 QCOMPARE(basic.oid(), QStringLiteral("2.5.29.19"));
1290 QCOMPARE(basic.name(), QStringLiteral("basicConstraints"));
1291 QVERIFY(!basic.isCritical());
1292 QVERIFY(basic.isSupported());
1293
1294 QVariantMap basicValue = basic.value().toMap();
1295 QCOMPARE(basicValue.keys(), QList<QString>() << QStringLiteral("ca"));
1296 QVERIFY(!basicValue[QStringLiteral("ca")].toBool());
1297
1298 // Subject key identifier
1299 QSslCertificateExtension subjectKey = extensions[subject_key_idx];
1300 QCOMPARE(subjectKey.oid(), QStringLiteral("2.5.29.14"));
1301 QCOMPARE(subjectKey.name(), QStringLiteral("subjectKeyIdentifier"));
1302 QVERIFY(!subjectKey.isCritical());
1303 QVERIFY(subjectKey.isSupported());
1304 QCOMPARE(subjectKey.value().toString(), QStringLiteral("5F:90:23:CD:24:CA:52:C9:36:29:F0:7E:9D:B1:FE:08:E0:EE:69:F0"));
1305
1306 // Authority key identifier
1307 QSslCertificateExtension authKey = extensions[auth_key_idx];
1308 QCOMPARE(authKey.oid(), QStringLiteral("2.5.29.35"));
1309 QCOMPARE(authKey.name(), QStringLiteral("authorityKeyIdentifier"));
1310 QVERIFY(!authKey.isCritical());
1311 QVERIFY(authKey.isSupported());
1312
1313 QVariantMap authValue = authKey.value().toMap();
1314 QCOMPARE(authValue.keys(), QList<QString>() << QStringLiteral("keyid"));
1315 QVERIFY(authValue[QStringLiteral("keyid")].toByteArray() ==
1316 QByteArray("4e43c81d76ef37537a4ff2586f94f338e2d5bddf"));
1317}
1318
1319void tst_QSslCertificate::extensionsCritical()
1320{
1321 QList<QSslCertificate> certList =
1322 QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-addons-mozilla-org-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1323 QVERIFY(certList.count() > 0);
1324
1325 QSslCertificate cert = certList[0];
1326 QList<QSslCertificateExtension> extensions = cert.extensions();
1327 QCOMPARE(extensions.count(), 9);
1328
1329 int basic_constraints_idx = -1;
1330 int key_usage_idx = -1;
1331
1332 for (int i=0; i < extensions.length(); ++i) {
1333 QSslCertificateExtension ext = extensions[i];
1334
1335 if (ext.name() == QStringLiteral("basicConstraints"))
1336 basic_constraints_idx = i;
1337 if (ext.name() == QStringLiteral("keyUsage"))
1338 key_usage_idx = i;
1339 }
1340
1341 QVERIFY(basic_constraints_idx != -1);
1342 QVERIFY(key_usage_idx != -1);
1343
1344 // Basic constraints
1345 QSslCertificateExtension basic = extensions[basic_constraints_idx];
1346 QCOMPARE(basic.oid(), QStringLiteral("2.5.29.19"));
1347 QCOMPARE(basic.name(), QStringLiteral("basicConstraints"));
1348 QVERIFY(basic.isCritical());
1349 QVERIFY(basic.isSupported());
1350
1351 QVariantMap basicValue = basic.value().toMap();
1352 QCOMPARE(basicValue.keys(), QList<QString>() << QStringLiteral("ca"));
1353 QVERIFY(!basicValue[QStringLiteral("ca")].toBool());
1354
1355 // Key Usage
1356 QSslCertificateExtension keyUsage = extensions[key_usage_idx];
1357 QCOMPARE(keyUsage.oid(), QStringLiteral("2.5.29.15"));
1358 QCOMPARE(keyUsage.name(), QStringLiteral("keyUsage"));
1359 QVERIFY(keyUsage.isCritical());
1360 QVERIFY(!keyUsage.isSupported());
1361}
1362
1363class TestThread : public QThread
1364{
1365public:
1366 void run()
1367 {
1368 effectiveDate = cert.effectiveDate();
1369 expiryDate = cert.expiryDate();
1370 extensions = cert.extensions();
1371 isBlacklisted = cert.isBlacklisted();
1372 issuerInfo = cert.issuerInfo(info: QSslCertificate::CommonName);
1373 issuerInfoAttributes = cert.issuerInfoAttributes();
1374 publicKey = cert.publicKey();
1375 serialNumber = cert.serialNumber();
1376 subjectInfo = cert.subjectInfo(info: QSslCertificate::CommonName);
1377 subjectInfoAttributes = cert.subjectInfoAttributes();
1378 toDer = cert.toDer();
1379 toPem = cert.toPem();
1380 toText = cert.toText();
1381 version = cert.version();
1382 }
1383 QSslCertificate cert;
1384 QDateTime effectiveDate;
1385 QDateTime expiryDate;
1386 QList<QSslCertificateExtension> extensions;
1387 bool isBlacklisted;
1388 QStringList issuerInfo;
1389 QList<QByteArray> issuerInfoAttributes;
1390 QSslKey publicKey;
1391 QByteArray serialNumber;
1392 QStringList subjectInfo;
1393 QList<QByteArray> subjectInfoAttributes;
1394 QByteArray toDer;
1395 QByteArray toPem;
1396 QString toText;
1397 QByteArray version;
1398};
1399
1400void tst_QSslCertificate::threadSafeConstMethods()
1401{
1402 if (!QSslSocket::supportsSsl())
1403 return;
1404
1405 QByteArray encoded = readFile(absFilePath: testDataDir + "certificates/cert.pem");
1406 QSslCertificate certificate(encoded);
1407 QVERIFY(!certificate.isNull());
1408
1409 TestThread t1;
1410 t1.cert = certificate; //shallow copy
1411 TestThread t2;
1412 t2.cert = certificate; //shallow copy
1413 t1.start();
1414 t2.start();
1415 QVERIFY(t1.wait(5000));
1416 QVERIFY(t2.wait(5000));
1417 QCOMPARE(t1.cert, t2.cert);
1418 QCOMPARE(t1.effectiveDate, t2.effectiveDate);
1419 QCOMPARE(t1.expiryDate, t2.expiryDate);
1420 //QVERIFY(t1.extensions == t2.extensions); // no equality operator, so not tested
1421 QCOMPARE(t1.isBlacklisted, t2.isBlacklisted);
1422 QCOMPARE(t1.issuerInfo, t2.issuerInfo);
1423 QCOMPARE(t1.issuerInfoAttributes, t2.issuerInfoAttributes);
1424 QCOMPARE(t1.publicKey, t2.publicKey);
1425 QCOMPARE(t1.serialNumber, t2.serialNumber);
1426 QCOMPARE(t1.subjectInfo, t2.subjectInfo);
1427 QCOMPARE(t1.subjectInfoAttributes, t2.subjectInfoAttributes);
1428 QCOMPARE(t1.toDer, t2.toDer);
1429 QCOMPARE(t1.toPem, t2.toPem);
1430 QCOMPARE(t1.toText, t2.toText);
1431 QCOMPARE(t1.version, t2.version);
1432
1433}
1434
1435void tst_QSslCertificate::version_data()
1436{
1437 QTest::addColumn<QSslCertificate>(name: "certificate");
1438 QTest::addColumn<QByteArray>(name: "result");
1439
1440 QTest::newRow(dataTag: "null certificate") << QSslCertificate() << QByteArray();
1441
1442 QList<QSslCertificate> certs;
1443 certs << QSslCertificate::fromPath(path: testDataDir + "verify-certs/test-ocsp-good-cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1444
1445 QTest::newRow(dataTag: "v3 certificate") << certs.first() << QByteArrayLiteral("3");
1446
1447 certs.clear();
1448 certs << QSslCertificate::fromPath(path: testDataDir + "certificates/cert.pem", format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1449 QTest::newRow(dataTag: "v1 certificate") << certs.first() << QByteArrayLiteral("1");
1450}
1451
1452void tst_QSslCertificate::version()
1453{
1454 if (!QSslSocket::supportsSsl())
1455 return;
1456
1457 QFETCH(QSslCertificate, certificate);
1458 QFETCH(QByteArray, result);
1459 QCOMPARE(certificate.version(), result);
1460}
1461
1462void tst_QSslCertificate::pkcs12()
1463{
1464 // See pkcs12/README for how to generate the PKCS12 files used here.
1465 if (!QSslSocket::supportsSsl()) {
1466 qWarning(msg: "SSL not supported, skipping test");
1467 return;
1468 }
1469
1470 QFile f(testDataDir + QLatin1String("pkcs12/leaf.p12"));
1471 bool ok = f.open(flags: QIODevice::ReadOnly);
1472 QVERIFY(ok);
1473
1474 QSslKey key;
1475 QSslCertificate cert;
1476 QList<QSslCertificate> caCerts;
1477
1478#ifdef QT_NO_OPENSSL
1479 QEXPECT_FAIL("", "QTBUG-40884: WinRT API does not support pkcs12 imports", Abort);
1480#endif
1481 ok = QSslCertificate::importPkcs12(device: &f, key: &key, cert: &cert, caCertificates: &caCerts);
1482 QVERIFY(ok);
1483 f.close();
1484
1485 QList<QSslCertificate> leafCert = QSslCertificate::fromPath(path: testDataDir + QLatin1String("pkcs12/leaf.crt"), format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1486 QVERIFY(!leafCert.isEmpty());
1487
1488 QCOMPARE(cert, leafCert.first());
1489
1490 QFile f2(testDataDir + QLatin1String("pkcs12/leaf.key"));
1491 ok = f2.open(flags: QIODevice::ReadOnly);
1492 QVERIFY(ok);
1493
1494 QSslKey leafKey(&f2, QSsl::Rsa);
1495 f2.close();
1496
1497 QVERIFY(!leafKey.isNull());
1498 QCOMPARE(key, leafKey);
1499
1500 QList<QSslCertificate> caCert = QSslCertificate::fromPath(path: testDataDir + QLatin1String("pkcs12/inter.crt"), format: QSsl::Pem, syntax: QSslCertificate::PatternSyntax::FixedString);
1501 QVERIFY(!caCert.isEmpty());
1502
1503 QVERIFY(!caCerts.isEmpty());
1504 QCOMPARE(caCerts.first(), caCert.first());
1505 QCOMPARE(caCerts, caCert);
1506
1507 // QTBUG-62335 - Fail (found no private key) but don't crash:
1508 QFile nocert(testDataDir + QLatin1String("pkcs12/leaf-nokey.p12"));
1509 ok = nocert.open(flags: QIODevice::ReadOnly);
1510 QVERIFY(ok);
1511 QTest::ignoreMessage(type: QtWarningMsg, message: "Unable to convert private key");
1512 ok = QSslCertificate::importPkcs12(device: &nocert, key: &key, cert: &cert, caCertificates: &caCerts);
1513 QVERIFY(!ok);
1514 nocert.close();
1515}
1516
1517#endif // QT_NO_SSL
1518
1519QTEST_MAIN(tst_QSslCertificate)
1520#include "tst_qsslcertificate.moc"
1521

source code of qtbase/tests/auto/network/ssl/qsslcertificate/tst_qsslcertificate.cpp