From 5382312e5c93c91be7e74e688331db0feeb438e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Laine=CC=81?= Date: Fri, 22 Aug 2014 17:20:49 +0200 Subject: Add SecureTransport based SSL backend for iOS and OS X Add support for SSL on iOS/OS X by adding a SecureTransport based backend. [ChangeLog][QtNetwork][QSslSocket] A new SSL backend for iOS and OS X, implemented with Apple's Secure Transport (Security Framework). Change-Id: I7466db471be2a8a2170f9af9d6ad4c7b6425738b Reviewed-by: Richard J. Moore --- src/network/ssl/qsslcertificate.cpp | 4 + src/network/ssl/qsslkey_mac.cpp | 91 +++ src/network/ssl/qsslsocket.cpp | 3 + src/network/ssl/qsslsocket_mac.cpp | 1443 +++++++++++++++++++++++++++++++++++ src/network/ssl/qsslsocket_mac_p.h | 125 +++ src/network/ssl/qsslsocket_p.h | 2 + src/network/ssl/ssl.pri | 9 + 7 files changed, 1677 insertions(+) create mode 100644 src/network/ssl/qsslkey_mac.cpp create mode 100644 src/network/ssl/qsslsocket_mac.cpp create mode 100644 src/network/ssl/qsslsocket_mac_p.h (limited to 'src/network') diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp index 302b2db285..2eef2f20aa 100644 --- a/src/network/ssl/qsslcertificate.cpp +++ b/src/network/ssl/qsslcertificate.cpp @@ -105,12 +105,16 @@ \value EmailAddress The email address associated with the certificate */ +#include #ifndef QT_NO_OPENSSL #include "qsslsocket_openssl_symbols_p.h" #endif #ifdef Q_OS_WINRT #include "qsslsocket_winrt_p.h" #endif +#ifdef QT_SECURETRANSPORT +#include "qsslsocket_mac_p.h" +#endif #include "qssl_p.h" #include "qsslcertificate.h" diff --git a/src/network/ssl/qsslkey_mac.cpp b/src/network/ssl/qsslkey_mac.cpp new file mode 100644 index 0000000000..6e15012dc9 --- /dev/null +++ b/src/network/ssl/qsslkey_mac.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsslkey.h" +#include "qsslkey_p.h" + +#include + +QT_USE_NAMESPACE + +static QByteArray wrapCCCrypt(CCOperation ccOp, + QSslKeyPrivate::Cipher cipher, + const QByteArray &data, + const QByteArray &key, const QByteArray &iv) +{ + int blockSize; + CCAlgorithm ccAlgorithm; + switch (cipher) { + case QSslKeyPrivate::DesCbc: + blockSize = kCCBlockSizeDES; + ccAlgorithm = kCCAlgorithmDES; + break; + case QSslKeyPrivate::DesEde3Cbc: + blockSize = kCCBlockSize3DES; + ccAlgorithm = kCCAlgorithm3DES; + break; + case QSslKeyPrivate::Rc2Cbc: + blockSize = kCCBlockSizeRC2; + ccAlgorithm = kCCAlgorithmRC2; + break; + }; + size_t plainLength = 0; + QByteArray plain(data.size() + blockSize, 0); + CCCryptorStatus status = CCCrypt( + ccOp, ccAlgorithm, kCCOptionPKCS7Padding, + key.constData(), key.size(), + iv.constData(), + data.constData(), data.size(), + plain.data(), plain.size(), &plainLength); + if (status == kCCSuccess) + return plain.left(plainLength); + return QByteArray(); +} + +QByteArray QSslKeyPrivate::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return wrapCCCrypt(kCCDecrypt, cipher, data, key, iv); +} + +QByteArray QSslKeyPrivate::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) +{ + return wrapCCCrypt(kCCEncrypt, cipher, data, key, iv); +} diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 98972f526a..e12ae33ac2 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -312,6 +312,9 @@ #ifdef Q_OS_WINRT #include "qsslsocket_winrt_p.h" #endif +#ifdef QT_SECURETRANSPORT +#include "qsslsocket_mac_p.h" +#endif #include "qsslconfiguration_p.h" #include diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp new file mode 100644 index 0000000000..08f4c1c6a1 --- /dev/null +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -0,0 +1,1443 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsslsocket.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 + +QT_BEGIN_NAMESPACE + +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; + + +#ifndef Q_OS_IOS // dhparam is not used on iOS. (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. (defined in MacErrors.h on OS X) +#ifdef Q_OS_IOS +# 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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; + + QCFType context(SSLCreateContext(Q_NULLPTR, kSSLClientSide, kSSLStreamType)); + 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[i])); + if (!ciph.isNull()) { + ciphers << ciph; + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + + if (!s_loadRootCertsOnDemand) + setDefaultCaCertificates(systemCaCertificates()); + } else { + qWarning() << Q_FUNC_INFO << "SSLCreateContext failed"; + s_loadedCiphersAndCerts = false; + } + +} + +long QSslSocketPrivate::sslLibraryVersionNumber() +{ + return 0; +} + +QString QSslSocketPrivate::sslLibraryVersionString() +{ + return QStringLiteral("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(); +} + + +QList QSslSocketPrivate::systemCaCertificates() +{ + QList systemCerts; +#ifdef Q_OS_OSX + // SecTrustSettingsCopyCertificates is not defined on iOS. + QCFType cfCerts; + OSStatus status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts); + if (status == noErr) { + const CFIndex size = CFArrayGetCount(cfCerts); + for (CFIndex i = 0; i < size; ++i) { + SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); + QCFType derData = SecCertificateCopyData(cfCert); + systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + } + } else { + // no detailed error handling here + qWarning("could not retrieve system CA certificates"); + } +#endif + return systemCerts; +} + +QSslSocketBackendPrivate::QSslSocketBackendPrivate() + : context(Q_NULLPTR) +{ +} + +QSslSocketBackendPrivate::~QSslSocketBackendPrivate() +{ + destroySslContext(); +} + +void QSslSocketBackendPrivate::continueHandshake() +{ +#ifdef QSSLSOCKET_DEBUG + qDebug() << Q_FUNC_INFO << 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) { + qWarning() << Q_FUNC_INFO << "SSLGetNegotiatedProtocolVersion failed:" + << int(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 ... + setError("Unable to init SSL Context", QAbstractSocket::SslInternalError); + 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 ... + setError("Unable to init SSL Context", QAbstractSocket::SslInternalError); + 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) { + const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); + size_t writtenBytes = 0; + const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); + if (err != noErr && err != errSSLWouldBlock) { + qWarning() << Q_FUNC_INFO << "SSL write failed with error:" << int(err); + setError("SSL write failed", QAbstractSocket::SslInternalError); + 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; + } + } + } + + if (connectionEncrypted) { + QVarLengthArray data; + while (plainSocket->bytesAvailable() > 0) { + size_t readBytes = 0; + data.resize(4096); + if (shutdown) { + // SSLRead(context, data.data(), data.size(), &readBytes) fails with errSSLClosedGraceful + // if the session was closed (see disconnectFromHost). + // SSLClose SSLRead fails and we'll stay in this loop forever. + // At the moment we're never here (see the test '!context || shutdown' above) - + // we read nothing from the socket as soon as SSL session closed. + qCritical() << Q_FUNC_INFO << "read attempt after SSL session closed"; + size_t nBytes = plainSocket->bytesAvailable(); + _q_SSLRead(plainSocket, data.data(), &nBytes); + } else { + const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); + if (err != noErr && err != errSSLWouldBlock) { + qWarning() << Q_FUNC_INFO << "SSLRead failed with:" << int(err); + setError("SSL read failed", QAbstractSocket::SslInternalError); + break; + } + } + + if (readBytes) { + char *const ptr = buffer.reserve(readBytes); + std::copy(data.data(), data.data() + readBytes, ptr); + if (readyReadEmittedPointer) + *readyReadEmittedPointer = true; + emit q->readyRead(); + } + } + } +} + + +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) { + 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; + + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + ciph.d->name = QLatin1String("DES-CBC3-SHA"); + break; + case TLS_RSA_WITH_AES_128_CBC_SHA: + ciph.d->name = QLatin1String("AES128-SHA"); + break; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + ciph.d->name = QLatin1String("AES128-SHA256"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA: + ciph.d->name = QLatin1String("AES256-SHA"); + 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_SHA: + ciph.d->name = QLatin1String("DHE-RSA-AES128-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_SHA: + ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA256"); + break; + + 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_128_CBC_SHA256: + ciph.d->name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); + 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-AES256-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_128_CBC_SHA256: + ciph.d->name = QLatin1String("ECDH-RSA-AES128-SHA256"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA384"); + 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_128_CBC_SHA256: + ciph.d->name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); + 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-AES256-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_128_CBC_SHA256: + ciph.d->name = QLatin1String("ECDHE-RSA-AES128-SHA256"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + ciph.d->name = QLatin1String("ECDHE-RSA-AES256-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + ciph.d->name = QLatin1String("ECDHE-RSA-AES256-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 QStringList bits = ciph.d->name.split('-'); + if (bits.size() >= 2) { + if (bits.size() == 2 || bits.size() == 3) { + ciph.d->keyExchangeMethod = QLatin1String("RSA"); + } else if (ciph.d->name.startsWith("DH-") || ciph.d->name.startsWith("DHE-")) { + ciph.d->keyExchangeMethod = QLatin1String("DH"); + } else if (ciph.d->name.startsWith("ECDH-") || ciph.d->name.startsWith("ECDHE-")) { + ciph.d->keyExchangeMethod = QLatin1String("ECDH"); + } else { + qWarning() << Q_FUNC_INFO << "Unknown Kx" << ciph.d->name; + } + + if (bits.size() == 2 || bits.size() == 3) { + ciph.d->authenticationMethod = QLatin1String("RSA"); + } else if (ciph.d->name.contains("-ECDSA-")) { + ciph.d->authenticationMethod = QLatin1String("ECDSA"); + } else if (ciph.d->name.contains("-RSA-")) { + ciph.d->authenticationMethod = QLatin1String("RSA"); + } else { + qWarning() << Q_FUNC_INFO << "Unknown Au" << ciph.d->name; + } + + if (ciph.d->name.contains("RC4-")) { + ciph.d->encryptionMethod = QLatin1String("RC4(128)"); + ciph.d->bits = 128; + ciph.d->supportedBits = 128; + } else if (ciph.d->name.contains("DES-CBC3-")) { + ciph.d->encryptionMethod = QLatin1String("3DES(168)"); + ciph.d->bits = 168; + ciph.d->supportedBits = 168; + } else if (ciph.d->name.contains("AES128-")) { + ciph.d->encryptionMethod = QLatin1String("AES(128)"); + ciph.d->bits = 128; + ciph.d->supportedBits = 128; + } else if (ciph.d->name.contains("AES256-")) { + ciph.d->encryptionMethod = QLatin1String("AES(256)"); + ciph.d->bits = 256; + ciph.d->supportedBits = 256; + } else if (ciph.d->name.contains("NULL-")) { + ciph.d->encryptionMethod = QLatin1String("NULL"); + } else { + qWarning() << Q_FUNC_INFO << "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); + + SSLProtocolSide side = kSSLClientSide; + if (mode == QSslSocket::SslServerMode) + side = kSSLServerSide; + + context = SSLCreateContext(Q_NULLPTR, side, kSSLStreamType); + if (!context) { + qWarning() << Q_FUNC_INFO << "SSLCreateContext failed"; + setError("SSLCreateContext failed", QAbstractSocket::SslInternalError); + return false; + } + + const OSStatus err = SSLSetIOFuncs(context, reinterpret_cast(&_q_SSLRead), + reinterpret_cast(&_q_SSLWrite)); + if (err != noErr) { + qWarning() << Q_FUNC_INFO << "SSLSetIOFuncs failed with error " << int(err); + destroySslContext(); + setError("SSLSetIOFuncs failed", QAbstractSocket::SslInternalError); + return false; + } + + SSLSetConnection(context, plainSocket); + + if (mode == QSslSocket::SslServerMode + && !configuration.localCertificateChain.isEmpty()) { + QString errorDescription; + QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; + if (!setSessionCertificate(errorDescription, errorCode)) { + destroySslContext(); + setError(errorDescription, errorCode); + return false; + } + } + + if (!setSessionProtocol()) { + qWarning() << Q_FUNC_INFO << "failed to set protocol version"; + destroySslContext(); + setError("Failed to set protocol version", QAbstractSocket::SslInternalError); + 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) { + qWarning() << Q_FUNC_INFO << "SSLSetSessionOption failed:"< &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[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[] = { kSecImportExportPassphrase }; + const void *values[] = { password }; + QCFType options(CFDictionaryCreate(Q_NULLPTR, keys, values, 1, + Q_NULLPTR, Q_NULLPTR)); + CFArrayRef items = Q_NULLPTR; + OSStatus err = SecPKCS12Import(pkcs12, options, &items); + if (err != noErr) { +#ifdef QSSLSOCKET_DEBUG + qWarning() << Q_FUNC_INFO << 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 + qWarning() << Q_FUNC_INFO << 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 + qWarning() << Q_FUNC_INFO << plainSocket << "SecPKCS12Import returned no identity"; +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); + return false; + } + + QCFType certs = CFArrayCreateMutable(Q_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 + qWarning() << Q_FUNC_INFO << 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)"); + + OSStatus err = noErr; + + // 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) { + qDebug() << Q_FUNC_INFO << "protocol QSsl::SslV2 is disabled"; + return false; + } + + if (configuration.protocol == QSsl::SslV3) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << 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 + qDebug() << Q_FUNC_INFO << plainSocket << "requesting : TLSv1 - TLSv1.2"; +#endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + if (err == noErr) + err = SSLSetProtocolVersionMax(context, kTLSProtocol12); + } else { + qDebug() << Q_FUNC_INFO << "no protocol version found in the configuration"; + return false; + } + + return err == noErr; +} + +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 + protocolOk = (sessionProtocol() == configuration.protocol); + + return protocolOk; +} + +bool QSslSocketBackendPrivate::verifyPeerTrust() +{ + Q_Q(QSslSocket); + + const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode; + const bool canIgnoreVerify = mode == QSslSocket::SslServerMode + && (verifyMode == QSslSocket::QueryPeer + || verifyMode == QSslSocket::AutoVerifyPeer + || verifyMode == QSslSocket::VerifyNone); + + 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) { + setError(QStringLiteral("Failed to obtain peer trust: %1").arg(err), + QAbstractSocket::SslHandshakeFailedError); + 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 ... + setError("SecTrustEvaluate failed", QAbstractSocket::SslHandshakeFailedError); + 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) + foreach (const QSslCertificate &cert, 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; + } + } + + // TODO: right now we have nothing on server side? + if (mode == QSslSocket::SslClientMode) { + // verify certificate chain + QCFType certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + foreach (const QSslCertificate &cert, 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; + if (!checkSslErrors()) + 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 { + setError(sslErrors.first().errorString(), + QAbstractSocket::SslHandshakeFailedError); + plainSocket->disconnectFromHost(); + } + return false; + } + + return true; +} + +bool QSslSocketBackendPrivate::startHandshake() +{ + Q_ASSERT(context); + Q_Q(QSslSocket); + + OSStatus err = SSLHandshake(context); +#ifdef QSSLSOCKET_DEBUG + qDebug() << Q_FUNC_INFO << plainSocket << "SSLHandhake returned" << err; +#endif + + if (err == errSSLWouldBlock) { + // startHandshake has to be called again ... later. + return false; + } else if (err == errSSLServerAuthCompleted) { + // TODO: in client mode this happens _before_ ClientCertRequested, + // this is the point there we should test the server certificate chain, + // not sending any client certificate if the server's certificate validation + // fails. + // if (!verifyPeerTrust()) + // .... + return startHandshake(); + } else if (err == errSSLClientCertRequested) { + // TODO: If we are here, the server's trust must + // be evaluated and accepted already, otherwise, + // we can not send our certificate. + 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)) { + qWarning() << Q_FUNC_INFO << "Failed to provide a client certificate"; + setError(errorDescription, errorCode); + 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) { + setError(QStringLiteral("Error during SSL handshake: %1").arg(err), + QAbstractSocket::SslHandshakeFailedError); + plainSocket->disconnectFromHost(); + return false; + } + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) { + qDebug() << Q_FUNC_INFO << "connection aborted"; + return false; + } + + // check protocol version ourselves, as Secure Transport does not enforce + // the requested min / max versions. + if (!verifySessionProtocol()) { + setError("Protocol version mismatch", + QAbstractSocket::SslHandshakeFailedError); + plainSocket->disconnectFromHost(); + return false; + } + + if (verifyPeerTrust()) { + continueHandshake(); + return true; + } else { + return false; + } +} + +void QSslSocketBackendPrivate::setError(const QString &errorString, + QAbstractSocket::SocketError errorCode) +{ + Q_Q(QSslSocket); + + q->setErrorString(errorString); + q->setSocketError(errorCode); + emit q->error(errorCode); +} + +/* + 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 diff --git a/src/network/ssl/qsslsocket_mac_p.h b/src/network/ssl/qsslsocket_mac_p.h new file mode 100644 index 0000000000..d8b7fbd019 --- /dev/null +++ b/src/network/ssl/qsslsocket_mac_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSSLSOCKET_MAC_P_H +#define QSSLSOCKET_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QtNetwork library. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include +#include + +#include "qabstractsocket.h" +#include "qsslsocket_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QSslSocketBackendPrivate : public QSslSocketPrivate +{ + Q_DECLARE_PUBLIC(QSslSocket) +public: + QSslSocketBackendPrivate(); + virtual ~QSslSocketBackendPrivate(); + + // Final-overriders (QSslSocketPrivate): + void continueHandshake() Q_DECL_OVERRIDE; + void disconnected() Q_DECL_OVERRIDE; + void disconnectFromHost() Q_DECL_OVERRIDE; + QSslCipher sessionCipher() const Q_DECL_OVERRIDE; + QSsl::SslProtocol sessionProtocol() const Q_DECL_OVERRIDE; + void startClientEncryption() Q_DECL_OVERRIDE; + void startServerEncryption() Q_DECL_OVERRIDE; + void transmit() Q_DECL_OVERRIDE; + + static QList (verify)(QList certificateChain, + const QString &hostName); + + static bool importPkcs12(QIODevice *device, + QSslKey *key, QSslCertificate *cert, + QList *caCertificates, + const QByteArray &passPhrase); + + static QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); + +private: + // SSL context management/properties: + bool initSslContext(); + void destroySslContext(); + bool setSessionCertificate(QString &errorDescription, + QAbstractSocket::SocketError &errorCode); + bool setSessionProtocol(); + // Aux. functions to do a verification during handshake phase: + bool verifySessionProtocol() const; + bool verifyPeerTrust(); + + bool checkSslErrors(); + bool startHandshake(); + + // Aux. function, sets: + //1) socket error code, + //2) error string (description) + //3) emits a signal. + void setError(const QString &errorString, + QAbstractSocket::SocketError errorCode); + + mutable QCFType context; + + Q_DISABLE_COPY(QSslSocketBackendPrivate); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index 3262b41bbd..f0e4896f2b 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -53,6 +53,8 @@ #include "qsslconfiguration_p.h" #ifndef QT_NO_OPENSSL #include +#else +class QSslContext; #endif #include diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri index 210606a1a0..29c47cd7c6 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -40,6 +40,15 @@ contains(QT_CONFIG, ssl) | contains(QT_CONFIG, openssl) | contains(QT_CONFIG, op ssl/qsslsocket_winrt.cpp \ ssl/qsslellipticcurve_dummy.cpp } + + contains(QT_CONFIG, securetransport) { + HEADERS += ssl/qsslsocket_mac_p.h + SOURCES += ssl/qsslcertificate_qt.cpp \ + ssl/qsslkey_qt.cpp \ + ssl/qsslkey_mac.cpp \ + ssl/qsslsocket_mac.cpp \ + ssl/qsslellipticcurve_dummy.cpp + } } contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { -- cgit v1.2.3