Current section

Files

Jump to
fast_tls c_src fast_tls.c
Raw

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)