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:
Richard J. Moore 2014-05-10 22:49:37 +01:00 committed by The Qt Project
parent 3e9904b98b
commit 50e8e95385
13 changed files with 206 additions and 0 deletions

View File

@ -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

View File

@ -95,6 +95,7 @@ public:
private:
QExplicitlySharedDataPointer<QSslKeyPrivate> d;
friend class QSslCertificate;
friend class QSslSocketBackendPrivate;
};
Q_DECLARE_SHARED(QSslKey)

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View 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.

Binary file not shown.

Binary file not shown.

View File

@ -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