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 QtNetwork module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qsslcertificate.h" |
41 | #include "qsslcertificate_p.h" |
42 | |
43 | #include "qssl_p.h" |
44 | #ifndef QT_NO_SSL |
45 | #include "qsslkey.h" |
46 | #include "qsslkey_p.h" |
47 | #endif |
48 | #include "qsslcertificateextension.h" |
49 | #include "qsslcertificateextension_p.h" |
50 | #include "qasn1element_p.h" |
51 | |
52 | #include <QtCore/qdatastream.h> |
53 | #include <QtCore/qendian.h> |
54 | #include <QtNetwork/qhostaddress.h> |
55 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | bool QSslCertificate::operator==(const QSslCertificate &other) const |
59 | { |
60 | if (d == other.d) |
61 | return true; |
62 | if (d->null && other.d->null) |
63 | return true; |
64 | return d->derData == other.d->derData; |
65 | } |
66 | |
67 | size_t qHash(const QSslCertificate &key, size_t seed) noexcept |
68 | { |
69 | // DER is the native encoding here, so toDer() is just "return d->derData": |
70 | return qHash(key.toDer(), seed); |
71 | } |
72 | |
73 | bool QSslCertificate::isNull() const |
74 | { |
75 | return d->null; |
76 | } |
77 | |
78 | bool QSslCertificate::isSelfSigned() const |
79 | { |
80 | if (d->null) |
81 | return false; |
82 | |
83 | qCWarning(lcSsl, |
84 | "QSslCertificate::isSelfSigned: This function does not check, whether the certificate " |
85 | "is actually signed. It just checks whether issuer and subject are identical" ); |
86 | return d->subjectMatchesIssuer; |
87 | } |
88 | |
89 | QByteArray QSslCertificate::version() const |
90 | { |
91 | return d->versionString; |
92 | } |
93 | |
94 | QByteArray QSslCertificate::serialNumber() const |
95 | { |
96 | return d->serialNumberString; |
97 | } |
98 | |
99 | QStringList QSslCertificate::issuerInfo(SubjectInfo info) const |
100 | { |
101 | return issuerInfo(QSslCertificatePrivate::subjectInfoToString(info)); |
102 | } |
103 | |
104 | QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const |
105 | { |
106 | return d->issuerInfo.values(attribute); |
107 | } |
108 | |
109 | QStringList QSslCertificate::subjectInfo(SubjectInfo info) const |
110 | { |
111 | return subjectInfo(QSslCertificatePrivate::subjectInfoToString(info)); |
112 | } |
113 | |
114 | QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const |
115 | { |
116 | return d->subjectInfo.values(attribute); |
117 | } |
118 | |
119 | QList<QByteArray> QSslCertificate::subjectInfoAttributes() const |
120 | { |
121 | return d->subjectInfo.uniqueKeys(); |
122 | } |
123 | |
124 | QList<QByteArray> QSslCertificate::issuerInfoAttributes() const |
125 | { |
126 | return d->issuerInfo.uniqueKeys(); |
127 | } |
128 | |
129 | QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const |
130 | { |
131 | return d->subjectAlternativeNames; |
132 | } |
133 | |
134 | QDateTime QSslCertificate::effectiveDate() const |
135 | { |
136 | return d->notValidBefore; |
137 | } |
138 | |
139 | QDateTime QSslCertificate::expiryDate() const |
140 | { |
141 | return d->notValidAfter; |
142 | } |
143 | |
144 | #if !QT_CONFIG(schannel) // implemented in qsslcertificate_schannel.cpp |
145 | Qt::HANDLE QSslCertificate::handle() const |
146 | { |
147 | Q_UNIMPLEMENTED(); |
148 | return nullptr; |
149 | } |
150 | #endif |
151 | |
152 | #ifndef QT_NO_SSL |
153 | QSslKey QSslCertificate::publicKey() const |
154 | { |
155 | QSslKey key; |
156 | key.d->type = QSsl::PublicKey; |
157 | if (d->publicKeyAlgorithm != QSsl::Opaque) { |
158 | key.d->algorithm = d->publicKeyAlgorithm; |
159 | key.d->decodeDer(d->publicKeyDerData); |
160 | } |
161 | return key; |
162 | } |
163 | #endif |
164 | |
165 | QList<QSslCertificateExtension> QSslCertificate::extensions() const |
166 | { |
167 | return d->extensions; |
168 | } |
169 | |
170 | #define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" |
171 | #define ENDCERTSTRING "-----END CERTIFICATE-----" |
172 | |
173 | QByteArray QSslCertificate::toPem() const |
174 | { |
175 | QByteArray array = toDer(); |
176 | |
177 | // Convert to Base64 - wrap at 64 characters. |
178 | array = array.toBase64(); |
179 | QByteArray tmp; |
180 | for (int i = 0; i <= array.size() - 64; i += 64) { |
181 | tmp += QByteArray::fromRawData(array.data() + i, 64); |
182 | tmp += '\n'; |
183 | } |
184 | if (int remainder = array.size() % 64) { |
185 | tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); |
186 | tmp += '\n'; |
187 | } |
188 | |
189 | return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n" ; |
190 | } |
191 | |
192 | QByteArray QSslCertificate::toDer() const |
193 | { |
194 | return d->derData; |
195 | } |
196 | |
197 | QString QSslCertificate::toText() const |
198 | { |
199 | Q_UNIMPLEMENTED(); |
200 | return QString(); |
201 | } |
202 | |
203 | void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format) |
204 | { |
205 | if (!data.isEmpty()) { |
206 | const QList<QSslCertificate> certs = (format == QSsl::Pem) |
207 | ? certificatesFromPem(data, 1) |
208 | : certificatesFromDer(data, 1); |
209 | if (!certs.isEmpty()) { |
210 | *this = *certs.first().d; |
211 | #if QT_CONFIG(schannel) |
212 | if (certificateContext) |
213 | certificateContext = CertDuplicateCertificateContext(certificateContext); |
214 | #endif |
215 | } |
216 | } |
217 | } |
218 | |
219 | static bool matchLineFeed(const QByteArray &pem, int *offset) |
220 | { |
221 | char ch = 0; |
222 | |
223 | // ignore extra whitespace at the end of the line |
224 | while (*offset < pem.size() && (ch = pem.at(*offset)) == ' ') |
225 | ++*offset; |
226 | |
227 | if (ch == '\n') { |
228 | *offset += 1; |
229 | return true; |
230 | } |
231 | if (ch == '\r' && pem.size() > (*offset + 1) && pem.at(*offset + 1) == '\n') { |
232 | *offset += 2; |
233 | return true; |
234 | } |
235 | return false; |
236 | } |
237 | |
238 | QList<QSslCertificate> QSslCertificatePrivate::certificatesFromPem(const QByteArray &pem, int count) |
239 | { |
240 | QList<QSslCertificate> certificates; |
241 | int offset = 0; |
242 | while (count == -1 || certificates.size() < count) { |
243 | int startPos = pem.indexOf(BEGINCERTSTRING, offset); |
244 | if (startPos == -1) |
245 | break; |
246 | startPos += sizeof(BEGINCERTSTRING) - 1; |
247 | if (!matchLineFeed(pem, &startPos)) |
248 | break; |
249 | |
250 | int endPos = pem.indexOf(ENDCERTSTRING, startPos); |
251 | if (endPos == -1) |
252 | break; |
253 | |
254 | offset = endPos + sizeof(ENDCERTSTRING) - 1; |
255 | if (offset < pem.size() && !matchLineFeed(pem, &offset)) |
256 | break; |
257 | |
258 | QByteArray decoded = QByteArray::fromBase64( |
259 | QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); |
260 | certificates << certificatesFromDer(decoded, 1);; |
261 | } |
262 | |
263 | return certificates; |
264 | } |
265 | |
266 | QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count) |
267 | { |
268 | QList<QSslCertificate> certificates; |
269 | |
270 | QByteArray data = der; |
271 | while (count == -1 || certificates.size() < count) { |
272 | QSslCertificate cert; |
273 | if (!cert.d->parse(data)) |
274 | break; |
275 | |
276 | certificates << cert; |
277 | data.remove(0, cert.d->derData.size()); |
278 | } |
279 | |
280 | return certificates; |
281 | } |
282 | |
283 | static QByteArray colonSeparatedHex(const QByteArray &value) |
284 | { |
285 | const int size = value.size(); |
286 | int i = 0; |
287 | while (i < size && !value.at(i)) // skip leading zeros |
288 | ++i; |
289 | |
290 | return value.mid(i).toHex(':'); |
291 | } |
292 | |
293 | bool QSslCertificatePrivate::parse(const QByteArray &data) |
294 | { |
295 | QAsn1Element root; |
296 | |
297 | QDataStream dataStream(data); |
298 | if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) |
299 | return false; |
300 | |
301 | QDataStream rootStream(root.value()); |
302 | QAsn1Element cert; |
303 | if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) |
304 | return false; |
305 | |
306 | // version or serial number |
307 | QAsn1Element elem; |
308 | QDataStream certStream(cert.value()); |
309 | if (!elem.read(certStream)) |
310 | return false; |
311 | |
312 | if (elem.type() == QAsn1Element::Context0Type) { |
313 | QDataStream versionStream(elem.value()); |
314 | if (!elem.read(versionStream) || elem.type() != QAsn1Element::IntegerType) |
315 | return false; |
316 | |
317 | versionString = QByteArray::number(elem.value().at(0) + 1); |
318 | if (!elem.read(certStream)) |
319 | return false; |
320 | } else { |
321 | versionString = QByteArray::number(1); |
322 | } |
323 | |
324 | // serial number |
325 | if (elem.type() != QAsn1Element::IntegerType) |
326 | return false; |
327 | serialNumberString = colonSeparatedHex(elem.value()); |
328 | |
329 | // algorithm ID |
330 | if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) |
331 | return false; |
332 | |
333 | // issuer info |
334 | if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) |
335 | return false; |
336 | |
337 | QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); |
338 | issuerInfo = elem.toInfo(); |
339 | |
340 | // validity period |
341 | if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) |
342 | return false; |
343 | |
344 | QDataStream validityStream(elem.value()); |
345 | if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) |
346 | return false; |
347 | |
348 | notValidBefore = elem.toDateTime(); |
349 | if (!notValidBefore.isValid()) |
350 | return false; |
351 | |
352 | if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) |
353 | return false; |
354 | |
355 | notValidAfter = elem.toDateTime(); |
356 | if (!notValidAfter.isValid()) |
357 | return false; |
358 | |
359 | |
360 | // subject name |
361 | if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) |
362 | return false; |
363 | |
364 | QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); |
365 | subjectInfo = elem.toInfo(); |
366 | subjectMatchesIssuer = issuerDer == subjectDer; |
367 | |
368 | // public key |
369 | qint64 keyStart = certStream.device()->pos(); |
370 | if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) |
371 | return false; |
372 | |
373 | publicKeyDerData.resize(certStream.device()->pos() - keyStart); |
374 | QDataStream keyStream(elem.value()); |
375 | if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) |
376 | return false; |
377 | |
378 | |
379 | // key algorithm |
380 | if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) |
381 | return false; |
382 | |
383 | const QByteArray oid = elem.toObjectId(); |
384 | if (oid == RSA_ENCRYPTION_OID) |
385 | publicKeyAlgorithm = QSsl::Rsa; |
386 | else if (oid == DSA_ENCRYPTION_OID) |
387 | publicKeyAlgorithm = QSsl::Dsa; |
388 | else if (oid == EC_ENCRYPTION_OID) |
389 | publicKeyAlgorithm = QSsl::Ec; |
390 | else |
391 | publicKeyAlgorithm = QSsl::Opaque; |
392 | |
393 | certStream.device()->seek(keyStart); |
394 | certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); |
395 | |
396 | // extensions |
397 | while (elem.read(certStream)) { |
398 | if (elem.type() == QAsn1Element::Context3Type) { |
399 | if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { |
400 | QDataStream extStream(elem.value()); |
401 | while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { |
402 | QSslCertificateExtension extension; |
403 | if (!parseExtension(elem.value(), &extension)) |
404 | return false; |
405 | |
406 | if (extension.oid() == QLatin1String("2.5.29.17" )) { |
407 | // subjectAltName |
408 | |
409 | // Note, parseExtension() returns true for this extensions, |
410 | // but considers it to be unsupported and assignes a useless |
411 | // value. OpenSSL also treats this extension as unsupported, |
412 | // but properly creates a map with 'name' and 'value' taken |
413 | // from the extension. We only support 'email', 'IP' and 'DNS', |
414 | // but this is what our subjectAlternativeNames map can contain |
415 | // anyway. |
416 | QVariantMap extValue; |
417 | QAsn1Element sanElem; |
418 | if (sanElem.read(extension.value().toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { |
419 | QDataStream nameStream(sanElem.value()); |
420 | QAsn1Element nameElem; |
421 | while (nameElem.read(nameStream)) { |
422 | switch (nameElem.type()) { |
423 | case QAsn1Element::Rfc822NameType: |
424 | subjectAlternativeNames.insert(QSsl::EmailEntry, nameElem.toString()); |
425 | extValue[QStringLiteral("email" )] = nameElem.toString(); |
426 | break; |
427 | case QAsn1Element::DnsNameType: |
428 | subjectAlternativeNames.insert(QSsl::DnsEntry, nameElem.toString()); |
429 | extValue[QStringLiteral("DNS" )] = nameElem.toString(); |
430 | break; |
431 | case QAsn1Element::IpAddressType: { |
432 | QHostAddress ipAddress; |
433 | QByteArray ipAddrValue = nameElem.value(); |
434 | switch (ipAddrValue.length()) { |
435 | case 4: // IPv4 |
436 | ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast<quint32 *>(ipAddrValue.data()))); |
437 | break; |
438 | case 16: // IPv6 |
439 | ipAddress = QHostAddress(reinterpret_cast<quint8 *>(ipAddrValue.data())); |
440 | break; |
441 | default: // Unknown IP address format |
442 | break; |
443 | } |
444 | if (!ipAddress.isNull()) { |
445 | subjectAlternativeNames.insert(QSsl::IpAddressEntry, ipAddress.toString()); |
446 | extValue[QStringLiteral("IP" )] = ipAddress.toString(); |
447 | } |
448 | break; |
449 | } |
450 | default: |
451 | break; |
452 | } |
453 | } |
454 | extension.d->value = extValue; |
455 | extension.d->supported = true; |
456 | } |
457 | } |
458 | |
459 | extensions << extension; |
460 | } |
461 | } |
462 | } |
463 | } |
464 | |
465 | derData = data.left(dataStream.device()->pos()); |
466 | null = false; |
467 | return true; |
468 | } |
469 | |
470 | bool QSslCertificatePrivate::parseExtension(const QByteArray &data, QSslCertificateExtension *extension) |
471 | { |
472 | bool ok; |
473 | bool critical = false; |
474 | QAsn1Element oidElem, valElem; |
475 | |
476 | QDataStream seqStream(data); |
477 | |
478 | // oid |
479 | if (!oidElem.read(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) |
480 | return false; |
481 | const QByteArray oid = oidElem.toObjectId(); |
482 | |
483 | // critical and value |
484 | if (!valElem.read(seqStream)) |
485 | return false; |
486 | if (valElem.type() == QAsn1Element::BooleanType) { |
487 | critical = valElem.toBool(&ok); |
488 | if (!ok || !valElem.read(seqStream)) |
489 | return false; |
490 | } |
491 | if (valElem.type() != QAsn1Element::OctetStringType) |
492 | return false; |
493 | |
494 | // interpret value |
495 | QAsn1Element val; |
496 | bool supported = true; |
497 | QVariant value; |
498 | if (oid == "1.3.6.1.5.5.7.1.1" ) { |
499 | // authorityInfoAccess |
500 | if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) |
501 | return false; |
502 | QVariantMap result; |
503 | const auto elems = val.toList(); |
504 | for (const QAsn1Element &el : elems) { |
505 | const auto items = el.toList(); |
506 | if (items.size() != 2) |
507 | return false; |
508 | const QString key = QString::fromLatin1(items.at(0).toObjectName()); |
509 | switch (items.at(1).type()) { |
510 | case QAsn1Element::Rfc822NameType: |
511 | case QAsn1Element::DnsNameType: |
512 | case QAsn1Element::UniformResourceIdentifierType: |
513 | result[key] = items.at(1).toString(); |
514 | break; |
515 | } |
516 | } |
517 | value = result; |
518 | } else if (oid == "2.5.29.14" ) { |
519 | // subjectKeyIdentifier |
520 | if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) |
521 | return false; |
522 | value = colonSeparatedHex(val.value()).toUpper(); |
523 | } else if (oid == "2.5.29.19" ) { |
524 | // basicConstraints |
525 | if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) |
526 | return false; |
527 | |
528 | QVariantMap result; |
529 | const auto items = val.toList(); |
530 | if (items.size() > 0) { |
531 | result[QStringLiteral("ca" )] = items.at(0).toBool(&ok); |
532 | if (!ok) |
533 | return false; |
534 | } else { |
535 | result[QStringLiteral("ca" )] = false; |
536 | } |
537 | if (items.size() > 1) { |
538 | result[QStringLiteral("pathLenConstraint" )] = items.at(1).toInteger(&ok); |
539 | if (!ok) |
540 | return false; |
541 | } |
542 | value = result; |
543 | } else if (oid == "2.5.29.35" ) { |
544 | // authorityKeyIdentifier |
545 | if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) |
546 | return false; |
547 | QVariantMap result; |
548 | const auto elems = val.toList(); |
549 | for (const QAsn1Element &el : elems) { |
550 | if (el.type() == 0x80) { |
551 | const QString key = QStringLiteral("keyid" ); |
552 | result[key] = el.value().toHex(); |
553 | } else if (el.type() == 0x82) { |
554 | const QString serial = QStringLiteral("serial" ); |
555 | result[serial] = colonSeparatedHex(el.value()); |
556 | } |
557 | } |
558 | value = result; |
559 | } else { |
560 | supported = false; |
561 | value = valElem.value(); |
562 | } |
563 | |
564 | extension->d->critical = critical; |
565 | extension->d->supported = supported; |
566 | extension->d->oid = QString::fromLatin1(oid); |
567 | extension->d->name = QString::fromLatin1(oidElem.toObjectName()); |
568 | extension->d->value = value; |
569 | |
570 | return true; |
571 | } |
572 | |
573 | QT_END_NAMESPACE |
574 | |