Current section

Files

Jump to
fast_tls c_src fast_tls_drv.c
Raw

c_src/fast_tls_drv.c

/*
* Copyright (C) 2002-2016 ProcessOne, SARL. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <stdio.h>
#include <string.h>
#include <erl_driver.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include "options.h"
#define BUF_SIZE 1024
typedef struct {
ErlDrvPort port;
BIO *bio_read;
BIO *bio_write;
SSL *ssl;
int handshakes;
char *send_buffer;
int send_buffer_size;
int send_buffer_len;
char *send_buffer2;
int send_buffer2_size;
int send_buffer2_len;
} tls_data;
static int ssl_index;
#ifdef _WIN32
typedef unsigned __int32 uint32_t;
#endif
#ifndef SSL_OP_NO_TICKET
#define SSL_OP_NO_TICKET 0
#endif
#define CIPHERS "DEFAULT:!EXPORT:!LOW:!RC4:!SSLv2"
/* Wrappers around driver_alloc() that check */
/* for OOM. */
void erl_exit(int n, char*, ...);
void *ftls_alloc(ErlDrvSizeT size);
void *ftls_realloc(void *ptr, ErlDrvSizeT size);
ErlDrvBinary *ftls_alloc_binary(ErlDrvSizeT size);
ErlDrvBinary *ftls_realloc_binary(ErlDrvBinary *bin, ErlDrvSizeT size);
void *ftls_alloc(ErlDrvSizeT size) {
void *p = driver_alloc(size);
if (p == NULL) {
erl_exit(1, "fast_tls: Can't allocate %lu bytes of memory\n", size);
}
return p;
}
void *ftls_realloc(void *ptr, ErlDrvSizeT size) {
void *p = driver_realloc(ptr, size);
if (p == NULL) {
erl_exit(1, "fast_tls: Can't reallocate %lu bytes of memory\n", size);
}
return p;
}
ErlDrvBinary *ftls_alloc_binary(ErlDrvSizeT size) {
ErlDrvBinary *p = driver_alloc_binary(size);
if (p == NULL) {
erl_exit(1, "fast_tls: Can't allocate %lu binary\n", size);
}
return p;
}
ErlDrvBinary *ftls_realloc_binary(ErlDrvBinary *bin, ErlDrvSizeT size) {
ErlDrvBinary *p = driver_realloc_binary(bin, size);
if (p == NULL) {
erl_exit(1, "fast_tls: Can't reallocate %lu binary\n", size);
}
return p;
}
/**
* Prepare the SSL options flag.
**/
static int set_option_flag(const char *opt, long *flag)
{
ssl_option_t *p;
for (p = ssl_options; p->name; p++) {
if (!strcmp(opt, p->name)) {
*flag |= p->code;
return 1;
}
}
return 0;
}
/*
* R15B changed several driver callbacks to use ErlDrvSizeT and
* ErlDrvSSizeT typedefs instead of int.
* This provides missing typedefs on older OTP versions.
*/
#if ERL_DRV_EXTENDED_MAJOR_VERSION < 2
typedef int ErlDrvSizeT;
typedef int ErlDrvSSizeT;
#endif
/*
* str_hash is based on the public domain code from
* http://www.burtleburtle.net/bob/hash/doobs.html
*/
static uint32_t str_hash(char *s)
{
unsigned char *key = (unsigned char *)s;
uint32_t hash = 0;
size_t i;
for (i = 0; key[i] != 0; i++) {
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
/* Linear hashing */
#define MIN_LEVEL 8
#define MAX_LEVEL 20
struct bucket {
uint32_t hash;
char *key;
time_t key_mtime;
time_t dh_mtime;
time_t ca_mtime;
SSL_CTX *ssl_ctx;
struct bucket *next;
};
struct hash_table {
int split;
int level;
struct bucket **buckets;
int size;
};
struct hash_table ht;
static void init_hash_table()
{
size_t size = 1 << (MIN_LEVEL + 1);
size_t i;
ht.buckets = ftls_alloc(sizeof(struct bucket *) * size);
ht.split = 0;
ht.level = MIN_LEVEL;
for (i = 0; i < size; i++)
ht.buckets[i] = NULL;
}
static void hash_table_insert(char *key, time_t key_mtime, time_t dh_mtime,
time_t ca_mtime, SSL_CTX *ssl_ctx)
{
int level, split;
uint32_t hash = str_hash(key);
size_t bucket;
int do_split = 0;
struct bucket *el;
struct bucket *new_bucket_el;
split = ht.split;
level = ht.level;
bucket = hash & ((1 << level) - 1);
if (bucket < split)
bucket = hash & ((1 << (level + 1)) - 1);
el = ht.buckets[bucket];
while (el != NULL) {
if (el->hash == hash && strcmp(el->key, key) == 0) {
el->key_mtime = key_mtime;
el->dh_mtime = dh_mtime;
el->ca_mtime = ca_mtime;
if (el->ssl_ctx != NULL)
SSL_CTX_free(el->ssl_ctx);
el->ssl_ctx = ssl_ctx;
break;
}
el = el->next;
}
if (el == NULL) {
if (ht.buckets[bucket] != NULL)
do_split = !0;
new_bucket_el = ftls_alloc(sizeof(struct bucket));
new_bucket_el->hash = hash;
new_bucket_el->key = ftls_alloc(strlen(key) + 1);
strcpy(new_bucket_el->key, key);
new_bucket_el->key_mtime = key_mtime;
new_bucket_el->dh_mtime = dh_mtime;
new_bucket_el->ca_mtime = ca_mtime;
new_bucket_el->ssl_ctx = ssl_ctx;
new_bucket_el->next = ht.buckets[bucket];
ht.buckets[bucket] = new_bucket_el;
}
if (do_split) {
struct bucket **el_ptr = &ht.buckets[split];
size_t new_bucket = split + (1 << level);
while (*el_ptr != NULL) {
uint32_t hash = (*el_ptr)->hash;
if ((hash & ((1 << (level + 1)) - 1)) == new_bucket) {
struct bucket *moved_el = *el_ptr;
*el_ptr = (*el_ptr)->next;
moved_el->next = ht.buckets[new_bucket];
ht.buckets[new_bucket] = moved_el;
} else
el_ptr = &(*el_ptr)->next;
}
split++;
if (split == 1 << level) {
size_t size;
size_t i;
split = 0;
level++;
size = 1 << (level + 1);
ht.split = split;
ht.level = level;
ht.buckets = ftls_realloc(ht.buckets, sizeof(struct bucket *) * size);
for (i = 1 << level; i < size; i++)
ht.buckets[i] = NULL;
} else
ht.split = split;
}
}
static SSL_CTX *hash_table_lookup(char *key, time_t *key_mtime,
time_t *dh_mtime, time_t *ca_mtime)
{
int level, split;
uint32_t hash = str_hash(key);
size_t bucket;
struct bucket *el;
split = ht.split;
level = ht.level;
bucket = hash & ((1 << level) - 1);
if (bucket < split)
bucket = hash & ((1 << (level + 1)) - 1);
el = ht.buckets[bucket];
while (el != NULL) {
if (el->hash == hash && strcmp(el->key, key) == 0) {
*key_mtime = el->key_mtime;
*dh_mtime = el->dh_mtime;
*ca_mtime = el->ca_mtime;
return el->ssl_ctx;
}
el = el->next;
}
return NULL;
}
static ErlDrvData tls_drv_start(ErlDrvPort port, char *buff)
{
tls_data *d = ftls_alloc(sizeof(tls_data));
d->port = port;
d->bio_read = NULL;
d->bio_write = NULL;
d->ssl = NULL;
d->handshakes = 0;
d->send_buffer = NULL;
d->send_buffer_len = 0;
d->send_buffer_size = 0;
d->send_buffer2 = NULL;
d->send_buffer2_len = 0;
d->send_buffer2_size = 0;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)d;
}
static void tls_drv_stop(ErlDrvData handle)
{
tls_data *d = (tls_data *)handle;
if (d->ssl != NULL)
SSL_free(d->ssl);
if (d->send_buffer != NULL)
driver_free(d->send_buffer);
if (d->send_buffer2 != NULL)
driver_free(d->send_buffer2);
driver_free((char *)handle);
}
static void tls_drv_finish()
{
int level;
struct bucket *el;
int i;
level = ht.level;
for (i = 0; i < 1 << (level + 1); i++) {
el = ht.buckets[i];
while (el != NULL) {
if (el->ssl_ctx != NULL)
SSL_CTX_free(el->ssl_ctx);
driver_free(el->key);
el = el->next;
}
}
driver_free(ht.buckets);
}
static int is_modified(char *file, time_t *known_mtime)
{
struct stat file_stat;
if (file == NULL) {
return 0;
} else if (stat(file, &file_stat)) {
*known_mtime = 0;
return 1;
} else {
if (*known_mtime != file_stat.st_mtime)
{
*known_mtime = file_stat.st_mtime;
return 1;
} else
return 0;
}
}
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
return 1;
}
/*
* ECDHE is enabled only on OpenSSL 1.0.0e and later.
* See http://www.openssl.org/news/secadv_20110906.txt
* for details.
*/
#ifndef OPENSSL_NO_ECDH
static void setup_ecdh(SSL_CTX *ctx)
{
EC_KEY *ecdh;
if (SSLeay() < 0x1000005fL) {
return;
}
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
}
#endif
#ifndef OPENSSL_NO_DH
/*
1024-bit MODP Group with 160-bit prime order subgroup (RFC5114)
-----BEGIN DH PARAMETERS-----
MIIBDAKBgQCxC4+WoIDgHd6S3l6uXVTsUsmfvPsGo8aaap3KUtI7YWBz4oZ1oj0Y
mDjvHi7mUsAT7LSuqQYRIySXXDzUm4O/rMvdfZDEvXCYSI6cIZpzck7/1vrlZEc4
+qMaT/VbzMChUa9fDci0vUW/N982XBpl5oz9p21NpwjfH7K8LkpDcQKBgQCk0cvV
w/00EmdlpELvuZkF+BBN0lisUH/WQGz/FCZtMSZv6h5cQVZLd35pD1UE8hMWAhe0
sBuIal6RVH+eJ0n01/vX07mpLuGQnQ0iY/gKdqaiTAh6CR9THb8KAWm2oorWYqTR
jnOvoy13nVkY0IvIhY9Nzvl8KiSFXm7rIrOy5QICAKA=
-----END DH PARAMETERS-----
*/
static unsigned char dh1024_p[] = {
0xB1,0x0B,0x8F,0x96,0xA0,0x80,0xE0,0x1D,0xDE,0x92,0xDE,0x5E,
0xAE,0x5D,0x54,0xEC,0x52,0xC9,0x9F,0xBC,0xFB,0x06,0xA3,0xC6,
0x9A,0x6A,0x9D,0xCA,0x52,0xD2,0x3B,0x61,0x60,0x73,0xE2,0x86,
0x75,0xA2,0x3D,0x18,0x98,0x38,0xEF,0x1E,0x2E,0xE6,0x52,0xC0,
0x13,0xEC,0xB4,0xAE,0xA9,0x06,0x11,0x23,0x24,0x97,0x5C,0x3C,
0xD4,0x9B,0x83,0xBF,0xAC,0xCB,0xDD,0x7D,0x90,0xC4,0xBD,0x70,
0x98,0x48,0x8E,0x9C,0x21,0x9A,0x73,0x72,0x4E,0xFF,0xD6,0xFA,
0xE5,0x64,0x47,0x38,0xFA,0xA3,0x1A,0x4F,0xF5,0x5B,0xCC,0xC0,
0xA1,0x51,0xAF,0x5F,0x0D,0xC8,0xB4,0xBD,0x45,0xBF,0x37,0xDF,
0x36,0x5C,0x1A,0x65,0xE6,0x8C,0xFD,0xA7,0x6D,0x4D,0xA7,0x08,
0xDF,0x1F,0xB2,0xBC,0x2E,0x4A,0x43,0x71,
};
static unsigned char dh1024_g[] = {
0xA4,0xD1,0xCB,0xD5,0xC3,0xFD,0x34,0x12,0x67,0x65,0xA4,0x42,
0xEF,0xB9,0x99,0x05,0xF8,0x10,0x4D,0xD2,0x58,0xAC,0x50,0x7F,
0xD6,0x40,0x6C,0xFF,0x14,0x26,0x6D,0x31,0x26,0x6F,0xEA,0x1E,
0x5C,0x41,0x56,0x4B,0x77,0x7E,0x69,0x0F,0x55,0x04,0xF2,0x13,
0x16,0x02,0x17,0xB4,0xB0,0x1B,0x88,0x6A,0x5E,0x91,0x54,0x7F,
0x9E,0x27,0x49,0xF4,0xD7,0xFB,0xD7,0xD3,0xB9,0xA9,0x2E,0xE1,
0x90,0x9D,0x0D,0x22,0x63,0xF8,0x0A,0x76,0xA6,0xA2,0x4C,0x08,
0x7A,0x09,0x1F,0x53,0x1D,0xBF,0x0A,0x01,0x69,0xB6,0xA2,0x8A,
0xD6,0x62,0xA4,0xD1,0x8E,0x73,0xAF,0xA3,0x2D,0x77,0x9D,0x59,
0x18,0xD0,0x8B,0xC8,0x85,0x8F,0x4D,0xCE,0xF9,0x7C,0x2A,0x24,
0x85,0x5E,0x6E,0xEB,0x22,0xB3,0xB2,0xE5,
};
static int setup_dh(SSL_CTX *ctx, char *dh_file)
{
DH *dh;
int res;
if (dh_file != NULL) {
BIO *bio = BIO_new_file(dh_file, "r");
if (bio == NULL) {
return 0;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if (dh == NULL) {
return 0;
}
} else {
dh = DH_new();
if (dh == NULL) {
return 0;
}
dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
if (dh->p == NULL || dh->g == NULL) {
DH_free(dh);
return 0;
}
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
res = (int)SSL_CTX_set_tmp_dh(ctx, dh);
DH_free(dh);
return res;
}
#endif
static void ssl_info_callback(const SSL *s, int where, int ret)
{
if (where == SSL_CB_ACCEPT_LOOP) {
int state = SSL_get_state(s);
if (state == SSL3_ST_SR_CLNT_HELLO_A ||
state == SSL23_ST_SR_CLNT_HELLO_A) {
tls_data *d = (tls_data *)SSL_get_ex_data(s, ssl_index);
d->handshakes++;
}
}
}
#define SET_CERTIFICATE_FILE_ACCEPT 1
#define SET_CERTIFICATE_FILE_CONNECT 2
#define SET_ENCRYPTED_INPUT 3
#define SET_DECRYPTED_OUTPUT 4
#define GET_ENCRYPTED_OUTPUT 5
#define GET_DECRYPTED_INPUT 6
#define GET_PEER_CERTIFICATE 7
#define GET_VERIFY_RESULT 8
#define VERIFY_NONE 0x10000
#define COMPRESSION_NONE 0x100000
#define die_unless(cond, errstr) \
if (!(cond)) \
{ \
int errstrlen = strlen(errstr); \
unsigned long error_code = ERR_get_error(); \
char *error_string = error_code ? \
ERR_error_string(error_code, NULL) : \
""; \
int error_string_length = strlen(error_string); \
if (error_code) \
rlen = errstrlen + error_string_length + 3; \
else \
rlen = errstrlen + 1; \
b = ftls_alloc_binary(rlen); \
b->orig_bytes[0] = 1; \
strncpy(b->orig_bytes + 1, errstr, errstrlen); \
if (error_code) { \
strncpy(b->orig_bytes + 1 + errstrlen, \
": ", 2); \
strncpy(b->orig_bytes + 3 + errstrlen, \
error_string, error_string_length); \
} \
*rbuf = (char *)b; \
return rlen; \
}
#ifdef _WIN32
/** public domain strtok_r() by Charlie Gordon
** from http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684
*/
char* strtok_r(
char *str,
const char *delim,
char **nextp)
{
char *ret;
if (str == NULL)
{
str = *nextp;
}
str += strspn(str, delim);
if (*str == '\0')
{
return NULL;
}
ret = str;
str += strcspn(str, delim);
if (*str)
{
*str++ = '\0';
}
*nextp = str;
return ret;
}
#endif
static ErlDrvSSizeT tls_drv_control(ErlDrvData handle,
unsigned int command,
char *buf, ErlDrvSizeT len,
char **rbuf, ErlDrvSizeT rlen)
{
tls_data *d = (tls_data *)handle;
int res;
int size;
ErlDrvBinary *b;
X509 *cert;
unsigned int flags = command;
command &= 0xffff;
ERR_clear_error();
switch (command)
{
case SET_CERTIFICATE_FILE_ACCEPT:
case SET_CERTIFICATE_FILE_CONNECT: {
time_t key_mtime = 0;
time_t dh_mtime = 0;
time_t ca_mtime = 0;
char *key_file = buf;
size_t key_file_len = strlen(key_file);
char *ciphers = key_file + key_file_len + 1;
size_t ciphers_len = strlen(ciphers);
char *protocol_options = ciphers + ciphers_len + 1;
size_t protocol_options_len = strlen(protocol_options);
char *dh_file = protocol_options + protocol_options_len + 1;
size_t dh_file_len = strlen(dh_file);
char *ca_file = dh_file + dh_file_len + 1;
size_t ca_file_len = strlen(ca_file);
char *hash_key = ftls_alloc(key_file_len +
ciphers_len +
protocol_options_len +
dh_file_len +
ca_file_len + 1);
long options = 0L;
if (protocol_options_len != 0) {
char *po = strdup(protocol_options), delim[] = "|";
char *popts = po;
char *strtok_buf;
if (!po) {
erl_exit(1, "fast_tls: strdup failed");
}
while ((po = strtok_r(po, delim, &strtok_buf)) != NULL) {
set_option_flag(po, &options);
po = NULL;
}
free(popts);
}
sprintf(hash_key, "%s%s%s%s%s", key_file, ciphers, protocol_options,
dh_file, ca_file);
SSL_CTX *ssl_ctx = hash_table_lookup(hash_key, &key_mtime, &dh_mtime, &ca_mtime);
if (dh_file_len == 0)
dh_file = NULL;
if (ca_file_len == 0)
ca_file = NULL;
if (is_modified(key_file, &key_mtime) ||
is_modified(dh_file, &dh_mtime) ||
is_modified(ca_file, &ca_mtime) ||
ssl_ctx == NULL)
{
SSL_CTX *ctx;
hash_table_insert(hash_key, key_mtime, dh_mtime, ca_mtime, NULL);
ctx = SSL_CTX_new(SSLv23_method());
die_unless(ctx, "SSL_CTX_new failed");
res = SSL_CTX_use_certificate_chain_file(ctx, key_file);
die_unless(res > 0, "SSL_CTX_use_certificate_file failed");
res = SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM);
die_unless(res > 0, "SSL_CTX_use_PrivateKey_file failed");
res = SSL_CTX_check_private_key(ctx);
die_unless(res > 0, "SSL_CTX_check_private_key failed");
if (ciphers_len == 0)
ciphers = CIPHERS;
SSL_CTX_set_cipher_list(ctx, ciphers);
#ifndef OPENSSL_NO_ECDH
setup_ecdh(ctx);
#endif
#ifndef OPENSSL_NO_DH
res = setup_dh(ctx, dh_file);
die_unless(res > 0, "Setting DH parameters failed");
#endif
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
if (ca_file)
SSL_CTX_load_verify_locations(ctx, ca_file, NULL);
else
SSL_CTX_set_default_verify_paths(ctx);
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
/* SSL_CTX_load_verify_locations(ctx, "/etc/ejabberd/ca_certificates.pem", NULL); */
/* SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ejabberd/ca_certs/"); */
/* This IF is commented to allow verification in all cases: */
/* if (command == SET_CERTIFICATE_FILE_ACCEPT) */
/* { */
SSL_CTX_set_verify(ctx,
SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE,
verify_callback);
/* } */
SSL_CTX_set_info_callback(ctx, &ssl_info_callback);
ssl_ctx = ctx;
hash_table_insert(hash_key, key_mtime, dh_mtime, ca_mtime, ssl_ctx);
}
driver_free(hash_key);
d->ssl = SSL_new(ssl_ctx);
die_unless(d->ssl, "SSL_new failed");
if (flags & VERIFY_NONE)
SSL_set_verify(d->ssl, SSL_VERIFY_NONE, verify_callback);
#ifdef SSL_OP_NO_COMPRESSION
if (flags & COMPRESSION_NONE)
SSL_set_options(d->ssl, SSL_OP_NO_COMPRESSION);
#endif
SSL_set_ex_data(d->ssl, ssl_index, d);
d->bio_read = BIO_new(BIO_s_mem());
d->bio_write = BIO_new(BIO_s_mem());
die_unless(d->bio_read, "BIO_new failed");
die_unless(d->bio_write, "BIO_new failed");
SSL_set_bio(d->ssl, d->bio_read, d->bio_write);
if (command == SET_CERTIFICATE_FILE_ACCEPT) {
options |= (SSL_OP_NO_TICKET|SSL_OP_ALL|SSL_OP_NO_SSLv2);
SSL_set_options(d->ssl, options);
SSL_set_accept_state(d->ssl);
} else {
options |= (SSL_OP_NO_TICKET|SSL_OP_NO_SSLv2);
SSL_set_options(d->ssl, options);
SSL_set_connect_state(d->ssl);
}
break;
}
case SET_ENCRYPTED_INPUT:
die_unless(d->ssl, "SSL not initialized");
BIO_write(d->bio_read, buf, len);
break;
case SET_DECRYPTED_OUTPUT:
die_unless(d->ssl, "SSL not initialized");
if (len > 0) {
if (d->send_buffer != NULL) {
if (d->send_buffer2 == NULL) {
d->send_buffer2_len = len;
d->send_buffer2_size = len;
d->send_buffer2 = ftls_alloc(d->send_buffer2_size);
memcpy(d->send_buffer2, buf, len);
} else {
if (d->send_buffer2_size <
d->send_buffer2_len + len) {
while (d->send_buffer2_size <
d->send_buffer2_len + len) {
d->send_buffer2_size *= 2;
}
d->send_buffer2 = ftls_realloc(d->send_buffer2,
d->send_buffer2_size);
}
memcpy(d->send_buffer2 + d->send_buffer2_len,
buf, len);
d->send_buffer2_len += len;
}
} else {
res = SSL_write(d->ssl, buf, len);
if (res <= 0) {
res = SSL_get_error(d->ssl, res);
if (res == SSL_ERROR_WANT_READ ||
res == SSL_ERROR_WANT_WRITE) {
d->send_buffer_len = len;
d->send_buffer_size = len;
d->send_buffer = ftls_alloc(d->send_buffer_size);
memcpy(d->send_buffer, buf, len);
} else {
die_unless(0, "SSL_write failed");
}
}
}
}
break;
case GET_ENCRYPTED_OUTPUT:
die_unless(d->ssl, "SSL not initialized");
size = BIO_ctrl_pending(d->bio_write) + 1;
b = ftls_alloc_binary(size);
b->orig_bytes[0] = 0;
BIO_read(d->bio_write, b->orig_bytes + 1, size - 1);
*rbuf = (char *)b;
return size;
case GET_DECRYPTED_INPUT: {
int retcode = 0;
die_unless(d->ssl, "SSL not initialized");
if (!SSL_is_init_finished(d->ssl))
{
retcode = 2;
res = SSL_do_handshake(d->ssl);
if (res <= 0)
die_unless(SSL_get_error(d->ssl, res) == SSL_ERROR_WANT_READ,
"SSL_do_handshake failed");
}
if (SSL_is_init_finished(d->ssl)) {
size_t req_size = 0;
int i;
for (i = 0; i < 2; i++)
if (d->send_buffer != NULL) {
res = SSL_write(d->ssl, d->send_buffer, d->send_buffer_len);
if (res <= 0) {
die_unless(0, "SSL_write failed");
}
retcode = 2;
driver_free(d->send_buffer);
d->send_buffer = d->send_buffer2;
d->send_buffer_len = d->send_buffer2_len;
d->send_buffer_size = d->send_buffer2_size;
d->send_buffer2 = NULL;
d->send_buffer2_len = 0;
d->send_buffer2_size = 0;
}
if (len == 4)
{
unsigned char *b = (unsigned char *)buf;
req_size =
(b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
}
size = BUF_SIZE + 1;
rlen = 1;
b = ftls_alloc_binary(size);
b->orig_bytes[0] = retcode;
res = 0;
while ((req_size == 0 || rlen < req_size + 1) &&
(res = SSL_read(d->ssl,
b->orig_bytes + rlen,
(req_size == 0 || req_size + 1 >= size) ?
size - rlen : req_size + 1 - rlen)) > 0)
{
//printf("%d bytes of decrypted data read from state machine\r\n",res);
rlen += res;
if (size - rlen < BUF_SIZE) {
size *= 2;
b = ftls_realloc_binary(b, size);
}
}
if (d->handshakes > 1) {
char *error = "client renegotiations forbidden";
int error_len = strlen(error);
rlen = error_len + 1;
b = ftls_alloc_binary(rlen);
b->orig_bytes[0] = 1;
strncpy(b->orig_bytes + 1, error, error_len);
*rbuf = (char *)b;
return rlen;
}
if (res < 0)
{
int err = SSL_get_error(d->ssl, res);
if (err == SSL_ERROR_WANT_READ)
{
//printf("SSL_read wants more data\r\n");
//return 0;
}
// TODO
}
b = ftls_realloc_binary(b, rlen);
*rbuf = (char *)b;
return rlen;
} else {
b = ftls_alloc_binary(1);
b->orig_bytes[0] = 2;
*rbuf = (char *)b;
return 1;
}
break;
}
case GET_PEER_CERTIFICATE:
cert = SSL_get_peer_certificate(d->ssl);
if (cert == NULL)
{
b = ftls_alloc_binary(1);
b->orig_bytes[0] = 1;
*rbuf = (char *)b;
return 1;
} else {
unsigned char *tmp_buf;
int encode_len = i2d_X509(cert, NULL);
if (encode_len >= 0)
{
rlen = encode_len + 1;
b = ftls_alloc_binary(rlen);
b->orig_bytes[0] = 0;
tmp_buf = (unsigned char *)&b->orig_bytes[1];
i2d_X509(cert, &tmp_buf);
X509_free(cert);
*rbuf = (char *)b;
return rlen;
} else
X509_free(cert);
}
break;
case GET_VERIFY_RESULT:
b = ftls_alloc_binary(1);
b->orig_bytes[0] = SSL_get_verify_result(d->ssl);
*rbuf = (char *)b;
return 1;
break;
}
b = ftls_alloc_binary(1);
b->orig_bytes[0] = 0;
*rbuf = (char *)b;
return 1;
}
ErlDrvEntry tls_driver_entry = {
NULL, /* F_PTR init, N/A */
tls_drv_start, /* L_PTR start, called when port is opened */
tls_drv_stop, /* F_PTR stop, called when port is closed */
NULL, /* F_PTR output, called when erlang has sent */
NULL, /* F_PTR ready_input, called when input descriptor ready */
NULL, /* F_PTR ready_output, called when output descriptor ready */
"fast_tls_drv", /* char *driver_name, the argument to open_port */
tls_drv_finish, /* F_PTR finish, called when unloaded */
NULL, /* handle */
tls_drv_control, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL, /* F_PTR outputv, reserved */
/* Added in Erlang/OTP R15B: */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL, /* event */
ERL_DRV_EXTENDED_MARKER, /* extended_marker */
ERL_DRV_EXTENDED_MAJOR_VERSION, /* major_version */
ERL_DRV_EXTENDED_MINOR_VERSION, /* minor_version */
0, /* driver_flags */
NULL, /* handle2 */
NULL, /* process_exit */
NULL /* stop_select */
};
DRIVER_INIT(fast_tls_drv) /* must match name in driver_entry */
{
CRYPTO_set_mem_functions(driver_alloc, driver_realloc, driver_free);
OpenSSL_add_ssl_algorithms();
SSL_load_error_strings();
init_hash_table();
ssl_index = SSL_get_ex_new_index(0, "ssl index", NULL, NULL, NULL);
return &tls_driver_entry;
}