hrev46679 adds 1 changeset to branch 'master' old head: b11772accaf1d303382672ae4c70665102b02614 new head: 5ebdc79955caf4781dfffd14b57849ce40df2117 overview: http://cgit.haiku-os.org/haiku/log/?qt=range&q=5ebdc79+%5Eb11772a ---------------------------------------------------------------------------- 5ebdc79: SecureSocket: add some certificate support * Instead of creating an OpenSSL context ofor each socket, use a global one and initialize it lazily when the first SecureSocket is created * Load the certificates from our certificate list so SSL certificates sent by servers can be validated. * Add a callback for signalling that certificate validation failed, the default implementation proceeds with the connection anyway (to keep the old behavior). * Introduce BCertificate class, that provides some information about a certificate. Currently it's only used by the callback mentionned above, but it will be possible to get the leaf certificate for the connection after it's established. Review of the API and implementation is welcome, before I start making use of this in HttpRequest and WebKit to allow the user to accept new certificates. [ Adrien Destugues <pulkomandy@xxxxxxxxxxxxx> ] ---------------------------------------------------------------------------- Revision: hrev46679 Commit: 5ebdc79955caf4781dfffd14b57849ce40df2117 URL: http://cgit.haiku-os.org/haiku/commit/?id=5ebdc79 Author: Adrien Destugues <pulkomandy@xxxxxxxxxxxxx> Date: Wed Jan 15 16:39:06 2014 UTC ---------------------------------------------------------------------------- 6 files changed, 287 insertions(+), 14 deletions(-) headers/os/net/Certificate.h | 32 +++++ headers/os/net/SecureSocket.h | 8 ++ src/kits/network/libnetapi/Certificate.cpp | 105 ++++++++++++++++ src/kits/network/libnetapi/CertificatePrivate.h | 23 ++++ src/kits/network/libnetapi/Jamfile | 2 +- src/kits/network/libnetapi/SecureSocket.cpp | 131 ++++++++++++++++++-- ---------------------------------------------------------------------------- diff --git a/headers/os/net/Certificate.h b/headers/os/net/Certificate.h new file mode 100644 index 0000000..c42207d --- /dev/null +++ b/headers/os/net/Certificate.h @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Haiku, Inc. + * Distributed under the terms of the MIT License. + */ +#ifndef _CERTIFICATE_H +#define _CERTIFICATE_H + + +#include <SecureSocket.h> +#include <String.h> + + +class BCertificate { +public: + ~BCertificate(); + + BString String(); + + bigtime_t StartDate(); + bigtime_t ExpirationDate(); + BString Issuer(); + BString Subject(); + +private: + friend class BSecureSocket::Private; + class Private; + BCertificate(Private* data); + + Private* fPrivate; +}; + +#endif diff --git a/headers/os/net/SecureSocket.h b/headers/os/net/SecureSocket.h index bd59fc5..d7186de 100644 --- a/headers/os/net/SecureSocket.h +++ b/headers/os/net/SecureSocket.h @@ -9,6 +9,9 @@ #include <Socket.h> +class BCertificate; + + class BSecureSocket : public BSocket { public: BSecureSocket(); @@ -17,6 +20,10 @@ public: BSecureSocket(const BSecureSocket& other); virtual ~BSecureSocket(); + virtual bool CertificateVerificationFailed(BCertificate); + + // BSocket implementation + virtual status_t Connect(const BNetworkAddress& peer, bigtime_t timeout = B_INFINITE_TIMEOUT); virtual void Disconnect(); @@ -30,6 +37,7 @@ public: virtual ssize_t Write(const void* buffer, size_t size); private: + friend class BCertificate; class Private; Private* fPrivate; }; diff --git a/src/kits/network/libnetapi/Certificate.cpp b/src/kits/network/libnetapi/Certificate.cpp new file mode 100644 index 0000000..d11f24e --- /dev/null +++ b/src/kits/network/libnetapi/Certificate.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2014 Haiku, Inc. + * Distributed under the terms of the MIT License. + */ + + +#include <Certificate.h> + +#include <String.h> + +#include "CertificatePrivate.h" + + +static time_t parse_ASN1(ASN1_GENERALIZEDTIME *asn1) +{ + // Get the raw string data out of the ASN1 container. It looks like this: + // "YYMMDDHHMMSSZ" + struct tm time; + + if (sscanf((char*)asn1->data, "%2d%2d%2d%2d%2d%2d", &time.tm_year, + &time.tm_mon, &time.tm_mday, &time.tm_hour, &time.tm_min, + &time.tm_sec) == 6) + return mktime(&time); + + return B_BAD_DATA; +} + + +static BString decode_X509_NAME(X509_NAME* name) +{ + int len = X509_NAME_get_text_by_NID(name, 0, NULL, 0); + char buffer[len]; + X509_NAME_get_text_by_NID(name, 0, buffer, len); + + return BString(buffer); +} + + +// #pragma mark - BCertificate + + +BCertificate::BCertificate(Private* data) +{ + fPrivate = data; +} + + +BCertificate::~BCertificate() +{ + delete fPrivate; +} + + +BString +BCertificate::String() +{ + BIO *buffer = BIO_new(BIO_s_mem()); + X509_print_ex(buffer, fPrivate->fX509, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + + char* pointer; + long length = BIO_get_mem_data(buffer, &pointer); + BString result(pointer, length); + + BIO_free(buffer); + return result; +} + + +bigtime_t +BCertificate::StartDate() +{ + return parse_ASN1(X509_get_notBefore(fPrivate->fX509)); +} + + +bigtime_t +BCertificate::ExpirationDate() +{ + return parse_ASN1(X509_get_notAfter(fPrivate->fX509)); +} + + +BString +BCertificate::Issuer() +{ + X509_NAME* name = X509_get_issuer_name(fPrivate->fX509); + return decode_X509_NAME(name); +} + + +BString +BCertificate::Subject() +{ + X509_NAME* name = X509_get_subject_name(fPrivate->fX509); + return decode_X509_NAME(name); +} + + +// #pragma mark - BCertificate::Private + + +BCertificate::Private::Private(X509* data) + : fX509(data) +{ +} diff --git a/src/kits/network/libnetapi/CertificatePrivate.h b/src/kits/network/libnetapi/CertificatePrivate.h new file mode 100644 index 0000000..d9c3414 --- /dev/null +++ b/src/kits/network/libnetapi/CertificatePrivate.h @@ -0,0 +1,23 @@ +/* + * Copyright 2014 Haiku, Inc. + * Distributed under the terms of the MIT License. + */ +#ifndef _CERTIFICATE_PRIVATE_H +#define _CERTIFICATE_PRIVATE_H + + +#ifdef OPENSSL_ENABLED +# include <openssl/ssl.h> +#endif + + +class BCertificate::Private { +public: + Private(X509* data); + +public: + X509* fX509; +}; + + +#endif diff --git a/src/kits/network/libnetapi/Jamfile b/src/kits/network/libnetapi/Jamfile index ee0c66b..a5e06de 100644 --- a/src/kits/network/libnetapi/Jamfile +++ b/src/kits/network/libnetapi/Jamfile @@ -14,7 +14,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { if [ FIsBuildFeatureEnabled openssl ] { SubDirC++Flags -DOPENSSL_ENABLED ; UseBuildFeatureHeaders openssl ; - sslSources = SSL.cpp ; + sslSources = SSL.cpp Certificate.cpp ; md5Sources = ; Includes [ FGristFiles $(sslSources) SecureSocket.cpp HttpAuthentication.cpp ] diff --git a/src/kits/network/libnetapi/SecureSocket.cpp b/src/kits/network/libnetapi/SecureSocket.cpp index f7eb148..f7c865f 100644 --- a/src/kits/network/libnetapi/SecureSocket.cpp +++ b/src/kits/network/libnetapi/SecureSocket.cpp @@ -1,4 +1,5 @@ /* + * Copyright 2014 Haiku, Inc. * Copyright 2011, Axel Dörfler, axeld@xxxxxxxxxxxxxxxx. * Copyright 2010, Clemens Zeidler <haiku@xxxxxxxxxxxxxxxxxx> * Distributed under the terms of the MIT License. @@ -11,6 +12,12 @@ # include <openssl/ssl.h> #endif +#include <Certificate.h> +#include <FindDirectory.h> +#include <Path.h> + +#include "CertificatePrivate.h" + //#define TRACE_SOCKET #ifdef TRACE_SOCKET @@ -25,12 +32,86 @@ class BSecureSocket::Private { public: - SSL_CTX* fCTX; + static SSL_CTX* Context(); + static int VerifyCallback(int ok, X509_STORE_CTX* ctx); +public: SSL* fSSL; BIO* fBIO; + static int sDataIndex; +private: + static SSL_CTX* sContext; + // FIXME When do we SSL_CTX_free it? + static vint32 sInitOnce; }; +/* static */ SSL_CTX* BSecureSocket::Private::sContext = NULL; +/* static */ int BSecureSocket::Private::sDataIndex; +/* static */ vint32 sInitOnce = false; + + +/* static */ SSL_CTX* +BSecureSocket::Private::Context() +{ + // We use lazy initialisation here, because reading certificates from disk + // and parsing them is a relatively long operation and uses some memory. + // We don't want programs that don't use SSL to waste resources with that. + if (!sInitOnce) { + sInitOnce = true; + sContext = SSL_CTX_new(SSLv23_method()); + + // Setup certificate verification + BPath certificateStore; + find_directory(B_SYSTEM_DATA_DIRECTORY, &certificateStore); + certificateStore.Append("ssl/CARootCertificates.pem"); + // TODO we may want to add a non-packaged certificate directory? + // (would make it possible to store user-added certificate exceptions + // there) + SSL_CTX_load_verify_locations(sContext, certificateStore.Path(), NULL); + SSL_CTX_set_verify(sContext, SSL_VERIFY_PEER, VerifyCallback); + + // Get an unique index number for storing application data in SSL + // structs. We will store a pointer to the BSecureSocket class there. + sDataIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + } + + return sContext; +} + + +// This is called each time a certificate verification occurs. It allows us to +// catch failures and report them. +/* static */ int +BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx) +{ + // OpenSSL already checked the certificate again the certificate store for + // us, and tells the result of that in the ok parameter. + + // If the verification succeeded, no need for any further checks. Let's + // proceed with the connection. + if (ok) + return ok; + + // The certificate verification failed. Signal this to the caller, and fail. + + // First of all, get the affected BSecureSocket + SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + BSecureSocket* socket = (BSecureSocket*)SSL_get_ex_data(ssl, sDataIndex); + + // Get the certificate that we could not validate (this may not be the one + // we got from the server, but something higher up in the certificate + // chain) + X509* certificate = X509_STORE_CTX_get_current_cert(ctx); + + return socket->CertificateVerificationFailed(BCertificate( + new BCertificate::Private(certificate))); +} + + +// # pragma mark - BSecureSocket + + BSecureSocket::BSecureSocket() : fPrivate(NULL) @@ -50,12 +131,14 @@ BSecureSocket::BSecureSocket(const BSecureSocket& other) : BSocket(other) { - // TODO: this won't work this way! fPrivate = (BSecureSocket::Private*)malloc(sizeof(BSecureSocket::Private)); - if (fPrivate != NULL) + if (fPrivate != NULL) { memcpy(fPrivate, other.fPrivate, sizeof(BSecureSocket::Private)); - else + // TODO: this won't work this way! + SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this); + } else fInitStatus = B_NO_MEMORY; + } @@ -79,16 +162,32 @@ BSecureSocket::Connect(const BNetworkAddress& peer, bigtime_t timeout) if (status != B_OK) return status; - fPrivate->fCTX = SSL_CTX_new(SSLv23_method()); - fPrivate->fSSL = SSL_new(fPrivate->fCTX); + fPrivate->fSSL = SSL_new(BSecureSocket::Private::Context()); fPrivate->fBIO = BIO_new_socket(fSocket, BIO_NOCLOSE); SSL_set_bio(fPrivate->fSSL, fPrivate->fBIO, fPrivate->fBIO); + SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this); - if (SSL_connect(fPrivate->fSSL) <= 0) { + printf("Connecting %p\n", fPrivate->fSSL); + int sslStatus = SSL_connect(fPrivate->fSSL); + + if (sslStatus <= 0) { TRACE("SSLConnection can't connect\n"); BSocket::Disconnect(); - // TODO: translate ssl to Haiku error - return B_ERROR; + + switch(SSL_get_error(fPrivate->fSSL, sslStatus)) + { + case SSL_ERROR_NONE: + // Shouldn't happen... + return B_NO_ERROR; + case SSL_ERROR_ZERO_RETURN: + // Socket is closed + return B_CANCELED; + case SSL_ERROR_SSL: + // Probably no certificate + return B_NOT_ALLOWED; + default: + return B_ERROR; + } } return B_OK; @@ -103,10 +202,6 @@ BSecureSocket::Disconnect() SSL_shutdown(fPrivate->fSSL); fPrivate->fSSL = NULL; } - if (fPrivate->fCTX != NULL) { - SSL_CTX_free(fPrivate->fCTX); - fPrivate->fCTX = NULL; - } BSocket::Disconnect(); // Must do this before freeing the BIO, to make sure any pending @@ -134,6 +229,16 @@ BSecureSocket::WaitForReadable(bigtime_t timeout) const } +bool +BSecureSocket::CertificateVerificationFailed(BCertificate) +{ + // Until apps actually make use of the certificate API, let's keep the old + // behavior and accept all connections, even if the certificate validation + // didn't work. + return true; +} + + // #pragma mark - BDataIO implementation