Allow Secure Transport backend to use a temporary keychain
Cherry picked:
this change was first merged into dev, but now we also need it in 5.9
to enable SSL socket tests on the new CI VMs (macOS 10.11, 10.12).
As we do not merge dev->5.9, we need this cherry-pick.
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>
(cherry picked from commit 17927392cf
)
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
de4aeade5e
commit
5377f4ec0e
@ -972,6 +972,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)
|
||||
|
@ -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;
|
||||
@ -815,11 +914,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
|
||||
@ -851,7 +963,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");
|
||||
|
Loading…
Reference in New Issue
Block a user