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