[haiku-commits] haiku: hrev46679 - src/kits/network/libnetapi headers/os/net

  • From: pulkomandy@xxxxxxxxxxxxx
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Wed, 15 Jan 2014 17:45:15 +0100 (CET)

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
 
 


Other related posts: