From 50e8e9538511e3cdf837264f8d676be9899b2b07 Mon Sep 17 00:00:00 2001 From: "Richard J. Moore" Date: Sat, 10 May 2014 22:49:37 +0100 Subject: 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 --- src/network/ssl/qsslkey.cpp | 32 ++++++++++++ src/network/ssl/qsslkey.h | 1 + src/network/ssl/qsslkey_p.h | 1 + src/network/ssl/qsslsocket.cpp | 20 ++++++++ src/network/ssl/qsslsocket.h | 5 ++ src/network/ssl/qsslsocket_openssl.cpp | 69 ++++++++++++++++++++++++++ src/network/ssl/qsslsocket_openssl_p.h | 4 ++ src/network/ssl/qsslsocket_openssl_symbols.cpp | 12 +++++ src/network/ssl/qsslsocket_openssl_symbols_p.h | 8 +++ 9 files changed, 152 insertions(+) (limited to 'src') diff --git a/src/network/ssl/qsslkey.cpp b/src/network/ssl/qsslkey.cpp index 95eed6e4b3..b43e28589f 100644 --- a/src/network/ssl/qsslkey.cpp +++ b/src/network/ssl/qsslkey.cpp @@ -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 diff --git a/src/network/ssl/qsslkey.h b/src/network/ssl/qsslkey.h index 145d4a28f1..59063e2e57 100644 --- a/src/network/ssl/qsslkey.h +++ b/src/network/ssl/qsslkey.h @@ -95,6 +95,7 @@ public: private: QExplicitlySharedDataPointer d; friend class QSslCertificate; + friend class QSslSocketBackendPrivate; }; Q_DECLARE_SHARED(QSslKey) diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h index 54d12d76fb..658be85621 100644 --- a/src/network/ssl/qsslkey_p.h +++ b/src/network/ssl/qsslkey_p.h @@ -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; diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 04c0fb0487..1c77fac769 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -1455,6 +1455,26 @@ QList 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 *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, diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h index 9cc5e02de3..a0c9002529 100644 --- a/src/network/ssl/qsslsocket.h +++ b/src/network/ssl/qsslsocket.h @@ -172,6 +172,11 @@ public: static QList defaultCaCertificates(); static QList systemCaCertificates(); + static bool importPKCS12(QIODevice *device, + QSslKey *key, QSslCertificate *cert, + QList *caCertificates=0, + const QByteArray &passPhrase=QByteArray()); + bool waitForConnected(int msecs = 30000); bool waitForEncrypted(int msecs = 30000); bool waitForReadyRead(int msecs = 30000); diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index 1b0c5afa00..b0d1ed2c4f 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -62,6 +62,7 @@ #include "qsslsocket.h" #include "qsslcertificate_p.h" #include "qsslcipher_p.h" +#include "qsslkey_p.h" #include #include @@ -1737,4 +1738,72 @@ QList QSslSocketBackendPrivate::verify(QList certifi return errors; } +bool QSslSocketBackendPrivate::importPKCS12(QIODevice *device, + QSslKey *key, QSslCertificate *cert, + QList *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(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(ca), reinterpret_cast(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(ca), reinterpret_cast(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 diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index 0a9d02287d..f4f2fe842c 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -146,6 +146,10 @@ public: Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); static QList verify(QList certificateChain, const QString &hostName); static QString getErrorsFromOpenSsl(); + static bool importPKCS12(QIODevice *device, + QSslKey *key, QSslCertificate *cert, + QList *caCertificates, + const QByteArray &passPhrase); }; #ifdef Q_OS_WIN diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index b0e14e0de1..b75893d00f 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -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; diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 36e196b072..75e239237e 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -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 -- cgit v1.2.3