diff --git a/headers/os/net/Certificate.h b/headers/os/net/Certificate.h new file mode 100644 index 0000000000..c42207db94 --- /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 +#include + + +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 bd59fc5505..d7186de6a4 100644 --- a/headers/os/net/SecureSocket.h +++ b/headers/os/net/SecureSocket.h @@ -9,6 +9,9 @@ #include +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 0000000000..d11f24e8c2 --- /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 + +#include + +#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 0000000000..d9c3414e00 --- /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 +#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 ee0c66b524..a5e06deed1 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 f7eb1489d7..f7c865f5e3 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@pinc-software.de. * Copyright 2010, Clemens Zeidler * Distributed under the terms of the MIT License. @@ -11,6 +12,12 @@ # include #endif +#include +#include +#include + +#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