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>
This commit is contained in:
parent
364b161122
commit
17927392cf
@ -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)
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user