1/****************************************************************************
2**
3** Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40
41#include "qasn1element_p.h"
42
43#include <QtCore/qdatastream.h>
44#include <QtCore/qdatetime.h>
45#include <QtCore/qlist.h>
46#include <QDebug>
47
48#include <limits>
49
50QT_BEGIN_NAMESPACE
51
52typedef QMap<QByteArray, QByteArray> OidNameMap;
53static OidNameMap createOidMap()
54{
55 OidNameMap oids;
56 // used by unit tests
57 oids.insert(oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink"));
58 oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress"));
59 oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess"));
60 oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP"));
61 oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers"));
62 oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier"));
63 oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage"));
64 oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName"));
65 oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints"));
66 oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier"));
67 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O"));
68 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU"));
69 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title"));
70 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description"));
71 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode"));
72 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN"));
73 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN"));
74 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name"));
75 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN"));
76 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials"));
77 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier"));
78 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber"));
79 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C"));
80 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L"));
81 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST"));
82 oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street"));
83 return oids;
84}
85Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap()))
86
87QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value)
88 : mType(type)
89 , mValue(value)
90{
91}
92
93bool QAsn1Element::read(QDataStream &stream)
94{
95 // type
96 quint8 tmpType;
97 stream >> tmpType;
98 if (!tmpType)
99 return false;
100
101 // length
102 quint64 length = 0;
103 quint8 first;
104 stream >> first;
105 if (first & 0x80) {
106 // long form
107 const quint8 bytes = (first & 0x7f);
108 if (bytes > 7)
109 return false;
110
111 quint8 b;
112 for (int i = 0; i < bytes; i++) {
113 stream >> b;
114 length = (length << 8) | b;
115 }
116 } else {
117 // short form
118 length = (first & 0x7f);
119 }
120
121 if (length > quint64(std::numeric_limits<int>::max()))
122 return false;
123 // value
124 QByteArray tmpValue;
125 tmpValue.resize(length);
126 int count = stream.readRawData(tmpValue.data(), tmpValue.size());
127 if (count != int(length))
128 return false;
129
130 mType = tmpType;
131 mValue.swap(tmpValue);
132 return true;
133}
134
135bool QAsn1Element::read(const QByteArray &data)
136{
137 QDataStream stream(data);
138 return read(stream);
139}
140
141void QAsn1Element::write(QDataStream &stream) const
142{
143 // type
144 stream << mType;
145
146 // length
147 qint64 length = mValue.size();
148 if (length >= 128) {
149 // long form
150 quint8 encodedLength = 0x80;
151 QByteArray ba;
152 while (length) {
153 ba.prepend(quint8((length & 0xff)));
154 length >>= 8;
155 encodedLength += 1;
156 }
157 stream << encodedLength;
158 stream.writeRawData(ba.data(), ba.size());
159 } else {
160 // short form
161 stream << quint8(length);
162 }
163
164 // value
165 stream.writeRawData(mValue.data(), mValue.size());
166}
167
168QAsn1Element QAsn1Element::fromBool(bool val)
169{
170 return QAsn1Element(QAsn1Element::BooleanType,
171 QByteArray(1, val ? 0xff : 0x00));
172}
173
174QAsn1Element QAsn1Element::fromInteger(unsigned int val)
175{
176 QAsn1Element elem(QAsn1Element::IntegerType);
177 while (val > 127) {
178 elem.mValue.prepend(val & 0xff);
179 val >>= 8;
180 }
181 elem.mValue.prepend(val & 0x7f);
182 return elem;
183}
184
185QAsn1Element QAsn1Element::fromVector(const QList<QAsn1Element> &items)
186{
187 QAsn1Element seq;
188 seq.mType = SequenceType;
189 QDataStream stream(&seq.mValue, QDataStream::WriteOnly);
190 for (auto it = items.cbegin(), end = items.cend(); it != end; ++it)
191 it->write(stream);
192 return seq;
193}
194
195QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id)
196{
197 QAsn1Element elem;
198 elem.mType = ObjectIdentifierType;
199 const QList<QByteArray> bits = id.split('.');
200 Q_ASSERT(bits.size() > 2);
201 elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt()));
202 for (int i = 2; i < bits.size(); ++i) {
203 char buffer[std::numeric_limits<unsigned int>::digits / 7 + 2];
204 char *pBuffer = buffer + sizeof(buffer);
205 *--pBuffer = '\0';
206 unsigned int node = bits[i].toUInt();
207 *--pBuffer = quint8((node & 0x7f));
208 node >>= 7;
209 while (node) {
210 *--pBuffer = quint8(((node & 0x7f) | 0x80));
211 node >>= 7;
212 }
213 elem.mValue += pBuffer;
214 }
215 return elem;
216}
217
218bool QAsn1Element::toBool(bool *ok) const
219{
220 if (*this == fromBool(true)) {
221 if (ok)
222 *ok = true;
223 return true;
224 } else if (*this == fromBool(false)) {
225 if (ok)
226 *ok = true;
227 return false;
228 } else {
229 if (ok)
230 *ok = false;
231 return false;
232 }
233}
234
235QDateTime QAsn1Element::toDateTime() const
236{
237 QDateTime result;
238
239 if (mValue.size() != 13 && mValue.size() != 15)
240 return result;
241
242 // QDateTime::fromString is lenient and accepts +- signs in front
243 // of the year; but ASN.1 doesn't allow them.
244 const auto isAsciiDigit = [](char c)
245 {
246 return c >= '0' && c <= '9';
247 };
248
249 if (!isAsciiDigit(mValue[0]))
250 return result;
251
252 // Timezone must be present, and UTC
253 if (mValue.back() != 'Z')
254 return result;
255
256 // In addition, check that we only have digits representing the
257 // date/time. This should not really be necessary (there's no such
258 // thing as negative months/days/etc.); it's a workaround for
259 // QTBUG-84349.
260 if (!std::all_of(mValue.begin(), mValue.end() - 1, isAsciiDigit))
261 return result;
262
263 if (mType == UtcTimeType && mValue.size() == 13) {
264 result = QDateTime::fromString(QString::fromLatin1(mValue),
265 QStringLiteral("yyMMddHHmmsst"));
266 if (!result.isValid())
267 return result;
268
269 Q_ASSERT(result.timeSpec() == Qt::UTC);
270
271 QDate date = result.date();
272
273 // RFC 2459:
274 // Where YY is greater than or equal to 50, the year shall be
275 // interpreted as 19YY; and
276 //
277 // Where YY is less than 50, the year shall be interpreted as 20YY.
278 //
279 // QDateTime interprets the 'yy' format as 19yy, so we may need to adjust
280 // the year (bring it in the [1950, 2049] range).
281 if (date.year() < 1950)
282 result.setDate(date.addYears(100));
283
284 Q_ASSERT(result.date().year() >= 1950);
285 Q_ASSERT(result.date().year() <= 2049);
286 } else if (mType == GeneralizedTimeType && mValue.size() == 15) {
287 result = QDateTime::fromString(QString::fromLatin1(mValue),
288 QStringLiteral("yyyyMMddHHmmsst"));
289 }
290
291 return result;
292}
293
294QMultiMap<QByteArray, QString> QAsn1Element::toInfo() const
295{
296 QMultiMap<QByteArray, QString> info;
297 QAsn1Element elem;
298 QDataStream issuerStream(mValue);
299 while (elem.read(issuerStream) && elem.mType == QAsn1Element::SetType) {
300 QAsn1Element issuerElem;
301 QDataStream setStream(elem.mValue);
302 if (issuerElem.read(setStream) && issuerElem.mType == QAsn1Element::SequenceType) {
303 const auto elems = issuerElem.toList();
304 if (elems.size() == 2) {
305 const QByteArray key = elems.front().toObjectName();
306 if (!key.isEmpty())
307 info.insert(key, elems.back().toString());
308 }
309 }
310 }
311 return info;
312}
313
314qint64 QAsn1Element::toInteger(bool *ok) const
315{
316 if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) {
317 if (ok)
318 *ok = false;
319 return 0;
320 }
321
322 // NOTE: negative numbers are not handled
323 if (mValue.at(0) & 0x80) {
324 if (ok)
325 *ok = false;
326 return 0;
327 }
328
329 qint64 value = mValue.at(0) & 0x7f;
330 for (int i = 1; i < mValue.size(); ++i)
331 value = (value << 8) | quint8(mValue.at(i));
332
333 if (ok)
334 *ok = true;
335 return value;
336}
337
338QList<QAsn1Element> QAsn1Element::toList() const
339{
340 QList<QAsn1Element> items;
341 if (mType == SequenceType) {
342 QAsn1Element elem;
343 QDataStream stream(mValue);
344 while (elem.read(stream))
345 items << elem;
346 }
347 return items;
348}
349
350QByteArray QAsn1Element::toObjectId() const
351{
352 QByteArray key;
353 if (mType == ObjectIdentifierType && !mValue.isEmpty()) {
354 quint8 b = mValue.at(0);
355 key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40);
356 unsigned int val = 0;
357 for (int i = 1; i < mValue.size(); ++i) {
358 b = mValue.at(i);
359 val = (val << 7) | (b & 0x7f);
360 if (!(b & 0x80)) {
361 key += '.' + QByteArray::number(val);
362 val = 0;
363 }
364 }
365 }
366 return key;
367}
368
369QByteArray QAsn1Element::toObjectName() const
370{
371 QByteArray key = toObjectId();
372 return oidNameMap->value(key, key);
373}
374
375QString QAsn1Element::toString() const
376{
377 // Detect embedded NULs and reject
378 if (qstrlen(mValue) < uint(mValue.size()))
379 return QString();
380
381 if (mType == PrintableStringType || mType == TeletexStringType
382 || mType == Rfc822NameType || mType == DnsNameType
383 || mType == UniformResourceIdentifierType)
384 return QString::fromLatin1(mValue, mValue.size());
385 if (mType == Utf8StringType)
386 return QString::fromUtf8(mValue, mValue.size());
387
388 return QString();
389}
390
391QT_END_NAMESPACE
392