Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
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(i).toHex(':');
33}
34
35} // Unnamed namespace.
36
38{
39 const auto &other = static_cast<const X509CertificateGeneric &>(rhs);
40 return derData == other.derData;
41}
42
44{
45 if (null)
46 return false;
47
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
60{
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(array.data() + i, 64);
67 tmp += '\n';
68 }
69 if (int remainder = array.size() % 64) {
70 tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder);
71 tmp += '\n';
72 }
73
74 return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n";
75}
76
81
83{
85 return {};
86}
87
89{
91 return nullptr;
92}
93
94size_t X509CertificateGeneric::hash(size_t seed) const noexcept
95{
96 return qHash(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, offset);
105 if (startPos == -1)
106 break;
107 startPos += sizeof(BEGINCERTSTRING) - 1;
108 if (!matchLineFeed(pem, &startPos))
109 break;
110
111 int endPos = pem.indexOf(ENDCERTSTRING, startPos);
112 if (endPos == -1)
113 break;
114
115 offset = endPos + sizeof(ENDCERTSTRING) - 1;
116 if (offset < pem.size() && !matchLineFeed(pem, &offset))
117 break;
118
120 QByteArray::fromRawData(pem.data() + startPos, endPos - startPos));
121 certificates << certificatesFromDer(decoded, 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) {
134 auto *certBackend = QTlsBackend::backend<X509CertificateGeneric>(cert);
135 if (!certBackend->parse(data))
136 break;
137
138 certificates << cert;
139 data.remove(0, certBackend->derData.size());
140 }
141
142 return certificates;
143}
144
146{
147 QAsn1Element root;
148
149 QDataStream dataStream(data);
150 if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType)
151 return false;
152
153 QDataStream rootStream(root.value());
155 if (!cert.read(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(certStream))
162 return false;
163
164 if (elem.type() == QAsn1Element::Context0Type) {
165 QDataStream versionStream(elem.value());
166 if (!elem.read(versionStream)
167 || elem.type() != QAsn1Element::IntegerType
168 || elem.value().isEmpty())
169 return false;
170
171 versionString = QByteArray::number(elem.value().at(0) + 1);
172 if (!elem.read(certStream))
173 return false;
174 } else {
176 }
177
178 // serial number
179 if (elem.type() != QAsn1Element::IntegerType)
180 return false;
181 serialNumberString = colonSeparatedHex(elem.value());
182
183 // algorithm ID
184 if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType)
185 return false;
186
187 // issuer info
188 if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType)
189 return false;
190
191 QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().size(), elem.value().size());
192 issuerInfoEntries = elem.toInfo();
193
194 // validity period
195 if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType)
196 return false;
197
198 QDataStream validityStream(elem.value());
199 if (!elem.read(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(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(certStream) || elem.type() != QAsn1Element::SequenceType)
216 return false;
217
218 QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().size(), 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(certStream) || elem.type() != QAsn1Element::SequenceType)
225 return false;
226
227 publicKeyDerData.resize(certStream.device()->pos() - keyStart);
228 QDataStream keyStream(elem.value());
229 if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType)
230 return false;
231
232
233 // key algorithm
234 if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType)
235 return false;
236
237 const QByteArray oid = elem.toObjectId();
238 if (oid == RSA_ENCRYPTION_OID)
240 else if (oid == DSA_ENCRYPTION_OID)
242 else if (oid == EC_ENCRYPTION_OID)
244 else
246
247 certStream.device()->seek(keyStart);
248 certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size());
249
250 // extensions
251 while (elem.read(certStream)) {
252 if (elem.type() == QAsn1Element::Context3Type) {
253 if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) {
254 QDataStream extStream(elem.value());
255 while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) {
257 if (!parseExtension(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(extension.value.toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) {
273 QDataStream nameStream(sanElem.value());
274 QAsn1Element nameElem;
275 while (nameElem.read(nameStream)) {
276 switch (nameElem.type()) {
278 saNames.insert(QSsl::EmailEntry, nameElem.toString());
279 extValue[QStringLiteral("email")] = nameElem.toString();
280 break;
282 saNames.insert(QSsl::DnsEntry, nameElem.toString());
283 extValue[QStringLiteral("DNS")] = nameElem.toString();
284 break;
286 QHostAddress ipAddress;
287 QByteArray ipAddrValue = nameElem.value();
288 switch (ipAddrValue.size()) {
289 case 4: // IPv4
290 ipAddress = QHostAddress(qFromBigEndian(*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(QSsl::IpAddressEntry, 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
314 }
315 }
316 }
317 }
318
319 derData = data.left(dataStream.device()->pos());
320 null = false;
321 return true;
322}
323
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(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType)
334 return false;
335
336 const QByteArray oid = oidElem.toObjectId();
337 // critical and value
338 if (!valElem.read(seqStream))
339 return false;
340
341 if (valElem.type() == QAsn1Element::BooleanType) {
342 critical = valElem.toBool(&ok);
343
344 if (!ok || !valElem.read(seqStream))
345 return false;
346 }
347
348 if (valElem.type() != QAsn1Element::OctetStringType)
349 return false;
350
351 // interpret value
353 bool supported = true;
355 if (oid == "1.3.6.1.5.5.7.1.1") {
356 // authorityInfoAccess
357 if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType)
358 return false;
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(items.at(0).toObjectName());
366 switch (items.at(1).type()) {
370 result[key] = items.at(1).toString();
371 break;
372 }
373 }
374 value = result;
375 } else if (oid == "2.5.29.14") {
376 // subjectKeyIdentifier
377 if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType)
378 return false;
379 value = colonSeparatedHex(val.value()).toUpper();
380 } else if (oid == "2.5.29.19") {
381 // basicConstraints
382 if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType)
383 return false;
384
386 const auto items = val.toList();
387 if (items.size() > 0) {
388 result[QStringLiteral("ca")] = items.at(0).toBool(&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(1).toInteger(&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(valElem.value()) || val.type() != QAsn1Element::SequenceType)
403 return false;
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(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;
424 extension.name = QString::fromLatin1(oidElem.toObjectName());
425 extension.value = value;
426
427 return true;
428}
429
430} // namespace QTlsPrivate
431
@ UniformResourceIdentifierType
bool read(QDataStream &data)
QByteArray value() const
quint8 type() const
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
QByteArray left(qsizetype n) const &
Definition qbytearray.h:169
static QByteArray fromBase64(const QByteArray &base64, Base64Options options=Base64Encoding)
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray mid(qsizetype index, qsizetype len=-1) const &
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
\inmodule QtCore\reentrant
Definition qdatastream.h:46
bool isValid() const
Returns true if this datetime represents a definite moment, otherwise false.
The QHostAddress class provides an IP address.
qsizetype size() const noexcept
Definition qlist.h:397
QList< T > toList() const noexcept
Definition qlist.h:723
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
T value(qsizetype i) const
Definition qlist.h:664
void remove(qsizetype i, qsizetype n=1)
Definition qlist.h:794
iterator insert(const Key &key, const T &value)
Definition qmap.h:1452
The QSslCertificate class provides a convenient API for an X509 certificate.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
QList< X509CertificateExtension > extensions
QMultiMap< QByteArray, QString > subjectInfoEntries
QMultiMap< QByteArray, QString > issuerInfoEntries
static bool matchLineFeed(const QByteArray &pem, int *offset)
QMultiMap< QSsl::AlternativeNameEntryType, QString > subjectAlternativeNames() const override
QByteArray toDer() const override
static QList< QSslCertificate > certificatesFromDer(const QByteArray &der, int count)
QMultiMap< QSsl::AlternativeNameEntryType, QString > saNames
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
static QList< QSslCertificate > certificatesFromPem(const QByteArray &pem, int count)
X509Certificate is an abstract class that allows a TLS backend to provide an implementation of the QS...
int type() const
Returns the type passed to the QTreeWidgetItem constructor.
\inmodule QtCore
Definition qvariant.h:65
void extension()
[6]
Definition dialogs.cpp:230
@ Rsa
Definition qssl.h:36
@ Ec
Definition qssl.h:38
@ Opaque
Definition qssl.h:35
@ Dsa
Definition qssl.h:37
@ IpAddressEntry
Definition qssl.h:46
@ EmailEntry
Definition qssl.h:44
@ DnsEntry
Definition qssl.h:45
Combined button and popup list for selecting options.
Namespace containing onternal types that TLS backends implement.
void * HANDLE
#define EC_ENCRYPTION_OID
#define DSA_ENCRYPTION_OID
#define RSA_ENCRYPTION_OID
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
constexpr T qFromBigEndian(T source)
Definition qendian.h:174
size_t qHash(const QFileSystemWatcherPathKey &key, size_t seed=0)
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLuint GLfloat * val
GLenum array
GLuint64EXT * result
[6]
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
Definition qrandom.cpp:196
#define QStringLiteral(str)
#define Q_UNIMPLEMENTED()
unsigned int quint32
Definition qtypes.h:50
long long qint64
Definition qtypes.h:60
unsigned char quint8
Definition qtypes.h:46
#define ENDCERTSTRING
#define BEGINCERTSTRING
QSharedPointer< T > other(t)
[5]
QList< QTreeWidgetItem * > items
QList< QSslCertificate > cert
[0]
QStringView el