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;
|
return QTlsPrivate::X509CertificateGeneric::certificatesFromDer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTlsPrivate::X509Pkcs12ReaderPtr QSchannelBackend::X509Pkcs12Reader() const
|
||||||
|
{
|
||||||
|
return QTlsPrivate::X509CertificateSchannel::importPkcs12;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType)
|
SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType)
|
||||||
|
@ -57,6 +57,7 @@ private:
|
|||||||
|
|
||||||
QTlsPrivate::X509PemReaderPtr X509PemReader() const override;
|
QTlsPrivate::X509PemReaderPtr X509PemReader() const override;
|
||||||
QTlsPrivate::X509DerReaderPtr X509DerReader() const override;
|
QTlsPrivate::X509DerReaderPtr X509DerReader() const override;
|
||||||
|
QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override;
|
||||||
|
|
||||||
static bool s_loadedCiphersAndCerts;
|
static bool s_loadedCiphersAndCerts;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (C) 2021 The Qt Company Ltd.
|
// 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
|
// 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 "qtlskey_schannel_p.h"
|
||||||
#include "qx509_schannel_p.h"
|
#include "qx509_schannel_p.h"
|
||||||
|
|
||||||
|
#include <QtCore/private/qsystemerror_p.h>
|
||||||
#include <QtNetwork/private/qsslcertificate_p.h>
|
#include <QtNetwork/private/qsslcertificate_p.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -46,6 +48,165 @@ QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const
|
|||||||
return certificate;
|
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
|
} // namespace QTlsPrivate
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -36,6 +36,10 @@ public:
|
|||||||
Qt::HANDLE handle() const override;
|
Qt::HANDLE handle() const override;
|
||||||
|
|
||||||
static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext);
|
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:
|
private:
|
||||||
const CERT_CONTEXT *certificateContext = nullptr;
|
const CERT_CONTEXT *certificateContext = nullptr;
|
||||||
|
|
||||||
|
@ -40,6 +40,16 @@ struct QHCertStoreDeleter {
|
|||||||
// A simple RAII type used by Schannel code and Window CA fetcher class:
|
// A simple RAII type used by Schannel code and Window CA fetcher class:
|
||||||
using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>;
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // QWINCRYPT_P_H
|
#endif // QWINCRYPT_P_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user