Qt 6.x
The Qt SDK
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
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
78{
79 return derData;
80}
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
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
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)
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);
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()) {
279 extValue[QStringLiteral("email")] = nameElem.toString();
280 break;
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()) {
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;
425 extension.value = value;
426
427 return true;
428}
429
430} // namespace QTlsPrivate
431
@ UniformResourceIdentifierType
QByteArray toObjectName() const
QMultiMap< QByteArray, QString > toInfo() const
QDateTime toDateTime() const
bool read(QDataStream &data)
QString toString() const
QByteArray toObjectId() const
bool toBool(bool *ok=nullptr) const
QByteArray value() const
quint8 type() const
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
qsizetype indexOf(char c, qsizetype from=0) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QByteArray fromBase64(const QByteArray &base64, Base64Options options=Base64Encoding)
char at(qsizetype i) const
Returns the byte at index position i in the byte array.
Definition qbytearray.h:523
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
QByteArray left(qsizetype len) const
Returns a byte array that contains the first len bytes of this byte array.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray mid(qsizetype index, qsizetype len=-1) const
Returns a byte array containing len bytes from this byte array, starting at position pos.
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:394
\inmodule QtCore\reentrant
Definition qdatastream.h:30
int readRawData(char *, int len)
Reads at most len bytes from the stream into s and returns the number of bytes read.
QIODevice * device() const
Returns the I/O device currently set, or \nullptr if no device is currently set.
bool isValid() const
Returns true if this datetime represents a definite moment, otherwise false.
The QHostAddress class provides an IP address.
bool isNull() const
Returns true if this host address is not valid for any host or interface.
QString toString() const
Returns the address as a string.
virtual qint64 pos() const
For random-access devices, this function returns the position that data is written to or read from.
virtual bool seek(qint64 pos)
For random-access devices, this function sets the current position to pos, returning true on success,...
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
QList< T > toList() const noexcept
Definition qlist.h:716
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
T value(qsizetype i) const
Definition qlist.h:661
void remove(qsizetype i, qsizetype n=1)
Definition qlist.h:787
iterator insert(const Key &key, const T &value)
Definition qmap.h:1425
The QSslCertificate class provides a convenient API for an X509 certificate.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
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:64
void extension()
[6]
Definition dialogs.cpp:230
@ Rsa
Definition qssl.h:31
@ Ec
Definition qssl.h:33
@ Opaque
Definition qssl.h:30
@ Dsa
Definition qssl.h:32
@ IpAddressEntry
Definition qssl.h:40
@ EmailEntry
Definition qssl.h:38
@ DnsEntry
Definition qssl.h:39
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:45
long long qint64
Definition qtypes.h:55
unsigned char quint8
Definition qtypes.h:41
#define ENDCERTSTRING
#define BEGINCERTSTRING
QSharedPointer< T > other(t)
[5]
QList< QTreeWidgetItem * > items
QList< QSslCertificate > cert
[0]
QStringView el