diff options
Diffstat (limited to 'src/network/ssl')
-rw-r--r-- | src/network/ssl/qasn1element.cpp | 55 | ||||
-rw-r--r-- | src/network/ssl/qasn1element_p.h | 20 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate.cpp | 150 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate_p.h | 5 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate_qt.cpp | 256 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_openssl.cpp | 3 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_p.cpp | 52 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_p.h | 13 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_qt.cpp | 82 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_winrt.cpp | 98 |
10 files changed, 560 insertions, 174 deletions
diff --git a/src/network/ssl/qasn1element.cpp b/src/network/ssl/qasn1element.cpp index d282a02827..f3f280d863 100644 --- a/src/network/ssl/qasn1element.cpp +++ b/src/network/ssl/qasn1element.cpp @@ -56,6 +56,14 @@ static OidNameMap createOidMap() // used by unit tests oids.insert(oids.end(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink")); oids.insert(oids.end(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP")); + oids.insert(oids.end(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints")); + oids.insert(oids.end(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU")); oids.insert(oids.end(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title")); @@ -155,6 +163,12 @@ void QAsn1Element::write(QDataStream &stream) const 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); @@ -199,6 +213,23 @@ QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id) 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 { if (mValue.endsWith('Z')) { @@ -242,6 +273,30 @@ QMultiMap<QByteArray, QString> QAsn1Element::toInfo() const 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 + if (mValue.at(0) & 0x80) { + 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; +} + QVector<QAsn1Element> QAsn1Element::toVector() const { QVector<QAsn1Element> items; diff --git a/src/network/ssl/qasn1element_p.h b/src/network/ssl/qasn1element_p.h index 6b3179ac35..36a7c90de3 100644 --- a/src/network/ssl/qasn1element_p.h +++ b/src/network/ssl/qasn1element_p.h @@ -59,11 +59,15 @@ QT_BEGIN_NAMESPACE +#define RSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.113549.1.1.1") +#define DSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10040.4.1") + class Q_AUTOTEST_EXPORT QAsn1Element { public: enum ElementType { // universal + BooleanType = 0x01, IntegerType = 0x02, BitStringType = 0x03, OctetStringType = 0x04, @@ -77,10 +81,6 @@ public: SequenceType = 0x30, SetType = 0x31, - // application - Rfc822NameType = 0x81, - DnsNameType = 0x82, - // context specific Context0Type = 0xA0, Context3Type = 0xA3 @@ -91,12 +91,15 @@ public: bool read(const QByteArray &data); void write(QDataStream &data) const; + static QAsn1Element fromBool(bool val); static QAsn1Element fromInteger(unsigned int val); static QAsn1Element fromVector(const QVector<QAsn1Element> &items); static QAsn1Element fromObjectId(const QByteArray &id); + bool toBool(bool *ok = 0) const; QDateTime toDateTime() const; QMultiMap<QByteArray, QString> toInfo() const; + qint64 toInteger(bool *ok = 0) const; QVector<QAsn1Element> toVector() const; QByteArray toObjectId() const; QByteArray toObjectName() const; @@ -105,12 +108,21 @@ public: quint8 type() const { return mType; } QByteArray value() const { return mValue; } + friend inline bool operator==(const QAsn1Element &, const QAsn1Element &); + friend inline bool operator!=(const QAsn1Element &, const QAsn1Element &); + private: quint8 mType; QByteArray mValue; }; Q_DECLARE_TYPEINFO(QAsn1Element, Q_MOVABLE_TYPE); +inline bool operator==(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType == e2.mType && e1.mValue == e2.mValue; } + +inline bool operator!=(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType != e2.mType || e1.mValue != e2.mValue; } + QT_END_NAMESPACE #endif diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp index bae78f4347..47ea3343ea 100644 --- a/src/network/ssl/qsslcertificate.cpp +++ b/src/network/ssl/qsslcertificate.cpp @@ -122,7 +122,6 @@ #include "qsslcertificate.h" #include "qsslcertificate_p.h" -#include "qasn1element_p.h" #include "qsslkey_p.h" #include <QtCore/qdir.h> @@ -642,155 +641,6 @@ static const char *certificate_blacklist[] = { 0 }; -bool QSslCertificatePrivate::parse(const QByteArray &data) -{ -#ifndef QT_NO_OPENSSL - Q_UNUSED(data); -#else - QAsn1Element root; - - QDataStream dataStream(data); - if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) - return false; - - QDataStream rootStream(root.value()); - QAsn1Element cert; - if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) - return false; - - // version or serial number - QAsn1Element elem; - QDataStream certStream(cert.value()); - if (!elem.read(certStream)) - return false; - - if (elem.type() == QAsn1Element::Context0Type) { - QDataStream versionStream(elem.value()); - if (!elem.read(versionStream) || elem.type() != QAsn1Element::IntegerType) - return false; - - versionString = QByteArray::number(elem.value()[0] + 1); - if (!elem.read(certStream)) - return false; - } else { - versionString = QByteArray::number(1); - } - - // serial number - if (elem.type() != QAsn1Element::IntegerType) - return false; - - QByteArray hexString; - hexString.reserve(elem.value().size() * 3); - for (int a = 0; a < elem.value().size(); ++a) { - const quint8 b = elem.value().at(a); - if (b || !hexString.isEmpty()) { // skip leading zeros - hexString += QByteArray::number(b, 16).rightJustified(2, '0'); - hexString += ':'; - } - } - hexString.chop(1); - serialNumberString = hexString; - - // algorithm ID - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - //qDebug() << "algorithm ID" << elem.type() << elem.length << elem.value().toHex(); - - // issuer info - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - issuerInfo = elem.toInfo(); - - // validity period - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QDataStream validityStream(elem.value()); - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidBefore = elem.toDateTime(); - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidAfter = elem.toDateTime(); - - // subject name - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - subjectInfo = elem.toInfo(); - subjectMatchesIssuer = issuerDer == subjectDer; - - // public key - qint64 keyStart = certStream.device()->pos(); - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - publicKeyDerData.resize(certStream.device()->pos() - keyStart); - QDataStream keyStream(elem.value()); - if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - - // key algorithm - if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) - return false; - - const QByteArray oid = elem.toObjectId(); - if (oid == "1.2.840.113549.1.1.1") - publicKeyAlgorithm = QSsl::Rsa; - else if (oid == "1.2.840.10040.4.1") - publicKeyAlgorithm = QSsl::Dsa; - else - publicKeyAlgorithm = QSsl::Opaque; - - certStream.device()->seek(keyStart); - certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); - - // extensions - while (elem.read(certStream)) { - if (elem.type() == QAsn1Element::Context3Type) { - if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { - QDataStream extStream(elem.value()); - while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { - QAsn1Element oidElem, valElem; - QDataStream seqStream(elem.value()); - if (oidElem.read(seqStream) && oidElem.type() == QAsn1Element::ObjectIdentifierType && - valElem.read(seqStream) && valElem.type() == QAsn1Element::OctetStringType) { - // alternative name - if (oidElem.toObjectId() == QByteArray("2.5.29.17")) { - QAsn1Element sanElem; - if (sanElem.read(valElem.value()) && sanElem.type() == QAsn1Element::SequenceType) { - QDataStream nameStream(sanElem.value()); - QAsn1Element nameElem; - while (nameElem.read(nameStream)) { - if (nameElem.type() == QAsn1Element::Rfc822NameType) { - subjectAlternativeNames.insert(QSsl::EmailEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); - } else if (nameElem.type() == QAsn1Element::DnsNameType) { - subjectAlternativeNames.insert(QSsl::DnsEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); - } - } - } - } - } - } - } - } - } - - derData = data.left(dataStream.device()->pos()); - null = false; - -#endif // QT_NO_OPENSSL - return true; -} - bool QSslCertificatePrivate::isBlacklisted(const QSslCertificate &certificate) { for (int a = 0; certificate_blacklist[a] != 0; a++) { diff --git a/src/network/ssl/qsslcertificate_p.h b/src/network/ssl/qsslcertificate_p.h index 472553c30c..b0c99e545d 100644 --- a/src/network/ssl/qsslcertificate_p.h +++ b/src/network/ssl/qsslcertificate_p.h @@ -109,13 +109,16 @@ public: QSsl::KeyAlgorithm publicKeyAlgorithm; QByteArray publicKeyDerData; QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames; + QList<QSslCertificateExtension> extensions; QByteArray derData; + + bool parse(const QByteArray &data); + bool parseExtension(const QByteArray &data, QSslCertificateExtension *extension); #endif X509 *x509; void init(const QByteArray &data, QSsl::EncodingFormat format); - bool parse(const QByteArray &data); static QByteArray asn1ObjectId(ASN1_OBJECT *object); static QByteArray asn1ObjectName(ASN1_OBJECT *object); diff --git a/src/network/ssl/qsslcertificate_qt.cpp b/src/network/ssl/qsslcertificate_qt.cpp index 391ee6f7f9..6ea78a408b 100644 --- a/src/network/ssl/qsslcertificate_qt.cpp +++ b/src/network/ssl/qsslcertificate_qt.cpp @@ -47,9 +47,17 @@ #include "qsslkey_p.h" #include "qsslcertificateextension.h" #include "qsslcertificateextension_p.h" +#include "qasn1element_p.h" QT_BEGIN_NAMESPACE +enum GeneralNameType +{ + Rfc822NameType = 0x81, + DnsNameType = 0x82, + UniformResourceIdentifierType = 0x86 +}; + bool QSslCertificate::operator==(const QSslCertificate &other) const { if (d == other.d) @@ -150,8 +158,7 @@ QSslKey QSslCertificate::publicKey() const QList<QSslCertificateExtension> QSslCertificate::extensions() const { - Q_UNIMPLEMENTED(); - return QList<QSslCertificateExtension>(); + return d->extensions; } #define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" @@ -263,4 +270,249 @@ QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteAr return certificates; } +static QByteArray colonSeparatedHex(const QByteArray &value) +{ + QByteArray hexString; + hexString.reserve(value.size() * 3); + for (int a = 0; a < value.size(); ++a) { + const quint8 b = value.at(a); + if (b || !hexString.isEmpty()) { // skip leading zeros + hexString += QByteArray::number(b, 16).rightJustified(2, '0'); + hexString += ':'; + } + } + hexString.chop(1); + return hexString; +} + +bool QSslCertificatePrivate::parse(const QByteArray &data) +{ + QAsn1Element root; + + QDataStream dataStream(data); + if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) + return false; + + QDataStream rootStream(root.value()); + QAsn1Element cert; + if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) + return false; + + // version or serial number + QAsn1Element elem; + QDataStream certStream(cert.value()); + if (!elem.read(certStream)) + return false; + + if (elem.type() == QAsn1Element::Context0Type) { + QDataStream versionStream(elem.value()); + if (!elem.read(versionStream) || elem.type() != QAsn1Element::IntegerType) + return false; + + versionString = QByteArray::number(elem.value()[0] + 1); + if (!elem.read(certStream)) + return false; + } else { + versionString = QByteArray::number(1); + } + + // serial number + if (elem.type() != QAsn1Element::IntegerType) + return false; + serialNumberString = colonSeparatedHex(elem.value()); + + // algorithm ID + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + // issuer info + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + issuerInfo = elem.toInfo(); + + // validity period + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QDataStream validityStream(elem.value()); + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidBefore = elem.toDateTime(); + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidAfter = elem.toDateTime(); + + // subject name + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + subjectInfo = elem.toInfo(); + subjectMatchesIssuer = issuerDer == subjectDer; + + // public key + qint64 keyStart = certStream.device()->pos(); + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + publicKeyDerData.resize(certStream.device()->pos() - keyStart); + QDataStream keyStream(elem.value()); + if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + + // key algorithm + if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) + return false; + + const QByteArray oid = elem.toObjectId(); + if (oid == RSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Rsa; + else if (oid == RSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Dsa; + else + publicKeyAlgorithm = QSsl::Opaque; + + certStream.device()->seek(keyStart); + certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); + + // extensions + while (elem.read(certStream)) { + if (elem.type() == QAsn1Element::Context3Type) { + if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { + QDataStream extStream(elem.value()); + while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { + QSslCertificateExtension extension; + if (!parseExtension(elem.value(), &extension)) + return false; + extensions << extension; + + if (extension.oid() == QLatin1String("2.5.29.17")) { + // subjectAltName + QAsn1Element sanElem; + if (sanElem.read(extension.value().toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { + QDataStream nameStream(sanElem.value()); + QAsn1Element nameElem; + while (nameElem.read(nameStream)) { + if (nameElem.type() == Rfc822NameType) { + subjectAlternativeNames.insert(QSsl::EmailEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); + } else if (nameElem.type() == DnsNameType) { + subjectAlternativeNames.insert(QSsl::DnsEntry, QString::fromLatin1(nameElem.value(), nameElem.value().size())); + } + } + } + } + } + } + } + } + + derData = data.left(dataStream.device()->pos()); + null = false; + return true; +} + +bool QSslCertificatePrivate::parseExtension(const QByteArray &data, QSslCertificateExtension *extension) +{ + bool ok; + bool critical = false; + QAsn1Element oidElem, valElem; + + QDataStream seqStream(data); + + // oid + if (!oidElem.read(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) + return false; + const QByteArray oid = oidElem.toObjectId(); + + // critical and value + if (!valElem.read(seqStream)) + return false; + if (valElem.type() == QAsn1Element::BooleanType) { + critical = valElem.toBool(&ok); + if (!ok || !valElem.read(seqStream)) + return false; + } + if (valElem.type() != QAsn1Element::OctetStringType) + return false; + + // interpret value + QAsn1Element val; + bool supported = true; + QVariant value; + if (oid == QByteArrayLiteral("1.3.6.1.5.5.7.1.1")) { + // authorityInfoAccess + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + foreach (const QAsn1Element &el, val.toVector()) { + QVector<QAsn1Element> items = el.toVector(); + if (items.size() != 2) + return false; + const QString key = QString::fromLatin1(items.at(0).toObjectName()); + switch (items.at(1).type()) { + case Rfc822NameType: + case DnsNameType: + case UniformResourceIdentifierType: + result[key] = QString::fromLatin1(items.at(1).value(), items.at(1).value().size()); + break; + } + } + value = result; + } else if (oid == QByteArrayLiteral("2.5.29.14")) { + // subjectKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) + return false; + value = colonSeparatedHex(val.value()).toUpper(); + } else if (oid == QByteArrayLiteral("2.5.29.19")) { + // basicConstraints + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + + QVariantMap result; + QVector<QAsn1Element> items = val.toVector(); + if (items.size() > 0) { + result[QStringLiteral("ca")] = items.at(0).toBool(&ok); + if (!ok) + return false; + } else { + result[QStringLiteral("ca")] = false; + } + if (items.size() > 1) { + result[QStringLiteral("pathLenConstraint")] = items.at(1).toInteger(&ok); + if (!ok) + return false; + } + value = result; + } else if (oid == QByteArrayLiteral("2.5.29.35")) { + // authorityKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + foreach (const QAsn1Element &el, val.toVector()) { + if (el.type() == 0x80) { + result[QStringLiteral("keyid")] = el.value().toHex(); + } else if (el.type() == 0x82) { + result[QStringLiteral("serial")] = colonSeparatedHex(el.value()); + } + } + value = result; + } else { + supported = false; + value = valElem.value(); + } + + extension->d->critical = critical; + extension->d->supported = supported; + extension->d->oid = QString::fromLatin1(oid); + extension->d->name = QString::fromLatin1(oidElem.toObjectName()); + extension->d->value = value; + + return true; +} + QT_END_NAMESPACE diff --git a/src/network/ssl/qsslkey_openssl.cpp b/src/network/ssl/qsslkey_openssl.cpp index 7e78ac0fee..6b0fa954eb 100644 --- a/src/network/ssl/qsslkey_openssl.cpp +++ b/src/network/ssl/qsslkey_openssl.cpp @@ -111,7 +111,8 @@ bool QSslKeyPrivate::fromEVP_PKEY(EVP_PKEY *pkey) void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) { - decodePem(pemFromDer(der), QByteArray(), deepClear); + QMap<QByteArray, QByteArray> headers; + decodePem(pemFromDer(der, headers), QByteArray(), deepClear); } void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, diff --git a/src/network/ssl/qsslkey_p.cpp b/src/network/ssl/qsslkey_p.cpp index 2b0dab9933..b051ec6874 100644 --- a/src/network/ssl/qsslkey_p.cpp +++ b/src/network/ssl/qsslkey_p.cpp @@ -63,6 +63,7 @@ #include <QtCore/qatomic.h> #include <QtCore/qbytearray.h> +#include <QtCore/qbytearraymatcher.h> #include <QtCore/qiodevice.h> #ifndef QT_NO_DEBUG_STREAM #include <QtCore/qdebug.h> @@ -130,7 +131,7 @@ QByteArray QSslKeyPrivate::pemFooter() const Returns a DER key formatted as PEM. */ -QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const +QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const { QByteArray pem(der.toBase64()); @@ -144,7 +145,16 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const if (rem) pem.append('\n'); // ### - pem.prepend(pemHeader() + '\n'); + QByteArray extra; + if (!headers.isEmpty()) { + QMap<QByteArray, QByteArray>::const_iterator it = headers.constEnd(); + do { + it--; + extra += it.key() + ": " + it.value() + '\n'; + } while (it != headers.constBegin()); + extra += '\n'; + } + pem.prepend(pemHeader() + '\n' + extra); pem.append(pemFooter() + '\n'); return pem; @@ -155,7 +165,7 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const Returns a PEM key formatted as DER. */ -QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const +QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const { const QByteArray header = pemHeader(); const QByteArray footer = pemFooter(); @@ -169,6 +179,39 @@ QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + const QByteArrayMatcher lf("\n"); + const QByteArrayMatcher colon(":"); + int i = 0; + while (i < der.count()) { + int j = colon.indexIn(der, i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = lf.indexIn(der, j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + return QByteArray::fromBase64(der); // ignores newlines } @@ -337,7 +380,8 @@ QByteArray QSslKey::toDer(const QByteArray &passPhrase) const return QByteArray(); #ifndef QT_NO_OPENSSL - return d->derFromPem(toPem(passPhrase)); + QMap<QByteArray, QByteArray> headers; + return d->derFromPem(toPem(passPhrase), &headers); #else return d->derData; #endif diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h index 9c1476038a..d24606e6a6 100644 --- a/src/network/ssl/qsslkey_p.h +++ b/src/network/ssl/qsslkey_p.h @@ -91,8 +91,8 @@ public: bool deepClear = true); QByteArray pemHeader() const; QByteArray pemFooter() const; - QByteArray pemFromDer(const QByteArray &der) const; - QByteArray derFromPem(const QByteArray &pem) const; + QByteArray pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const; + QByteArray derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const; int length() const; QByteArray toPem(const QByteArray &passPhrase) const; @@ -106,6 +106,15 @@ public: RSA *rsa; DSA *dsa; #else + enum Cipher { + DesCbc, + DesEde3Cbc, + Rc2Cbc + }; + + Q_AUTOTEST_EXPORT static QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Q_AUTOTEST_EXPORT static QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Qt::HANDLE opaque; QByteArray derData; int keyLength; diff --git a/src/network/ssl/qsslkey_qt.cpp b/src/network/ssl/qsslkey_qt.cpp index feeb7d6f87..bc1ebc3c08 100644 --- a/src/network/ssl/qsslkey_qt.cpp +++ b/src/network/ssl/qsslkey_qt.cpp @@ -43,6 +43,8 @@ #include "qsslkey_p.h" #include "qasn1element_p.h" +#include <QtCore/qcryptographichash.h> + QT_USE_NAMESPACE static const quint8 bits_table[256] = { @@ -78,6 +80,31 @@ static int numberOfBits(const QByteArray &modulus) return bits; } +static QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, const QByteArray &iv) +{ + QByteArray key; + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(passPhrase); + hash.addData(iv); + switch (cipher) { + case QSslKeyPrivate::DesCbc: + key = hash.result().left(8); + break; + case QSslKeyPrivate::DesEde3Cbc: + key = hash.result(); + hash.reset(); + hash.addData(key); + hash.addData(passPhrase); + hash.addData(iv); + key += hash.result().left(8); + break; + case QSslKeyPrivate::Rc2Cbc: + key = hash.result(); + break; + } + return key; +} + void QSslKeyPrivate::clear(bool deep) { Q_UNUSED(deep); @@ -106,7 +133,7 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) if (infoItems.size() < 2 || infoItems[0].type() != QAsn1Element::ObjectIdentifierType) return; if (algorithm == QSsl::Rsa) { - if (infoItems[0].toObjectId() != "1.2.840.113549.1.1.1") + if (infoItems[0].toObjectId() != RSA_ENCRYPTION_OID) return; // key data if (!elem.read(keyStream) || elem.type() != QAsn1Element::BitStringType || elem.value().isEmpty()) @@ -117,7 +144,7 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) return; keyLength = numberOfBits(elem.value()); } else if (algorithm == QSsl::Dsa) { - if (infoItems[0].toObjectId() != "1.2.840.10040.4.1") + if (infoItems[0].toObjectId() != DSA_ENCRYPTION_OID) return; if (infoItems[1].type() != QAsn1Element::SequenceType) return; @@ -155,12 +182,32 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear) void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, bool deepClear) { - if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) { - Q_UNIMPLEMENTED(); - return; - } + QMap<QByteArray, QByteArray> headers; + QByteArray data = derFromPem(pem, &headers); + if (headers.value("Proc-Type") == "4,ENCRYPTED") { + QList<QByteArray> dekInfo = headers.value("DEK-Info").split(','); + if (dekInfo.size() != 2) { + clear(deepClear); + return; + } + + Cipher cipher; + if (dekInfo.first() == "DES-CBC") { + cipher = DesCbc; + } else if (dekInfo.first() == "DES-EDE3-CBC") { + cipher = DesEde3Cbc; + } else if (dekInfo.first() == "RC2-CBC") { + cipher = Rc2Cbc; + } else { + clear(deepClear); + return; + } - decodeDer(derFromPem(pem), deepClear); + const QByteArray iv = QByteArray::fromHex(dekInfo.last()); + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = decrypt(cipher, data, key, iv); + } + decodeDer(data, deepClear); } int QSslKeyPrivate::length() const @@ -170,12 +217,27 @@ int QSslKeyPrivate::length() const QByteArray QSslKeyPrivate::toPem(const QByteArray &passPhrase) const { + QByteArray data; + QMap<QByteArray, QByteArray> headers; + if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) { - Q_UNIMPLEMENTED(); - return QByteArray(); + // ### use a cryptographically secure random number generator + QByteArray iv; + iv.resize(8); + for (int i = 0; i < iv.size(); ++i) + iv[i] = (qrand() & 0xff); + + Cipher cipher = DesEde3Cbc; + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = encrypt(cipher, derData, key, iv); + + headers.insert("Proc-Type", "4,ENCRYPTED"); + headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex()); + } else { + data = derData; } - return pemFromDer(derData); + return pemFromDer(data, headers); } Qt::HANDLE QSslKeyPrivate::handle() const diff --git a/src/network/ssl/qsslkey_winrt.cpp b/src/network/ssl/qsslkey_winrt.cpp index 2c83069694..c5b4146ee9 100644 --- a/src/network/ssl/qsslkey_winrt.cpp +++ b/src/network/ssl/qsslkey_winrt.cpp @@ -61,3 +61,101 @@ using namespace ABI::Windows::Security::Cryptography::Core; using namespace ABI::Windows::Storage::Streams; QT_USE_NAMESPACE + +struct SslKeyGlobal +{ + SslKeyGlobal() + { + HRESULT hr; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_CryptographicEngine).Get(), + &engine); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<ISymmetricKeyAlgorithmProviderStatics> keyProviderFactory; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_SymmetricKeyAlgorithmProvider).Get(), + &keyProviderFactory); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"DES_CBC").Get(), + &keyProviders[QSslKeyPrivate::DesCbc]); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"3DES_CBC").Get(), + &keyProviders[QSslKeyPrivate::DesEde3Cbc]); + Q_ASSERT_SUCCEEDED(hr); + hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"RC2_CBC").Get(), + &keyProviders[QSslKeyPrivate::Rc2Cbc]); + Q_ASSERT_SUCCEEDED(hr); + + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_CryptographicBuffer).Get(), + &bufferFactory); + Q_ASSERT_SUCCEEDED(hr); + } + + ComPtr<ICryptographicEngineStatics> engine; + QHash<QSslKeyPrivate::Cipher, ComPtr<ISymmetricKeyAlgorithmProvider>> keyProviders; + ComPtr<ICryptographicBufferStatics> bufferFactory; +}; +Q_GLOBAL_STATIC(SslKeyGlobal, g) + +static QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, QByteArray data, const QByteArray &key, const QByteArray &iv, bool encrypt) +{ + HRESULT hr; + + ISymmetricKeyAlgorithmProvider *keyProvider = g->keyProviders[cipher].Get(); + Q_ASSERT(keyProvider); + + ComPtr<IBuffer> keyBuffer; + hr = g->bufferFactory->CreateFromByteArray(key.length(), (BYTE *)key.data(), &keyBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<ICryptographicKey> cryptographicKey; + hr = keyProvider->CreateSymmetricKey(keyBuffer.Get(), &cryptographicKey); + Q_ASSERT_SUCCEEDED(hr); + + UINT32 blockLength; + hr = keyProvider->get_BlockLength(&blockLength); + Q_ASSERT_SUCCEEDED(hr); + if (encrypt) { // Add padding + const char padding = blockLength - data.length() % blockLength; + data += QByteArray(padding, padding); + } + + ComPtr<IBuffer> dataBuffer; + hr = g->bufferFactory->CreateFromByteArray(data.length(), (BYTE *)data.data(), &dataBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IBuffer> ivBuffer; + hr = g->bufferFactory->CreateFromByteArray(iv.length(), (BYTE *)iv.data(), &ivBuffer); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<IBuffer> resultBuffer; + hr = encrypt ? g->engine->Encrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer) + : g->engine->Decrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer); + Q_ASSERT_SUCCEEDED(hr); + + UINT32 resultLength; + hr = resultBuffer->get_Length(&resultLength); + Q_ASSERT_SUCCEEDED(hr); + ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferAccess; + hr = resultBuffer.As(&bufferAccess); + Q_ASSERT_SUCCEEDED(hr); + byte *resultData; + hr = bufferAccess->Buffer(&resultData); + Q_ASSERT_SUCCEEDED(hr); + + if (!encrypt) { // Remove padding + const uchar padding = resultData[resultLength - 1]; + if (padding > 0 && padding <= blockLength) + resultLength -= padding; + else + qWarning("Invalid padding length of %u; decryption likely failed.", padding); + } + + return QByteArray(reinterpret_cast<const char *>(resultData), resultLength); +} + +QByteArray QSslKeyPrivate::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return doCrypt(cipher, data, key, iv, false); +} + +QByteArray QSslKeyPrivate::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return doCrypt(cipher, data, key, iv, true); +} |