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:
parent
392b6e657c
commit
2162e0dfc4
@ -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)
|
||||
|
@ -57,6 +57,7 @@ private:
|
||||
|
||||
QTlsPrivate::X509PemReaderPtr X509PemReader() const override;
|
||||
QTlsPrivate::X509DerReaderPtr X509DerReader() const override;
|
||||
QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override;
|
||||
|
||||
static bool s_loadedCiphersAndCerts;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user