* Copyright 2013-2016 Haiku, Inc.
* Copyright 2011-2015, Axel Dörfler, axeld@pinc-software.de.
* Copyright 2016, Rene Gollent, rene@gollent.com.
* Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
* Distributed under the terms of the MIT License.
*/
#include <SecureSocket.h>
#ifdef OPENSSL_ENABLED
# include <openssl/ssl.h>
# include <openssl/ssl3.h>
# include <openssl/err.h>
#endif
#include <pthread.h>
#include <Certificate.h>
#include <FindDirectory.h>
#include <Path.h>
#include <AutoDeleter.h>
#include "CertificatePrivate.h"
#ifdef TRACE_SOCKET
# define TRACE(x...) printf(x)
#else
# define TRACE(x...) ;
#endif
#ifdef OPENSSL_ENABLED
#ifdef TRACE_SESSION_KEY
int SSL_SESSION_print_client_random(BIO *bp, const SSL *ssl)
{
const SSL_SESSION *x = SSL_get_session(ssl);
size_t i;
if (x == NULL)
goto err;
if (x->session_id_length == 0 || x->master_key_length == 0)
goto err;
if (BIO_puts(bp, "CLIENT_RANDOM ") <= 0)
goto err;
for (i = 0; i < sizeof(ssl->s3->client_random); i++) {
if (BIO_printf(bp, "%02X", ssl->s3->client_random[i]) <= 0)
goto err;
}
if (BIO_puts(bp, " ") <= 0)
goto err;
for (i = 0; i < (size_t)x->master_key_length; i++) {
if (BIO_printf(bp, "%02X", x->master_key[i]) <= 0)
goto err;
}
if (BIO_puts(bp, "\n") <= 0)
goto err;
return (1);
err:
return (0);
}
#endif
class BSecureSocket::Private {
public:
Private();
~Private();
status_t InitCheck();
status_t ErrorCode(int returnValue);
static SSL_CTX* Context();
static int VerifyCallback(int ok, X509_STORE_CTX* ctx);
private:
static void _CreateContext();
public:
SSL* fSSL;
BIO* fBIO;
static int sDataIndex;
private:
static SSL_CTX* sContext;
static pthread_once_t sInitOnce;
#ifdef TRACE_SESSION_KEY
public:
static BIO* sKeyLogBIO;
#endif
};
SSL_CTX* BSecureSocket::Private::sContext = NULL;
int BSecureSocket::Private::sDataIndex;
pthread_once_t BSecureSocket::Private::sInitOnce
= PTHREAD_ONCE_INIT;
#ifdef TRACE_SESSION_KEY
BIO* BSecureSocket::Private::sKeyLogBIO = NULL;
#endif
BSecureSocket::Private::Private()
:
fSSL(NULL),
fBIO(BIO_new(BIO_s_socket()))
{
}
BSecureSocket::Private::~Private()
{
if (fSSL != NULL)
SSL_free(fSSL);
else {
BIO_free(fBIO);
}
}
status_t
BSecureSocket::Private::InitCheck()
{
if (fBIO == NULL)
return B_NO_MEMORY;
return B_OK;
}
status_t
BSecureSocket::Private::ErrorCode(int returnValue)
{
int error = SSL_get_error(fSSL, returnValue);
switch (error) {
case SSL_ERROR_NONE:
return B_NO_ERROR;
case SSL_ERROR_ZERO_RETURN:
return B_IO_ERROR;
case SSL_ERROR_SSL:
return B_NOT_ALLOWED;
case SSL_ERROR_SYSCALL:
{
unsigned long error2;
for (;;) {
error2 = ERR_get_error();
if (error2 == 0)
break;
fprintf(stderr, "SSL ERR %s\n", ERR_error_string(error2, NULL));
}
if (returnValue == 0)
{
return ECONNREFUSED;
}
if (returnValue == -1)
{
fprintf(stderr, "SSL rv -1 %s\n", ERR_error_string(error, NULL));
return errno;
}
fprintf(stderr, "SSL rv other %s\n", ERR_error_string(error, NULL));
return B_ERROR;
}
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
default:
fprintf(stderr, "SSL other %s\n", ERR_error_string(error, NULL));
return B_ERROR;
}
}
SSL_CTX*
BSecureSocket::Private::Context()
{
pthread_once(&sInitOnce, _CreateContext);
return sContext;
}
catch failures and report them.
*/
int
BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx)
{
if (ok)
return ok;
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);
X509* x509 = X509_STORE_CTX_get_current_cert(ctx);
BCertificate::Private* certificate
= new(std::nothrow) BCertificate::Private(x509);
if (certificate == NULL)
return 0;
int error = X509_STORE_CTX_get_error(ctx);
const char* message = X509_verify_cert_error_string(error);
BCertificate failedCertificate(certificate);
return socket->CertificateVerificationFailed(failedCertificate, message);
}
#if TRACE_SSL
static void apps_ssl_info_callback(const SSL *s, int where, int ret)
{
const char *str;
int w;
w=where& ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str="SSL_connect";
else if (w & SSL_ST_ACCEPT)
str="SSL_accept";
else
str="undefined";
if (where & SSL_CB_LOOP) {
fprintf(stderr, "%s:%s\n", str, SSL_state_string_long(s));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "read" : "write";
fprintf(stderr, "SSL3 alert %s:%s:%s\n",
str,
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
} else if (where & SSL_CB_EXIT) {
if (ret == 0)
fprintf(stderr, "%s:failed in %s\n",
str, SSL_state_string_long(s));
else if (ret < 0) {
fprintf(stderr, "%s:error in %s\n",
str, SSL_state_string_long(s));
}
}
}
#endif
void
BSecureSocket::Private::_CreateContext()
{
sContext = SSL_CTX_new(SSLv23_method());
#if TRACE_SSL
SSL_CTX_set_info_callback(sContext, apps_ssl_info_callback);
#endif
SSL_CTX_set_options(sContext, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_options(sContext, SSL_OP_NO_COMPRESSION);
SSL_CTX_set_mode(sContext, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_cipher_list(sContext, "HIGH:!aNULL:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_default_verify_file(sContext);
#ifdef X509_V_FLAG_TRUSTED_FIRST
X509_VERIFY_PARAM* verifyParam = X509_VERIFY_PARAM_new();
X509_VERIFY_PARAM_set_flags(verifyParam, X509_V_FLAG_TRUSTED_FIRST);
SSL_CTX_set1_param(sContext, verifyParam);
#endif
sDataIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
#ifdef TRACE_SESSION_KEY
FILE *keylog = NULL;
const char *logpath = getenv("SSLKEYLOGFILE");
if (logpath)
keylog = fopen(logpath, "w+");
if (keylog) {
fprintf(keylog, "# Key Log File generated by Haiku Network Kit\n");
sKeyLogBIO = BIO_new_fp(keylog, BIO_NOCLOSE);
}
#endif
}
BSecureSocket::BSecureSocket()
:
fPrivate(new(std::nothrow) BSecureSocket::Private())
{
fInitStatus = fPrivate != NULL ? fPrivate->InitCheck() : B_NO_MEMORY;
}
BSecureSocket::BSecureSocket(const BNetworkAddress& peer, bigtime_t timeout)
:
fPrivate(new(std::nothrow) BSecureSocket::Private())
{
fInitStatus = fPrivate != NULL ? fPrivate->InitCheck() : B_NO_MEMORY;
Connect(peer, timeout);
}
BSecureSocket::BSecureSocket(const BSecureSocket& other)
:
BSocket(other)
{
fPrivate = new(std::nothrow) BSecureSocket::Private(*other.fPrivate);
if (fPrivate != NULL)
SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this);
else
fInitStatus = B_NO_MEMORY;
}
BSecureSocket::~BSecureSocket()
{
delete fPrivate;
}
status_t
BSecureSocket::Accept(BAbstractSocket*& _socket)
{
int fd = -1;
BNetworkAddress peer;
status_t error = AcceptNext(fd, peer);
if (error != B_OK)
return error;
BSecureSocket* socket = new(std::nothrow) BSecureSocket();
ObjectDeleter<BSecureSocket> socketDeleter(socket);
if (socket == NULL || socket->InitCheck() != B_OK) {
close(fd);
return B_NO_MEMORY;
}
socket->_SetTo(fd, fLocal, peer);
error = socket->_SetupAccept();
if (error != B_OK)
return error;
_socket = socket;
socketDeleter.Detach();
return B_OK;
}
status_t
BSecureSocket::Connect(const BNetworkAddress& peer, bigtime_t timeout)
{
status_t status = InitCheck();
if (status != B_OK)
return status;
status = BSocket::Connect(peer, timeout);
if (status != B_OK)
return status;
return _SetupConnect(peer.HostName().String());
}
void
BSecureSocket::Disconnect()
{
if (IsConnected()) {
if (fPrivate->fSSL != NULL)
SSL_shutdown(fPrivate->fSSL);
BSocket::Disconnect();
}
}
status_t
BSecureSocket::WaitForReadable(bigtime_t timeout) const
{
if (fInitStatus != B_OK)
return fInitStatus;
if (!IsConnected())
return B_ERROR;
if (SSL_pending(fPrivate->fSSL) > 0)
return B_OK;
return BSocket::WaitForReadable(timeout);
}
status_t
BSecureSocket::InitCheck()
{
if (fPrivate == NULL)
return B_NO_MEMORY;
status_t state = fPrivate->InitCheck();
return state;
}
bool
BSecureSocket::CertificateVerificationFailed(BCertificate&, const char*)
{
return false;
}
ssize_t
BSecureSocket::Read(void* buffer, size_t size)
{
if (!IsConnected())
return B_ERROR;
int bytesRead;
int retry;
do {
bytesRead = SSL_read(fPrivate->fSSL, buffer, size);
if (bytesRead > 0)
return bytesRead;
if (errno != EINTR) {
int error = SSL_get_error(fPrivate->fSSL, bytesRead);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)
return B_WOULD_BLOCK;
}
retry = BIO_should_retry(SSL_get_rbio(fPrivate->fSSL));
} while (retry != 0);
return fPrivate->ErrorCode(bytesRead);
}
ssize_t
BSecureSocket::Write(const void* buffer, size_t size)
{
if (!IsConnected())
return B_ERROR;
int bytesWritten;
int retry;
do {
bytesWritten = SSL_write(fPrivate->fSSL, buffer, size);
if (bytesWritten >= 0)
return bytesWritten;
if (errno != EINTR) {
int error = SSL_get_error(fPrivate->fSSL, bytesWritten);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)
return B_WOULD_BLOCK;
}
retry = BIO_should_retry(SSL_get_wbio(fPrivate->fSSL));
} while (retry != 0);
return fPrivate->ErrorCode(bytesWritten);
}
status_t
BSecureSocket::_SetupCommon(const char* host)
{
if (fPrivate->fSSL != NULL) {
SSL_free(fPrivate->fSSL);
}
fPrivate->fSSL = SSL_new(BSecureSocket::Private::Context());
if (fPrivate->fSSL == NULL) {
BSocket::Disconnect();
return B_NO_MEMORY;
}
BIO_set_fd(fPrivate->fBIO, fSocket, BIO_NOCLOSE);
SSL_set_bio(fPrivate->fSSL, fPrivate->fBIO, fPrivate->fBIO);
SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this);
if (host != NULL && host[0] != '\0') {
SSL_set_tlsext_host_name(fPrivate->fSSL, host);
X509_VERIFY_PARAM_set1_host(SSL_get0_param(fPrivate->fSSL), host, 0);
}
return B_OK;
}
status_t
BSecureSocket::_SetupConnect(const char* host)
{
status_t error = _SetupCommon(host);
if (error != B_OK)
return error;
int returnValue = SSL_connect(fPrivate->fSSL);
if (returnValue <= 0) {
TRACE("SSLConnection can't connect\n");
BSocket::Disconnect();
return fPrivate->ErrorCode(returnValue);
}
#ifdef TRACE_SESSION_KEY
fprintf(stderr, "SSL SESSION INFO:\n");
SSL_SESSION_print_keylog(fPrivate->sKeyLogBIO, SSL_get_session(fPrivate->fSSL));
SSL_SESSION_print_client_random(fPrivate->sKeyLogBIO, fPrivate->fSSL);
fprintf(stderr, "\n");
#endif
return B_OK;
}
status_t
BSecureSocket::_SetupAccept()
{
status_t error = _SetupCommon();
if (error != B_OK)
return error;
int returnValue = SSL_accept(fPrivate->fSSL);
if (returnValue <= 0) {
TRACE("SSLConnection can't accept\n");
BSocket::Disconnect();
return fPrivate->ErrorCode(returnValue);
}
return B_OK;
}
#else
BSecureSocket::BSecureSocket()
{
}
BSecureSocket::BSecureSocket(const BNetworkAddress& peer, bigtime_t timeout)
{
fInitStatus = B_UNSUPPORTED;
}
BSecureSocket::BSecureSocket(const BSecureSocket& other)
:
BSocket(other)
{
}
BSecureSocket::~BSecureSocket()
{
}
bool
BSecureSocket::CertificateVerificationFailed(BCertificate& certificate, const char*)
{
(void)certificate;
return false;
}
status_t
BSecureSocket::Accept(BAbstractSocket*& _socket)
{
return B_UNSUPPORTED;
}
status_t
BSecureSocket::Connect(const BNetworkAddress& peer, bigtime_t timeout)
{
return fInitStatus = B_UNSUPPORTED;
}
void
BSecureSocket::Disconnect()
{
}
status_t
BSecureSocket::WaitForReadable(bigtime_t timeout) const
{
return B_UNSUPPORTED;
}
ssize_t
BSecureSocket::Read(void* buffer, size_t size)
{
return B_UNSUPPORTED;
}
ssize_t
BSecureSocket::Write(const void* buffer, size_t size)
{
return B_UNSUPPORTED;
}
status_t
BSecureSocket::InitCheck()
{
return B_UNSUPPORTED;
}
status_t
BSecureSocket::_SetupCommon(const char* host)
{
return B_UNSUPPORTED;
}
status_t
BSecureSocket::_SetupConnect(const char* host)
{
return B_UNSUPPORTED;
}
status_t
BSecureSocket::_SetupAccept()
{
return B_UNSUPPORTED;
}
#endif