1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <QtNetwork/private/qsslcertificate_p.h> |
5 | #include <QtNetwork/private/qssl_p.h> |
6 | |
7 | #include "qasn1element_p.h" |
8 | #include "qx509_generic_p.h" |
9 | |
10 | #include <QtNetwork/qhostaddress.h> |
11 | |
12 | #include <QtCore/qendian.h> |
13 | #include <QtCore/qhash.h> |
14 | |
15 | #include <memory> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | namespace QTlsPrivate { |
22 | |
23 | namespace { |
24 | |
25 | QByteArray colonSeparatedHex(const QByteArray &value) |
26 | { |
27 | const int size = value.size(); |
28 | int i = 0; |
29 | while (i < size && !value.at(i)) // skip leading zeros |
30 | ++i; |
31 | |
32 | return value.mid(index: i).toHex(separator: ':'); |
33 | } |
34 | |
35 | } // Unnamed namespace. |
36 | |
37 | bool X509CertificateGeneric::isEqual(const X509Certificate &rhs) const |
38 | { |
39 | const auto &other = static_cast<const X509CertificateGeneric &>(rhs); |
40 | return derData == other.derData; |
41 | } |
42 | |
43 | bool X509CertificateGeneric::isSelfSigned() const |
44 | { |
45 | if (null) |
46 | return false; |
47 | |
48 | return subjectMatchesIssuer; |
49 | } |
50 | |
51 | QMultiMap<QSsl::AlternativeNameEntryType, QString> X509CertificateGeneric::subjectAlternativeNames() const |
52 | { |
53 | return saNames; |
54 | } |
55 | |
56 | #define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" |
57 | #define ENDCERTSTRING "-----END CERTIFICATE-----" |
58 | |
59 | QByteArray X509CertificateGeneric::toPem() const |
60 | { |
61 | QByteArray array = toDer(); |
62 | // Convert to Base64 - wrap at 64 characters. |
63 | array = array.toBase64(); |
64 | QByteArray tmp; |
65 | for (int i = 0; i <= array.size() - 64; i += 64) { |
66 | tmp += QByteArray::fromRawData(data: array.data() + i, size: 64); |
67 | tmp += '\n'; |
68 | } |
69 | if (int remainder = array.size() % 64) { |
70 | tmp += QByteArray::fromRawData(data: array.data() + array.size() - remainder, size: remainder); |
71 | tmp += '\n'; |
72 | } |
73 | |
74 | return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n" ; |
75 | } |
76 | |
77 | QByteArray X509CertificateGeneric::toDer() const |
78 | { |
79 | return derData; |
80 | } |
81 | |
82 | QString X509CertificateGeneric::toText() const |
83 | { |
84 | Q_UNIMPLEMENTED(); |
85 | return {}; |
86 | } |
87 | |
88 | Qt::HANDLE X509CertificateGeneric::handle() const |
89 | { |
90 | Q_UNIMPLEMENTED(); |
91 | return nullptr; |
92 | } |
93 | |
94 | size_t X509CertificateGeneric::hash(size_t seed) const noexcept |
95 | { |
96 | return qHash(key: toDer(), seed); |
97 | } |
98 | |
99 | QList<QSslCertificate> X509CertificateGeneric::certificatesFromPem(const QByteArray &pem, int count) |
100 | { |
101 | QList<QSslCertificate> certificates; |
102 | int offset = 0; |
103 | while (count == -1 || certificates.size() < count) { |
104 | int startPos = pem.indexOf(BEGINCERTSTRING, from: offset); |
105 | if (startPos == -1) |
106 | break; |
107 | startPos += sizeof(BEGINCERTSTRING) - 1; |
108 | if (!matchLineFeed(pem, offset: &startPos)) |
109 | break; |
110 | |
111 | int endPos = pem.indexOf(ENDCERTSTRING, from: startPos); |
112 | if (endPos == -1) |
113 | break; |
114 | |
115 | offset = endPos + sizeof(ENDCERTSTRING) - 1; |
116 | if (offset < pem.size() && !matchLineFeed(pem, offset: &offset)) |
117 | break; |
118 | |
119 | QByteArray decoded = QByteArray::fromBase64( |
120 | base64: QByteArray::fromRawData(data: pem.data() + startPos, size: endPos - startPos)); |
121 | certificates << certificatesFromDer(der: decoded, count: 1);; |
122 | } |
123 | |
124 | return certificates; |
125 | } |
126 | |
127 | QList<QSslCertificate> X509CertificateGeneric::certificatesFromDer(const QByteArray &der, int count) |
128 | { |
129 | QList<QSslCertificate> certificates; |
130 | |
131 | QByteArray data = der; |
132 | while (count == -1 || certificates.size() < count) { |
133 | QSslCertificate cert; |
134 | auto *certBackend = QTlsBackend::backend<X509CertificateGeneric>(o: cert); |
135 | if (!certBackend->parse(data)) |
136 | break; |
137 | |
138 | certificates << cert; |
139 | data.remove(index: 0, len: certBackend->derData.size()); |
140 | } |
141 | |
142 | return certificates; |
143 | } |
144 | |
145 | bool X509CertificateGeneric::parse(const QByteArray &data) |
146 | { |
147 | QAsn1Element root; |
148 | |
149 | QDataStream dataStream(data); |
150 | if (!root.read(data&: dataStream) || root.type() != QAsn1Element::SequenceType) |
151 | return false; |
152 | |
153 | QDataStream rootStream(root.value()); |
154 | QAsn1Element cert; |
155 | if (!cert.read(data&: rootStream) || cert.type() != QAsn1Element::SequenceType) |
156 | return false; |
157 | |
158 | // version or serial number |
159 | QAsn1Element elem; |
160 | QDataStream certStream(cert.value()); |
161 | if (!elem.read(data&: certStream)) |
162 | return false; |
163 | |
164 | if (elem.type() == QAsn1Element::Context0Type) { |
165 | QDataStream versionStream(elem.value()); |
166 | if (!elem.read(data&: versionStream) |
167 | || elem.type() != QAsn1Element::IntegerType |
168 | || elem.value().isEmpty()) |
169 | return false; |
170 | |
171 | versionString = QByteArray::number(elem.value().at(i: 0) + 1); |
172 | if (!elem.read(data&: certStream)) |
173 | return false; |
174 | } else { |
175 | versionString = QByteArray::number(1); |
176 | } |
177 | |
178 | // serial number |
179 | if (elem.type() != QAsn1Element::IntegerType) |
180 | return false; |
181 | serialNumberString = colonSeparatedHex(value: elem.value()); |
182 | |
183 | // algorithm ID |
184 | if (!elem.read(data&: certStream) || elem.type() != QAsn1Element::SequenceType) |
185 | return false; |
186 | |
187 | // issuer info |
188 | if (!elem.read(data&: certStream) || elem.type() != QAsn1Element::SequenceType) |
189 | return false; |
190 | |
191 | QByteArray issuerDer = data.mid(index: dataStream.device()->pos() - elem.value().size(), len: elem.value().size()); |
192 | issuerInfoEntries = elem.toInfo(); |
193 | |
194 | // validity period |
195 | if (!elem.read(data&: certStream) || elem.type() != QAsn1Element::SequenceType) |
196 | return false; |
197 | |
198 | QDataStream validityStream(elem.value()); |
199 | if (!elem.read(data&: validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) |
200 | return false; |
201 | |
202 | notValidBefore = elem.toDateTime(); |
203 | if (!notValidBefore.isValid()) |
204 | return false; |
205 | |
206 | if (!elem.read(data&: validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) |
207 | return false; |
208 | |
209 | notValidAfter = elem.toDateTime(); |
210 | if (!notValidAfter.isValid()) |
211 | return false; |
212 | |
213 | |
214 | // subject name |
215 | if (!elem.read(data&: certStream) || elem.type() != QAsn1Element::SequenceType) |
216 | return false; |
217 | |
218 | QByteArray subjectDer = data.mid(index: dataStream.device()->pos() - elem.value().size(), len: elem.value().size()); |
219 | subjectInfoEntries = elem.toInfo(); |
220 | subjectMatchesIssuer = issuerDer == subjectDer; |
221 | |
222 | // public key |
223 | qint64 keyStart = certStream.device()->pos(); |
224 | if (!elem.read(data&: certStream) || elem.type() != QAsn1Element::SequenceType) |
225 | return false; |
226 | |
227 | publicKeyDerData.resize(size: certStream.device()->pos() - keyStart); |
228 | QDataStream keyStream(elem.value()); |
229 | if (!elem.read(data&: keyStream) || elem.type() != QAsn1Element::SequenceType) |
230 | return false; |
231 | |
232 | |
233 | // key algorithm |
234 | if (!elem.read(data: elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) |
235 | return false; |
236 | |
237 | const QByteArray oid = elem.toObjectId(); |
238 | if (oid == RSA_ENCRYPTION_OID) |
239 | publicKeyAlgorithm = QSsl::Rsa; |
240 | else if (oid == DSA_ENCRYPTION_OID) |
241 | publicKeyAlgorithm = QSsl::Dsa; |
242 | else if (oid == EC_ENCRYPTION_OID) |
243 | publicKeyAlgorithm = QSsl::Ec; |
244 | else |
245 | publicKeyAlgorithm = QSsl::Opaque; |
246 | |
247 | certStream.device()->seek(pos: keyStart); |
248 | certStream.readRawData(publicKeyDerData.data(), len: publicKeyDerData.size()); |
249 | |
250 | // extensions |
251 | while (elem.read(data&: certStream)) { |
252 | if (elem.type() == QAsn1Element::Context3Type) { |
253 | if (elem.read(data: elem.value()) && elem.type() == QAsn1Element::SequenceType) { |
254 | QDataStream extStream(elem.value()); |
255 | while (elem.read(data&: extStream) && elem.type() == QAsn1Element::SequenceType) { |
256 | X509CertificateExtension extension; |
257 | if (!parseExtension(data: elem.value(), extension)) |
258 | return false; |
259 | |
260 | if (extension.oid == "2.5.29.17"_L1 ) { |
261 | // subjectAltName |
262 | |
263 | // Note, parseExtension() returns true for this extensions, |
264 | // but considers it to be unsupported and assigns a useless |
265 | // value. OpenSSL also treats this extension as unsupported, |
266 | // but properly creates a map with 'name' and 'value' taken |
267 | // from the extension. We only support 'email', 'IP' and 'DNS', |
268 | // but this is what our subjectAlternativeNames map can contain |
269 | // anyway. |
270 | QVariantMap extValue; |
271 | QAsn1Element sanElem; |
272 | if (sanElem.read(data: extension.value.toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { |
273 | QDataStream nameStream(sanElem.value()); |
274 | QAsn1Element nameElem; |
275 | while (nameElem.read(data&: nameStream)) { |
276 | switch (nameElem.type()) { |
277 | case QAsn1Element::Rfc822NameType: |
278 | saNames.insert(key: QSsl::EmailEntry, value: nameElem.toString()); |
279 | extValue[QStringLiteral("email" )] = nameElem.toString(); |
280 | break; |
281 | case QAsn1Element::DnsNameType: |
282 | saNames.insert(key: QSsl::DnsEntry, value: nameElem.toString()); |
283 | extValue[QStringLiteral("DNS" )] = nameElem.toString(); |
284 | break; |
285 | case QAsn1Element::IpAddressType: { |
286 | QHostAddress ipAddress; |
287 | QByteArray ipAddrValue = nameElem.value(); |
288 | switch (ipAddrValue.size()) { |
289 | case 4: // IPv4 |
290 | ipAddress = QHostAddress(qFromBigEndian(source: *reinterpret_cast<quint32 *>(ipAddrValue.data()))); |
291 | break; |
292 | case 16: // IPv6 |
293 | ipAddress = QHostAddress(reinterpret_cast<quint8 *>(ipAddrValue.data())); |
294 | break; |
295 | default: // Unknown IP address format |
296 | break; |
297 | } |
298 | if (!ipAddress.isNull()) { |
299 | saNames.insert(key: QSsl::IpAddressEntry, value: ipAddress.toString()); |
300 | extValue[QStringLiteral("IP" )] = ipAddress.toString(); |
301 | } |
302 | break; |
303 | } |
304 | default: |
305 | break; |
306 | } |
307 | } |
308 | extension.value = extValue; |
309 | extension.supported = true; |
310 | } |
311 | } |
312 | |
313 | extensions << extension; |
314 | } |
315 | } |
316 | } |
317 | } |
318 | |
319 | derData = data.left(len: dataStream.device()->pos()); |
320 | null = false; |
321 | return true; |
322 | } |
323 | |
324 | bool X509CertificateGeneric::parseExtension(const QByteArray &data, X509CertificateExtension &extension) |
325 | { |
326 | bool ok = false; |
327 | bool critical = false; |
328 | QAsn1Element oidElem, valElem; |
329 | |
330 | QDataStream seqStream(data); |
331 | |
332 | // oid |
333 | if (!oidElem.read(data&: seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) |
334 | return false; |
335 | |
336 | const QByteArray oid = oidElem.toObjectId(); |
337 | // critical and value |
338 | if (!valElem.read(data&: seqStream)) |
339 | return false; |
340 | |
341 | if (valElem.type() == QAsn1Element::BooleanType) { |
342 | critical = valElem.toBool(ok: &ok); |
343 | |
344 | if (!ok || !valElem.read(data&: seqStream)) |
345 | return false; |
346 | } |
347 | |
348 | if (valElem.type() != QAsn1Element::OctetStringType) |
349 | return false; |
350 | |
351 | // interpret value |
352 | QAsn1Element val; |
353 | bool supported = true; |
354 | QVariant value; |
355 | if (oid == "1.3.6.1.5.5.7.1.1" ) { |
356 | // authorityInfoAccess |
357 | if (!val.read(data: valElem.value()) || val.type() != QAsn1Element::SequenceType) |
358 | return false; |
359 | QVariantMap result; |
360 | const auto elems = val.toList(); |
361 | for (const QAsn1Element &el : elems) { |
362 | const auto items = el.toList(); |
363 | if (items.size() != 2) |
364 | return false; |
365 | const QString key = QString::fromLatin1(ba: items.at(i: 0).toObjectName()); |
366 | switch (items.at(i: 1).type()) { |
367 | case QAsn1Element::Rfc822NameType: |
368 | case QAsn1Element::DnsNameType: |
369 | case QAsn1Element::UniformResourceIdentifierType: |
370 | result[key] = items.at(i: 1).toString(); |
371 | break; |
372 | } |
373 | } |
374 | value = result; |
375 | } else if (oid == "2.5.29.14" ) { |
376 | // subjectKeyIdentifier |
377 | if (!val.read(data: valElem.value()) || val.type() != QAsn1Element::OctetStringType) |
378 | return false; |
379 | value = colonSeparatedHex(value: val.value()).toUpper(); |
380 | } else if (oid == "2.5.29.19" ) { |
381 | // basicConstraints |
382 | if (!val.read(data: valElem.value()) || val.type() != QAsn1Element::SequenceType) |
383 | return false; |
384 | |
385 | QVariantMap result; |
386 | const auto items = val.toList(); |
387 | if (items.size() > 0) { |
388 | result[QStringLiteral("ca" )] = items.at(i: 0).toBool(ok: &ok); |
389 | if (!ok) |
390 | return false; |
391 | } else { |
392 | result[QStringLiteral("ca" )] = false; |
393 | } |
394 | if (items.size() > 1) { |
395 | result[QStringLiteral("pathLenConstraint" )] = items.at(i: 1).toInteger(ok: &ok); |
396 | if (!ok) |
397 | return false; |
398 | } |
399 | value = result; |
400 | } else if (oid == "2.5.29.35" ) { |
401 | // authorityKeyIdentifier |
402 | if (!val.read(data: valElem.value()) || val.type() != QAsn1Element::SequenceType) |
403 | return false; |
404 | QVariantMap result; |
405 | const auto elems = val.toList(); |
406 | for (const QAsn1Element &el : elems) { |
407 | if (el.type() == 0x80) { |
408 | const QString key = QStringLiteral("keyid" ); |
409 | result[key] = el.value().toHex(); |
410 | } else if (el.type() == 0x82) { |
411 | const QString serial = QStringLiteral("serial" ); |
412 | result[serial] = colonSeparatedHex(value: el.value()); |
413 | } |
414 | } |
415 | value = result; |
416 | } else { |
417 | supported = false; |
418 | value = valElem.value(); |
419 | } |
420 | |
421 | extension.critical = critical; |
422 | extension.supported = supported; |
423 | extension.oid = QString::fromLatin1(ba: oid); |
424 | extension.name = QString::fromLatin1(ba: oidElem.toObjectName()); |
425 | extension.value = value; |
426 | |
427 | return true; |
428 | } |
429 | |
430 | } // namespace QTlsPrivate |
431 | |
432 | QT_END_NAMESPACE |
433 | |