summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2017-02-01 13:55:32 +0100
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2017-02-11 16:18:49 +0000
commit17927392cf1cecb20cef7cb9cd77131391de087c (patch)
treee21bba8d6a99c97ac6f7dbb88b089d2cd479bccb /src/network
parent364b161122b567e3a6f7343d438fb540b9fb7e5c (diff)
Allow Secure Transport backend to use a temporary keychain
Since day one Secure Transport socket has two annoying problems on macOS: when we call SecPKCS12Import, we indeed import certs and keys into the default keychain and also (which is more serious) later a dialog can pop up, asking for permission to use a private key (this is especially annoying if you're running SSL autotests or have a server application). Apparently, it's possible to work around those problems if we create our own (temporary) keychain and pass it in the 'options' parameter to SecPKCS12Import. [ChangeLog][QtNetwork] Allow QSslSocket to use a temporary keychain on macOS. Task-number: QTBUG-56102 Change-Id: Ic3a56c905100dc80d907a25fe6ebfa232dcf5b9e Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/ssl/qsslsocket.cpp7
-rw-r--r--src/network/ssl/qsslsocket_mac.cpp124
2 files changed, 125 insertions, 6 deletions
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index bb0d949684..b4109cadb5 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -971,6 +971,13 @@ QList<QSslCertificate> QSslSocket::localCertificateChain() const
sockets, but are also rarely used by client sockets if the server requires
the client to authenticate.
+ \note Secure Transport SSL backend on macOS may update the default keychain
+ (the default is probably your login keychain) by importing your local certificates
+ and keys. This can also result in system dialogs showing up and asking for
+ permission when your application is using these private keys. If such behavior
+ is undesired, set the QT_SSL_USE_TEMPORARY_KEYCHAIN environment variable to a
+ non-zero value; this will prompt QSslSocket to use its own temporary keychain.
+
\sa localCertificate(), setPrivateKey()
*/
void QSslSocket::setLocalCertificate(const QSslCertificate &certificate)
diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp
index 752640bd46..5646aadf55 100644
--- a/src/network/ssl/qsslsocket_mac.cpp
+++ b/src/network/ssl/qsslsocket_mac.cpp
@@ -53,9 +53,12 @@
#include <QtCore/qvector.h>
#include <QtCore/qmutex.h>
#include <QtCore/qdebug.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qdir.h>
#include <algorithm>
#include <cstddef>
+#include <vector>
#include <QtCore/private/qcore_mac_p.h>
@@ -65,6 +68,102 @@
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<CFStringRef> cfName = keychainName.toCFString();
+ std::vector<char> 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<uint8_t> 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;
@@ -794,11 +893,24 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription,
QCFType<CFDataRef> pkcs12 = _q_makePkcs12(configuration.localCertificateChain,
configuration.privateKey, passPhrase).toCFData();
QCFType<CFStringRef> password = passPhrase.toCFString();
- const void *keys[] = { kSecImportExportPassphrase };
- const void *values[] = { password };
- QCFType<CFDictionaryRef> options(CFDictionaryCreate(Q_NULLPTR, keys, values, 1,
- Q_NULLPTR, Q_NULLPTR));
- CFArrayRef items = Q_NULLPTR;
+ 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<CFDictionaryRef> options = CFDictionaryCreate(nullptr, keys, values, nKeys,
+ nullptr, nullptr);
+ CFArrayRef items = nullptr;
OSStatus err = SecPKCS12Import(pkcs12, options, &items);
if (err != noErr) {
#ifdef QSSLSOCKET_DEBUG
@@ -830,7 +942,7 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription,
return false;
}
- QCFType<CFMutableArrayRef> certs = CFArrayCreateMutable(Q_NULLPTR, 0, &kCFTypeArrayCallBacks);
+ QCFType<CFMutableArrayRef> certs = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
if (!certs) {
errorCode = QAbstractSocket::SslInternalError;
errorDescription = QStringLiteral("Failed to allocate certificates array");