diff options
-rw-r--r-- | src/network/ssl/qsslkey.cpp | 32 | ||||
-rw-r--r-- | src/network/ssl/qsslkey.h | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslkey_p.h | 1 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.cpp | 20 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket.h | 5 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 69 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_p.h | 4 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols.cpp | 12 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols_p.h | 8 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/certs/README | 7 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/certs/fluke.p12 | bin | 0 -> 2797 bytes | |||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/certs/leaf.p12 | bin | 0 -> 3821 bytes | |||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 47 |
13 files changed, 206 insertions, 0 deletions
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<QSslKeyPrivate> 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 @@ -1456,6 +1456,26 @@ QList<QSslCertificate> QSslSocket::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, this function returns \c true; otherwise it returns \c false. 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<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); 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 <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 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<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 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 diff --git a/tests/auto/network/ssl/qsslsocket/certs/README b/tests/auto/network/ssl/qsslsocket/certs/README new file mode 100644 index 0000000000..704646c212 --- /dev/null +++ b/tests/auto/network/ssl/qsslsocket/certs/README @@ -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. diff --git a/tests/auto/network/ssl/qsslsocket/certs/fluke.p12 b/tests/auto/network/ssl/qsslsocket/certs/fluke.p12 Binary files differnew file mode 100644 index 0000000000..f11e550e88 --- /dev/null +++ b/tests/auto/network/ssl/qsslsocket/certs/fluke.p12 diff --git a/tests/auto/network/ssl/qsslsocket/certs/leaf.p12 b/tests/auto/network/ssl/qsslsocket/certs/leaf.p12 Binary files differnew file mode 100644 index 0000000000..cb89aadb73 --- /dev/null +++ b/tests/auto/network/ssl/qsslsocket/certs/leaf.p12 diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 30a9e19138..1ed2a98ed7 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -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 |