Add support for loading PKCS#12 bundles.
Add support for loading certificates and keys from PKCS#12 bundles (also known as pfx files). Task-number: QTBUG-1565 [ChangeLog][QtNetwork][QSslSocket] Support for loading PKCS#12 bundles was added. These are often used to transport keys and certificates conveniently, particularly when making use of client certificates. Change-Id: Idaeb2cb4dac4b19881a5c99c7c0a7eea00c2b207 Reviewed-by: Daniel Molkentin <daniel@molkentin.de>
This commit is contained in:
parent
3e9904b98b
commit
50e8e95385
@ -96,6 +96,38 @@ void QSslKeyPrivate::clear(bool deep)
|
||||
}
|
||||
}
|
||||
|
||||
bool QSslKeyPrivate::fromEVP_PKEY(EVP_PKEY *pkey)
|
||||
{
|
||||
if (pkey->type == EVP_PKEY_RSA) {
|
||||
isNull = false;
|
||||
algorithm = QSsl::Rsa;
|
||||
type = QSsl::PrivateKey;
|
||||
|
||||
rsa = q_RSA_new();
|
||||
memcpy(rsa, q_EVP_PKEY_get1_RSA(pkey), sizeof(RSA));
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (pkey->type == EVP_PKEY_DSA) {
|
||||
isNull = false;
|
||||
algorithm = QSsl::Dsa;
|
||||
type = QSsl::PrivateKey;
|
||||
|
||||
dsa = q_DSA_new();
|
||||
memcpy(rsa, q_EVP_PKEY_get1_DSA(pkey), sizeof(DSA));
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Unknown key type. This could be handled as opaque, but then
|
||||
// we'd eventually leak memory since we wouldn't be able to free
|
||||
// the underlying EVP_PKEY structure. For now, we won't support
|
||||
// this.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
|
@ -95,6 +95,7 @@ public:
|
||||
private:
|
||||
QExplicitlySharedDataPointer<QSslKeyPrivate> d;
|
||||
friend class QSslCertificate;
|
||||
friend class QSslSocketBackendPrivate;
|
||||
};
|
||||
|
||||
Q_DECLARE_SHARED(QSslKey)
|
||||
|
@ -79,6 +79,7 @@ public:
|
||||
|
||||
void clear(bool deep = true);
|
||||
|
||||
bool fromEVP_PKEY(EVP_PKEY *pkey);
|
||||
void decodePem(const QByteArray &pem, const QByteArray &passPhrase,
|
||||
bool deepClear = true);
|
||||
QByteArray pemHeader() const;
|
||||
|
@ -1455,6 +1455,26 @@ QList<QSslCertificate> QSslSocket::systemCaCertificates()
|
||||
return QSslSocketPrivate::systemCaCertificates();
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.4
|
||||
|
||||
Imports a PKCS#12 (pfx) file from the specified \a device. A PKCS#12
|
||||
file is a bundle that can contain a number of certificates and keys.
|
||||
This method reads a single \a key, it's \a certificate and any
|
||||
associated \a caCertificates from the bundle. If a \a passPhrase is
|
||||
specified then this will be used to decrypt the bundle. Returns
|
||||
\c true if the PKCS#12 file was successfully loaded.
|
||||
|
||||
\note The \a device must be open and ready to be read from.
|
||||
*/
|
||||
bool QSslSocket::importPKCS12(QIODevice *device,
|
||||
QSslKey *key, QSslCertificate *certificate,
|
||||
QList<QSslCertificate> *caCertificates,
|
||||
const QByteArray &passPhrase)
|
||||
{
|
||||
return QSslSocketBackendPrivate::importPKCS12(device, key, certificate, caCertificates, passPhrase);
|
||||
}
|
||||
|
||||
/*!
|
||||
Waits until the socket is connected, or \a msecs milliseconds,
|
||||
whichever happens first. If the connection has been established,
|
||||
|
@ -172,6 +172,11 @@ public:
|
||||
static QList<QSslCertificate> defaultCaCertificates();
|
||||
static QList<QSslCertificate> systemCaCertificates();
|
||||
|
||||
static bool importPKCS12(QIODevice *device,
|
||||
QSslKey *key, QSslCertificate *cert,
|
||||
QList<QSslCertificate> *caCertificates=0,
|
||||
const QByteArray &passPhrase=QByteArray());
|
||||
|
||||
bool waitForConnected(int msecs = 30000);
|
||||
bool waitForEncrypted(int msecs = 30000);
|
||||
bool waitForReadyRead(int msecs = 30000);
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "qsslsocket.h"
|
||||
#include "qsslcertificate_p.h"
|
||||
#include "qsslcipher_p.h"
|
||||
#include "qsslkey_p.h"
|
||||
|
||||
#include <QtCore/qdatetime.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
@ -1737,4 +1738,72 @@ QList<QSslError> QSslSocketBackendPrivate::verify(QList<QSslCertificate> certifi
|
||||
return errors;
|
||||
}
|
||||
|
||||
bool QSslSocketBackendPrivate::importPKCS12(QIODevice *device,
|
||||
QSslKey *key, QSslCertificate *cert,
|
||||
QList<QSslCertificate> *caCertificates,
|
||||
const QByteArray &passPhrase)
|
||||
{
|
||||
if (!supportsSsl())
|
||||
return false;
|
||||
|
||||
// These are required
|
||||
Q_ASSERT(device);
|
||||
Q_ASSERT(key);
|
||||
Q_ASSERT(cert);
|
||||
|
||||
// Read the file into a BIO
|
||||
QByteArray pkcs12data = device->readAll();
|
||||
if (pkcs12data.size() == 0)
|
||||
return false;
|
||||
|
||||
BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pkcs12data.constData()), pkcs12data.size());
|
||||
|
||||
// Create the PKCS#12 object
|
||||
PKCS12 *p12 = q_d2i_PKCS12_bio(bio, 0);
|
||||
if (!p12) {
|
||||
qWarning("Unable to read PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), 0));
|
||||
q_BIO_free(bio);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the data
|
||||
EVP_PKEY *pkey;
|
||||
X509 *x509;
|
||||
STACK_OF(X509) *ca = 0;
|
||||
|
||||
if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) {
|
||||
qWarning("Unable to parse PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), 0));
|
||||
q_PKCS12_free(p12);
|
||||
q_BIO_free(bio);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to Qt types
|
||||
if (!key->d->fromEVP_PKEY(pkey)) {
|
||||
qWarning("Unable to convert private key");
|
||||
q_sk_pop_free(reinterpret_cast<STACK *>(ca), reinterpret_cast<void(*)(void*)>(q_sk_free));
|
||||
q_X509_free(x509);
|
||||
q_EVP_PKEY_free(pkey);
|
||||
q_PKCS12_free(p12);
|
||||
q_BIO_free(bio);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
*cert = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
|
||||
|
||||
if (caCertificates)
|
||||
*caCertificates = QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(ca);
|
||||
|
||||
// Clean up
|
||||
q_sk_pop_free(reinterpret_cast<STACK *>(ca), reinterpret_cast<void(*)(void*)>(q_sk_free));
|
||||
q_X509_free(x509);
|
||||
q_EVP_PKEY_free(pkey);
|
||||
q_PKCS12_free(p12);
|
||||
q_BIO_free(bio);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -146,6 +146,10 @@ public:
|
||||
Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname);
|
||||
static QList<QSslError> verify(QList<QSslCertificate> certificateChain, const QString &hostName);
|
||||
static QString getErrorsFromOpenSsl();
|
||||
static bool importPKCS12(QIODevice *device,
|
||||
QSslKey *key, QSslCertificate *cert,
|
||||
QList<QSslCertificate> *caCertificates,
|
||||
const QByteArray &passPhrase);
|
||||
};
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -146,6 +146,7 @@ DEFINEFUNC(int, CRYPTO_num_locks, DUMMYARG, DUMMYARG, return 0, return)
|
||||
DEFINEFUNC(void, CRYPTO_set_locking_callback, void (*a)(int, int, const char *, int), a, return, DUMMYARG)
|
||||
DEFINEFUNC(void, CRYPTO_set_id_callback, unsigned long (*a)(), a, return, DUMMYARG)
|
||||
DEFINEFUNC(void, CRYPTO_free, void *a, a, return, DUMMYARG)
|
||||
DEFINEFUNC(DSA *, DSA_new, DUMMYARG, DUMMYARG, return 0, return)
|
||||
DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG)
|
||||
#if OPENSSL_VERSION_NUMBER < 0x00908000L
|
||||
DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, unsigned char **b, b, long c, c, return 0, return)
|
||||
@ -186,6 +187,7 @@ DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, retur
|
||||
DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return)
|
||||
DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG)
|
||||
DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return)
|
||||
DEFINEFUNC(RSA *, RSA_new, DUMMYARG, DUMMYARG, return 0, return)
|
||||
DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG)
|
||||
DEFINEFUNC(int, sk_num, STACK *a, a, return -1, return)
|
||||
DEFINEFUNC2(void, sk_pop_free, STACK *a, a, void (*b)(void*), b, return, DUMMYARG)
|
||||
@ -369,6 +371,11 @@ DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM
|
||||
DEFINEFUNC(EC_KEY *, EC_KEY_new_by_curve_name, int nid, nid, return 0, return)
|
||||
DEFINEFUNC(void, EC_KEY_free, EC_KEY *ecdh, ecdh, return, DUMMYARG)
|
||||
|
||||
DEFINEFUNC5(int, PKCS12_parse, PKCS12 *p12, p12, const char *pass, pass, EVP_PKEY **pkey, pkey, \
|
||||
X509 **cert, cert, STACK_OF(X509) **ca, ca, return 1, return);
|
||||
DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, return 0, return);
|
||||
DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG)
|
||||
|
||||
#define RESOLVEFUNC(func) \
|
||||
if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \
|
||||
&& !(_q_##func = _q_PTR_##func(libs.second->resolve(#func)))) \
|
||||
@ -687,6 +694,7 @@ bool q_resolveOpenSslSymbols()
|
||||
RESOLVEFUNC(CRYPTO_num_locks)
|
||||
RESOLVEFUNC(CRYPTO_set_id_callback)
|
||||
RESOLVEFUNC(CRYPTO_set_locking_callback)
|
||||
RESOLVEFUNC(DSA_new)
|
||||
RESOLVEFUNC(DSA_free)
|
||||
RESOLVEFUNC(ERR_error_string)
|
||||
RESOLVEFUNC(ERR_get_error)
|
||||
@ -719,6 +727,7 @@ bool q_resolveOpenSslSymbols()
|
||||
RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY)
|
||||
RESOLVEFUNC(RAND_seed)
|
||||
RESOLVEFUNC(RAND_status)
|
||||
RESOLVEFUNC(RSA_new)
|
||||
RESOLVEFUNC(RSA_free)
|
||||
RESOLVEFUNC(sk_new_null)
|
||||
RESOLVEFUNC(sk_push)
|
||||
@ -849,6 +858,9 @@ bool q_resolveOpenSslSymbols()
|
||||
RESOLVEFUNC(BN_bin2bn)
|
||||
RESOLVEFUNC(EC_KEY_new_by_curve_name)
|
||||
RESOLVEFUNC(EC_KEY_free)
|
||||
RESOLVEFUNC(PKCS12_parse)
|
||||
RESOLVEFUNC(d2i_PKCS12_bio)
|
||||
RESOLVEFUNC(PKCS12_free)
|
||||
|
||||
symbolsResolved = true;
|
||||
delete libs.first;
|
||||
|
@ -233,6 +233,7 @@ int q_CRYPTO_num_locks();
|
||||
void q_CRYPTO_set_locking_callback(void (*a)(int, int, const char *, int));
|
||||
void q_CRYPTO_set_id_callback(unsigned long (*a)());
|
||||
void q_CRYPTO_free(void *a);
|
||||
DSA *q_DSA_new();
|
||||
void q_DSA_free(DSA *a);
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
|
||||
// 0.9.8 broke SC and BC by changing this function's signature.
|
||||
@ -277,6 +278,7 @@ int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b);
|
||||
int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b);
|
||||
void q_RAND_seed(const void *a, int b);
|
||||
int q_RAND_status();
|
||||
RSA *q_RSA_new();
|
||||
void q_RSA_free(RSA *a);
|
||||
int q_sk_num(STACK *a);
|
||||
void q_sk_pop_free(STACK *a, void (*b)(void *));
|
||||
@ -440,6 +442,12 @@ EC_KEY *q_EC_KEY_new_by_curve_name(int nid);
|
||||
void q_EC_KEY_free(EC_KEY *ecdh);
|
||||
#define q_SSL_CTX_set_tmp_ecdh(ctx, ecdh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_ECDH, 0, (char *)ecdh)
|
||||
|
||||
// PKCS#12 support
|
||||
int q_PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca);
|
||||
PKCS12 *q_d2i_PKCS12_bio(BIO *bio, PKCS12 **pkcs12);
|
||||
void q_PKCS12_free(PKCS12 *pkcs12);
|
||||
|
||||
|
||||
#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp)
|
||||
#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL)
|
||||
#ifdef SSLEAY_MACROS
|
||||
|
7
tests/auto/network/ssl/qsslsocket/certs/README
Normal file
7
tests/auto/network/ssl/qsslsocket/certs/README
Normal file
@ -0,0 +1,7 @@
|
||||
The PKCS#12 bundle was created by running:
|
||||
|
||||
openssl pkcs12 -export -in leaf.crt -inkey leaf.key \
|
||||
-out leaf.p12 \
|
||||
-certfile inter.crt -CAfile ca.crt
|
||||
|
||||
No password was provided.
|
BIN
tests/auto/network/ssl/qsslsocket/certs/fluke.p12
Normal file
BIN
tests/auto/network/ssl/qsslsocket/certs/fluke.p12
Normal file
Binary file not shown.
BIN
tests/auto/network/ssl/qsslsocket/certs/leaf.p12
Normal file
BIN
tests/auto/network/ssl/qsslsocket/certs/leaf.p12
Normal file
Binary file not shown.
@ -194,6 +194,7 @@ private slots:
|
||||
void qtbug18498_peek2();
|
||||
void dhServer();
|
||||
void ecdhServer();
|
||||
void pkcs12();
|
||||
void setEmptyDefaultConfiguration(); // this test should be last
|
||||
|
||||
static void exitLoop()
|
||||
@ -2734,6 +2735,52 @@ void tst_QSslSocket::ecdhServer()
|
||||
QVERIFY(client->state() == QAbstractSocket::ConnectedState);
|
||||
}
|
||||
|
||||
void tst_QSslSocket::pkcs12()
|
||||
{
|
||||
if (!QSslSocket::supportsSsl()) {
|
||||
qWarning("SSL not supported, skipping test");
|
||||
return;
|
||||
}
|
||||
|
||||
QFETCH_GLOBAL(bool, setProxy);
|
||||
if (setProxy)
|
||||
return;
|
||||
|
||||
QFile f(QLatin1String(SRCDIR "certs/leaf.p12"));
|
||||
bool ok = f.open(QIODevice::ReadOnly);
|
||||
QVERIFY(ok);
|
||||
|
||||
QSslKey key;
|
||||
QSslCertificate cert;
|
||||
QList<QSslCertificate> caCerts;
|
||||
|
||||
ok = QSslSocket::importPKCS12(&f, &key, &cert, &caCerts);
|
||||
QVERIFY(ok);
|
||||
f.close();
|
||||
|
||||
QList<QSslCertificate> leafCert = QSslCertificate::fromPath(SRCDIR "certs/leaf.crt");
|
||||
QVERIFY(!leafCert.isEmpty());
|
||||
|
||||
QCOMPARE(cert, leafCert.first());
|
||||
|
||||
QFile f2(QLatin1String(SRCDIR "certs/leaf.key"));
|
||||
ok = f2.open(QIODevice::ReadOnly);
|
||||
QVERIFY(ok);
|
||||
|
||||
QSslKey leafKey(&f2, QSsl::Rsa);
|
||||
f2.close();
|
||||
|
||||
QVERIFY(!leafKey.isNull());
|
||||
QCOMPARE(key, leafKey);
|
||||
|
||||
QList<QSslCertificate> caCert = QSslCertificate::fromPath(SRCDIR "certs/inter.crt");
|
||||
QVERIFY(!caCert.isEmpty());
|
||||
|
||||
QVERIFY(!caCerts.isEmpty());
|
||||
QCOMPARE(caCerts.first(), caCert.first());
|
||||
QCOMPARE(caCerts, caCert);
|
||||
}
|
||||
|
||||
void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last, as it has some side effects
|
||||
{
|
||||
// used to produce a crash in QSslConfigurationPrivate::deepCopyDefaultConfiguration, QTBUG-13265
|
||||
|
Loading…
Reference in New Issue
Block a user