Revision control

Copy as Markdown

// Copyright (C) 2014 Jeremy Lainé <jeremy.laine@m4x.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qasn1element_p.h"
#include <QtCore/qdatastream.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qtimezone.h>
#include <QtCore/qlist.h>
#include <QDebug>
#include <private/qtools_p.h>
#include <limits>
QT_BEGIN_NAMESPACE
using namespace QtMiscUtils;
typedef QMap<QByteArray, QByteArray> OidNameMap;
static OidNameMap createOidMap()
{
OidNameMap oids;
// used by unit tests
oids.insert(oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink"));
oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress"));
oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess"));
oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP"));
oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST"));
oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street"));
return oids;
}
Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap()))
QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value)
: mType(type)
, mValue(value)
{
}
bool QAsn1Element::read(QDataStream &stream)
{
// type
quint8 tmpType;
stream >> tmpType;
if (!tmpType)
return false;
// length
quint64 length = 0;
quint8 first;
stream >> first;
if (first & 0x80) {
// long form
const quint8 bytes = (first & 0x7f);
if (bytes > 7)
return false;
quint8 b;
for (int i = 0; i < bytes; i++) {
stream >> b;
length = (length << 8) | b;
}
} else {
// short form
length = (first & 0x7f);
}
if (length > quint64(std::numeric_limits<int>::max()))
return false;
// read value in blocks to avoid being fooled by incorrect length
const int BUFFERSIZE = 4 * 1024;
QByteArray tmpValue;
int remainingLength = length;
while (remainingLength) {
char readBuffer[BUFFERSIZE];
const int bytesToRead = qMin(remainingLength, BUFFERSIZE);
const int count = stream.readRawData(readBuffer, bytesToRead);
if (count != int(bytesToRead))
return false;
tmpValue.append(readBuffer, bytesToRead);
remainingLength -= bytesToRead;
}
mType = tmpType;
mValue.swap(tmpValue);
return true;
}
bool QAsn1Element::read(const QByteArray &data)
{
QDataStream stream(data);
return read(stream);
}
void QAsn1Element::write(QDataStream &stream) const
{
// type
stream << mType;
// length
qint64 length = mValue.size();
if (length >= 128) {
// long form
quint8 encodedLength = 0x80;
QByteArray ba;
while (length) {
ba.prepend(quint8((length & 0xff)));
length >>= 8;
encodedLength += 1;
}
stream << encodedLength;
stream.writeRawData(ba.data(), ba.size());
} else {
// short form
stream << quint8(length);
}
// value
stream.writeRawData(mValue.data(), mValue.size());
}
QAsn1Element QAsn1Element::fromBool(bool val)
{
return QAsn1Element(QAsn1Element::BooleanType,
QByteArray(1, val ? 0xff : 0x00));
}
QAsn1Element QAsn1Element::fromInteger(unsigned int val)
{
QAsn1Element elem(QAsn1Element::IntegerType);
while (val > 127) {
elem.mValue.prepend(val & 0xff);
val >>= 8;
}
elem.mValue.prepend(val & 0x7f);
return elem;
}
QAsn1Element QAsn1Element::fromVector(const QList<QAsn1Element> &items)
{
QAsn1Element seq;
seq.mType = SequenceType;
QDataStream stream(&seq.mValue, QDataStream::WriteOnly);
for (auto it = items.cbegin(), end = items.cend(); it != end; ++it)
it->write(stream);
return seq;
}
QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id)
{
QAsn1Element elem;
elem.mType = ObjectIdentifierType;
const QList<QByteArray> bits = id.split('.');
Q_ASSERT(bits.size() > 2);
elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt()));
for (int i = 2; i < bits.size(); ++i) {
char buffer[std::numeric_limits<unsigned int>::digits / 7 + 2];
char *pBuffer = buffer + sizeof(buffer);
*--pBuffer = '\0';
unsigned int node = bits[i].toUInt();
*--pBuffer = quint8((node & 0x7f));
node >>= 7;
while (node) {
*--pBuffer = quint8(((node & 0x7f) | 0x80));
node >>= 7;
}
elem.mValue += pBuffer;
}
return elem;
}
bool QAsn1Element::toBool(bool *ok) const
{
if (*this == fromBool(true)) {
if (ok)
*ok = true;
return true;
} else if (*this == fromBool(false)) {
if (ok)
*ok = true;
return false;
} else {
if (ok)
*ok = false;
return false;
}
}
QDateTime QAsn1Element::toDateTime() const
{
QDateTime result;
if (mValue.size() != 13 && mValue.size() != 15)
return result;
// QDateTime::fromString is lenient and accepts +- signs in front
// of the year; but ASN.1 doesn't allow them.
if (!isAsciiDigit(mValue[0]))
return result;
// Timezone must be present, and UTC
if (mValue.back() != 'Z')
return result;
if (mType == UtcTimeType && mValue.size() == 13) {
// RFC 2459:
// Where YY is greater than or equal to 50, the year shall be
// interpreted as 19YY; and
//
// Where YY is less than 50, the year shall be interpreted as 20YY.
//
// so use 1950 as base year.
constexpr int rfc2459CenturyStart = 1950;
const QLatin1StringView inputView(mValue);
QDate date = QDate::fromString(inputView.first(6), u"yyMMdd", rfc2459CenturyStart);
if (!date.isValid())
return result;
Q_ASSERT(date.year() >= rfc2459CenturyStart);
Q_ASSERT(date.year() < 100 + rfc2459CenturyStart);
QTime time = QTime::fromString(inputView.sliced(6, 6), u"HHmmss");
if (!time.isValid())
return result;
result = QDateTime(date, time, QTimeZone::UTC);
} else if (mType == GeneralizedTimeType && mValue.size() == 15) {
result = QDateTime::fromString(QString::fromLatin1(mValue), u"yyyyMMddHHmmsst");
}
return result;
}
QMultiMap<QByteArray, QString> QAsn1Element::toInfo() const
{
QMultiMap<QByteArray, QString> info;
QAsn1Element elem;
QDataStream issuerStream(mValue);
while (elem.read(issuerStream) && elem.mType == QAsn1Element::SetType) {
QAsn1Element issuerElem;
QDataStream setStream(elem.mValue);
if (issuerElem.read(setStream) && issuerElem.mType == QAsn1Element::SequenceType) {
const auto elems = issuerElem.toList();
if (elems.size() == 2) {
const QByteArray key = elems.front().toObjectName();
if (!key.isEmpty())
info.insert(key, elems.back().toString());
}
}
}
return info;
}
qint64 QAsn1Element::toInteger(bool *ok) const
{
if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) {
if (ok)
*ok = false;
return 0;
}
// NOTE: - negative numbers are not handled
// - greater sizes would overflow
if (mValue.at(0) & 0x80 || mValue.size() > 8) {
if (ok)
*ok = false;
return 0;
}
qint64 value = mValue.at(0) & 0x7f;
for (int i = 1; i < mValue.size(); ++i)
value = (value << 8) | quint8(mValue.at(i));
if (ok)
*ok = true;
return value;
}
QList<QAsn1Element> QAsn1Element::toList() const
{
QList<QAsn1Element> items;
if (mType == SequenceType) {
QAsn1Element elem;
QDataStream stream(mValue);
while (elem.read(stream))
items << elem;
}
return items;
}
QByteArray QAsn1Element::toObjectId() const
{
QByteArray key;
if (mType == ObjectIdentifierType && !mValue.isEmpty()) {
quint8 b = mValue.at(0);
key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40);
unsigned int val = 0;
for (int i = 1; i < mValue.size(); ++i) {
b = mValue.at(i);
val = (val << 7) | (b & 0x7f);
if (!(b & 0x80)) {
key += '.' + QByteArray::number(val);
val = 0;
}
}
}
return key;
}
QByteArray QAsn1Element::toObjectName() const
{
QByteArray key = toObjectId();
return oidNameMap->value(key, key);
}
QString QAsn1Element::toString() const
{
// Detect embedded NULs and reject
if (mValue.contains('\0'))
return QString();
if (mType == PrintableStringType || mType == TeletexStringType
|| mType == Rfc822NameType || mType == DnsNameType
|| mType == UniformResourceIdentifierType)
return QString::fromLatin1(mValue);
if (mType == Utf8StringType)
return QString::fromUtf8(mValue);
return QString();
}
QT_END_NAMESPACE