1// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
5#include "qasn1element_p.h"
6
7#include <QtCore/qdatastream.h>
8#include <QtCore/qdatetime.h>
9#include <QtCore/qlist.h>
10#include <QDebug>
11#include <private/qtools_p.h>
12
13#include <limits>
14
15QT_BEGIN_NAMESPACE
16
17using namespace QtMiscUtils;
18
19typedef QMap<QByteArray, QByteArray> OidNameMap;
20static OidNameMap createOidMap()
21{
22 OidNameMap oids;
23 // used by unit tests
24 oids.insert(pos: oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink"));
25 oids.insert(pos: oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress"));
26 oids.insert(pos: oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess"));
27 oids.insert(pos: oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP"));
28 oids.insert(pos: oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers"));
29 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier"));
30 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage"));
31 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName"));
32 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints"));
33 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier"));
34 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O"));
35 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU"));
36 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title"));
37 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description"));
38 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode"));
39 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN"));
40 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN"));
41 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name"));
42 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN"));
43 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials"));
44 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier"));
45 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber"));
46 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C"));
47 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L"));
48 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST"));
49 oids.insert(pos: oids.cend(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street"));
50 return oids;
51}
52Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap()))
53
54QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value)
55 : mType(type)
56 , mValue(value)
57{
58}
59
60bool QAsn1Element::read(QDataStream &stream)
61{
62 // type
63 quint8 tmpType;
64 stream >> tmpType;
65 if (!tmpType)
66 return false;
67
68 // length
69 quint64 length = 0;
70 quint8 first;
71 stream >> first;
72 if (first & 0x80) {
73 // long form
74 const quint8 bytes = (first & 0x7f);
75 if (bytes > 7)
76 return false;
77
78 quint8 b;
79 for (int i = 0; i < bytes; i++) {
80 stream >> b;
81 length = (length << 8) | b;
82 }
83 } else {
84 // short form
85 length = (first & 0x7f);
86 }
87
88 if (length > quint64(std::numeric_limits<int>::max()))
89 return false;
90
91 // read value in blocks to avoid being fooled by incorrect length
92 const int BUFFERSIZE = 4 * 1024;
93 QByteArray tmpValue;
94 int remainingLength = length;
95 while (remainingLength) {
96 char readBuffer[BUFFERSIZE];
97 const int bytesToRead = qMin(a: remainingLength, b: BUFFERSIZE);
98 const int count = stream.readRawData(readBuffer, len: bytesToRead);
99 if (count != int(bytesToRead))
100 return false;
101 tmpValue.append(s: readBuffer, len: bytesToRead);
102 remainingLength -= bytesToRead;
103 }
104
105 mType = tmpType;
106 mValue.swap(other&: tmpValue);
107 return true;
108}
109
110bool QAsn1Element::read(const QByteArray &data)
111{
112 QDataStream stream(data);
113 return read(stream);
114}
115
116void QAsn1Element::write(QDataStream &stream) const
117{
118 // type
119 stream << mType;
120
121 // length
122 qint64 length = mValue.size();
123 if (length >= 128) {
124 // long form
125 quint8 encodedLength = 0x80;
126 QByteArray ba;
127 while (length) {
128 ba.prepend(c: quint8((length & 0xff)));
129 length >>= 8;
130 encodedLength += 1;
131 }
132 stream << encodedLength;
133 stream.writeRawData(ba.data(), len: ba.size());
134 } else {
135 // short form
136 stream << quint8(length);
137 }
138
139 // value
140 stream.writeRawData(mValue.data(), len: mValue.size());
141}
142
143QAsn1Element QAsn1Element::fromBool(bool val)
144{
145 return QAsn1Element(QAsn1Element::BooleanType,
146 QByteArray(1, val ? 0xff : 0x00));
147}
148
149QAsn1Element QAsn1Element::fromInteger(unsigned int val)
150{
151 QAsn1Element elem(QAsn1Element::IntegerType);
152 while (val > 127) {
153 elem.mValue.prepend(c: val & 0xff);
154 val >>= 8;
155 }
156 elem.mValue.prepend(c: val & 0x7f);
157 return elem;
158}
159
160QAsn1Element QAsn1Element::fromVector(const QList<QAsn1Element> &items)
161{
162 QAsn1Element seq;
163 seq.mType = SequenceType;
164 QDataStream stream(&seq.mValue, QDataStream::WriteOnly);
165 for (auto it = items.cbegin(), end = items.cend(); it != end; ++it)
166 it->write(stream);
167 return seq;
168}
169
170QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id)
171{
172 QAsn1Element elem;
173 elem.mType = ObjectIdentifierType;
174 const QList<QByteArray> bits = id.split(sep: '.');
175 Q_ASSERT(bits.size() > 2);
176 elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt()));
177 for (int i = 2; i < bits.size(); ++i) {
178 char buffer[std::numeric_limits<unsigned int>::digits / 7 + 2];
179 char *pBuffer = buffer + sizeof(buffer);
180 *--pBuffer = '\0';
181 unsigned int node = bits[i].toUInt();
182 *--pBuffer = quint8((node & 0x7f));
183 node >>= 7;
184 while (node) {
185 *--pBuffer = quint8(((node & 0x7f) | 0x80));
186 node >>= 7;
187 }
188 elem.mValue += pBuffer;
189 }
190 return elem;
191}
192
193bool QAsn1Element::toBool(bool *ok) const
194{
195 if (*this == fromBool(val: true)) {
196 if (ok)
197 *ok = true;
198 return true;
199 } else if (*this == fromBool(val: false)) {
200 if (ok)
201 *ok = true;
202 return false;
203 } else {
204 if (ok)
205 *ok = false;
206 return false;
207 }
208}
209
210QDateTime QAsn1Element::toDateTime() const
211{
212 QDateTime result;
213
214 if (mValue.size() != 13 && mValue.size() != 15)
215 return result;
216
217 // QDateTime::fromString is lenient and accepts +- signs in front
218 // of the year; but ASN.1 doesn't allow them.
219 if (!isAsciiDigit(c: mValue[0]))
220 return result;
221
222 // Timezone must be present, and UTC
223 if (mValue.back() != 'Z')
224 return result;
225
226 if (mType == UtcTimeType && mValue.size() == 13) {
227 result = QDateTime::fromString(string: QString::fromLatin1(ba: mValue),
228 QStringLiteral("yyMMddHHmmsst"));
229 if (!result.isValid())
230 return result;
231
232 Q_ASSERT(result.timeSpec() == Qt::UTC);
233
234 QDate date = result.date();
235
236 // RFC 2459:
237 // Where YY is greater than or equal to 50, the year shall be
238 // interpreted as 19YY; and
239 //
240 // Where YY is less than 50, the year shall be interpreted as 20YY.
241 //
242 // QDateTime interprets the 'yy' format as 19yy, so we may need to adjust
243 // the year (bring it in the [1950, 2049] range).
244 if (date.year() < 1950)
245 result.setDate(date.addYears(years: 100));
246
247 Q_ASSERT(result.date().year() >= 1950);
248 Q_ASSERT(result.date().year() <= 2049);
249 } else if (mType == GeneralizedTimeType && mValue.size() == 15) {
250 result = QDateTime::fromString(string: QString::fromLatin1(ba: mValue),
251 QStringLiteral("yyyyMMddHHmmsst"));
252 }
253
254 return result;
255}
256
257QMultiMap<QByteArray, QString> QAsn1Element::toInfo() const
258{
259 QMultiMap<QByteArray, QString> info;
260 QAsn1Element elem;
261 QDataStream issuerStream(mValue);
262 while (elem.read(stream&: issuerStream) && elem.mType == QAsn1Element::SetType) {
263 QAsn1Element issuerElem;
264 QDataStream setStream(elem.mValue);
265 if (issuerElem.read(stream&: setStream) && issuerElem.mType == QAsn1Element::SequenceType) {
266 const auto elems = issuerElem.toList();
267 if (elems.size() == 2) {
268 const QByteArray key = elems.front().toObjectName();
269 if (!key.isEmpty())
270 info.insert(key, value: elems.back().toString());
271 }
272 }
273 }
274 return info;
275}
276
277qint64 QAsn1Element::toInteger(bool *ok) const
278{
279 if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) {
280 if (ok)
281 *ok = false;
282 return 0;
283 }
284
285 // NOTE: - negative numbers are not handled
286 // - greater sizes would overflow
287 if (mValue.at(i: 0) & 0x80 || mValue.size() > 8) {
288 if (ok)
289 *ok = false;
290 return 0;
291 }
292
293 qint64 value = mValue.at(i: 0) & 0x7f;
294 for (int i = 1; i < mValue.size(); ++i)
295 value = (value << 8) | quint8(mValue.at(i));
296
297 if (ok)
298 *ok = true;
299 return value;
300}
301
302QList<QAsn1Element> QAsn1Element::toList() const
303{
304 QList<QAsn1Element> items;
305 if (mType == SequenceType) {
306 QAsn1Element elem;
307 QDataStream stream(mValue);
308 while (elem.read(stream))
309 items << elem;
310 }
311 return items;
312}
313
314QByteArray QAsn1Element::toObjectId() const
315{
316 QByteArray key;
317 if (mType == ObjectIdentifierType && !mValue.isEmpty()) {
318 quint8 b = mValue.at(i: 0);
319 key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40);
320 unsigned int val = 0;
321 for (int i = 1; i < mValue.size(); ++i) {
322 b = mValue.at(i);
323 val = (val << 7) | (b & 0x7f);
324 if (!(b & 0x80)) {
325 key += '.' + QByteArray::number(val);
326 val = 0;
327 }
328 }
329 }
330 return key;
331}
332
333QByteArray QAsn1Element::toObjectName() const
334{
335 QByteArray key = toObjectId();
336 return oidNameMap->value(key, defaultValue: key);
337}
338
339QString QAsn1Element::toString() const
340{
341 // Detect embedded NULs and reject
342 if (qstrlen(str: mValue) < uint(mValue.size()))
343 return QString();
344
345 if (mType == PrintableStringType || mType == TeletexStringType
346 || mType == Rfc822NameType || mType == DnsNameType
347 || mType == UniformResourceIdentifierType)
348 return QString::fromLatin1(str: mValue, size: mValue.size());
349 if (mType == Utf8StringType)
350 return QString::fromUtf8(utf8: mValue, size: mValue.size());
351
352 return QString();
353}
354
355QT_END_NAMESPACE
356

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