Schannel: Add support for import of PKCS12/PFX files

Add the missing functionality to the Schannel backend to
make QSslCertificate::importPkcs12() work on Windows.

[ChangeLog][QtNetwork][QSslCertificate] Add support for
PKCS12 import with Schannel backend.

Change-Id: Ibb501724d0dc78b0507ac8becf4776fbba0a0623
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Tobias Koenig 2023-04-25 09:25:44 +02:00
parent 392b6e657c
commit 2162e0dfc4
5 changed files with 181 additions and 0 deletions

View File

@ -313,6 +313,11 @@ QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const
return QTlsPrivate::X509CertificateGeneric::certificatesFromDer;
}
QTlsPrivate::X509Pkcs12ReaderPtr QSchannelBackend::X509Pkcs12Reader() const
{
return QTlsPrivate::X509CertificateSchannel::importPkcs12;
}
namespace {
SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType)

View File

@ -57,6 +57,7 @@ private:
QTlsPrivate::X509PemReaderPtr X509PemReader() const override;
QTlsPrivate::X509DerReaderPtr X509DerReader() const override;
QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override;
static bool s_loadedCiphersAndCerts;
};

View File

@ -1,9 +1,11 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qtlsbackend_schannel_p.h"
#include "qtlskey_schannel_p.h"
#include "qx509_schannel_p.h"
#include <QtCore/private/qsystemerror_p.h>
#include <QtNetwork/private/qsslcertificate_p.h>
#include <memory>
@ -46,6 +48,165 @@ QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const
return certificate;
}
bool X509CertificateSchannel::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert,
QList<QSslCertificate> *caCertificates,
const QByteArray &passPhrase)
{
// These are required
Q_ASSERT(device);
Q_ASSERT(key);
Q_ASSERT(cert);
QByteArray pkcs12data = device->readAll();
if (pkcs12data.size() == 0)
return false;
CRYPT_DATA_BLOB dataBlob;
dataBlob.cbData = pkcs12data.size();
dataBlob.pbData = reinterpret_cast<BYTE*>(pkcs12data.data());
const auto password = QString::fromUtf8(passPhrase);
const DWORD flags = (CRYPT_EXPORTABLE | PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP);
auto certStore = QHCertStorePointer(PFXImportCertStore(&dataBlob,
reinterpret_cast<LPCWSTR>(password.utf16()),
flags));
if (!certStore) {
qCWarning(lcTlsBackendSchannel, "Failed to import PFX data: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
// first extract the certificate with the private key
const auto certContext = QPCCertContextPointer(CertFindCertificateInStore(certStore.get(),
X509_ASN_ENCODING |
PKCS_7_ASN_ENCODING,
0,
CERT_FIND_HAS_PRIVATE_KEY,
nullptr, nullptr));
if (!certContext) {
qCWarning(lcTlsBackendSchannel, "Failed to find certificate in PFX store: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
*cert = QSslCertificate_from_CERT_CONTEXT(certContext.get());
// retrieve the private key for the certificate
NCRYPT_KEY_HANDLE keyHandle = {};
DWORD keyHandleSize = sizeof(keyHandle);
if (!CertGetCertificateContextProperty(certContext.get(), CERT_NCRYPT_KEY_HANDLE_PROP_ID,
&keyHandle, &keyHandleSize)) {
qCWarning(lcTlsBackendSchannel, "Failed to find private key handle in certificate context: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
SECURITY_STATUS securityStatus = ERROR_SUCCESS;
// we need the 'NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG' to make NCryptExportKey succeed
DWORD policy = (NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG);
DWORD policySize = sizeof(policy);
securityStatus = NCryptSetProperty(keyHandle, NCRYPT_EXPORT_POLICY_PROPERTY,
reinterpret_cast<BYTE*>(&policy), policySize, 0);
if (securityStatus != ERROR_SUCCESS) {
qCWarning(lcTlsBackendSchannel, "Failed to update export policy of private key: 0x%x",
static_cast<unsigned int>(securityStatus));
return false;
}
DWORD blobSize = {};
securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB,
nullptr, nullptr, 0, &blobSize, 0);
if (securityStatus != ERROR_SUCCESS) {
qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key size: 0x%x",
static_cast<unsigned int>(securityStatus));
return false;
}
std::vector<BYTE> blob(blobSize);
securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB,
nullptr, blob.data(), blobSize, &blobSize, 0);
if (securityStatus != ERROR_SUCCESS) {
qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key from certificate: 0x%x",
static_cast<unsigned int>(securityStatus));
return false;
}
DWORD privateKeySize = {};
if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CNG_RSA_PRIVATE_KEY_BLOB,
blob.data(), nullptr, &privateKeySize)) {
qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
std::vector<BYTE> privateKeyData(privateKeySize);
CRYPT_PRIVATE_KEY_INFO privateKeyInfo = {};
privateKeyInfo.Algorithm.pszObjId = const_cast<PSTR>(szOID_RSA_RSA);
privateKeyInfo.PrivateKey.cbData = privateKeySize;
privateKeyInfo.PrivateKey.pbData = privateKeyData.data();
if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
CNG_RSA_PRIVATE_KEY_BLOB, blob.data(),
privateKeyInfo.PrivateKey.pbData, &privateKeyInfo.PrivateKey.cbData)) {
qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
DWORD derSize = {};
if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO,
&privateKeyInfo, nullptr, &derSize)) {
qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
QByteArray derData(derSize, Qt::Uninitialized);
if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO,
&privateKeyInfo, reinterpret_cast<BYTE*>(derData.data()), &derSize)) {
qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s",
qPrintable(QSystemError::windowsString()));
return false;
}
*key = QSslKey(derData, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey);
if (key->isNull()) {
qCWarning(lcTlsBackendSchannel, "Failed to parse private key from DER format");
return false;
}
// fetch all the remaining certificates as CA certificates
if (caCertificates) {
PCCERT_CONTEXT caCertContext = nullptr;
while ((caCertContext = CertFindCertificateInStore(certStore.get(),
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0, CERT_FIND_ANY, nullptr, caCertContext))) {
if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
certContext->pCertInfo, caCertContext->pCertInfo))
continue; // ignore the certificate with private key
auto caCertificate = QSslCertificate_from_CERT_CONTEXT(caCertContext);
caCertificates->append(caCertificate);
}
}
return true;
}
} // namespace QTlsPrivate
QT_END_NAMESPACE

View File

@ -36,6 +36,10 @@ public:
Qt::HANDLE handle() const override;
static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext);
static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert,
QList<QSslCertificate> *caCertificates,
const QByteArray &passPhrase);
private:
const CERT_CONTEXT *certificateContext = nullptr;

View File

@ -40,6 +40,16 @@ struct QHCertStoreDeleter {
// A simple RAII type used by Schannel code and Window CA fetcher class:
using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>;
struct QPCCertContextDeleter {
void operator()(PCCERT_CONTEXT context) const
{
CertFreeCertificateContext(context);
}
};
// A simple RAII type used by Schannel code
using QPCCertContextPointer = std::unique_ptr<const CERT_CONTEXT, QPCCertContextDeleter>;
QT_END_NAMESPACE
#endif // QWINCRYPT_P_H