Packages
fast_tls
1.0.5
1.1.26
1.1.25
1.1.24
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.16
1.1.15
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.26
1.0.25
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.16
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-beta1
TLS / SSL OpenSSL-based native driver for Erlang / Elixir
Current section
Files
Jump to
Current section
Files
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 <openssl/opensslv.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include "options.h"
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define DH_set0_pqg(dh, dh_p, NULL, dh_g) (dh)->p = dh_p; (dh)->g = dh_g
#endif
#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. */
#ifdef HAS_ERTS_EXIT
void erts_exit(int n, char* v, ...);
#define erl_exit erts_exit
#else
void erl_exit(int n, char* v, ...);
#endif
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, unsigned 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);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
OPENSSL_cleanup();
#endif
}
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;
}
BIGNUM *dh_p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
BIGNUM *dh_g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
if (dh_p == NULL || dh_g == NULL) {
BN_free(dh_p);
BN_free(dh_g);
DH_free(dh);
return 0;
}
DH_set0_pqg(dh, dh_p, NULL, dh_g);
}
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)
{
tls_data *d = (tls_data *)SSL_get_ex_data(s, ssl_index);
if ((where & SSL_CB_HANDSHAKE_START) && d->handshakes) {
d->handshakes++;
} else if ((where & SSL_CB_HANDSHAKE_DONE) && !d->handshakes) {
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);
unsigned 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 *b2 = (unsigned char *)buf;
req_size =
(b2[0] << 24) | (b2[1] << 16) | (b2[2] << 8) | b2[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 */
};
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define our_alloc driver_alloc
#define our_realloc driver_realloc
#define our_free driver_free
#else
static void *our_alloc(size_t size, const char *file, int line) {
return driver_alloc(size);
}
static void * our_realloc(void *ptr, size_t size, const char *file, int line) {
return driver_realloc(ptr, size);
}
static void our_free(void *ptr, const char *file, int line) {
driver_free(ptr);
}
#endif
DRIVER_INIT(fast_tls_drv) /* must match name in driver_entry */
{
CRYPTO_set_mem_functions(our_alloc, our_realloc, our_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;
}