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.
This commit is contained in:
parent
b11772acca
commit
5ebdc79955
32
headers/os/net/Certificate.h
Normal file
32
headers/os/net/Certificate.h
Normal file
@ -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
|
@ -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;
|
||||
};
|
||||
|
105
src/kits/network/libnetapi/Certificate.cpp
Normal file
105
src/kits/network/libnetapi/Certificate.cpp
Normal file
@ -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)
|
||||
{
|
||||
}
|
23
src/kits/network/libnetapi/CertificatePrivate.h
Normal file
23
src/kits/network/libnetapi/CertificatePrivate.h
Normal file
@ -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
|
@ -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 ]
|
||||
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 Haiku, Inc.
|
||||
* Copyright 2011, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
|
||||
* 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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user