stunnel4/src/ctx.c

1261 lines
40 KiB
C

/*
* stunnel TLS offloading and load-balancing proxy
* Copyright (C) 1998-2017 Michal Trojnara <Michal.Trojnara@stunnel.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses>.
*
* Linking stunnel statically or dynamically with other modules is making
* a combined work based on stunnel. Thus, the terms and conditions of
* the GNU General Public License cover the whole combination.
*
* In addition, as a special exception, the copyright holder of stunnel
* gives you permission to combine stunnel with free software programs or
* libraries that are released under the GNU LGPL and with code included
* in the standard release of OpenSSL under the OpenSSL License (or
* modified versions of such code, with unchanged license). You may copy
* and distribute such a system following the terms of the GNU GPL for
* stunnel and the licenses of the other code concerned.
*
* Note that people who make modified versions of stunnel are not obligated
* to grant this special exception for their modified versions; it is their
* choice whether to do so. The GNU General Public License gives permission
* to release a modified version without this exception; this exception
* also makes it possible to release a modified version which carries
* forward this exception.
*/
#include "common.h"
#include "prototypes.h"
SERVICE_OPTIONS *current_section=NULL;
/* try an empty passphrase first */
static char cached_passwd[PEM_BUFSIZE]="";
static int cached_len=0;
#ifndef OPENSSL_NO_DH
DH *dh_params=NULL;
int dh_needed=0;
#endif /* OPENSSL_NO_DH */
/**************************************** prototypes */
/* SNI */
#ifndef OPENSSL_NO_TLSEXT
NOEXPORT int servername_cb(SSL *, int *, void *);
NOEXPORT int matches_wildcard(char *, char *);
#endif
/* DH/ECDH */
#ifndef OPENSSL_NO_DH
NOEXPORT int dh_init(SERVICE_OPTIONS *);
NOEXPORT DH *dh_read(char *);
#endif /* OPENSSL_NO_DH */
#ifndef OPENSSL_NO_ECDH
NOEXPORT int ecdh_init(SERVICE_OPTIONS *);
#endif /* USE_ECDH */
/* configuration commands */
NOEXPORT int conf_init(SERVICE_OPTIONS *section);
/* authentication */
NOEXPORT int auth_init(SERVICE_OPTIONS *);
#ifndef OPENSSL_NO_PSK
NOEXPORT unsigned psk_client_callback(SSL *, const char *,
char *, unsigned, unsigned char *, unsigned);
NOEXPORT unsigned psk_server_callback(SSL *, const char *,
unsigned char *, unsigned);
#endif /* !defined(OPENSSL_NO_PSK) */
NOEXPORT int load_cert_file(SERVICE_OPTIONS *);
NOEXPORT int load_key_file(SERVICE_OPTIONS *);
NOEXPORT int pkcs12_extension(const char *);
NOEXPORT int load_pkcs12_file(SERVICE_OPTIONS *);
#ifndef OPENSSL_NO_ENGINE
NOEXPORT int load_cert_engine(SERVICE_OPTIONS *);
NOEXPORT int load_key_engine(SERVICE_OPTIONS *);
#endif
NOEXPORT int cache_passwd_get_cb(char *, int, int, void *);
NOEXPORT int cache_passwd_set_cb(char *, int, int, void *);
NOEXPORT void set_prompt(const char *);
NOEXPORT int ui_retry();
/* session callbacks */
NOEXPORT int sess_new_cb(SSL *, SSL_SESSION *);
NOEXPORT SSL_SESSION *sess_get_cb(SSL *,
#if OPENSSL_VERSION_NUMBER>=0x10100000L
const
#endif
unsigned char *, int, int *);
NOEXPORT void sess_remove_cb(SSL_CTX *, SSL_SESSION *);
/* sessiond interface */
NOEXPORT void cache_new(SSL *, SSL_SESSION *);
NOEXPORT SSL_SESSION *cache_get(SSL *, const unsigned char *, int);
NOEXPORT void cache_remove(SSL_CTX *, SSL_SESSION *);
NOEXPORT void cache_transfer(SSL_CTX *, const u_char, const long,
const u_char *, const size_t,
const u_char *, const size_t,
unsigned char **, size_t *);
/* info callbacks */
NOEXPORT void info_callback(const SSL *, int, int);
NOEXPORT void sslerror_queue(void);
NOEXPORT void sslerror_log(unsigned long, char *);
/**************************************** initialize section->ctx */
#if OPENSSL_VERSION_NUMBER>=0x10100000L
typedef long unsigned SSL_OPTIONS_TYPE;
#else
typedef long SSL_OPTIONS_TYPE;
#endif
int context_init(SERVICE_OPTIONS *section) { /* init TLS context */
/* create TLS context */
if(section->option.client)
section->ctx=SSL_CTX_new(section->client_method);
else /* server mode */
section->ctx=SSL_CTX_new(section->server_method);
if(!section->ctx) {
sslerror("SSL_CTX_new");
return 1; /* FAILED */
}
/* for callbacks */
if(!SSL_CTX_set_ex_data(section->ctx, index_ssl_ctx_opt, section)) {
sslerror("SSL_CTX_set_ex_data");
return 1; /* FAILED */
}
current_section=section; /* setup current section for callbacks */
/* ciphers */
if(section->cipher_list) {
s_log(LOG_DEBUG, "Ciphers: %s", section->cipher_list);
if(!SSL_CTX_set_cipher_list(section->ctx, section->cipher_list)) {
sslerror("SSL_CTX_set_cipher_list");
return 1; /* FAILED */
}
}
/* options */
SSL_CTX_set_options(section->ctx,
(SSL_OPTIONS_TYPE)(section->ssl_options_set));
#if OPENSSL_VERSION_NUMBER>=0x009080dfL
SSL_CTX_clear_options(section->ctx,
(SSL_OPTIONS_TYPE)(section->ssl_options_clear));
s_log(LOG_DEBUG, "TLS options: 0x%08lX (+0x%08lX, -0x%08lX)",
SSL_CTX_get_options(section->ctx),
section->ssl_options_set, section->ssl_options_clear);
#else /* OpenSSL older than 0.9.8m */
s_log(LOG_DEBUG, "TLS options: 0x%08lX (+0x%08lX)",
SSL_CTX_get_options(section->ctx),
section->ssl_options_set);
#endif /* OpenSSL 0.9.8m or later */
/* initialize OpenSSL CONF options */
if(conf_init(section))
return 1; /* FAILED */
/* mode */
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(section->ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
SSL_MODE_RELEASE_BUFFERS);
#else
SSL_CTX_set_mode(section->ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
#endif
/* setup session cache */
if(!section->option.client) {
unsigned servname_len=(unsigned)strlen(section->servname);
if(servname_len>SSL_MAX_SSL_SESSION_ID_LENGTH)
servname_len=SSL_MAX_SSL_SESSION_ID_LENGTH;
if(!SSL_CTX_set_session_id_context(section->ctx,
(unsigned char *)section->servname, servname_len)) {
sslerror("SSL_CTX_set_session_id_context");
return 1; /* FAILED */
}
}
SSL_CTX_sess_set_cache_size(section->ctx, section->session_size);
SSL_CTX_set_timeout(section->ctx, section->session_timeout);
SSL_CTX_sess_set_new_cb(section->ctx, sess_new_cb);
SSL_CTX_sess_set_get_cb(section->ctx, sess_get_cb);
SSL_CTX_sess_set_remove_cb(section->ctx, sess_remove_cb);
/* set info callback */
SSL_CTX_set_info_callback(section->ctx, info_callback);
/* load certificate and private key to be verified by the peer server */
if(auth_init(section))
return 1; /* FAILED */
/* initialize verification of the peer server certificate */
if(verify_init(section))
return 1; /* FAILED */
/* initialize the DH/ECDH key agreement in the server mode */
if(!section->option.client) {
#ifndef OPENSSL_NO_TLSEXT
SSL_CTX_set_tlsext_servername_arg(section->ctx, section);
SSL_CTX_set_tlsext_servername_callback(section->ctx, servername_cb);
#endif /* OPENSSL_NO_TLSEXT */
#ifndef OPENSSL_NO_DH
dh_init(section); /* ignore the result (errors are not critical) */
#endif /* OPENSSL_NO_DH */
#ifndef OPENSSL_NO_ECDH
ecdh_init(section); /* ignore the result (errors are not critical) */
#endif /* OPENSSL_NO_ECDH */
}
return 0; /* OK */
}
/**************************************** SNI callback */
#ifndef OPENSSL_NO_TLSEXT
NOEXPORT int servername_cb(SSL *ssl, int *ad, void *arg) {
SERVICE_OPTIONS *section=(SERVICE_OPTIONS *)arg;
const char *servername=SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
SERVERNAME_LIST *list;
CLI *c;
#ifdef USE_LIBWRAP
char *accepted_address;
#endif /* USE_LIBWRAP */
/* leave the alert type at SSL_AD_UNRECOGNIZED_NAME */
(void)ad; /* squash the unused parameter warning */
if(!section->servername_list_head) {
s_log(LOG_DEBUG, "SNI: no virtual services defined");
return SSL_TLSEXT_ERR_OK;
}
if(!servername) {
s_log(LOG_NOTICE, "SNI: no servername received");
return SSL_TLSEXT_ERR_NOACK;
}
s_log(LOG_INFO, "SNI: requested servername: %s", servername);
for(list=section->servername_list_head; list; list=list->next)
if(matches_wildcard((char *)servername, list->servername)) {
s_log(LOG_DEBUG, "SNI: matched pattern: %s", list->servername);
c=SSL_get_ex_data(ssl, index_ssl_cli);
c->opt=list->opt;
SSL_set_SSL_CTX(ssl, c->opt->ctx);
SSL_set_verify(ssl, SSL_CTX_get_verify_mode(c->opt->ctx),
SSL_CTX_get_verify_callback(c->opt->ctx));
s_log(LOG_NOTICE, "SNI: switched to service [%s]",
c->opt->servname);
#ifdef USE_LIBWRAP
accepted_address=s_ntop(&c->peer_addr, c->peer_addr_len);
libwrap_auth(c, accepted_address); /* retry on a service switch */
str_free(accepted_address);
#endif /* USE_LIBWRAP */
return SSL_TLSEXT_ERR_OK;
}
s_log(LOG_ERR, "SNI: no pattern matched servername: %s", servername);
return SSL_TLSEXT_ERR_OK;
}
/* TLSEXT callback return codes:
* - SSL_TLSEXT_ERR_OK
* - SSL_TLSEXT_ERR_ALERT_WARNING
* - SSL_TLSEXT_ERR_ALERT_FATAL
* - SSL_TLSEXT_ERR_NOACK */
NOEXPORT int matches_wildcard(char *servername, char *pattern) {
ssize_t diff;
if(!servername || !pattern)
return 0;
if(*pattern=='*') { /* wildcard comparison */
diff=(ssize_t)strlen(servername)-(ssize_t)strlen(++pattern);
if(diff<0) /* pattern longer than servername */
return 0;
servername+=diff;
}
return !strcasecmp(servername, pattern);
}
#endif /* OPENSSL_NO_TLSEXT */
/**************************************** DH initialization */
#ifndef OPENSSL_NO_DH
#if OPENSSL_VERSION_NUMBER<0x10100000L
NOEXPORT STACK_OF(SSL_CIPHER) *SSL_CTX_get_ciphers(const SSL_CTX *ctx) {
return ctx->cipher_list;
}
#endif
NOEXPORT int dh_init(SERVICE_OPTIONS *section) {
DH *dh=NULL;
int i, n;
char description[128];
STACK_OF(SSL_CIPHER) *ciphers;
/* check if DH is actually enabled for this section */
section->option.dh_needed=0;
ciphers=SSL_CTX_get_ciphers(section->ctx);
if(!ciphers)
return 1; /* ERROR (unlikely) */
n=sk_SSL_CIPHER_num(ciphers);
for(i=0; i<n; ++i) {
*description='\0';
SSL_CIPHER_description(sk_SSL_CIPHER_value(ciphers, i),
description, sizeof description);
/* s_log(LOG_INFO, "Ciphersuite: %s", description); */
if(strstr(description, " Kx=DH")) {
section->option.dh_needed=1; /* update this context */
break;
}
}
if(!section->option.dh_needed) /* no DH ciphers found */
return 0; /* OK */
s_log(LOG_DEBUG, "DH initialization");
#ifndef OPENSSL_NO_ENGINE
if(!section->engine) /* cert is a file and not an identifier */
#endif
dh=dh_read(section->cert);
if(dh) {
SSL_CTX_set_tmp_dh(section->ctx, dh);
s_log(LOG_INFO, "%d-bit DH parameters loaded", 8*DH_size(dh));
DH_free(dh);
return 0; /* OK */
}
stunnel_read_lock(&stunnel_locks[LOCK_DH]);
SSL_CTX_set_tmp_dh(section->ctx, dh_params);
stunnel_read_unlock(&stunnel_locks[LOCK_DH]);
dh_needed=1; /* generate temporary DH parameters in cron */
section->option.dh_needed=1; /* update this context */
s_log(LOG_INFO, "Using dynamic DH parameters");
return 0; /* OK */
}
NOEXPORT DH *dh_read(char *cert) {
DH *dh;
BIO *bio;
if(!cert) {
s_log(LOG_DEBUG, "No certificate available to load DH parameters");
return NULL; /* FAILED */
}
bio=BIO_new_file(cert, "r");
if(!bio) {
sslerror("BIO_new_file");
return NULL; /* FAILED */
}
dh=PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if(!dh) {
while(ERR_get_error())
; /* OpenSSL error queue cleanup */
s_log(LOG_DEBUG, "Could not load DH parameters from %s", cert);
return NULL; /* FAILED */
}
s_log(LOG_DEBUG, "Using DH parameters from %s", cert);
return dh;
}
#endif /* OPENSSL_NO_DH */
/**************************************** ECDH initialization */
#ifndef OPENSSL_NO_ECDH
NOEXPORT int ecdh_init(SERVICE_OPTIONS *section) {
EC_KEY *ecdh;
s_log(LOG_DEBUG, "ECDH initialization");
ecdh=EC_KEY_new_by_curve_name(section->curve);
if(!ecdh) {
sslerror("EC_KEY_new_by_curve_name");
s_log(LOG_ERR, "Cannot create curve %s",
OBJ_nid2ln(section->curve));
return 1; /* FAILED */
}
SSL_CTX_set_tmp_ecdh(section->ctx, ecdh);
EC_KEY_free(ecdh);
s_log(LOG_DEBUG, "ECDH initialized with curve %s",
OBJ_nid2ln(section->curve));
return 0; /* OK */
}
#endif /* OPENSSL_NO_ECDH */
/**************************************** initialize OpenSSL CONF */
NOEXPORT int conf_init(SERVICE_OPTIONS *section) {
#if OPENSSL_VERSION_NUMBER>=0x10002000L
SSL_CONF_CTX *cctx;
NAME_LIST *curr;
char *cmd, *param;
if(!section->config)
return 0; /* OK */
cctx=SSL_CONF_CTX_new();
if(!cctx) {
sslerror("SSL_CONF_CTX_new");
return 1; /* FAILED */
}
SSL_CONF_CTX_set_ssl_ctx(cctx, section->ctx);
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
SSL_CONF_CTX_set_flags(cctx, section->option.client ?
SSL_CONF_FLAG_CLIENT : SSL_CONF_FLAG_SERVER);
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
for(curr=section->config; curr; curr=curr->next) {
cmd=str_dup(curr->name);
param=strchr(cmd, ':');
if(param)
*param++='\0';
switch(SSL_CONF_cmd(cctx, cmd, param)) {
case 2:
s_log(LOG_DEBUG, "OpenSSL config \"%s\" set to \"%s\"", cmd, param);
break;
case 1:
s_log(LOG_DEBUG, "OpenSSL config command \"%s\" executed", cmd);
break;
case -2:
s_log(LOG_ERR,
"OpenSSL config command \"%s\" was not recognised", cmd);
str_free(cmd);
SSL_CONF_CTX_free(cctx);
return 1; /* FAILED */
case -3:
s_log(LOG_ERR,
"OpenSSL config command \"%s\" requires a parameter", cmd);
str_free(cmd);
SSL_CONF_CTX_free(cctx);
return 1; /* FAILED */
default:
sslerror("SSL_CONF_cmd");
str_free(cmd);
SSL_CONF_CTX_free(cctx);
return 1; /* FAILED */
}
str_free(cmd);
}
if(!SSL_CONF_CTX_finish(cctx)) {
sslerror("SSL_CONF_CTX_finish");
SSL_CONF_CTX_free(cctx);
return 1; /* FAILED */
}
SSL_CONF_CTX_free(cctx);
#else /* OpenSSL earlier than 1.0.2 */
(void)section; /* squash the unused parameter warning */
#endif /* OpenSSL 1.0.2 or later */
return 0; /* OK */
}
/**************************************** initialize authentication */
NOEXPORT int auth_init(SERVICE_OPTIONS *section) {
int cert_needed=1, key_needed=1;
/* initialize PSK */
#ifndef OPENSSL_NO_PSK
if(section->psk_keys) {
if(section->option.client)
SSL_CTX_set_psk_client_callback(section->ctx, psk_client_callback);
else
SSL_CTX_set_psk_server_callback(section->ctx, psk_server_callback);
}
#endif /* !defined(OPENSSL_NO_PSK) */
/* initialize the client cert engine */
#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_VERSION_NUMBER>=0x0090809fL
/* SSL_CTX_set_client_cert_engine() was introduced in OpenSSL 0.9.8i */
if(section->option.client && section->engine) {
if(SSL_CTX_set_client_cert_engine(section->ctx, section->engine)) {
s_log(LOG_INFO, "Client certificate engine (%s) enabled",
ENGINE_get_id(section->engine));
} else { /* no client certificate functionality in this engine */
while(ERR_get_error())
; /* OpenSSL error queue cleanup */
s_log(LOG_INFO, "Client certificate engine (%s) not supported",
ENGINE_get_id(section->engine));
}
}
#endif
/* load the certificate and private key */
if(!section->cert || !section->key) {
s_log(LOG_DEBUG, "No certificate or private key specified");
return 0; /* OK */
}
#ifndef OPENSSL_NO_ENGINE
if(section->engine) { /* try to use the engine first */
cert_needed=load_cert_engine(section);
key_needed=load_key_engine(section);
}
#endif
if (cert_needed && pkcs12_extension(section->cert)) {
if (load_pkcs12_file(section)) {
return 1; /* FAILED */
}
cert_needed=key_needed=0; /* don't load any PEM files */
}
if(cert_needed && load_cert_file(section))
return 1; /* FAILED */
if(key_needed && load_key_file(section))
return 1; /* FAILED */
/* validate the private key against the certificate */
if(!SSL_CTX_check_private_key(section->ctx)) {
sslerror("Private key does not match the certificate");
return 1; /* FAILED */
}
s_log(LOG_DEBUG, "Private key check succeeded");
return 0; /* OK */
}
#ifndef OPENSSL_NO_PSK
NOEXPORT unsigned psk_client_callback(SSL *ssl, const char *hint,
char *identity, unsigned max_identity_len,
unsigned char *psk, unsigned max_psk_len) {
CLI *c;
size_t identity_len;
(void)hint; /* squash the unused parameter warning */
c=SSL_get_ex_data(ssl, index_ssl_cli);
if(!c->opt->psk_selected) {
s_log(LOG_ERR, "INTERNAL ERROR: No PSK identity selected");
return 0;
}
/* the source seems to have its buffer large enough for
* the trailing null character, but the manual page says
* nothing about it -- lets play safe */
identity_len=strlen(c->opt->psk_selected->identity)+1;
if(identity_len>max_identity_len) {
s_log(LOG_ERR, "PSK identity too long (%lu>%d bytes)",
(long unsigned)identity_len, max_psk_len);
return 0;
}
if(c->opt->psk_selected->key_len>max_psk_len) {
s_log(LOG_ERR, "PSK too long (%lu>%d bytes)",
(long unsigned)c->opt->psk_selected->key_len, max_psk_len);
return 0;
}
strcpy(identity, c->opt->psk_selected->identity);
memcpy(psk, c->opt->psk_selected->key_val, c->opt->psk_selected->key_len);
s_log(LOG_INFO, "PSK client configured for identity \"%s\"", identity);
return (unsigned)(c->opt->psk_selected->key_len);
}
NOEXPORT unsigned psk_server_callback(SSL *ssl, const char *identity,
unsigned char *psk, unsigned max_psk_len) {
CLI *c;
PSK_KEYS *found;
size_t len;
c=SSL_get_ex_data(ssl, index_ssl_cli);
found=psk_find(&c->opt->psk_sorted, identity);
if(found) {
len=found->key_len;
} else {
s_log(LOG_ERR, "No key found for PSK identity \"%s\"", identity);
len=0;
}
if(len>max_psk_len) {
s_log(LOG_ERR, "PSK too long (%lu>%d bytes)",
(long unsigned)len, max_psk_len);
len=0;
}
if(len) {
memcpy(psk, found->key_val, len);
s_log(LOG_NOTICE, "Key configured for PSK identity \"%s\"", identity);
} else { /* block identity probes if possible */
if(max_psk_len>=32 && RAND_bytes(psk, 32)>0) {
len=32; /* 256 random bits */
s_log(LOG_ERR, "Configured random PSK");
} else {
s_log(LOG_ERR, "Rejecting with unknown_psk_identity alert");
}
}
return (unsigned)len;
}
NOEXPORT int psk_compar(const void *a, const void *b) {
PSK_KEYS *x=*(PSK_KEYS **)a, *y=*(PSK_KEYS **)b;
#if 0
s_log(LOG_DEBUG, "PSK cmp: %s %s", x->identity, y->identity);
#endif
return strcmp(x->identity, y->identity);
}
void psk_sort(PSK_TABLE *table, PSK_KEYS *head) {
PSK_KEYS *curr;
size_t i;
table->num=0;
for(curr=head; curr; curr=curr->next)
++table->num;
s_log(LOG_INFO, "PSK identities: %lu retrieved",
(long unsigned)table->num);
table->val=str_alloc(table->num*sizeof(PSK_KEYS *));
for(curr=head, i=0; i<table->num; ++i) {
table->val[i]=curr;
curr=curr->next;
}
qsort(table->val, table->num, sizeof(PSK_KEYS *), psk_compar);
#if 0
for(i=0; i<table->num; ++i)
s_log(LOG_DEBUG, "PSK table: %s", table->val[i]->identity);
#endif
}
PSK_KEYS *psk_find(const PSK_TABLE *table, const char *identity) {
PSK_KEYS key, *ptr=&key, **ret;
key.identity=(char *)identity;
ret=bsearch(&ptr,
table->val, table->num, sizeof(PSK_KEYS *), psk_compar);
return ret ? *ret : NULL;
}
#endif /* !defined(OPENSSL_NO_PSK) */
NOEXPORT int pkcs12_extension(const char *filename) {
const char *ext=strrchr(filename, '.');
return ext && (!strcasecmp(ext, ".p12") || !strcasecmp(ext, ".pfx"));
}
NOEXPORT int load_pkcs12_file(SERVICE_OPTIONS *section) {
size_t len;
int i, success;
BIO *bio=NULL;
PKCS12 *p12=NULL;
X509 *cert=NULL;
STACK_OF(X509) *ca=NULL;
EVP_PKEY *pkey=NULL;
char pass[PEM_BUFSIZE];
s_log(LOG_INFO, "Loading certificate and private key from file: %s",
section->cert);
if(file_permissions(section->cert))
return 1; /* FAILED */
bio=BIO_new_file(section->cert, "rb");
if(!bio) {
sslerror("BIO_new_file");
return 1; /* FAILED */
}
p12=d2i_PKCS12_bio(bio, NULL);
if(!p12) {
sslerror("d2i_PKCS12_bio");
BIO_free(bio);
return 1; /* FAILED */
}
BIO_free(bio);
/* try the cached value first */
set_prompt(section->cert);
len=(size_t)cache_passwd_get_cb(pass, sizeof pass, 0, NULL);
if(len>=sizeof pass)
len=sizeof pass-1;
pass[len]='\0'; /* null-terminate */
success=PKCS12_parse(p12, pass, &pkey, &cert, &ca);
/* invoke the UI */
for(i=0; !success && i<3; i++) {
if(!ui_retry())
break;
if(i==0) { /* silence the cached attempt */
ERR_clear_error();
} else {
sslerror_queue(); /* dump the error queue */
s_log(LOG_ERR, "Wrong passphrase: retrying");
}
/* invoke the UI on subsequent calls */
len=(size_t)cache_passwd_set_cb(pass, sizeof pass, 0, NULL);
if(len>=sizeof pass)
len=sizeof pass-1;
pass[len]='\0'; /* null-terminate */
success=PKCS12_parse(p12, pass, &pkey, &cert, &ca);
}
if(!success) {
sslerror("PKCS12_parse");
PKCS12_free(p12);
return 1; /* FAILED */
}
PKCS12_free(p12);
if(!SSL_CTX_use_certificate(section->ctx, cert)) {
sslerror("SSL_CTX_use_certificate");
return 1; /* FAILED */
}
if(!SSL_CTX_use_PrivateKey(section->ctx, pkey)) {
sslerror("SSL_CTX_use_PrivateKey");
return 1; /* FAILED */
}
s_log(LOG_INFO, "Certificate and private key loaded from file: %s",
section->cert);
return 0; /* OK */
}
NOEXPORT int load_cert_file(SERVICE_OPTIONS *section) {
s_log(LOG_INFO, "Loading certificate from file: %s", section->cert);
if(!SSL_CTX_use_certificate_chain_file(section->ctx, section->cert)) {
sslerror("SSL_CTX_use_certificate_chain_file");
return 1; /* FAILED */
}
s_log(LOG_INFO, "Certificate loaded from file: %s", section->cert);
return 0; /* OK */
}
NOEXPORT int load_key_file(SERVICE_OPTIONS *section) {
int i, success;
s_log(LOG_INFO, "Loading private key from file: %s", section->key);
if(file_permissions(section->key))
return 1; /* FAILED */
/* try the cached value first */
set_prompt(section->key);
SSL_CTX_set_default_passwd_cb(section->ctx, cache_passwd_get_cb);
success=SSL_CTX_use_PrivateKey_file(section->ctx, section->key,
SSL_FILETYPE_PEM);
/* invoke the UI on subsequent calls */
SSL_CTX_set_default_passwd_cb(section->ctx, cache_passwd_set_cb);
/* invoke the UI */
for(i=0; !success && i<3; i++) {
if(!ui_retry())
break;
if(i==0) { /* silence the cached attempt */
ERR_clear_error();
} else {
sslerror_queue(); /* dump the error queue */
s_log(LOG_ERR, "Wrong passphrase: retrying");
}
success=SSL_CTX_use_PrivateKey_file(section->ctx, section->key,
SSL_FILETYPE_PEM);
}
if(!success) {
sslerror("SSL_CTX_use_PrivateKey_file");
return 1; /* FAILED */
}
s_log(LOG_INFO, "Private key loaded from file: %s", section->key);
return 0; /* OK */
}
#ifndef OPENSSL_NO_ENGINE
NOEXPORT int load_cert_engine(SERVICE_OPTIONS *section) {
struct {
const char *id;
X509 *cert;
} parms;
s_log(LOG_INFO, "Loading certificate from engine ID: %s", section->cert);
parms.id=section->cert;
parms.cert=NULL;
ENGINE_ctrl_cmd(section->engine, "LOAD_CERT_CTRL", 0, &parms, NULL, 1);
if(!parms.cert) {
sslerror("ENGINE_ctrl_cmd");
return 1; /* FAILED */
}
if(!SSL_CTX_use_certificate(section->ctx, parms.cert)) {
sslerror("SSL_CTX_use_certificate");
return 1; /* FAILED */
}
s_log(LOG_INFO, "Certificate loaded from engine ID: %s", section->cert);
return 0; /* OK */
}
NOEXPORT int load_key_engine(SERVICE_OPTIONS *section) {
int i;
EVP_PKEY *pkey;
s_log(LOG_INFO, "Initializing private key on engine ID: %s", section->key);
/* do not use caching for engine PINs to prevent device lockout */
SSL_CTX_set_default_passwd_cb(section->ctx, ui_passwd_cb);
for(i=0; i<3; i++) {
pkey=ENGINE_load_private_key(section->engine, section->key,
UI_stunnel(), NULL);
if(!pkey) {
if(i<2 && ui_retry()) { /* wrong PIN */
sslerror_queue(); /* dump the error queue */
s_log(LOG_ERR, "Wrong PIN: retrying");
continue;
}
sslerror("ENGINE_load_private_key");
return 1; /* FAILED */
}
if(SSL_CTX_use_PrivateKey(section->ctx, pkey))
break; /* success */
sslerror("SSL_CTX_use_PrivateKey");
return 1; /* FAILED */
}
s_log(LOG_INFO, "Private key initialized on engine ID: %s", section->key);
return 0; /* OK */
}
#endif /* !defined(OPENSSL_NO_ENGINE) */
/* additional caching layer on top of ui_passwd_cb() */
/* retrieve the cached passwd */
NOEXPORT int cache_passwd_get_cb(char *buf, int size,
int rwflag, void *userdata) {
int len=cached_len;
(void)rwflag; /* squash the unused parameter warning */
(void)userdata; /* squash the unused parameter warning */
if(len<0 || size<0) /* the API uses signed integers */
return 0;
if(len>size) /* truncate the returned data if needed */
len=size;
memcpy(buf, cached_passwd, (size_t)len);
return len;
}
/* cache the passwd retrieved from UI */
NOEXPORT int cache_passwd_set_cb(char *buf, int size,
int rwflag, void *userdata) {
memset(cached_passwd, 0, sizeof cached_passwd);
cached_len=ui_passwd_cb(cached_passwd, sizeof cached_passwd,
rwflag, userdata);
return cache_passwd_get_cb(buf, size, rwflag, userdata);
}
NOEXPORT void set_prompt(const char *name) {
char *prompt;
prompt=str_printf("Enter %s pass phrase:", name);
EVP_set_pw_prompt(prompt);
str_free(prompt);
}
NOEXPORT int ui_retry() {
unsigned long err=ERR_peek_error();
switch(ERR_GET_LIB(err)) {
case ERR_LIB_ASN1:
return 1;
case ERR_LIB_PKCS12:
switch(ERR_GET_REASON(err)) {
case PKCS12_R_MAC_VERIFY_FAILURE:
return 1;
default:
return 0;
}
case ERR_LIB_EVP:
switch(ERR_GET_REASON(err)) {
case EVP_R_BAD_DECRYPT:
return 1;
default:
return 0;
}
case ERR_LIB_PEM:
switch(ERR_GET_REASON(err)) {
case PEM_R_BAD_PASSWORD_READ:
return 1;
default:
return 0;
}
case ERR_LIB_UI:
switch(ERR_GET_REASON(err)) {
case UI_R_RESULT_TOO_LARGE:
case UI_R_RESULT_TOO_SMALL:
return 1;
default:
return 0;
}
case ERR_LIB_USER: /* PKCS#11 hacks */
switch(ERR_GET_REASON(err)) {
case 7UL: /* CKR_ARGUMENTS_BAD */
case 0xa0UL: /* CKR_PIN_INCORRECT */
return 1;
default:
return 0;
}
default:
return 0;
}
}
/**************************************** session callbacks */
NOEXPORT int sess_new_cb(SSL *ssl, SSL_SESSION *sess) {
CLI *c;
s_log(LOG_DEBUG, "New session callback");
c=SSL_get_ex_data(ssl, index_ssl_cli);
if(c->opt->option.sessiond)
cache_new(ssl, sess);
return 0; /* the OpenSSL's manual is really bad -> use the source here */
}
NOEXPORT SSL_SESSION *sess_get_cb(SSL *ssl,
#if OPENSSL_VERSION_NUMBER>=0x10100000L
const
#endif
unsigned char *key, int key_len, int *do_copy) {
CLI *c;
s_log(LOG_DEBUG, "Get session callback");
*do_copy=0; /* allow the session to be freed automatically */
c=SSL_get_ex_data(ssl, index_ssl_cli);
if(c->opt->option.sessiond)
return cache_get(ssl, key, key_len);
return NULL; /* no session to resume */
}
NOEXPORT void sess_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) {
SERVICE_OPTIONS *opt;
s_log(LOG_DEBUG, "Remove session callback");
opt=SSL_CTX_get_ex_data(ctx, index_ssl_ctx_opt);
if(opt->option.sessiond)
cache_remove(ctx, sess);
}
/**************************************** sessiond functionality */
#define CACHE_CMD_NEW 0x00
#define CACHE_CMD_GET 0x01
#define CACHE_CMD_REMOVE 0x02
#define CACHE_RESP_ERR 0x80
#define CACHE_RESP_OK 0x81
NOEXPORT void cache_new(SSL *ssl, SSL_SESSION *sess) {
unsigned char *val, *val_tmp;
ssize_t val_len;
const unsigned char *session_id;
unsigned int session_id_length;
val_len=i2d_SSL_SESSION(sess, NULL);
val_tmp=val=str_alloc((size_t)val_len);
i2d_SSL_SESSION(sess, &val_tmp);
#if OPENSSL_VERSION_NUMBER>=0x0090800fL
session_id=SSL_SESSION_get_id(sess, &session_id_length);
#else
session_id=(const unsigned char *)sess->session_id;
session_id_length=sess->session_id_length;
#endif
cache_transfer(SSL_get_SSL_CTX(ssl), CACHE_CMD_NEW,
SSL_SESSION_get_timeout(sess),
session_id, session_id_length, val, (size_t)val_len, NULL, NULL);
str_free(val);
}
NOEXPORT SSL_SESSION *cache_get(SSL *ssl,
const unsigned char *key, int key_len) {
unsigned char *val=NULL, *val_tmp=NULL;
ssize_t val_len=0;
SSL_SESSION *sess;
cache_transfer(SSL_get_SSL_CTX(ssl), CACHE_CMD_GET, 0,
key, (size_t)key_len, NULL, 0, &val, (size_t *)&val_len);
if(!val)
return NULL;
val_tmp=val;
sess=d2i_SSL_SESSION(NULL,
#if OPENSSL_VERSION_NUMBER>=0x0090800fL
(const unsigned char **)
#endif /* OpenSSL version >= 0.8.0 */
&val_tmp, (long)val_len);
str_free(val);
return sess;
}
NOEXPORT void cache_remove(SSL_CTX *ctx, SSL_SESSION *sess) {
const unsigned char *session_id;
unsigned int session_id_length;
#if OPENSSL_VERSION_NUMBER>=0x0090800fL
session_id=SSL_SESSION_get_id(sess, &session_id_length);
#else
session_id=(const unsigned char *)sess->session_id;
session_id_length=sess->session_id_length;
#endif
cache_transfer(ctx, CACHE_CMD_REMOVE, 0,
session_id, session_id_length, NULL, 0, NULL, NULL);
}
#define MAX_VAL_LEN 512
typedef struct {
u_char version, type;
u_short timeout;
u_char key[SSL_MAX_SSL_SESSION_ID_LENGTH];
u_char val[MAX_VAL_LEN];
} CACHE_PACKET;
NOEXPORT void cache_transfer(SSL_CTX *ctx, const u_char type,
const long timeout,
const u_char *key, const size_t key_len,
const u_char *val, const size_t val_len,
unsigned char **ret, size_t *ret_len) {
char session_id_txt[2*SSL_MAX_SSL_SESSION_ID_LENGTH+1];
const char hex[16]="0123456789ABCDEF";
const char *type_description[]={"new", "get", "remove"};
unsigned i;
SOCKET s;
ssize_t len;
struct timeval t;
CACHE_PACKET *packet;
SERVICE_OPTIONS *section;
if(ret) /* set error as the default result if required */
*ret=NULL;
/* log the request information */
for(i=0; i<key_len && i<SSL_MAX_SSL_SESSION_ID_LENGTH; ++i) {
session_id_txt[2*i]=hex[key[i]>>4];
session_id_txt[2*i+1]=hex[key[i]&0x0f];
}
session_id_txt[2*i]='\0';
s_log(LOG_INFO,
"cache_transfer: request=%s, timeout=%ld, id=%s, length=%lu",
type_description[type], timeout, session_id_txt, (long unsigned)val_len);
/* allocate UDP packet buffer */
if(key_len>SSL_MAX_SSL_SESSION_ID_LENGTH) {
s_log(LOG_ERR, "cache_transfer: session id too big (%lu bytes)",
(unsigned long)key_len);
return;
}
if(val_len>MAX_VAL_LEN) {
s_log(LOG_ERR, "cache_transfer: encoded session too big (%lu bytes)",
(unsigned long)key_len);
return;
}
packet=str_alloc(sizeof(CACHE_PACKET));
/* setup packet */
packet->version=1;
packet->type=type;
packet->timeout=htons((u_short)(timeout<64800?timeout:64800));/* 18 hours */
memcpy(packet->key, key, key_len);
if(val && val_len) /* only check it to make code analysis tools happy */
memcpy(packet->val, val, val_len);
/* create the socket */
s=s_socket(AF_INET, SOCK_DGRAM, 0, 0, "cache_transfer: socket");
if(s==INVALID_SOCKET) {
str_free(packet);
return;
}
/* retrieve pointer to the section structure of this ctx */
section=SSL_CTX_get_ex_data(ctx, index_ssl_ctx_opt);
if(sendto(s, (void *)packet,
#ifdef USE_WIN32
(int)
#endif
(sizeof(CACHE_PACKET)-MAX_VAL_LEN+val_len),
0, &section->sessiond_addr.sa,
addr_len(&section->sessiond_addr))<0) {
sockerror("cache_transfer: sendto");
closesocket(s);
str_free(packet);
return;
}
if(!ret || !ret_len) { /* no response is required */
closesocket(s);
str_free(packet);
return;
}
/* set recvfrom timeout to 200ms */
t.tv_sec=0;
t.tv_usec=200;
if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (void *)&t, sizeof t)<0) {
sockerror("cache_transfer: setsockopt SO_RCVTIMEO");
closesocket(s);
str_free(packet);
return;
}
/* retrieve response */
len=recv(s, (void *)packet, sizeof(CACHE_PACKET), 0);
closesocket(s);
if(len<0) {
if(get_last_socket_error()==S_EWOULDBLOCK ||
get_last_socket_error()==S_EAGAIN)
s_log(LOG_INFO, "cache_transfer: recv timeout");
else
sockerror("cache_transfer: recv");
str_free(packet);
return;
}
/* parse results */
if(len<(int)sizeof(CACHE_PACKET)-MAX_VAL_LEN || /* too short */
packet->version!=1 || /* wrong version */
safe_memcmp(packet->key, key, key_len)) { /* wrong session id */
s_log(LOG_DEBUG, "cache_transfer: malformed packet received");
str_free(packet);
return;
}
if(packet->type!=CACHE_RESP_OK) {
s_log(LOG_INFO, "cache_transfer: session not found");
str_free(packet);
return;
}
*ret_len=(size_t)len-(sizeof(CACHE_PACKET)-MAX_VAL_LEN);
*ret=str_alloc(*ret_len);
s_log(LOG_INFO, "cache_transfer: session found");
memcpy(*ret, packet->val, *ret_len);
str_free(packet);
}
/**************************************** informational callback */
NOEXPORT void info_callback(const SSL *ssl, int where, int ret) {
CLI *c;
SSL_CTX *ctx;
const char *state_string;
c=SSL_get_ex_data((SSL *)ssl, index_ssl_cli);
if(c) {
int state=SSL_get_state((SSL *)ssl);
#if 0
s_log(LOG_DEBUG, "state = %x", state);
#endif
/* log the client certificate request (if received) */
#ifndef SSL3_ST_CR_CERT_REQ_A
if(state==TLS_ST_CR_CERT_REQ)
#else
if(state==SSL3_ST_CR_CERT_REQ_A)
#endif
print_client_CA_list(SSL_get_client_CA_list(ssl));
#ifndef SSL3_ST_CR_SRVR_DONE_A
if(state==TLS_ST_CR_SRVR_DONE)
#else
if(state==SSL3_ST_CR_SRVR_DONE_A)
#endif
if(!SSL_get_client_CA_list(ssl))
s_log(LOG_INFO, "Client certificate not requested");
/* prevent renegotiation DoS attack */
if((where&SSL_CB_HANDSHAKE_DONE)
&& c->reneg_state==RENEG_INIT) {
/* first (initial) handshake was completed, remember this,
* so that further renegotiation attempts can be detected */
c->reneg_state=RENEG_ESTABLISHED;
} else if((where&SSL_CB_ACCEPT_LOOP)
&& c->reneg_state==RENEG_ESTABLISHED) {
#ifndef SSL3_ST_SR_CLNT_HELLO_A
if(state==TLS_ST_SR_CLNT_HELLO) {
#else
if(state==SSL3_ST_SR_CLNT_HELLO_A
|| state==SSL23_ST_SR_CLNT_HELLO_A) {
#endif
/* client hello received after initial handshake,
* this means renegotiation -> mark it */
c->reneg_state=RENEG_DETECTED;
}
}
if(c->opt->log_level<LOG_DEBUG) /* performance optimization */
return;
}
if(where & SSL_CB_LOOP) {
state_string=SSL_state_string_long(ssl);
if(strcmp(state_string, "unknown state"))
s_log(LOG_DEBUG, "TLS state (%s): %s",
(where & SSL_ST_CONNECT) ? "connect" :
(where & SSL_ST_ACCEPT) ? "accept" :
"undefined", state_string);
} else if(where & SSL_CB_ALERT) {
s_log(LOG_DEBUG, "TLS alert (%s): %s: %s",
(where & SSL_CB_READ) ? "read" : "write",
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
} else if(where==SSL_CB_HANDSHAKE_DONE) {
ctx=SSL_get_SSL_CTX((SSL *)ssl);
if(c->opt->option.client) {
s_log(LOG_DEBUG, "%6ld client connect(s) requested",
SSL_CTX_sess_connect(ctx));
s_log(LOG_DEBUG, "%6ld client connect(s) succeeded",
SSL_CTX_sess_connect_good(ctx));
s_log(LOG_DEBUG, "%6ld client renegotiation(s) requested",
SSL_CTX_sess_connect_renegotiate(ctx));
} else {
s_log(LOG_DEBUG, "%6ld server accept(s) requested",
SSL_CTX_sess_accept(ctx));
s_log(LOG_DEBUG, "%6ld server accept(s) succeeded",
SSL_CTX_sess_accept_good(ctx));
s_log(LOG_DEBUG, "%6ld server renegotiation(s) requested",
SSL_CTX_sess_accept_renegotiate(ctx));
}
/* according to the source it not only includes internal
and external session caches, but also session tickets */
s_log(LOG_DEBUG, "%6ld session reuse(s)",
SSL_CTX_sess_hits(ctx));
if(!c->opt->option.client) { /* server session cache stats */
s_log(LOG_DEBUG, "%6ld internal session cache item(s)",
SSL_CTX_sess_number(ctx));
s_log(LOG_DEBUG, "%6ld internal session cache fill-up(s)",
SSL_CTX_sess_cache_full(ctx));
s_log(LOG_DEBUG, "%6ld internal session cache miss(es)",
SSL_CTX_sess_misses(ctx));
s_log(LOG_DEBUG, "%6ld external session cache hit(s)",
SSL_CTX_sess_cb_hits(ctx));
s_log(LOG_DEBUG, "%6ld expired session(s) retrieved",
SSL_CTX_sess_timeouts(ctx));
}
}
}
/**************************************** TLS error reporting */
void sslerror(char *txt) { /* OpenSSL error handler */
unsigned long err;
err=ERR_get_error();
if(err) {
sslerror_queue();
sslerror_log(err, txt);
} else {
s_log(LOG_ERR, "%s: Peer suddenly disconnected", txt);
}
}
NOEXPORT void sslerror_queue(void) { /* recursive dump of the error queue */
unsigned long err;
err=ERR_get_error();
if(err) {
sslerror_queue();
sslerror_log(err, "error queue");
}
}
NOEXPORT void sslerror_log(unsigned long err, char *txt) {
char *error_string;
error_string=str_alloc(256);
ERR_error_string_n(err, error_string, 256);
s_log(LOG_ERR, "%s: %lX: %s", txt, err, error_string);
str_free(error_string);
}
/* end of ctx.c */