/**************************************************************************** ** ** Copyright (C) 2014 Jeremy Lainé ** 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$ ** ****************************************************************************/ #include "qsslsocket.h" #include "qssl_p.h" #include "qsslsocket_mac_p.h" #include "qasn1element_p.h" #include "qsslcertificate_p.h" #include "qsslcipher_p.h" #include "qsslkey_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_OSX #include #endif QT_BEGIN_NAMESPACE namespace { #ifdef Q_OS_MACOS /* Our own temporarykeychain is needed only on macOS where SecPKCS12Import changes the default keychain and where we see annoying pop-ups asking about accessing a private key. */ struct EphemeralSecKeychain { EphemeralSecKeychain(); ~EphemeralSecKeychain(); SecKeychainRef keychain = nullptr; Q_DISABLE_COPY(EphemeralSecKeychain) }; EphemeralSecKeychain::EphemeralSecKeychain() { const auto uuid = QUuid::createUuid(); if (uuid.isNull()) { qCWarning(lcSsl) << "Failed to create an unique keychain name"; return; } QString uuidAsString(uuid.toString()); Q_ASSERT(uuidAsString.size() > 2); Q_ASSERT(uuidAsString.startsWith(QLatin1Char('{')) && uuidAsString.endsWith(QLatin1Char('}'))); uuidAsString = uuidAsString.mid(1, uuidAsString.size() - 2); QString keychainName(QDir::tempPath()); keychainName.append(QDir::separator()); keychainName += uuidAsString; keychainName += QLatin1String(".keychain"); // SecKeychainCreate, pathName parameter: // // "A constant character string representing the POSIX path indicating where // to store the keychain." // // Internally they seem to use std::string, but this does not really help. // Fortunately, CFString has a convenient API. QCFType cfName = keychainName.toCFString(); std::vector posixPath; // "Extracts the contents of a string as a NULL-terminated 8-bit string // appropriate for passing to POSIX APIs." posixPath.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfName)); const auto ok = CFStringGetFileSystemRepresentation(cfName, &posixPath[0], CFIndex(posixPath.size())); if (!ok) { qCWarning(lcSsl) << "Failed to create a unique keychain name from" << "QDir::tempPath()"; return; } std::vector passUtf8(256); if (SecRandomCopyBytes(kSecRandomDefault, passUtf8.size(), &passUtf8[0])) { qCWarning(lcSsl) << "SecRandomCopyBytes: failed to create a key"; return; } const OSStatus status = SecKeychainCreate(&posixPath[0], passUtf8.size(), &passUtf8[0], FALSE, nullptr, &keychain); if (status != errSecSuccess || !keychain) { qCWarning(lcSsl) << "SecKeychainCreate: failed to create a custom keychain"; if (keychain) { SecKeychainDelete(keychain); CFRelease(keychain); keychain = nullptr; } } #ifdef QSSLSOCKET_DEBUG if (keychain) { qCDebug(lcSsl) << "Custom keychain with name" << keychainName << "was created" << "successfully"; } #endif } EphemeralSecKeychain::~EphemeralSecKeychain() { if (keychain) { // clear file off disk SecKeychainDelete(keychain); CFRelease(keychain); } } #endif // Q_OS_MACOS } static SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) { const bool isServer = mode == QSslSocket::SslServerMode; const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. SSLContextRef context = SSLCreateContext(Q_NULLPTR, side, kSSLStreamType); if (!context) qCWarning(lcSsl) << "SSLCreateContext failed"; return context; } static void qt_releaseSecureTransportContext(SSLContextRef context) { if (context) CFRelease(context); } QSecureTransportContext::QSecureTransportContext(SSLContextRef c) : context(c) { } QSecureTransportContext::~QSecureTransportContext() { qt_releaseSecureTransportContext(context); } QSecureTransportContext::operator SSLContextRef()const { return context; } void QSecureTransportContext::reset(SSLContextRef newContext) { qt_releaseSecureTransportContext(context); context = newContext; } Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_securetransport_mutex, (QMutex::Recursive)) //#define QSSLSOCKET_DEBUG bool QSslSocketPrivate::s_libraryLoaded = false; bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; #if !defined(QT_PLATFORM_UIKIT) // dhparam is only used on macOS. (see the SSLSetDiffieHellmanParams call below) static const uint8_t dhparam[] = "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7" "\x63\xaf\xcc\x30\xd0\x67\x97\x7d\x1b\xab\x59\x30\xa9\x13\x67\x21\xd7\xd4" "\x0e\x46\xcf\xe5\x80\xdf\xc9\xb9\xba\x54\x9b\x46\x2f\x3b\x45\xfc\x2f\xaf" "\xad\xc0\x17\x56\xdd\x52\x42\x57\x45\x70\x14\xe5\xbe\x67\xaa\xde\x69\x75" "\x30\x0d\xf9\xa2\xc4\x63\x4d\x7a\x39\xef\x14\x62\x18\x33\x44\xa1\xf9\xc1" "\x52\xd1\xb6\x72\x21\x98\xf8\xab\x16\x1b\x7b\x37\x65\xe3\xc5\x11\x00\xf6" "\x36\x1f\xd8\x5f\xd8\x9f\x43\xa8\xce\x9d\xbf\x5e\xd6\x2d\xfa\x0a\xc2\x01" "\x54\xc2\xd9\x81\x54\x55\xb5\x26\xf8\x88\x37\xf5\xfe\xe0\xef\x4a\x34\x81" "\xdc\x5a\xb3\x71\x46\x27\xe3\xcd\x24\xf6\x1b\xf1\xe2\x0f\xc2\xa1\x39\x53" "\x5b\xc5\x38\x46\x8e\x67\x4c\xd9\xdd\xe4\x37\x06\x03\x16\xf1\x1d\x7a\xba" "\x2d\xc1\xe4\x03\x1a\x58\xe5\x29\x5a\x29\x06\x69\x61\x7a\xd8\xa9\x05\x9f" "\xc1\xa2\x45\x9c\x17\xad\x52\x69\x33\xdc\x18\x8d\x15\xa6\x5e\xcd\x94\xf4" "\x45\xbb\x9f\xc2\x7b\x85\x00\x61\xb0\x1a\xdc\x3c\x86\xaa\x9f\x5c\x04\xb3" "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; #endif // No ioErr on iOS/tvOS/watchOS. (defined in MacErrors.h on macOS) #if defined(QT_PLATFORM_UIKIT) # define ioErr -36 #endif static OSStatus _q_SSLRead(QTcpSocket *plainSocket, char *data, size_t *dataLength) { Q_ASSERT(plainSocket); Q_ASSERT(data); Q_ASSERT(dataLength); const qint64 bytes = plainSocket->read(data, *dataLength); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "read" << bytes; #endif if (bytes < 0) { *dataLength = 0; return ioErr; } const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : noErr; *dataLength = bytes; return err; } static OSStatus _q_SSLWrite(QTcpSocket *plainSocket, const char *data, size_t *dataLength) { Q_ASSERT(plainSocket); Q_ASSERT(data); Q_ASSERT(dataLength); const qint64 bytes = plainSocket->write(data, *dataLength); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "write" << bytes; #endif if (bytes < 0) { *dataLength = 0; return ioErr; } const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : noErr; *dataLength = bytes; return err; } void QSslSocketPrivate::ensureInitialized() { const QMutexLocker locker(qt_securetransport_mutex); if (s_loadedCiphersAndCerts) return; // We have to set it before setDefaultSupportedCiphers, // since this function can trigger static (global)'s initialization // and as a result - recursive ensureInitialized call // from QSslCertificatePrivate's ctor. s_loadedCiphersAndCerts = true; const QSecureTransportContext context(qt_createSecureTransportContext(QSslSocket::SslClientMode)); if (context) { QList ciphers; QList defaultCiphers; size_t numCiphers = 0; // Fails only if any of parameters is null. SSLGetNumberSupportedCiphers(context, &numCiphers); QVector cfCiphers(numCiphers); // Fails only if any of parameter is null or number of ciphers is wrong. SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { const QSslCipher ciph(QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); if (!ciph.isNull()) { ciphers << ciph; if (ciph.usedBits() >= 128) defaultCiphers << ciph; } } setDefaultSupportedCiphers(ciphers); setDefaultCiphers(defaultCiphers); if (!s_loadRootCertsOnDemand) setDefaultCaCertificates(systemCaCertificates()); } else { s_loadedCiphersAndCerts = false; } } long QSslSocketPrivate::sslLibraryVersionNumber() { return 0; } QString QSslSocketPrivate::sslLibraryVersionString() { return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); } long QSslSocketPrivate::sslLibraryBuildVersionNumber() { return 0; } QString QSslSocketPrivate::sslLibraryBuildVersionString() { return sslLibraryVersionString(); } bool QSslSocketPrivate::supportsSsl() { return true; } void QSslSocketPrivate::resetDefaultCiphers() { Q_UNIMPLEMENTED(); } void QSslSocketPrivate::resetDefaultEllipticCurves() { // No public API for this (?). Q_UNIMPLEMENTED(); } QSslSocketBackendPrivate::QSslSocketBackendPrivate() : context(Q_NULLPTR) { } QSslSocketBackendPrivate::~QSslSocketBackendPrivate() { destroySslContext(); } void QSslSocketBackendPrivate::continueHandshake() { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "connection encrypted"; #endif Q_Q(QSslSocket); connectionEncrypted = true; emit q->encrypted(); if (autoStartHandshake && pendingClose) { pendingClose = false; q->disconnectFromHost(); } } void QSslSocketBackendPrivate::disconnected() { if (plainSocket->bytesAvailable() <= 0) destroySslContext(); // If there is still buffered data in the plain socket, don't destroy the ssl context yet. // It will be destroyed when the socket is deleted. } void QSslSocketBackendPrivate::disconnectFromHost() { if (context) { if (!shutdown) { SSLClose(context); shutdown = true; } } plainSocket->disconnectFromHost(); } QSslCipher QSslSocketBackendPrivate::sessionCipher() const { SSLCipherSuite cipher = 0; if (context && SSLGetNegotiatedCipher(context, &cipher) == noErr) return QSslCipher_from_SSLCipherSuite(cipher); return QSslCipher(); } QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const { if (!context) return QSsl::UnknownProtocol; SSLProtocol protocol = kSSLProtocolUnknown; const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); if (err != noErr) { qCWarning(lcSsl) << "SSLGetNegotiatedProtocolVersion failed:" << err; return QSsl::UnknownProtocol; } switch (protocol) { case kSSLProtocol2: return QSsl::SslV2; case kSSLProtocol3: return QSsl::SslV3; case kTLSProtocol1: return QSsl::TlsV1_0; case kTLSProtocol11: return QSsl::TlsV1_1; case kTLSProtocol12: return QSsl::TlsV1_2; default: return QSsl::UnknownProtocol; } } void QSslSocketBackendPrivate::startClientEncryption() { if (!initSslContext()) { // Error description/code were set, 'error' emitted // by initSslContext, but OpenSSL socket also sets error // emits a signal twice, so ... setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); return; } startHandshake(); } void QSslSocketBackendPrivate::startServerEncryption() { if (!initSslContext()) { // Error description/code were set, 'error' emitted // by initSslContext, but OpenSSL socket also sets error // emits a signal twice, so ... setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); return; } startHandshake(); } void QSslSocketBackendPrivate::transmit() { Q_Q(QSslSocket); // If we don't have any SSL context, don't bother transmitting. // Edit: if SSL session closed, don't bother either. if (!context || shutdown) return; if (!connectionEncrypted) startHandshake(); if (connectionEncrypted && !writeBuffer.isEmpty()) { qint64 totalBytesWritten = 0; while (writeBuffer.nextDataBlockSize() > 0 && context) { const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); size_t writtenBytes = 0; const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "SSLWrite returned" << err; #endif if (err != noErr && err != errSSLWouldBlock) { setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLWrite failed: %1").arg(err)); break; } if (writtenBytes) { writeBuffer.free(writtenBytes); totalBytesWritten += writtenBytes; } if (writtenBytes < nextDataBlockSize) break; } if (totalBytesWritten > 0) { // Don't emit bytesWritten() recursively. if (!emittedBytesWritten) { emittedBytesWritten = true; emit q->bytesWritten(totalBytesWritten); emittedBytesWritten = false; } emit q->channelBytesWritten(0, totalBytesWritten); } } if (connectionEncrypted) { QVarLengthArray data; while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { size_t readBytes = 0; data.resize(4096); const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "SSLRead returned" << err; #endif if (err == errSSLClosedGraceful) { shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, QSslSocket::tr("The TLS/SSL connection has been closed")); break; } else if (err != noErr && err != errSSLWouldBlock) { setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLRead failed: %1").arg(err)); break; } if (readBytes) { buffer.append(data.constData(), readBytes); if (readyReadEmittedPointer) *readyReadEmittedPointer = true; emit q->readyRead(); emit q->channelReadyRead(0); } if (err == errSSLWouldBlock) break; } } } QList (QSslSocketBackendPrivate::verify)(QList certificateChain, const QString &hostName) { Q_UNIMPLEMENTED(); Q_UNUSED(certificateChain) Q_UNUSED(hostName) QList errors; errors << QSslError(QSslError::UnspecifiedError); return errors; } bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, QList *caCertificates, const QByteArray &passPhrase) { Q_UNIMPLEMENTED(); Q_UNUSED(device) Q_UNUSED(key) Q_UNUSED(cert) Q_UNUSED(caCertificates) Q_UNUSED(passPhrase) return false; } QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) { QSslCipher ciph; switch (cipher) { // Sorted as in CipherSuite.h (and groupped by their RFC) case SSL_RSA_WITH_NULL_MD5: ciph.d->name = QLatin1String("NULL-MD5"); ciph.d->protocol = QSsl::SslV3; break; case SSL_RSA_WITH_NULL_SHA: ciph.d->name = QLatin1String("NULL-SHA"); ciph.d->protocol = QSsl::SslV3; break; case SSL_RSA_WITH_RC4_128_MD5: ciph.d->name = QLatin1String("RC4-MD5"); ciph.d->protocol = QSsl::SslV3; break; case SSL_RSA_WITH_RC4_128_SHA: ciph.d->name = QLatin1String("RC4-SHA"); ciph.d->protocol = QSsl::SslV3; break; // TLS addenda using AES, per RFC 3268 case TLS_RSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("AES128-SHA"); break; case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("DHE-RSA-AES128-SHA"); break; case TLS_RSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("AES256-SHA"); break; case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA"); break; // ECDSA addenda, RFC 4492 case TLS_ECDH_ECDSA_WITH_NULL_SHA: ciph.d->name = QLatin1String("ECDH-ECDSA-NULL-SHA"); break; case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: ciph.d->name = QLatin1String("ECDH-ECDSA-RC4-SHA"); break; case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); break; case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("ECDH-ECDSA-AES128-SHA"); break; case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA"); break; case TLS_ECDHE_ECDSA_WITH_NULL_SHA: ciph.d->name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); break; case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: ciph.d->name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); break; case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); break; case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); break; case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); break; case TLS_ECDH_RSA_WITH_NULL_SHA: ciph.d->name = QLatin1String("ECDH-RSA-NULL-SHA"); break; case TLS_ECDH_RSA_WITH_RC4_128_SHA: ciph.d->name = QLatin1String("ECDH-RSA-RC4-SHA"); break; case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); break; case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("ECDH-RSA-AES128-SHA"); break; case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA"); break; case TLS_ECDHE_RSA_WITH_NULL_SHA: ciph.d->name = QLatin1String("ECDHE-RSA-NULL-SHA"); break; case TLS_ECDHE_RSA_WITH_RC4_128_SHA: ciph.d->name = QLatin1String("ECDHE-RSA-RC4-SHA"); break; case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); break; case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-RSA-AES128-SHA"); break; case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: ciph.d->name = QLatin1String("ECDHE-RSA-AES256-SHA"); break; // TLS 1.2 addenda, RFC 5246 case TLS_RSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("DES-CBC3-SHA"); break; case TLS_RSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("AES128-SHA256"); break; case TLS_RSA_WITH_AES_256_CBC_SHA256: ciph.d->name = QLatin1String("AES256-SHA256"); break; case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: ciph.d->name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); break; case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("DHE-RSA-AES128-SHA256"); break; case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA256"); break; // Addendum from RFC 4279, TLS PSK // all missing atm. // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption // all missing atm. // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS case TLS_RSA_WITH_AES_256_GCM_SHA384: ciph.d->name = QLatin1String("AES256-GCM-SHA384"); break; // RFC 5487 - PSK with SHA-256/384 and AES GCM // all missing atm. // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); break; case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); break; case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); break; case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); break; case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("ECDHE-RSA-AES128-SHA256"); break; case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: ciph.d->name = QLatin1String("ECDHE-RSA-AES256-SHA384"); break; case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: ciph.d->name = QLatin1String("ECDH-RSA-AES128-SHA256"); break; case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA384"); break; // Addenda from rfc 5289 Elliptic Curve Cipher Suites // with SHA-256/384 and AES Galois Counter Mode (GCM) case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: ciph.d->name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); break; default: return ciph; } ciph.d->isNull = false; // protocol if (ciph.d->protocol == QSsl::SslV3) { ciph.d->protocolString = QLatin1String("SSLv3"); } else { ciph.d->protocol = QSsl::TlsV1_2; ciph.d->protocolString = QLatin1String("TLSv1.2"); } const auto bits = ciph.d->name.splitRef(QLatin1Char('-')); if (bits.size() >= 2) { if (bits.size() == 2 || bits.size() == 3) { ciph.d->keyExchangeMethod = QLatin1String("RSA"); } else if (bits.front() == QLatin1String("DH") || bits.front() == QLatin1String("DHE")) { ciph.d->keyExchangeMethod = QLatin1String("DH"); } else if (bits.front() == QLatin1String("ECDH") || bits.front() == QLatin1String("ECDHE")) { ciph.d->keyExchangeMethod = QLatin1String("ECDH"); } else { qCWarning(lcSsl) << "Unknown Kx" << ciph.d->name; } if (bits.size() == 2 || bits.size() == 3) { ciph.d->authenticationMethod = QLatin1String("RSA"); } else if (ciph.d->name.contains(QLatin1String("-ECDSA-"))) { ciph.d->authenticationMethod = QLatin1String("ECDSA"); } else if (ciph.d->name.contains(QLatin1String("-RSA-"))) { ciph.d->authenticationMethod = QLatin1String("RSA"); } else { qCWarning(lcSsl) << "Unknown Au" << ciph.d->name; } if (ciph.d->name.contains(QLatin1String("RC4-"))) { ciph.d->encryptionMethod = QLatin1String("RC4(128)"); ciph.d->bits = 128; ciph.d->supportedBits = 128; } else if (ciph.d->name.contains(QLatin1String("DES-CBC3-"))) { ciph.d->encryptionMethod = QLatin1String("3DES(168)"); ciph.d->bits = 168; ciph.d->supportedBits = 168; } else if (ciph.d->name.contains(QLatin1String("AES128-"))) { ciph.d->encryptionMethod = QLatin1String("AES(128)"); ciph.d->bits = 128; ciph.d->supportedBits = 128; } else if (ciph.d->name.contains(QLatin1String("AES256-GCM"))) { ciph.d->encryptionMethod = QLatin1String("AESGCM(256)"); ciph.d->bits = 256; ciph.d->supportedBits = 256; } else if (ciph.d->name.contains(QLatin1String("AES256-"))) { ciph.d->encryptionMethod = QLatin1String("AES(256)"); ciph.d->bits = 256; ciph.d->supportedBits = 256; } else if (ciph.d->name.contains(QLatin1String("NULL-"))) { ciph.d->encryptionMethod = QLatin1String("NULL"); } else { qCWarning(lcSsl) << "Unknown Enc" << ciph.d->name; } } return ciph; } bool QSslSocketBackendPrivate::initSslContext() { Q_Q(QSslSocket); Q_ASSERT_X(!context, Q_FUNC_INFO, "invalid socket state, context is not null"); Q_ASSERT(plainSocket); context.reset(qt_createSecureTransportContext(mode)); if (!context) { setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); return false; } const OSStatus err = SSLSetIOFuncs(context, reinterpret_cast(&_q_SSLRead), reinterpret_cast(&_q_SSLWrite)); if (err != noErr) { destroySslContext(); setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); return false; } SSLSetConnection(context, plainSocket); if (mode == QSslSocket::SslServerMode && !configuration.localCertificateChain.isEmpty()) { QString errorDescription; QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; if (!setSessionCertificate(errorDescription, errorCode)) { destroySslContext(); setErrorAndEmit(errorCode, errorDescription); return false; } } if (!setSessionProtocol()) { destroySslContext(); setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); return false; } if (mode == QSslSocket::SslClientMode) { // enable Server Name Indication (SNI) QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); if (tlsHostName.isEmpty()) tlsHostName = hostName; const QByteArray ace(QUrl::toAce(tlsHostName)); SSLSetPeerDomainName(context, ace.data(), ace.size()); // tell SecureTransport we handle peer verification ourselves OSStatus err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); if (err == noErr) err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnCertRequested, true); if (err != noErr) { destroySslContext(); setErrorAndEmit(QSslSocket::SslInternalError, QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); return false; } // } else { if (configuration.peerVerifyMode != QSslSocket::VerifyNone) { // kAlwaysAuthenticate - always fails even if we set break on client auth. OSStatus err = SSLSetClientSideAuthenticate(context, kTryAuthenticate); if (err == noErr) { // We'd like to verify peer ourselves, otherwise handshake will // most probably fail before we can do anything. err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnClientAuth, true); } if (err != noErr) { destroySslContext(); setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); return false; } } #if !defined(QT_PLATFORM_UIKIT) // No SSLSetDiffieHellmanParams on iOS; calling it is optional according to docs. SSLSetDiffieHellmanParams(context, dhparam, sizeof(dhparam)); #endif } return true; } void QSslSocketBackendPrivate::destroySslContext() { context.reset(Q_NULLPTR); } static QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, const QString &passPhrase); bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) { Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); QSslCertificate localCertificate; if (!configuration.localCertificateChain.isEmpty()) localCertificate = configuration.localCertificateChain.at(0); if (!localCertificate.isNull()) { // Require a private key as well. if (configuration.privateKey.isNull()) { errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("Cannot provide a certificate with no key"); return false; } // import certificates and key const QString passPhrase(QString::fromLatin1("foobar")); QCFType pkcs12 = _q_makePkcs12(configuration.localCertificateChain, configuration.privateKey, passPhrase).toCFData(); QCFType password = passPhrase.toCFString(); const void *keys[2] = { kSecImportExportPassphrase }; const void *values[2] = { password }; CFIndex nKeys = 1; #ifdef Q_OS_MACOS bool envOk = false; const int env = qEnvironmentVariableIntValue("QT_SSL_USE_TEMPORARY_KEYCHAIN", &envOk); if (envOk && env) { static const EphemeralSecKeychain temporaryKeychain; if (temporaryKeychain.keychain) { nKeys = 2; keys[1] = kSecImportExportKeychain; values[1] = temporaryKeychain.keychain; } } #endif QCFType options = CFDictionaryCreate(nullptr, keys, values, nKeys, nullptr, nullptr); CFArrayRef items = nullptr; OSStatus err = SecPKCS12Import(pkcs12, options, &items); if (err != noErr) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << QStringLiteral("SecPKCS12Import failed: %1").arg(err); #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import failed: %1").arg(err); return false; } if (!CFArrayGetCount(items)) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << "SecPKCS12Import returned no items"; #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import returned no items"); return false; } CFDictionaryRef import = (CFDictionaryRef)CFArrayGetValueAtIndex(items, 0); SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(import, kSecImportItemIdentity); if (!identity) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << "SecPKCS12Import returned no identity"; #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); return false; } QCFType certs = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); if (!certs) { errorCode = QAbstractSocket::SslInternalError; errorDescription = QStringLiteral("Failed to allocate certificates array"); return false; } CFArrayAppendValue(certs, identity); QCFType chain((CFArrayRef)CFDictionaryGetValue(import, kSecImportItemCertChain)); if (chain) { for (CFIndex i = 1, e = CFArrayGetCount(chain); i < e; ++i) CFArrayAppendValue(certs, CFArrayGetValueAtIndex(chain, i)); } err = SSLSetCertificate(context, certs); if (err != noErr) { #ifdef QSSLSOCKET_DEBUG qCWarning(lcSsl) << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("Cannot set certificate and key: %1").arg(err); return false; } } return true; } bool QSslSocketBackendPrivate::setSessionProtocol() { Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); // QSsl::SslV2 == kSSLProtocol2 is disabled in Secure Transport and // always fails with errSSLIllegalParam: // if (version < MINIMUM_STREAM_VERSION || version > MAXIMUM_STREAM_VERSION) // return errSSLIllegalParam; // where MINIMUM_STREAM_VERSION is SSL_Version_3_0, MAXIMUM_STREAM_VERSION is TLS_Version_1_2. if (configuration.protocol == QSsl::SslV2) { qCDebug(lcSsl) << "protocol QSsl::SslV2 is disabled"; return false; } OSStatus err = noErr; if (configuration.protocol == QSsl::SslV3) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : SSLv3"; #endif err = SSLSetProtocolVersionMin(context, kSSLProtocol3); if (err == noErr) err = SSLSetProtocolVersionMax(context, kSSLProtocol3); } else if (configuration.protocol == QSsl::TlsV1_0) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.0"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::TlsV1_1) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol11); } else if (configuration.protocol == QSsl::TlsV1_2) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::AnyProtocol) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : any"; #endif // kSSLProtocol3, since kSSLProtocol2 is disabled: err = SSLSetProtocolVersionMin(context, kSSLProtocol3); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1SslV3) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : SSLv3 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kSSLProtocol3); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::SecureProtocols) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_0OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_1OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::TlsV1_2OrLater) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); if (err == noErr) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "no protocol version found in the configuration"; #endif return false; } return err == noErr; } bool QSslSocketBackendPrivate::canIgnoreTrustVerificationFailure() const { const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode; return mode == QSslSocket::SslServerMode && (verifyMode == QSslSocket::QueryPeer || verifyMode == QSslSocket::AutoVerifyPeer || verifyMode == QSslSocket::VerifyNone); } bool QSslSocketBackendPrivate::verifySessionProtocol() const { bool protocolOk = false; if (configuration.protocol == QSsl::AnyProtocol) protocolOk = true; else if (configuration.protocol == QSsl::TlsV1SslV3) protocolOk = (sessionProtocol() >= QSsl::SslV3); else if (configuration.protocol == QSsl::SecureProtocols) protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); else if (configuration.protocol == QSsl::TlsV1_0OrLater) protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); else if (configuration.protocol == QSsl::TlsV1_1OrLater) protocolOk = (sessionProtocol() >= QSsl::TlsV1_1); else if (configuration.protocol == QSsl::TlsV1_2OrLater) protocolOk = (sessionProtocol() >= QSsl::TlsV1_2); else protocolOk = (sessionProtocol() == configuration.protocol); return protocolOk; } bool QSslSocketBackendPrivate::verifyPeerTrust() { Q_Q(QSslSocket); const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode; const bool canIgnoreVerify = canIgnoreTrustVerificationFailure(); Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); Q_ASSERT(plainSocket); QCFType trust; OSStatus err = SSLCopyPeerTrust(context, &trust); // !trust - SSLCopyPeerTrust can return noErr but null trust. if (err != noErr || !trust) { if (!canIgnoreVerify) { setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Failed to obtain peer trust: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } else { return true; } } QList errors; // store certificates const int certCount = SecTrustGetCertificateCount(trust); // TODO: why this test depends on configuration.peerCertificateChain not being empty???? if (configuration.peerCertificateChain.isEmpty()) { // Apple's docs say SetTrustEvaluate must be called before // SecTrustGetCertificateAtIndex, but this results // in 'kSecTrustResultRecoverableTrustFailure', so // here we just ignore 'res' (later we'll use SetAnchor etc. // and evaluate again). SecTrustResultType res = kSecTrustResultInvalid; err = SecTrustEvaluate(trust, &res); if (err != noErr) { // We can not ignore this, it's not even about trust verification // probably ... setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } for (int i = 0; i < certCount; ++i) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); QCFType derData = SecCertificateCopyData(cert); configuration.peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); } } if (certCount > 0) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0); QCFType derData = SecCertificateCopyData(cert); configuration.peerCertificate = QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); } // check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) for (const QSslCertificate &cert : qAsConst(configuration.peerCertificateChain)) { if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) { const QSslError error(QSslError::CertificateBlacklisted, cert); errors << error; emit q->peerVerifyError(error); if (q->state() != QAbstractSocket::ConnectedState) return false; } } const bool doVerifyPeer = verifyMode == QSslSocket::VerifyPeer || (verifyMode == QSslSocket::AutoVerifyPeer && mode == QSslSocket::SslClientMode); // Check the peer certificate itself. First try the subject's common name // (CN) as a wildcard, then try all alternate subject name DNS entries the // same way. if (!configuration.peerCertificate.isNull()) { // but only if we're a client connecting to a server // if we're the server, don't check CN if (mode == QSslSocket::SslClientMode) { const QString peerName(verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); if (!isMatchingHostname(configuration.peerCertificate, peerName) && !canIgnoreVerify) { // No matches in common names or alternate names. const QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate); errors << error; emit q->peerVerifyError(error); if (q->state() != QAbstractSocket::ConnectedState) return false; } } } else { // No peer certificate presented. Report as error if the socket // expected one. if (doVerifyPeer && !canIgnoreVerify) { const QSslError error(QSslError::NoPeerCertificate); errors << error; emit q->peerVerifyError(error); if (q->state() != QAbstractSocket::ConnectedState) return false; } } // verify certificate chain QCFType certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); for (const QSslCertificate &cert : qAsConst(configuration.caCertificates)) { QCFType certData = cert.d->derData.toCFData(); QCFType certRef = SecCertificateCreateWithData(NULL, certData); CFArrayAppendValue(certArray, certRef); } SecTrustSetAnchorCertificates(trust, certArray); SecTrustSetAnchorCertificatesOnly(trust, false); SecTrustResultType trustResult = kSecTrustResultInvalid; SecTrustEvaluate(trust, &trustResult); switch (trustResult) { case kSecTrustResultUnspecified: case kSecTrustResultProceed: break; default: if (!canIgnoreVerify) { const QSslError error(QSslError::CertificateUntrusted, configuration.peerCertificate); errors << error; emit q->peerVerifyError(error); } } // report errors if (!errors.isEmpty() && !canIgnoreVerify) { sslErrors = errors; // checkSslErrors unconditionally emits sslErrors: // a user's slot can abort/close/disconnect on this // signal, so we also test the socket's state: if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) return false; } else { sslErrors.clear(); } return true; } /* Copied verbatim from qsslsocket_openssl.cpp */ bool QSslSocketBackendPrivate::checkSslErrors() { Q_Q(QSslSocket); if (sslErrors.isEmpty()) return true; emit q->sslErrors(sslErrors); const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer && mode == QSslSocket::SslClientMode); const bool doEmitSslError = !verifyErrorsHaveBeenIgnored(); // check whether we need to emit an SSL handshake error if (doVerifyPeer && doEmitSslError) { if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { pauseSocketNotifiers(q); paused = true; } else { setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); plainSocket->disconnectFromHost(); } return false; } return true; } bool QSslSocketBackendPrivate::startHandshake() { Q_ASSERT(context); Q_Q(QSslSocket); OSStatus err = SSLHandshake(context); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << plainSocket << "SSLHandhake returned" << err; #endif if (err == errSSLWouldBlock) { // startHandshake has to be called again ... later. return false; } else if (err == errSSLServerAuthCompleted) { // errSSLServerAuthCompleted is a define for errSSLPeerAuthCompleted, // it works for both server/client modes. // In future we'll evaluate peer's trust at this point, // for now we just continue. // if (!verifyPeerTrust()) // ... return startHandshake(); } else if (err == errSSLClientCertRequested) { Q_ASSERT(mode == QSslSocket::SslClientMode); QString errorDescription; QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; // setSessionCertificate does not fail if we have no certificate. // Failure means a real error (invalid certificate, no private key, etc). if (!setSessionCertificate(errorDescription, errorCode)) { setErrorAndEmit(errorCode, errorDescription); return false; } else { // We try to resume a handshake, even if have no // local certificates ... (up to server to deal with our failure). return startHandshake(); } } else if (err != errSecSuccess) { if (err == errSSLBadCert && canIgnoreTrustVerificationFailure()) { // We're on the server side and client did not provide any // certificate. This is the new 'nice' error returned by // Security Framework after it was recently updated. return startHandshake(); } setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("SSLHandshake failed: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } // Connection aborted during handshake phase. if (q->state() != QAbstractSocket::ConnectedState) { qCDebug(lcSsl) << "connection aborted"; return false; } // check protocol version ourselves, as Secure Transport does not enforce // the requested min / max versions. if (!verifySessionProtocol()) { setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); plainSocket->disconnectFromHost(); return false; } if (verifyPeerTrust()) { continueHandshake(); return true; } else { return false; } } /* PKCS12 helpers. */ static QAsn1Element wrap(quint8 type, const QAsn1Element &child) { QByteArray value; QDataStream stream(&value, QIODevice::WriteOnly); child.write(stream); return QAsn1Element(type, value); } static QAsn1Element _q_PKCS7_data(const QByteArray &data) { QVector items; items << QAsn1Element::fromObjectId("1.2.840.113549.1.7.1"); items << wrap(QAsn1Element::Context0Type, QAsn1Element(QAsn1Element::OctetStringType, data)); return QAsn1Element::fromVector(items); } /*! PKCS #12 key derivation. Some test vectors: http://www.drh-consultancy.demon.co.uk/test.txt */ static QByteArray _q_PKCS12_keygen(char id, const QByteArray &salt, const QString &passPhrase, int n, int r) { const int u = 20; const int v = 64; // password formatting QByteArray passUnicode(passPhrase.size() * 2 + 2, '\0'); char *p = passUnicode.data(); for (int i = 0; i < passPhrase.size(); ++i) { quint16 ch = passPhrase[i].unicode(); *(p++) = (ch & 0xff00) >> 8; *(p++) = (ch & 0xff); } // prepare I QByteArray D(64, id); QByteArray S, P; const int sSize = v * ((salt.size() + v - 1) / v); S.resize(sSize); for (int i = 0; i < sSize; ++i) { S[i] = salt[i % salt.size()]; } const int pSize = v * ((passUnicode.size() + v - 1) / v); P.resize(pSize); for (int i = 0; i < pSize; ++i) { P[i] = passUnicode[i % passUnicode.size()]; } QByteArray I = S + P; // apply hashing const int c = (n + u - 1) / u; QByteArray A; QByteArray B; B.resize(v); QCryptographicHash hash(QCryptographicHash::Sha1); for (int i = 0; i < c; ++i) { // hash r iterations QByteArray Ai = D + I; for (int j = 0; j < r; ++j) { hash.reset(); hash.addData(Ai); Ai = hash.result(); } for (int j = 0; j < v; ++j) { B[j] = Ai[j % u]; } // modify I as Ij = (Ij + B + 1) modulo 2^v for (int p = 0; p < I.size(); p += v) { quint8 carry = 1; for (int j = v - 1; j >= 0; --j) { quint16 v = quint8(I[p+j]) + quint8(B[j]) + carry; I[p+j] = v & 0xff; carry = (v & 0xff00) >> 8; } } A += Ai; } return A.left(n); } static QByteArray _q_PKCS12_salt() { QByteArray salt; salt.resize(8); for (int i = 0; i < salt.size(); ++i) { salt[i] = (qrand() & 0xff); } return salt; } static QByteArray _q_PKCS12_certBag(const QSslCertificate &cert) { QVector items; items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.3"); // certificate QVector certItems; certItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.22.1"); certItems << wrap(QAsn1Element::Context0Type, QAsn1Element(QAsn1Element::OctetStringType, cert.toDer())); items << wrap(QAsn1Element::Context0Type, QAsn1Element::fromVector(certItems)); // local key id const QByteArray localKeyId = cert.digest(QCryptographicHash::Sha1); QVector idItems; idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); idItems << wrap(QAsn1Element::SetType, QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); items << wrap(QAsn1Element::SetType, QAsn1Element::fromVector(idItems)); // dump QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); QByteArray ba; QDataStream stream(&ba, QIODevice::WriteOnly); root.write(stream); return ba; } static QAsn1Element _q_PKCS12_key(const QSslKey &key) { Q_ASSERT(key.algorithm() == QSsl::Rsa || key.algorithm() == QSsl::Dsa); QVector keyItems; keyItems << QAsn1Element::fromInteger(0); QVector algoItems; if (key.algorithm() == QSsl::Rsa) algoItems << QAsn1Element::fromObjectId(RSA_ENCRYPTION_OID); else if (key.algorithm() == QSsl::Dsa) algoItems << QAsn1Element::fromObjectId(DSA_ENCRYPTION_OID); algoItems << QAsn1Element(QAsn1Element::NullType); keyItems << QAsn1Element::fromVector(algoItems); keyItems << QAsn1Element(QAsn1Element::OctetStringType, key.toDer()); return QAsn1Element::fromVector(keyItems); } static QByteArray _q_PKCS12_shroudedKeyBag(const QSslKey &key, const QString &passPhrase, const QByteArray &localKeyId) { const int iterations = 2048; QByteArray salt = _q_PKCS12_salt(); QByteArray cKey = _q_PKCS12_keygen(1, salt, passPhrase, 24, iterations); QByteArray cIv = _q_PKCS12_keygen(2, salt, passPhrase, 8, iterations); // prepare and encrypt data QByteArray plain; QDataStream plainStream(&plain, QIODevice::WriteOnly); _q_PKCS12_key(key).write(plainStream); QByteArray crypted = QSslKeyPrivate::encrypt(QSslKeyPrivate::DesEde3Cbc, plain, cKey, cIv); QVector items; items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.2"); // key QVector keyItems; QVector algoItems; algoItems << QAsn1Element::fromObjectId("1.2.840.113549.1.12.1.3"); QVector paramItems; paramItems << QAsn1Element(QAsn1Element::OctetStringType, salt); paramItems << QAsn1Element::fromInteger(iterations); algoItems << QAsn1Element::fromVector(paramItems); keyItems << QAsn1Element::fromVector(algoItems); keyItems << QAsn1Element(QAsn1Element::OctetStringType, crypted); items << wrap(QAsn1Element::Context0Type, QAsn1Element::fromVector(keyItems)); // local key id QVector idItems; idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); idItems << wrap(QAsn1Element::SetType, QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); items << wrap(QAsn1Element::SetType, QAsn1Element::fromVector(idItems)); // dump QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); QByteArray ba; QDataStream stream(&ba, QIODevice::WriteOnly); root.write(stream); return ba; } static QByteArray _q_PKCS12_bag(const QList &certs, const QSslKey &key, const QString &passPhrase) { QVector items; // certs for (int i = 0; i < certs.size(); ++i) items << _q_PKCS7_data(_q_PKCS12_certBag(certs[i])); // key const QByteArray localKeyId = certs.first().digest(QCryptographicHash::Sha1); items << _q_PKCS7_data(_q_PKCS12_shroudedKeyBag(key, passPhrase, localKeyId)); // dump QAsn1Element root = QAsn1Element::fromVector(items); QByteArray ba; QDataStream stream(&ba, QIODevice::WriteOnly); root.write(stream); return ba; } static QAsn1Element _q_PKCS12_mac(const QByteArray &data, const QString &passPhrase) { const int iterations = 2048; // salt generation QByteArray macSalt = _q_PKCS12_salt(); QByteArray key = _q_PKCS12_keygen(3, macSalt, passPhrase, 20, iterations); // HMAC calculation QMessageAuthenticationCode hmac(QCryptographicHash::Sha1, key); hmac.addData(data); QVector algoItems; algoItems << QAsn1Element::fromObjectId("1.3.14.3.2.26"); algoItems << QAsn1Element(QAsn1Element::NullType); QVector digestItems; digestItems << QAsn1Element::fromVector(algoItems); digestItems << QAsn1Element(QAsn1Element::OctetStringType, hmac.result()); QVector macItems; macItems << QAsn1Element::fromVector(digestItems); macItems << QAsn1Element(QAsn1Element::OctetStringType, macSalt); macItems << QAsn1Element::fromInteger(iterations); return QAsn1Element::fromVector(macItems); } QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, const QString &passPhrase) { QVector items; // version items << QAsn1Element::fromInteger(3); // auth safe const QByteArray data = _q_PKCS12_bag(certs, key, passPhrase); items << _q_PKCS7_data(data); // HMAC items << _q_PKCS12_mac(data, passPhrase); // dump QAsn1Element root = QAsn1Element::fromVector(items); QByteArray ba; QDataStream stream(&ba, QIODevice::WriteOnly); root.write(stream); return ba; } QT_END_NAMESPACE