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