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