| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtNetwork module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| |
| /*! |
| \class QSslKey |
| \brief The QSslKey class provides an interface for private and public keys. |
| \since 4.3 |
| |
| \reentrant |
| \ingroup network |
| \ingroup ssl |
| \ingroup shared |
| \inmodule QtNetwork |
| |
| QSslKey provides a simple API for managing keys. |
| |
| \sa QSslSocket, QSslCertificate, QSslCipher |
| */ |
| |
| #include "qsslkey.h" |
| #include "qsslkey_p.h" |
| #ifndef QT_NO_OPENSSL |
| #include "qsslsocket_openssl_symbols_p.h" |
| #endif |
| #include "qsslsocket.h" |
| #include "qsslsocket_p.h" |
| #include "qasn1element_p.h" |
| |
| #include <QtCore/qatomic.h> |
| #include <QtCore/qbytearray.h> |
| #include <QtCore/qiodevice.h> |
| #ifndef QT_NO_DEBUG_STREAM |
| #include <QtCore/qdebug.h> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \fn void QSslKeyPrivate::clear(bool deep) |
| \internal |
| */ |
| |
| /*! |
| \fn void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, |
| bool deepClear) |
| \internal |
| |
| Allocates a new rsa or dsa struct and decodes \a pem into it |
| according to the current algorithm and type. |
| |
| If \a deepClear is true, the rsa/dsa struct is freed if it is was |
| already allocated, otherwise we "leak" memory (which is exactly |
| what we want for copy construction). |
| |
| If \a passPhrase is non-empty, it will be used for decrypting |
| \a pem. |
| */ |
| |
| /*! |
| Constructs a null key. |
| |
| \sa isNull() |
| */ |
| QSslKey::QSslKey() |
| : d(new QSslKeyPrivate) |
| { |
| } |
| |
| /*! |
| \internal |
| */ |
| QByteArray QSslKeyPrivate::pemHeader() const |
| { |
| if (type == QSsl::PublicKey) |
| return QByteArrayLiteral("-----BEGIN PUBLIC KEY-----"); |
| else if (algorithm == QSsl::Rsa) |
| return QByteArrayLiteral("-----BEGIN RSA PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Dsa) |
| return QByteArrayLiteral("-----BEGIN DSA PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Ec) |
| return QByteArrayLiteral("-----BEGIN EC PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Dh) |
| return QByteArrayLiteral("-----BEGIN PRIVATE KEY-----"); |
| |
| Q_UNREACHABLE(); |
| return QByteArray(); |
| } |
| |
| static QByteArray pkcs8Header(bool encrypted) |
| { |
| return encrypted |
| ? QByteArrayLiteral("-----BEGIN ENCRYPTED PRIVATE KEY-----") |
| : QByteArrayLiteral("-----BEGIN PRIVATE KEY-----"); |
| } |
| |
| /*! |
| \internal |
| */ |
| QByteArray QSslKeyPrivate::pemFooter() const |
| { |
| if (type == QSsl::PublicKey) |
| return QByteArrayLiteral("-----END PUBLIC KEY-----"); |
| else if (algorithm == QSsl::Rsa) |
| return QByteArrayLiteral("-----END RSA PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Dsa) |
| return QByteArrayLiteral("-----END DSA PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Ec) |
| return QByteArrayLiteral("-----END EC PRIVATE KEY-----"); |
| else if (algorithm == QSsl::Dh) |
| return QByteArrayLiteral("-----END PRIVATE KEY-----"); |
| |
| Q_UNREACHABLE(); |
| return QByteArray(); |
| } |
| |
| static QByteArray pkcs8Footer(bool encrypted) |
| { |
| return encrypted |
| ? QByteArrayLiteral("-----END ENCRYPTED PRIVATE KEY-----") |
| : QByteArrayLiteral("-----END PRIVATE KEY-----"); |
| } |
| |
| /*! |
| \internal |
| |
| Returns a DER key formatted as PEM. |
| */ |
| QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const |
| { |
| QByteArray pem(der.toBase64()); |
| |
| const int lineWidth = 64; // RFC 1421 |
| const int newLines = pem.size() / lineWidth; |
| const bool rem = pem.size() % lineWidth; |
| |
| // ### optimize |
| for (int i = 0; i < newLines; ++i) |
| pem.insert((i + 1) * lineWidth + i, '\n'); |
| if (rem) |
| pem.append('\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'; |
| } |
| |
| if (isEncryptedPkcs8(der)) { |
| pem.prepend(pkcs8Header(true) + '\n' + extra); |
| pem.append(pkcs8Footer(true) + '\n'); |
| #if !QT_CONFIG(openssl) |
| } else if (isPkcs8) { |
| pem.prepend(pkcs8Header(false) + '\n' + extra); |
| pem.append(pkcs8Footer(false) + '\n'); |
| #endif |
| } else { |
| pem.prepend(pemHeader() + '\n' + extra); |
| pem.append(pemFooter() + '\n'); |
| } |
| |
| return pem; |
| } |
| |
| /*! |
| \internal |
| |
| Returns a PEM key formatted as DER. |
| */ |
| QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const |
| { |
| QByteArray header = pemHeader(); |
| QByteArray footer = pemFooter(); |
| |
| QByteArray der(pem); |
| |
| int headerIndex = der.indexOf(header); |
| int footerIndex = der.indexOf(footer, headerIndex + header.length()); |
| if (type != QSsl::PublicKey) { |
| if (headerIndex == -1 || footerIndex == -1) { |
| header = pkcs8Header(true); |
| footer = pkcs8Footer(true); |
| headerIndex = der.indexOf(header); |
| footerIndex = der.indexOf(footer, headerIndex + header.length()); |
| } |
| if (headerIndex == -1 || footerIndex == -1) { |
| header = pkcs8Header(false); |
| footer = pkcs8Footer(false); |
| headerIndex = der.indexOf(header); |
| footerIndex = der.indexOf(footer, headerIndex + header.length()); |
| } |
| } |
| if (headerIndex == -1 || footerIndex == -1) |
| return QByteArray(); |
| |
| der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); |
| |
| if (der.contains("Proc-Type:")) { |
| // taken from QHttpNetworkReplyPrivate::parseHeader |
| int i = 0; |
| while (i < der.count()) { |
| int j = der.indexOf(':', 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 = der.indexOf('\n', 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 |
| } |
| |
| bool QSslKeyPrivate::isEncryptedPkcs8(const QByteArray &der) const |
| { |
| static const QVector<QByteArray> pbes1OIds { |
| // PKCS5 |
| {PKCS5_MD2_DES_CBC_OID}, |
| {PKCS5_MD2_RC2_CBC_OID}, |
| {PKCS5_MD5_DES_CBC_OID}, |
| {PKCS5_MD5_RC2_CBC_OID}, |
| {PKCS5_SHA1_DES_CBC_OID}, |
| {PKCS5_SHA1_RC2_CBC_OID}, |
| }; |
| QAsn1Element elem; |
| if (!elem.read(der) || elem.type() != QAsn1Element::SequenceType) |
| return false; |
| |
| const QVector<QAsn1Element> items = elem.toVector(); |
| if (items.size() != 2 |
| || items[0].type() != QAsn1Element::SequenceType |
| || items[1].type() != QAsn1Element::OctetStringType) { |
| return false; |
| } |
| |
| const QVector<QAsn1Element> encryptionSchemeContainer = items[0].toVector(); |
| if (encryptionSchemeContainer.size() != 2 |
| || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType |
| || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { |
| return false; |
| } |
| |
| const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); |
| return encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID |
| || pbes1OIds.contains(encryptionScheme) |
| || encryptionScheme.startsWith(PKCS12_OID); |
| } |
| |
| /*! |
| Constructs a QSslKey by decoding the string in the byte array |
| \a encoded using a specified \a algorithm and \a encoding format. |
| \a type specifies whether the key is public or private. |
| |
| If the key is encrypted then \a passPhrase is used to decrypt it. |
| |
| After construction, use isNull() to check if \a encoded contained |
| a valid key. |
| */ |
| QSslKey::QSslKey(const QByteArray &encoded, QSsl::KeyAlgorithm algorithm, |
| QSsl::EncodingFormat encoding, QSsl::KeyType type, const QByteArray &passPhrase) |
| : d(new QSslKeyPrivate) |
| { |
| d->type = type; |
| d->algorithm = algorithm; |
| if (encoding == QSsl::Der) |
| d->decodeDer(encoded, passPhrase); |
| else |
| d->decodePem(encoded, passPhrase); |
| } |
| |
| /*! |
| Constructs a QSslKey by reading and decoding data from a |
| \a device using a specified \a algorithm and \a encoding format. |
| \a type specifies whether the key is public or private. |
| |
| If the key is encrypted then \a passPhrase is used to decrypt it. |
| |
| After construction, use isNull() to check if \a device provided |
| a valid key. |
| */ |
| QSslKey::QSslKey(QIODevice *device, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding, |
| QSsl::KeyType type, const QByteArray &passPhrase) |
| : d(new QSslKeyPrivate) |
| { |
| QByteArray encoded; |
| if (device) |
| encoded = device->readAll(); |
| d->type = type; |
| d->algorithm = algorithm; |
| if (encoding == QSsl::Der) |
| d->decodeDer(encoded, passPhrase); |
| else |
| d->decodePem(encoded, passPhrase); |
| } |
| |
| /*! |
| \since 5.0 |
| Constructs a QSslKey from a valid native key \a handle. |
| \a type specifies whether the key is public or private. |
| |
| QSslKey will take ownership for this key and you must not |
| free the key using the native library. |
| */ |
| QSslKey::QSslKey(Qt::HANDLE handle, QSsl::KeyType type) |
| : d(new QSslKeyPrivate) |
| { |
| #ifndef QT_NO_OPENSSL |
| EVP_PKEY *evpKey = reinterpret_cast<EVP_PKEY *>(handle); |
| if (!evpKey || !d->fromEVP_PKEY(evpKey)) { |
| d->opaque = evpKey; |
| d->algorithm = QSsl::Opaque; |
| } else { |
| q_EVP_PKEY_free(evpKey); |
| } |
| #else |
| d->opaque = handle; |
| d->algorithm = QSsl::Opaque; |
| #endif |
| d->type = type; |
| d->isNull = !d->opaque; |
| } |
| |
| /*! |
| Constructs an identical copy of \a other. |
| */ |
| QSslKey::QSslKey(const QSslKey &other) : d(other.d) |
| { |
| } |
| |
| QSslKey::QSslKey(QSslKey &&other) noexcept |
| : d(nullptr) |
| { |
| qSwap(d, other.d); |
| } |
| |
| QSslKey &QSslKey::operator=(QSslKey &&other) noexcept |
| { |
| if (this == &other) |
| return *this; |
| |
| // If no one else is referencing the key data we want to make sure |
| // before we swap the d-ptr that it is not left in memory. |
| d.reset(); |
| qSwap(d, other.d); |
| return *this; |
| } |
| |
| /*! |
| Destroys the QSslKey object. |
| */ |
| QSslKey::~QSslKey() |
| { |
| } |
| |
| /*! |
| Copies the contents of \a other into this key, making the two keys |
| identical. |
| |
| Returns a reference to this QSslKey. |
| */ |
| QSslKey &QSslKey::operator=(const QSslKey &other) |
| { |
| d = other.d; |
| return *this; |
| } |
| |
| /*! |
| \fn void QSslKey::swap(QSslKey &other) |
| \since 5.0 |
| |
| Swaps this ssl key with \a other. This function is very fast and |
| never fails. |
| */ |
| |
| /*! |
| Returns \c true if this is a null key; otherwise false. |
| |
| \sa clear() |
| */ |
| bool QSslKey::isNull() const |
| { |
| return d->isNull; |
| } |
| |
| /*! |
| Clears the contents of this key, making it a null key. |
| |
| \sa isNull() |
| */ |
| void QSslKey::clear() |
| { |
| d = new QSslKeyPrivate; |
| } |
| |
| /*! |
| Returns the length of the key in bits, or -1 if the key is null. |
| */ |
| int QSslKey::length() const |
| { |
| return d->length(); |
| } |
| |
| /*! |
| Returns the type of the key (i.e., PublicKey or PrivateKey). |
| */ |
| QSsl::KeyType QSslKey::type() const |
| { |
| return d->type; |
| } |
| |
| /*! |
| Returns the key algorithm. |
| */ |
| QSsl::KeyAlgorithm QSslKey::algorithm() const |
| { |
| return d->algorithm; |
| } |
| |
| /*! |
| Returns the key in DER encoding. |
| |
| The \a passPhrase argument should be omitted as DER cannot be |
| encrypted. It will be removed in a future version of Qt. |
| */ |
| QByteArray QSslKey::toDer(const QByteArray &passPhrase) const |
| { |
| if (d->isNull || d->algorithm == QSsl::Opaque) |
| return QByteArray(); |
| |
| // Encrypted DER is nonsense, see QTBUG-41038. |
| if (d->type == QSsl::PrivateKey && !passPhrase.isEmpty()) |
| return QByteArray(); |
| |
| #ifndef QT_NO_OPENSSL |
| QMap<QByteArray, QByteArray> headers; |
| return d->derFromPem(toPem(passPhrase), &headers); |
| #else |
| return d->derData; |
| #endif |
| } |
| |
| /*! |
| Returns the key in PEM encoding. The result is encrypted with |
| \a passPhrase if the key is a private key and \a passPhrase is |
| non-empty. |
| */ |
| QByteArray QSslKey::toPem(const QByteArray &passPhrase) const |
| { |
| return d->toPem(passPhrase); |
| } |
| |
| /*! |
| Returns a pointer to the native key handle, if there is |
| one, else \nullptr. |
| |
| You can use this handle together with the native API to access |
| extended information about the key. |
| |
| \warning Use of this function has a high probability of being |
| non-portable, and its return value may vary across platforms, and |
| between minor Qt releases. |
| */ |
| Qt::HANDLE QSslKey::handle() const |
| { |
| return d->handle(); |
| } |
| |
| /*! |
| Returns \c true if this key is equal to \a other; otherwise returns \c false. |
| */ |
| bool QSslKey::operator==(const QSslKey &other) const |
| { |
| if (isNull()) |
| return other.isNull(); |
| if (other.isNull()) |
| return isNull(); |
| if (algorithm() != other.algorithm()) |
| return false; |
| if (type() != other.type()) |
| return false; |
| if (length() != other.length()) |
| return false; |
| if (algorithm() == QSsl::Opaque) |
| return handle() == other.handle(); |
| return toDer() == other.toDer(); |
| } |
| |
| /*! \fn bool QSslKey::operator!=(const QSslKey &other) const |
| |
| Returns \c true if this key is not equal to key \a other; otherwise |
| returns \c false. |
| */ |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| QDebug operator<<(QDebug debug, const QSslKey &key) |
| { |
| QDebugStateSaver saver(debug); |
| debug.resetFormat().nospace(); |
| debug << "QSslKey(" |
| << (key.type() == QSsl::PublicKey ? "PublicKey" : "PrivateKey") |
| << ", " << (key.algorithm() == QSsl::Opaque ? "OPAQUE" : |
| (key.algorithm() == QSsl::Rsa ? "RSA" : |
| (key.algorithm() == QSsl::Dsa ? "DSA" : |
| (key.algorithm() == QSsl::Dh ? "DH" : "EC")))) |
| << ", " << key.length() |
| << ')'; |
| return debug; |
| } |
| #endif |
| |
| QT_END_NAMESPACE |