Packages
fast_tls
1.1.21
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.c
/*
* Copyright (C) 2002-2024 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_nif.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/pkcs12.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/core_names.h>
#include <openssl/decoder.h>
#include <openssl/provider.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <ctype.h>
#include "options.h"
#include "uthash.h"
#include "ioqueue.h"
#define BUF_SIZE 1024
#define enif_alloc malloc
#define enif_free free
#define enif_realloc realloc
typedef struct {
BIO *bio_read;
BIO *bio_write;
SSL *ssl;
int handshakes;
ErlNifMutex *mtx;
int valid;
ioqueue *to_send_queue;
char *cert_file;
char *key_file;
char *ciphers;
unsigned char *dh;
size_t dh_size;
char *dh_file;
char *ca_file;
long options;
char *sni_error;
long command;
} state_t;
static int ssl_index;
#ifdef _WIN32
typedef unsigned __int32 uint32_t;
#endif
#ifndef SSL_OP_NO_TICKET
#define SSL_OP_NO_TICKET 0
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x30500000L)
#define DH_set0_pqg(dh, dh_p, param, dh_g) (dh)->p = dh_p; (dh)->g = dh_g
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#define SSL_get_extms_support(s) 0
#endif
#if OPENSSL_VERSION_NUMBER < 0x10002000L
#define SSL_is_server(s) (s)->server
#endif
void __free(void *ptr, size_t size) {
enif_free(ptr);
}
#undef uthash_malloc
#undef uthash_free
#define uthash_malloc enif_alloc
#define uthash_free __free
#if OPENSSL_VERSION_NUMBER >= 0x10100000L || OPENSSL_VERSION_NUMBER < 0x10002000
#undef SSL_CTX_set_ecdh_auto
#define SSL_CTX_set_ecdh_auto(A, B) do {} while(0)
#endif
#define CIPHERS "HIGH:!aNULL:!eNULL:!3DES:@STRENGTH"
#define PROTOCOL_OPTIONS "no_sslv3|cipher_server_preference|no_compression"
static ErlNifResourceType *tls_state_t = NULL;
static ErlNifMutex **mtx_buf = NULL;
/**
* Prepare the SSL options flag.
**/
static int set_option_flag(const unsigned char *opt, size_t len, long *flag) {
ssl_option_t *p;
for (p = ssl_options; p->name; p++) {
if (!memcmp(opt, p->name, len) && p->name[len] == '\0') {
*flag |= p->code;
return 1;
}
}
return 0;
}
typedef struct {
char *key;
char *file;
SSL_CTX *ssl_ctx;
UT_hash_handle hh;
} cert_info_t;
static cert_info_t *certs_map = NULL;
static cert_info_t *certfiles_map = NULL;
static ErlNifRWLock *certs_map_lock = NULL;
static ErlNifRWLock *certfiles_map_lock = NULL;
static void free_cert_info(cert_info_t *info) {
if (info) {
enif_free(info->key);
enif_free(info->file);
if (info->ssl_ctx)
SSL_CTX_free(info->ssl_ctx);
enif_free(info);
}
}
static void clear_certs_map() {
cert_info_t *info = NULL;
cert_info_t *tmp = NULL;
enif_rwlock_rwlock(certs_map_lock);
HASH_ITER(hh, certs_map, info, tmp) {
HASH_DEL(certs_map, info);
free_cert_info(info);
}
enif_rwlock_rwunlock(certs_map_lock);
}
static void clear_certfiles_map() {
cert_info_t *info = NULL;
cert_info_t *tmp = NULL;
enif_rwlock_rwlock(certfiles_map_lock);
HASH_ITER(hh, certfiles_map, info, tmp) {
HASH_DEL(certfiles_map, info);
free_cert_info(info);
}
enif_rwlock_rwunlock(certfiles_map_lock);
}
static state_t *init_tls_state() {
state_t *state = enif_alloc_resource(tls_state_t, sizeof(state_t));
if (!state) return NULL;
memset(state, 0, sizeof(state_t));
state->mtx = enif_mutex_create("");
if (!state->mtx) {
enif_release_resource(state);
return NULL;
}
state->to_send_queue = ioqueue_create();
if (!state->to_send_queue) {
enif_release_resource(state);
enif_mutex_destroy(state->mtx);
return NULL;
}
state->valid = 1;
return state;
}
static void destroy_tls_state(ErlNifEnv *env, void *data) {
state_t *state = (state_t *) data;
if (state) {
if (state->ssl)
SSL_free(state->ssl);
if (state->mtx)
enif_mutex_destroy(state->mtx);
if (state->cert_file)
enif_free(state->cert_file);
if (state->to_send_queue)
ioqueue_free(state->to_send_queue);
memset(state, 0, sizeof(state_t));
}
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined LIBRESSL_VERSION_NUMBER
static void locking_callback(int mode, int n, const char *file, int line) {
if (mode & CRYPTO_LOCK)
enif_mutex_lock(mtx_buf[n]);
else
enif_mutex_unlock(mtx_buf[n]);
}
static void thread_id_callback(CRYPTO_THREADID *id) {
CRYPTO_THREADID_set_pointer(id, enif_thread_self());
}
static int atomic_add_callback(int *pointer, int amount, int type, const char *file, int line) {
return __sync_add_and_fetch(pointer, amount);
}
#endif
static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) {
int i;
OpenSSL_add_ssl_algorithms();
SSL_load_error_strings();
mtx_buf = enif_alloc(CRYPTO_num_locks() * sizeof(ErlNifMutex *));
for (i = 0; i < CRYPTO_num_locks(); i++)
mtx_buf[i] = enif_mutex_create("");
CRYPTO_set_add_lock_callback(atomic_add_callback);
CRYPTO_set_locking_callback(locking_callback);
CRYPTO_THREADID_set_callback(thread_id_callback);
certs_map_lock = enif_rwlock_create("certs_map_lock");
certfiles_map_lock = enif_rwlock_create("certfiles_map_lock");
ssl_index = SSL_get_ex_new_index(0, "ssl index", NULL, NULL, NULL);
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
tls_state_t = enif_open_resource_type(env, NULL, "tls_state_t",
destroy_tls_state,
flags, NULL);
return 0;
}
static void unload(ErlNifEnv *env, void *priv) {
int i;
clear_certs_map();
clear_certfiles_map();
enif_rwlock_destroy(certs_map_lock);
enif_rwlock_destroy(certfiles_map_lock);
certs_map = NULL;
certs_map_lock = NULL;
certfiles_map = NULL;
certfiles_map_lock = NULL;
for (i = 0; i < CRYPTO_num_locks(); i++)
enif_mutex_destroy(mtx_buf[i]);
enif_free(mtx_buf);
mtx_buf = NULL;
}
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) {
#if OPENSSL_VERSION_NUMBER < 0x10002000
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);
#else
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
}
#endif
#ifndef OPENSSL_NO_DH
/*
2048-bit MODP Group with 256-bit Prime Order Subgroup (RFC5114)
*/
static unsigned char dh2048_p[] = {
0x87, 0xA8, 0xE6, 0x1D, 0xB4, 0xB6, 0x66, 0x3C,
0xFF, 0xBB, 0xD1, 0x9C, 0x65, 0x19, 0x59, 0x99,
0x8C, 0xEE, 0xF6, 0x08, 0x66, 0x0D, 0xD0, 0xF2,
0x5D, 0x2C, 0xEE, 0xD4, 0x43, 0x5E, 0x3B, 0x00,
0xE0, 0x0D, 0xF8, 0xF1, 0xD6, 0x19, 0x57, 0xD4,
0xFA, 0xF7, 0xDF, 0x45, 0x61, 0xB2, 0xAA, 0x30,
0x16, 0xC3, 0xD9, 0x11, 0x34, 0x09, 0x6F, 0xAA,
0x3B, 0xF4, 0x29, 0x6D, 0x83, 0x0E, 0x9A, 0x7C,
0x20, 0x9E, 0x0C, 0x64, 0x97, 0x51, 0x7A, 0xBD,
0x5A, 0x8A, 0x9D, 0x30, 0x6B, 0xCF, 0x67, 0xED,
0x91, 0xF9, 0xE6, 0x72, 0x5B, 0x47, 0x58, 0xC0,
0x22, 0xE0, 0xB1, 0xEF, 0x42, 0x75, 0xBF, 0x7B,
0x6C, 0x5B, 0xFC, 0x11, 0xD4, 0x5F, 0x90, 0x88,
0xB9, 0x41, 0xF5, 0x4E, 0xB1, 0xE5, 0x9B, 0xB8,
0xBC, 0x39, 0xA0, 0xBF, 0x12, 0x30, 0x7F, 0x5C,
0x4F, 0xDB, 0x70, 0xC5, 0x81, 0xB2, 0x3F, 0x76,
0xB6, 0x3A, 0xCA, 0xE1, 0xCA, 0xA6, 0xB7, 0x90,
0x2D, 0x52, 0x52, 0x67, 0x35, 0x48, 0x8A, 0x0E,
0xF1, 0x3C, 0x6D, 0x9A, 0x51, 0xBF, 0xA4, 0xAB,
0x3A, 0xD8, 0x34, 0x77, 0x96, 0x52, 0x4D, 0x8E,
0xF6, 0xA1, 0x67, 0xB5, 0xA4, 0x18, 0x25, 0xD9,
0x67, 0xE1, 0x44, 0xE5, 0x14, 0x05, 0x64, 0x25,
0x1C, 0xCA, 0xCB, 0x83, 0xE6, 0xB4, 0x86, 0xF6,
0xB3, 0xCA, 0x3F, 0x79, 0x71, 0x50, 0x60, 0x26,
0xC0, 0xB8, 0x57, 0xF6, 0x89, 0x96, 0x28, 0x56,
0xDE, 0xD4, 0x01, 0x0A, 0xBD, 0x0B, 0xE6, 0x21,
0xC3, 0xA3, 0x96, 0x0A, 0x54, 0xE7, 0x10, 0xC3,
0x75, 0xF2, 0x63, 0x75, 0xD7, 0x01, 0x41, 0x03,
0xA4, 0xB5, 0x43, 0x30, 0xC1, 0x98, 0xAF, 0x12,
0x61, 0x16, 0xD2, 0x27, 0x6E, 0x11, 0x71, 0x5F,
0x69, 0x38, 0x77, 0xFA, 0xD7, 0xEF, 0x09, 0xCA,
0xDB, 0x09, 0x4A, 0xE9, 0x1E, 0x1A, 0x15, 0x97,
};
static unsigned char dh2048_g[] = {
0x3F, 0xB3, 0x2C, 0x9B, 0x73, 0x13, 0x4D, 0x0B,
0x2E, 0x77, 0x50, 0x66, 0x60, 0xED, 0xBD, 0x48,
0x4C, 0xA7, 0xB1, 0x8F, 0x21, 0xEF, 0x20, 0x54,
0x07, 0xF4, 0x79, 0x3A, 0x1A, 0x0B, 0xA1, 0x25,
0x10, 0xDB, 0xC1, 0x50, 0x77, 0xBE, 0x46, 0x3F,
0xFF, 0x4F, 0xED, 0x4A, 0xAC, 0x0B, 0xB5, 0x55,
0xBE, 0x3A, 0x6C, 0x1B, 0x0C, 0x6B, 0x47, 0xB1,
0xBC, 0x37, 0x73, 0xBF, 0x7E, 0x8C, 0x6F, 0x62,
0x90, 0x12, 0x28, 0xF8, 0xC2, 0x8C, 0xBB, 0x18,
0xA5, 0x5A, 0xE3, 0x13, 0x41, 0x00, 0x0A, 0x65,
0x01, 0x96, 0xF9, 0x31, 0xC7, 0x7A, 0x57, 0xF2,
0xDD, 0xF4, 0x63, 0xE5, 0xE9, 0xEC, 0x14, 0x4B,
0x77, 0x7D, 0xE6, 0x2A, 0xAA, 0xB8, 0xA8, 0x62,
0x8A, 0xC3, 0x76, 0xD2, 0x82, 0xD6, 0xED, 0x38,
0x64, 0xE6, 0x79, 0x82, 0x42, 0x8E, 0xBC, 0x83,
0x1D, 0x14, 0x34, 0x8F, 0x6F, 0x2F, 0x91, 0x93,
0xB5, 0x04, 0x5A, 0xF2, 0x76, 0x71, 0x64, 0xE1,
0xDF, 0xC9, 0x67, 0xC1, 0xFB, 0x3F, 0x2E, 0x55,
0xA4, 0xBD, 0x1B, 0xFF, 0xE8, 0x3B, 0x9C, 0x80,
0xD0, 0x52, 0xB9, 0x85, 0xD1, 0x82, 0xEA, 0x0A,
0xDB, 0x2A, 0x3B, 0x73, 0x13, 0xD3, 0xFE, 0x14,
0xC8, 0x48, 0x4B, 0x1E, 0x05, 0x25, 0x88, 0xB9,
0xB7, 0xD2, 0xBB, 0xD2, 0xDF, 0x01, 0x61, 0x99,
0xEC, 0xD0, 0x6E, 0x15, 0x57, 0xCD, 0x09, 0x15,
0xB3, 0x35, 0x3B, 0xBB, 0x64, 0xE0, 0xEC, 0x37,
0x7F, 0xD0, 0x28, 0x37, 0x0D, 0xF9, 0x2B, 0x52,
0xC7, 0x89, 0x14, 0x28, 0xCD, 0xC6, 0x7E, 0xB6,
0x18, 0x4B, 0x52, 0x3D, 0x1D, 0xB2, 0x46, 0xC3,
0x2F, 0x63, 0x07, 0x84, 0x90, 0xF0, 0x0E, 0xF8,
0xD6, 0x47, 0xD1, 0x48, 0xD4, 0x79, 0x54, 0x51,
0x5E, 0x23, 0x27, 0xCF, 0xEF, 0x98, 0xC5, 0x82,
0x66, 0x4B, 0x4C, 0x0F, 0x6C, 0xC4, 0x16, 0x59,
};
static int setup_dh(SSL_CTX *ctx, const unsigned char *dh_der, size_t dh_size, char *dh_file) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
DH *dh;
int res;
if (dh_der != NULL) {
dh = d2i_DHparams(NULL, &dh_der, dh_size);
if (dh == NULL) {
return 0;
}
} else 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(dh2048_p, sizeof(dh2048_p), NULL);
BIGNUM *dh_g = BN_bin2bn(dh2048_g, sizeof(dh2048_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;
#else
if (!dh_der && !dh_file) {
OSSL_PARAM params[] = {
OSSL_PARAM_BN(OSSL_PKEY_PARAM_FFC_P, dh2048_p, sizeof(dh2048_p)),
OSSL_PARAM_BN(OSSL_PKEY_PARAM_FFC_G, dh2048_g, sizeof(dh2048_g)),
OSSL_PARAM_END
};
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);
if (!pctx)
return 0;
if (EVP_PKEY_fromdata_init(pctx) <= 0 ||
EVP_PKEY_fromdata(pctx, &pkey, EVP_PKEY_KEY_PARAMETERS, params) <= 0) {
EVP_PKEY_CTX_free(pctx);
return 0;
}
if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) != 1) {
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(pctx);
return 0;
}
EVP_PKEY_CTX_free(pctx);
return 1;
}
if (dh_der != NULL) {
OSSL_DECODER_CTX *dctx;
EVP_PKEY *pkey = NULL;
dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", NULL, "DH",
OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
NULL, NULL);
if (!dctx)
return 0;
if (OSSL_DECODER_from_data(dctx, &dh_der, &dh_size) <= 0)
return 0;
if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) != 1) {
EVP_PKEY_free(pkey);
return 0;
}
return 1;
}
BIO *bio = BIO_new_file(dh_file, "r");
if (!bio)
return 0;
EVP_PKEY *pkey = PEM_read_bio_Parameters(bio, NULL);
if (!pkey) {
BIO_free(bio);
return 0;
}
if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) != 1) {
EVP_PKEY_free(pkey);
BIO_free(bio);
return 0;
}
BIO_free(bio);
return 1;
#endif
}
#endif
#ifndef SSL_OP_NO_RENEGOTIATION
static void ssl_info_callback(const SSL *s, int where, int ret) {
state_t *d = (state_t *) SSL_get_ex_data(s, ssl_index);
if ((where & SSL_CB_HANDSHAKE_START)) {
d->handshakes++;
}
}
#endif
static char *create_ssl_for_cert(char *, state_t *);
static cert_info_t *lookup_certfile(const char *domain) {
cert_info_t *ret = NULL;
cert_info_t *info = NULL;
if (domain) {
size_t len = strlen(domain);
if (len) {
char *name = enif_alloc(len + 1);
if (!name)
return ret;
name[len] = 0;
size_t i = 0;
for (i = 0; i < len; i++)
name[i] = tolower(domain[i]);
HASH_FIND_STR(certfiles_map, name, info);
if (info && info->file)
ret = info;
else {
/* Replace the first domain part with '*' and retry */
char *dot = strchr(name, '.');
if (dot != NULL && name[0] != '.') {
char *glob = dot - 1;
glob[0] = '*';
HASH_FIND_STR(certfiles_map, glob, info);
if (info && info->file)
ret = info;
}
}
enif_free(name);
}
}
return ret;
}
static int ssl_sni_callback(const SSL *s, int *foo, void *data) {
cert_info_t *info = NULL;
char *err_str = NULL;
const char *servername = NULL;
int ret = SSL_TLSEXT_ERR_OK;
state_t *state = (state_t *) SSL_get_ex_data(s, ssl_index);
servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
enif_rwlock_rlock(certfiles_map_lock);
info = lookup_certfile(servername);
if (info) {
if (strcmp(info->file, state->cert_file))
err_str = create_ssl_for_cert(info->file, state);
if (err_str) {
state->sni_error = err_str;
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
} else if (strlen(state->cert_file) == 0) {
state->sni_error =
"Failed to find a certificate matching the domain in SNI extension";
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
enif_rwlock_runlock(certfiles_map_lock);
return ret;
}
#define ERR_T(T) enif_make_tuple2(env, enif_make_atom(env, "error"), T)
#define OK_T(T) enif_make_tuple2(env, enif_make_atom(env, "ok"), T)
#define SET_CERTIFICATE_FILE_ACCEPT 1
#define SET_CERTIFICATE_FILE_CONNECT 2
#define VERIFY_NONE 0x10000
#define COMPRESSION_NONE 0x100000
static ERL_NIF_TERM ssl_error(ErlNifEnv *env, const char *errstr) {
size_t rlen;
ErlNifBinary err;
char error_string[256];
size_t error_string_length;
size_t errstrlen = strlen(errstr);
unsigned long error_code = ERR_get_error();
if (error_code) {
ERR_error_string_n(error_code, error_string, sizeof(error_string));
error_string_length = strlen(error_string),
rlen = errstrlen + error_string_length + 2;
} else {
error_string_length = 0;
rlen = errstrlen;
}
enif_alloc_binary(rlen, &err);
memcpy(err.data, errstr, errstrlen);
if (error_code) {
memcpy(err.data + errstrlen, ": ", 2);
memcpy(err.data + 2 + errstrlen, error_string, error_string_length);
}
return ERR_T(enif_make_binary(env, &err));
}
static SSL_CTX *create_new_ctx(char *cert_file, char *key_file,
char *ciphers, unsigned char *dh, size_t dh_size,
char *dh_file, char *ca_file,
unsigned int command,
char **err_str) {
long verifyopts;
int res = 0;
SSL_CTX *ctx = SSL_CTX_new(SSLv23_method());
if (!ctx) {
*err_str = "SSL_CTX_new failed";
return NULL;
}
if (cert_file) {
res = SSL_CTX_use_certificate_chain_file(ctx, cert_file);
if (res <= 0) {
SSL_CTX_free(ctx);
*err_str = "SSL_CTX_use_certificate_file failed";
return NULL;
}
if (key_file) {
res = SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM);
} else {
res = SSL_CTX_use_PrivateKey_file(ctx, cert_file, SSL_FILETYPE_PEM);
}
if (res <= 0) {
SSL_CTX_free(ctx);
*err_str = "SSL_CTX_use_PrivateKey_file failed";
return NULL;
}
res = SSL_CTX_check_private_key(ctx);
if (res <= 0) {
SSL_CTX_free(ctx);
*err_str = "SSL_CTX_check_private_key failed";
return NULL;
}
}
if (command == SET_CERTIFICATE_FILE_ACCEPT) {
SSL_CTX_set_tlsext_servername_callback(ctx, &ssl_sni_callback);
verifyopts = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
if (ca_file) {
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(ca_file));
}
} else {
verifyopts = SSL_VERIFY_PEER;
}
if (ciphers[0] == 0)
SSL_CTX_set_cipher_list(ctx, CIPHERS);
else
SSL_CTX_set_cipher_list(ctx, ciphers);
#ifndef OPENSSL_NO_ECDH
setup_ecdh(ctx);
#endif
#ifndef OPENSSL_NO_DH
res = setup_dh(ctx, dh, dh_size, dh_file);
if (res <= 0) {
SSL_CTX_free(ctx);
*err_str = "Setting DH parameters failed";
return NULL;
}
#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_set_verify(ctx, verifyopts, verify_callback);
#ifndef SSL_OP_NO_RENEGOTIATION
SSL_CTX_set_info_callback(ctx, &ssl_info_callback);
#endif
*err_str = NULL;
return ctx;
}
static void set_ctx(state_t *state, SSL_CTX *ctx) {
if (state->ssl)
SSL_set_SSL_CTX(state->ssl, ctx);
else
state->ssl = SSL_new(ctx);
}
static const char *hex_encode(char *dst, const unsigned char *src, size_t size) {
size_t i;
for (i = 0; i < size; i++) {
sprintf(dst + 2*i, "%02x", (unsigned char)src[i]);
}
dst[size * 2] = 0;
return dst;
}
static char *create_ssl_for_cert(char *cert_file, state_t *state) {
char *key_file = state->key_file;
char *ciphers = state->ciphers;
unsigned char *dh = state->dh;
size_t dh_size = state->dh_size;
char *dh_file = state->dh_file;
char *ca_file = state->ca_file;
long options = state->options;
unsigned int command = state->command;
char *ret = NULL;
cert_info_t *info = NULL;
cert_info_t *new_info = NULL;
cert_info_t *old_info = NULL;
char *dh_hex = enif_alloc(dh_size * 2 + 1);
size_t key_size =
strlen(cert_file) + strlen(key_file) + strlen(ciphers) + 8 +
dh_size * 2 + strlen(dh_file) + strlen(ca_file) + 1;
char *key = enif_alloc(key_size);
if (!dh_hex || !key) {
enif_free(dh_hex);
enif_free(key);
return "Memory allocation failed";
}
sprintf(key, "%s%s%s%08lx%s%s%s",
cert_file, key_file, ciphers,
options, dh_file, ca_file,
hex_encode(dh_hex, dh, dh_size));
enif_rwlock_rlock(certs_map_lock);
HASH_FIND_STR(certs_map, key, info);
if (strlen(cert_file) == 0) cert_file = NULL;
if (strlen(key_file) == 0) key_file = NULL;
if (dh_size == 0) dh = NULL;
if (strlen(dh_file) == 0) dh_file = NULL;
if (strlen(ca_file) == 0) ca_file = NULL;
if (info == NULL) {
enif_rwlock_runlock(certs_map_lock);
enif_rwlock_rwlock(certs_map_lock);
SSL_CTX *ctx = create_new_ctx(cert_file, key_file, ciphers, dh, dh_size,
dh_file, ca_file, command, &ret);
if (ret == NULL) {
new_info = enif_alloc(sizeof(cert_info_t));
if (new_info) {
memset(new_info, 0, sizeof(cert_info_t));
new_info->key = enif_alloc(key_size);
if (new_info->key) {
memcpy(new_info->key, key, key_size);
new_info->ssl_ctx = ctx;
HASH_REPLACE_STR(certs_map, key, new_info, old_info);
free_cert_info(old_info);
set_ctx(state, ctx);
} else {
enif_free(new_info);
SSL_CTX_free(ctx);
ret = "Memory allocation failed";
}
} else {
SSL_CTX_free(ctx);
ret = "Memory allocation failed";
}
}
enif_rwlock_rwunlock(certs_map_lock);
} else {
set_ctx(state, info->ssl_ctx);
enif_rwlock_runlock(certs_map_lock);
}
enif_free(key);
enif_free(dh_hex);
return ret;
}
static ERL_NIF_TERM open_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
unsigned int command;
unsigned int flags;
char *sni = NULL;
ErlNifBinary ciphers_bin;
ErlNifBinary certfile_bin;
ErlNifBinary keyfile_bin;
ErlNifBinary protocol_options_bin;
ErlNifBinary dh_bin;
ErlNifBinary dhfile_bin;
ErlNifBinary cafile_bin;
ErlNifBinary sni_bin;
ErlNifBinary alpn_bin;
long options = 0L;
state_t *state = NULL;
size_t po_len_left = 0;
unsigned char *po = NULL;
ERR_clear_error();
if (argc != 10)
return enif_make_badarg(env);
if (!enif_get_uint(env, argv[0], &flags))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[1], &certfile_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[2], &keyfile_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[3], &ciphers_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[4], &protocol_options_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[5], &dh_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[6], &dhfile_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[7], &cafile_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[8], &sni_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[9], &alpn_bin))
return enif_make_badarg(env);
command = flags & 0xffff;
if (protocol_options_bin.size) {
po_len_left = protocol_options_bin.size;
po = protocol_options_bin.data;
} else {
po = (unsigned char *) PROTOCOL_OPTIONS;
po_len_left = strlen((char *) po);
}
while (po_len_left) {
unsigned char *pos = memchr(po, '|', po_len_left);
if (!pos) {
set_option_flag(po, po_len_left, &options);
break;
}
set_option_flag(po, pos - po, &options);
po_len_left -= pos - po + 1;
po = pos + 1;
}
state = init_tls_state();
if (!state) return ERR_T(enif_make_atom(env, "enomem"));
state->cert_file = enif_alloc(certfile_bin.size + 1 +
keyfile_bin.size + 1 +
ciphers_bin.size + 1 +
dh_bin.size + 1 +
dhfile_bin.size + 1 +
cafile_bin.size + 1 +
sni_bin.size + 1);
if (!state->cert_file) {
enif_release_resource(state);
return ERR_T(enif_make_atom(env, "enomem"));
}
state->key_file = state->cert_file + certfile_bin.size + 1;
state->ciphers = state->key_file + keyfile_bin.size + 1;
state->dh = (unsigned char*)(state->ciphers + ciphers_bin.size + 1);
state->dh_file = (char*)(state->dh + dh_bin.size + 1);
state->ca_file = state->dh_file + dhfile_bin.size + 1;
sni = state->ca_file + cafile_bin.size + 1;
state->options = options;
state->command = command;
memcpy(state->cert_file, certfile_bin.data, certfile_bin.size);
state->cert_file[certfile_bin.size] = 0;
memcpy(state->key_file, keyfile_bin.data, keyfile_bin.size);
state->key_file[keyfile_bin.size] = 0;
memcpy(state->ciphers, ciphers_bin.data, ciphers_bin.size);
state->ciphers[ciphers_bin.size] = 0;
memcpy(state->dh, dh_bin.data, dh_bin.size);
state->dh[dh_bin.size] = 0;
state->dh_size = dh_bin.size;
memcpy(state->dh_file, dhfile_bin.data, dhfile_bin.size);
state->dh_file[dhfile_bin.size] = 0;
memcpy(state->ca_file, cafile_bin.data, cafile_bin.size);
state->ca_file[cafile_bin.size] = 0;
memcpy(sni, sni_bin.data, sni_bin.size);
sni[sni_bin.size] = 0;
char *err_str = create_ssl_for_cert(state->cert_file, state);
if (err_str) {
enif_release_resource(state);
return ssl_error(env, err_str);
}
if (!state->ssl) {
enif_release_resource(state);
return ssl_error(env, "SSL_new failed");
}
if (flags & VERIFY_NONE)
SSL_set_verify(state->ssl, SSL_VERIFY_NONE, verify_callback);
#ifdef SSL_OP_NO_COMPRESSION
if (flags & COMPRESSION_NONE)
SSL_set_options(state->ssl, SSL_OP_NO_COMPRESSION);
#endif
SSL_set_ex_data(state->ssl, ssl_index, state);
state->bio_read = BIO_new(BIO_s_mem());
state->bio_write = BIO_new(BIO_s_mem());
SSL_set_bio(state->ssl, state->bio_read, state->bio_write);
if (command == SET_CERTIFICATE_FILE_ACCEPT) {
options |= (SSL_OP_NO_TICKET | SSL_OP_ALL | SSL_OP_NO_SSLv2);
SSL_set_options(state->ssl, options);
SSL_set_accept_state(state->ssl);
} else {
options |= (SSL_OP_NO_TICKET | SSL_OP_NO_SSLv2);
SSL_set_options(state->ssl, options);
if (strlen(sni) > 0) SSL_set_tlsext_host_name(state->ssl, sni);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (alpn_bin.size)
SSL_set_alpn_protos(state->ssl, alpn_bin.data, alpn_bin.size);
#endif
SSL_set_connect_state(state->ssl);
}
#ifdef SSL_OP_NO_RENEGOTIATION
SSL_set_options(state->ssl, SSL_OP_NO_RENEGOTIATION);
#endif
ERL_NIF_TERM result = enif_make_resource(env, state);
enif_release_resource(state);
return OK_T(result);
}
static ERL_NIF_TERM
get_data_to_write(ErlNifEnv *env, state_t* state) {
ERL_NIF_TERM data;
size_t size = BIO_ctrl_pending(state->bio_write);
unsigned char *buf = enif_make_new_binary(env, size, &data);
BIO_read(state->bio_write, buf, (int)size);
return data;
}
static int
get_decrypted_data(ErlNifEnv *env, state_t* state, int bytes_to_read, ERL_NIF_TERM *ret) {
ErlNifBinary buf;
size_t pos = 0;
int res;
if (bytes_to_read == 0) {
enif_make_new_binary(env, 0, ret);
return 1;
}
if (bytes_to_read < 0 || bytes_to_read > BUF_SIZE) {
res = enif_alloc_binary(BUF_SIZE, &buf);
} else {
res = enif_alloc_binary((size_t)bytes_to_read, &buf);
}
if (!res) {
*ret = ERR_T(enif_make_atom(env, "enomem"));
return 2;
}
while ((res = SSL_read(state->ssl, buf.data + pos, (int)(buf.size - pos))) > 0) {
pos += res;
if (pos == bytes_to_read)
break;
if (buf.size - pos < BUF_SIZE && buf.size != bytes_to_read) {
size_t new_size = bytes_to_read > 0 && buf.size * 2 > bytes_to_read ?
bytes_to_read : buf.size * 2;
if (!enif_realloc_binary(&buf, new_size)) {
*ret = ERR_T(enif_make_atom(env, "enomem"));
return 2;
}
}
}
#ifndef SSL_OP_NO_RENEGOTIATION
// Forbid client-initiated renegotiation for OpenSSL < 1.1.0h
if (state->handshakes > 1 && SSL_is_server(state->ssl)) {
enif_release_binary(&buf);
*ret = ERR_T(enif_make_atom(env, "closed"));
return 2;
}
#endif
enif_realloc_binary(&buf, pos);
*ret = enif_make_binary(env, &buf);
return 1;
}
static ERL_NIF_TERM
return_read_write(ErlNifEnv *env, state_t* state, int bytes_to_read, ERL_NIF_TERM tag) {
ERL_NIF_TERM read;
if (get_decrypted_data(env, state, bytes_to_read, &read) == 2) {
enif_mutex_unlock(state->mtx);
return read;
}
ERL_NIF_TERM write = get_data_to_write(env, state);
enif_mutex_unlock(state->mtx);
return enif_make_tuple3(env, tag, write, read);
}
static int
do_recv(ErlNifEnv *env, state_t *state, ERL_NIF_TERM *err, ErlNifBinary *recv) {
int res;
if (recv->size == 0)
return 1;
res = BIO_write(state->bio_read, recv->data, (int)recv->size);
if (res <= 0) {
enif_mutex_unlock(state->mtx);
*err = ERR_T(enif_make_atom(env, "write_failed"));
return 2;
}
return 1;
}
static int
do_send(ErlNifEnv *env, state_t *state, ERL_NIF_TERM *err, ErlNifBinary *to_send) {
int res = 1;
if (state->to_send_queue->size > 0) {
res = SSL_write(state->ssl, state->to_send_queue->buf,
(int) state->to_send_queue->size);
if (res > 0) {
ioqueue_consume(state->to_send_queue, (size_t) res);
}
}
if (to_send->size) {
int consumed = 0;
if (res > 0 && state->to_send_queue->size == 0) {
res = SSL_write(state->ssl, to_send->data, (int) to_send->size);
consumed = res > 0 ? res : 0;
}
if (consumed < to_send->size) {
if (!ioqueue_append(state->to_send_queue, (char *) to_send->data + consumed,
to_send->size - consumed)) {
enif_mutex_unlock(state->mtx);
*err = ERR_T(enif_make_atom(env, "enomem"));
return 2;
}
}
}
return res > 0 ? 1 : res;
}
static int
do_send_queue(ErlNifEnv *env, state_t *state, ERL_NIF_TERM *err, ErlNifBinary *to_send) {
int res = 1;
if (to_send->size) {
if (!ioqueue_append(state->to_send_queue, (char *) to_send->data,
to_send->size)) {
enif_mutex_unlock(state->mtx);
*err = ERR_T(enif_make_atom(env, "enomem"));
return 2;
}
}
return res > 0 ? 1 : res;
}
static ERL_NIF_TERM
loop_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
ErlNifBinary to_send;
ErlNifBinary received;
int bytes_to_read;
if (argc != 4)
return enif_make_badarg(env);
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[1], &to_send))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[2], &received))
return enif_make_badarg(env);
if (!enif_get_int(env, argv[3], &bytes_to_read))
return enif_make_badarg(env);
if (!state->mtx || !state->ssl)
return enif_make_badarg(env);
enif_mutex_lock(state->mtx);
if (!state->valid) {
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "closed"));
}
ERR_clear_error();
int res;
ERL_NIF_TERM err_term;
res = do_recv(env, state, &err_term, &received);
if (res == 2) {
return err_term;
}
if (!SSL_is_init_finished(state->ssl)) {
res = SSL_do_handshake(state->ssl);
if (res <= 0) {
res = SSL_get_error(state->ssl, res);
if (res == SSL_ERROR_WANT_READ || res == SSL_ERROR_WANT_WRITE) {
res = do_send_queue(env, state, &err_term, &to_send);
if (res == 2) {
return err_term;
}
return return_read_write(env, state, bytes_to_read, enif_make_atom(env, "ok"));
} else {
res = do_send_queue(env, state, &err_term, &to_send);
if (res == 2) {
return err_term;
}
int reason = ERR_GET_REASON(ERR_peek_error());
if (reason == SSL_R_DATA_LENGTH_TOO_LONG ||
reason == SSL_R_PACKET_LENGTH_TOO_LONG ||
reason == SSL_R_UNKNOWN_PROTOCOL ||
reason == SSL_R_UNEXPECTED_MESSAGE ||
reason == SSL_R_WRONG_VERSION_NUMBER ||
reason == SSL_R_HTTP_REQUEST ||
reason == SSL_R_HTTPS_PROXY_REQUEST)
/* Do not report badly formed Client Hello */
err_term = ERR_T(enif_make_atom(env, "closed"));
else if (state->sni_error)
err_term = ssl_error(env, state->sni_error);
else
err_term = ssl_error(env, "SSL_do_handshake failed");
return return_read_write(env, state, bytes_to_read, err_term);
}
}
if (!SSL_is_init_finished(state->ssl)) {
res = do_send_queue(env, state, &err_term, &to_send);
if (res == 2) {
return err_term;
}
return return_read_write(env, state, bytes_to_read, enif_make_atom(env, "ok"));
}
}
res = do_send(env, state, &err_term, &to_send);
if (res == 2) {
return err_term;
}
if (res <= 0)
res = SSL_get_error(state->ssl, res);
return return_read_write(env, state, bytes_to_read, enif_make_atom(env, "ok"));
}
static ERL_NIF_TERM get_verify_result_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
long res;
state_t *state = NULL;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
if (!state->mtx || !state->ssl) return enif_make_badarg(env);
enif_mutex_lock(state->mtx);
if (!state->valid) {
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "closed"));
}
ERR_clear_error();
res = SSL_get_verify_result(state->ssl);
enif_mutex_unlock(state->mtx);
return OK_T(enif_make_long(env, res));
}
static ERL_NIF_TERM get_peer_certificate_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
X509 *cert = NULL;
state_t *state = NULL;
int rlen;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
if (!state->mtx || !state->ssl) return enif_make_badarg(env);
enif_mutex_lock(state->mtx);
if (!state->valid) {
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "closed"));
}
ERR_clear_error();
cert = SSL_get_peer_certificate(state->ssl);
if (!cert) {
enif_mutex_unlock(state->mtx);
return ssl_error(env, "SSL_get_peer_certificate failed");
}
rlen = i2d_X509(cert, NULL);
if (rlen >= 0) {
ERL_NIF_TERM bin;
unsigned char *buf = enif_make_new_binary(env, rlen, &bin);
if (!buf) {
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "enomem"));
}
i2d_X509(cert, &buf);
X509_free(cert);
enif_mutex_unlock(state->mtx);
return OK_T(bin);
} else {
X509_free(cert);
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "notfound"));
}
}
static ERL_NIF_TERM add_certfile_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
ErlNifBinary domain, file;
cert_info_t *info = NULL;
cert_info_t *old_info = NULL;
if (!enif_inspect_iolist_as_binary(env, argv[0], &domain))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[1], &file))
return enif_make_badarg(env);
info = enif_alloc(sizeof(cert_info_t));
if (info) {
memset(info, 0, sizeof(cert_info_t));
info->key = enif_alloc(domain.size + 1);
info->file = enif_alloc(file.size + 1);
if (info->key && info->file) {
memcpy(info->key, domain.data, domain.size);
memcpy(info->file, file.data, file.size);
info->key[domain.size] = 0;
info->file[file.size] = 0;
enif_rwlock_rwlock(certfiles_map_lock);
HASH_REPLACE_STR(certfiles_map, key, info, old_info);
free_cert_info(old_info);
enif_rwlock_rwunlock(certfiles_map_lock);
} else {
free_cert_info(info);
}
}
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM delete_certfile_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
ErlNifBinary domain;
char *ret = "false";
cert_info_t *info = NULL;
if (!enif_inspect_iolist_as_binary(env, argv[0], &domain))
return enif_make_badarg(env);
char *key = enif_alloc(domain.size + 1);
if (!key)
return enif_make_atom(env, "false");
memcpy(key, domain.data, domain.size);
key[domain.size] = 0;
enif_rwlock_rwlock(certfiles_map_lock);
HASH_FIND_STR(certfiles_map, key, info);
if (info) {
HASH_DEL(certfiles_map, info);
free_cert_info(info);
ret = "true";
}
enif_rwlock_rwunlock(certfiles_map_lock);
enif_free(key);
return enif_make_atom(env, ret);
}
static ERL_NIF_TERM get_certfile_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
ErlNifBinary domain;
cert_info_t *info = NULL;
ERL_NIF_TERM file, result;
if (!enif_inspect_iolist_as_binary(env, argv[0], &domain))
return enif_make_badarg(env);
char *key = enif_alloc(domain.size + 1);
if (!key)
return enif_make_atom(env, "error");
memcpy(key, domain.data, domain.size);
key[domain.size] = 0;
enif_rwlock_rlock(certfiles_map_lock);
info = lookup_certfile(key);
if (info) {
unsigned char *tmp = enif_make_new_binary(env, strlen(info->file), &file);
if (tmp) {
memcpy(tmp, info->file, strlen(info->file));
result = enif_make_tuple2(env, enif_make_atom(env, "ok"), file);
} else
result = enif_make_atom(env, "error");
} else {
result = enif_make_atom(env, "error");
}
enif_rwlock_runlock(certfiles_map_lock);
enif_free(key);
return result;
}
static ERL_NIF_TERM clear_cache_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
clear_certs_map();
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM invalidate_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
if (!state->mtx || !state->ssl) return enif_make_badarg(env);
enif_mutex_lock(state->mtx);
state->valid = 0;
enif_mutex_unlock(state->mtx);
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM get_negotiated_cipher_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
if (!state->mtx || !state->ssl) return enif_make_badarg(env);
enif_mutex_lock(state->mtx);
if (!state->valid) {
enif_mutex_unlock(state->mtx);
return ERR_T(enif_make_atom(env, "closed"));
}
const char *version = SSL_get_version(state->ssl);
const char *cipher = SSL_get_cipher_name(state->ssl);
enif_mutex_unlock(state->mtx);
ErlNifBinary bin;
size_t vl = strlen(version);
size_t cl = strlen(cipher);
if (!enif_alloc_binary(vl + cl + 1, &bin)) {
return ERR_T(enif_make_atom(env, "enomem"));
}
memcpy(bin.data, version, vl);
bin.data[vl] = ' ';
memcpy(bin.data + vl + 1, cipher, cl);
return enif_make_binary(env, &bin);
}
static ERL_NIF_TERM tls_get_peer_finished_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
#ifdef TLS1_3_VERSION
if (SSL_version(state->ssl) >= TLS1_3_VERSION)
return ERR_T(enif_make_atom(env, "undefined"));
#endif
/* OpenSSL does not offer an API to directly get the length of the
* expected TLS Finished message, so just do a dummy call to grab this
* information to allow caller to do an allocation with a correct size.
*/
ERL_NIF_TERM bin;
size_t len = SSL_get_peer_finished(state->ssl, NULL, 0);
if (len == 0)
return ERR_T(enif_make_atom(env, "undefined"));
unsigned char *buf = enif_make_new_binary(env, len, &bin);
if (!buf)
return ERR_T(enif_make_atom(env, "enomem"));
(void) SSL_get_peer_finished(state->ssl, buf, len);
return OK_T(bin);
}
static ERL_NIF_TERM tls_get_finished_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
#ifdef TLS1_3_VERSION
if (SSL_version(state->ssl) >= TLS1_3_VERSION)
return ERR_T(enif_make_atom(env, "undefined"));
#endif
ERL_NIF_TERM bin;
size_t len = SSL_get_finished(state->ssl, NULL, 0);
if (len == 0)
return ERR_T(enif_make_atom(env, "undefined"));
unsigned char *buf = enif_make_new_binary(env, len, &bin);
if (!buf)
return ERR_T(enif_make_atom(env, "enomem"));
(void) SSL_get_finished(state->ssl, buf, len);
return OK_T(bin);
}
static ERL_NIF_TERM get_tls_cb_exporter_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
state_t *state = NULL;
if (!enif_get_resource(env, argv[0], tls_state_t, (void *) &state))
return enif_make_badarg(env);
#ifdef TLS1_3_VERSION
ERL_NIF_TERM bin;
unsigned char *buf = enif_make_new_binary(env, 32, &bin);
if (!buf)
return ERR_T(enif_make_atom(env, "enomem"));
if (SSL_version(state->ssl) < TLS1_3_VERSION && SSL_get_extms_support(state->ssl) <= 0)
return ERR_T(enif_make_atom(env, "undefined"));
if (SSL_export_keying_material(state->ssl, buf, 32,
"EXPORTER-Channel-Binding", 24, NULL, 0, 1) != 1)
return ERR_T(enif_make_atom(env, "undefined"));
return OK_T(bin);
#else
return ERR_T(enif_make_atom(env, "undefined"));
#endif
}
static ERL_NIF_TERM set_fips_mode_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
int ret = 1;
int enable;
if (argc != 1)
return enif_make_badarg(env);
if (!enif_get_int(env, argv[0], &enable))
return enif_make_badarg(env);
#if OPENSSL_VERSION_NUMBER < 0x30000000L
int fips_mode = FIPS_mode();
if ((fips_mode == 0 && enable != 0) ||
(fips_mode != 0 && enable == 0))
ret = FIPS_mode_set(enable);
if (ret != 1)
return ssl_error(env, "FIPS_mode_set() failed");
#else
int fips_mode = EVP_default_properties_is_fips_enabled(NULL);
if ((fips_mode == 0 && enable != 0) ||
(fips_mode != 0 && enable == 0))
ret = EVP_default_properties_enable_fips(NULL, enable);
if (ret != 1)
return ssl_error(env, "FIPS_mode_set() failed");
#endif
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM get_fips_mode_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
const char *ret = FIPS_mode() ? "true" : "false";
#else
const char *ret = EVP_default_properties_is_fips_enabled(NULL) ? "true" : "false";
#endif
return enif_make_atom(env, ret);
}
static ERL_NIF_TERM p12_to_pem_nif(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
ErlNifBinary p12_bin, pass_bin;
cert_info_t *info = NULL;
cert_info_t *old_info = NULL;
if (argc != 2)
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[0], &p12_bin))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env, argv[1], &pass_bin))
return enif_make_badarg(env);
PKCS12 *p12 = NULL;
EVP_PKEY *pkey = NULL;
X509 *cert;
BIO *bio;
ERL_NIF_TERM res;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
OSSL_LIB_CTX *lib_ctx = OSSL_LIB_CTX_new();
OSSL_PROVIDER *prov_def, *prov_leg;
if (lib_ctx) {
prov_def = OSSL_PROVIDER_load(lib_ctx, "default");
prov_leg = OSSL_PROVIDER_load(lib_ctx, "legacy");
} else {
return ERR_T(enif_make_atom(env, "enomem"));
}
p12 = PKCS12_init_ex(NID_pkcs7_data, lib_ctx, NULL);
if (!p12) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
#endif
pkey = EVP_PKEY_new();
if (!pkey) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
cert = X509_new();
if (!cert) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
bio = BIO_new(BIO_s_mem());
if (!bio) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
BIO* input = BIO_new_mem_buf(p12_bin.data, p12_bin.size);
if (!input) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
PKCS12 *p12_tmp = p12;
if (!d2i_PKCS12_bio(input, &p12_tmp)) {
BIO_free(input);
res = ssl_error(env, "p12_to_pem:decode");
goto clean;
}
BIO_free(input);
#else
p12 = d2i_PKCS12_bio(input, NULL);
BIO_free(input);
if (!p12) {
res = ssl_error(env, "p12_to_pem:decode");
goto clean;
}
#endif
if (!PKCS12_parse(p12, pass_bin.data, &pkey, &cert, NULL)) {
unsigned long err = ERR_peek_error();
if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
res = ERR_T(enif_make_atom(env, "bad_pass"));
goto clean;
}
res = ssl_error(env, "p12_to_pem:parse");
goto clean;
}
if (!PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, 0, NULL)) {
res = ssl_error(env, "p12_to_pem:privkey");
goto clean;
}
ERL_NIF_TERM pkey_bin;
size_t bio_size = BIO_ctrl_pending(bio);
unsigned char *bin_buf = enif_make_new_binary(env, bio_size, &pkey_bin);
if (!bin_buf) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
BIO_read(bio, bin_buf, bio_size);
BIO_reset(bio);
if (!PEM_write_bio_X509(bio, cert)) {
res = ssl_error(env, "p12_to_pem:cert");
goto clean;
}
ERL_NIF_TERM cert_bin;
bio_size = BIO_ctrl_pending(bio);
bin_buf = enif_make_new_binary(env, bio_size, &cert_bin);
if (!bin_buf) {
res = ERR_T(enif_make_atom(env, "enomem"));
goto clean;
}
BIO_read(bio, bin_buf, bio_size);
res = OK_T(enif_make_tuple2(env, pkey_bin, cert_bin));
clean:
PKCS12_free(p12);
EVP_PKEY_free(pkey);
X509_free(cert);
BIO_free(bio);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
if (lib_ctx) {
OSSL_PROVIDER_unload(prov_leg);
OSSL_PROVIDER_unload(prov_def);
OSSL_LIB_CTX_free(lib_ctx);
}
#endif
ERR_clear_error();
return res;
}
static ErlNifFunc nif_funcs[] =
{
{"open_nif", 10, open_nif},
{"loop_nif", 4, loop_nif},
{"get_verify_result_nif", 1, get_verify_result_nif},
{"get_peer_certificate_nif", 1, get_peer_certificate_nif},
{"add_certfile_nif", 2, add_certfile_nif},
{"delete_certfile_nif", 1, delete_certfile_nif},
{"get_certfile_nif", 1, get_certfile_nif},
{"clear_cache_nif", 0, clear_cache_nif},
{"invalidate_nif", 1, invalidate_nif},
{"get_negotiated_cipher_nif", 1, get_negotiated_cipher_nif},
{"tls_get_peer_finished_nif", 1, tls_get_peer_finished_nif},
{"tls_get_finished_nif", 1, tls_get_finished_nif},
{"get_tls_cb_exporter_nif", 1, get_tls_cb_exporter_nif},
{"set_fips_mode_nif", 1, set_fips_mode_nif},
{"get_fips_mode_nif", 0, get_fips_mode_nif},
{"p12_to_pem_nif", 2, p12_to_pem_nif}
};
ERL_NIF_INIT(fast_tls, nif_funcs, load, NULL, NULL, unload)