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
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21namespace QTlsPrivate {
22
23namespace {
24
25QByteArray 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
37bool X509CertificateGeneric::isEqual(const X509Certificate &rhs) const
38{
39 const auto &other = static_cast<const X509CertificateGeneric &>(rhs);
40 return derData == other.derData;
41}
42
43bool X509CertificateGeneric::isSelfSigned() const
44{
45 if (null)
46 return false;
47
48 return subjectMatchesIssuer;
49}
50
51QMultiMap<QSsl::AlternativeNameEntryType, QString> X509CertificateGeneric::subjectAlternativeNames() const
52{
53 return saNames;
54}
55
56#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----"
57#define ENDCERTSTRING "-----END CERTIFICATE-----"
58
59QByteArray 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
77QByteArray X509CertificateGeneric::toDer() const
78{
79 return derData;
80}
81
82QString X509CertificateGeneric::toText() const
83{
84 Q_UNIMPLEMENTED();
85 return {};
86}
87
88Qt::HANDLE X509CertificateGeneric::handle() const
89{
90 Q_UNIMPLEMENTED();
91 return nullptr;
92}
93
94size_t X509CertificateGeneric::hash(size_t seed) const noexcept
95{
96 return qHash(key: toDer(), seed);
97}
98
99QList<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
127QList<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
145bool 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
324bool 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
432QT_END_NAMESPACE
433

source code of qtbase/src/plugins/tls/shared/qx509_generic.cpp