935 lines
24 KiB
C
935 lines
24 KiB
C
|
|
/*
|
|
* This OpenSSL interface code has been proudly copied from
|
|
* the excellent NGINX web server.
|
|
*
|
|
* Its license is reproduced here.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2002-2013 Igor Sysoev
|
|
* Copyright (C) 2011-2013 Nginx, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* OpenSSL thread-safe example code (ssl_thread_* functions) have been
|
|
* proudly copied from the excellent CURL package, the original author
|
|
* is Jeremy Brown.
|
|
*
|
|
* https://github.com/bagder/curl/blob/master/docs/examples/opensslthreadlock.c
|
|
*
|
|
* COPYRIGHT AND PERMISSION NOTICE
|
|
* Copyright (c) 1996 - 2013, Daniel Stenberg, <daniel@haxx.se>.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any purpose
|
|
* with or without fee is hereby granted, provided that the above copyright
|
|
* notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
|
|
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Except as contained in this notice, the name of a copyright holder shall not
|
|
* be used in advertising or otherwise to promote the sale, use or other dealings
|
|
* in this Software without prior written authorization of the copyright holder.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "ssl.h"
|
|
#include "hlog.h"
|
|
#include "hmalloc.h"
|
|
#include "worker.h"
|
|
|
|
#ifdef USE_SSL
|
|
|
|
#include <ctype.h>
|
|
#include <pthread.h>
|
|
#include <openssl/conf.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
|
|
#define SSL_DEFAULT_CIPHERS "HIGH:eNULL:!aNULL:!MD5"
|
|
#define SSL_PROTOCOLS (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)
|
|
|
|
/* ssl error strings */
|
|
#define SSL_ERR_LABELS_COUNT 6
|
|
static const char *ssl_err_labels[][2] = {
|
|
{ "invalid_err", "Invalid, unknown error" },
|
|
{ "internal_err", "Internal error" },
|
|
{ "peer_cert_unverified", "Peer certificate is not valid or not trusted" },
|
|
{ "no_peer_cert", "Peer did not present a certificate" },
|
|
{ "cert_no_subj", "Certificate does not contain a Subject field" },
|
|
{ "cert_no_callsign", "Certificate does not contain a TQSL callsign in CN - not a ham cert" },
|
|
{ "cert_callsign_mismatch", "Certificate callsign does not match login username" }
|
|
};
|
|
|
|
/* pthread wrapping for openssl */
|
|
#define MUTEX_TYPE pthread_mutex_t
|
|
#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
|
|
#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
|
|
#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
|
|
#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
|
|
#define THREAD_ID pthread_self( )
|
|
|
|
int ssl_available;
|
|
int ssl_connection_index;
|
|
int ssl_server_conf_index;
|
|
int ssl_session_cache_index;
|
|
|
|
/* This array will store all of the mutexes available to OpenSSL. */
|
|
static MUTEX_TYPE *mutex_buf= NULL;
|
|
|
|
static void ssl_thread_locking_function(int mode, int n, const char * file, int line)
|
|
{
|
|
int me;
|
|
|
|
if (mode & CRYPTO_LOCK) {
|
|
me = MUTEX_LOCK(mutex_buf[n]);
|
|
if (me)
|
|
hlog(LOG_ERR, "ssl: could not lock mutex %d: %s", n, strerror(me));
|
|
} else {
|
|
me = MUTEX_UNLOCK(mutex_buf[n]);
|
|
if (me)
|
|
hlog(LOG_ERR, "ssl: could not unlock mutex %d: %s", n, strerror(me));
|
|
}
|
|
}
|
|
|
|
static unsigned long ssl_thread_id_function(void)
|
|
{
|
|
return ((unsigned long)THREAD_ID);
|
|
}
|
|
|
|
static int ssl_thread_setup(void)
|
|
{
|
|
int i;
|
|
|
|
hlog(LOG_DEBUG, "Creating OpenSSL mutexes (%d)...", CRYPTO_num_locks());
|
|
|
|
mutex_buf = hmalloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
|
|
|
|
for (i = 0; i < CRYPTO_num_locks(); i++)
|
|
MUTEX_SETUP(mutex_buf[i]);
|
|
|
|
CRYPTO_set_id_callback(ssl_thread_id_function);
|
|
CRYPTO_set_locking_callback(ssl_thread_locking_function);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssl_thread_cleanup(void)
|
|
{
|
|
int i;
|
|
|
|
if (!mutex_buf)
|
|
return 0;
|
|
|
|
CRYPTO_set_id_callback(NULL);
|
|
CRYPTO_set_locking_callback(NULL);
|
|
|
|
for (i = 0; i < CRYPTO_num_locks( ); i++)
|
|
MUTEX_CLEANUP(mutex_buf[i]);
|
|
|
|
hfree(mutex_buf);
|
|
mutex_buf = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* string representations for error codes
|
|
*/
|
|
|
|
const char *ssl_strerror(int code)
|
|
{
|
|
code *= -1;
|
|
|
|
if (code >= 0 && code < (sizeof ssl_err_labels / sizeof ssl_err_labels[0]))
|
|
return ssl_err_labels[code][1];
|
|
|
|
return ssl_err_labels[0][1];
|
|
}
|
|
|
|
/*
|
|
* Clear OpenSSL error queue
|
|
*/
|
|
|
|
static void ssl_error(int level, const char *msg)
|
|
{
|
|
unsigned long n;
|
|
char errstr[512];
|
|
|
|
for ( ;; ) {
|
|
n = ERR_get_error();
|
|
|
|
if (n == 0)
|
|
break;
|
|
|
|
ERR_error_string_n(n, errstr, sizeof(errstr));
|
|
errstr[sizeof(errstr)-1] = 0;
|
|
|
|
hlog(level, "%s (%d): %s", msg, n, errstr);
|
|
}
|
|
}
|
|
|
|
static void ssl_clear_error(void)
|
|
{
|
|
while (ERR_peek_error()) {
|
|
ssl_error(LOG_INFO, "Ignoring stale SSL error");
|
|
}
|
|
|
|
ERR_clear_error();
|
|
}
|
|
|
|
/*
|
|
* TrustedQSL custom X.509 certificate objects
|
|
*/
|
|
|
|
#define TRUSTEDQSL_OID "1.3.6.1.4.1.12348.1."
|
|
#define TRUSTEDQSL_OID_CALLSIGN TRUSTEDQSL_OID "1"
|
|
#define TRUSTEDQSL_OID_QSO_NOT_BEFORE TRUSTEDQSL_OID "2"
|
|
#define TRUSTEDQSL_OID_QSO_NOT_AFTER TRUSTEDQSL_OID "3"
|
|
#define TRUSTEDQSL_OID_DXCC_ENTITY TRUSTEDQSL_OID "4"
|
|
#define TRUSTEDQSL_OID_SUPERCEDED_CERT TRUSTEDQSL_OID "5"
|
|
#define TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATION TRUSTEDQSL_OID "6"
|
|
#define TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATIONAL_UNIT TRUSTEDQSL_OID "7"
|
|
|
|
static const char *tqsl_NIDs[][2] = {
|
|
{ TRUSTEDQSL_OID_CALLSIGN, "AROcallsign" },
|
|
{ TRUSTEDQSL_OID_QSO_NOT_BEFORE, "QSONotBeforeDate" },
|
|
{ TRUSTEDQSL_OID_QSO_NOT_AFTER, "QSONotAfterDate" },
|
|
{ TRUSTEDQSL_OID_DXCC_ENTITY, "dxccEntity" },
|
|
{ TRUSTEDQSL_OID_SUPERCEDED_CERT, "supercededCertificate" },
|
|
{ TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATION, "tqslCRQIssuerOrganization" },
|
|
{ TRUSTEDQSL_OID_CRQ_ISSUER_ORGANIZATIONAL_UNIT, "tqslCRQIssuerOrganizationalUnit" },
|
|
};
|
|
|
|
static int load_tqsl_custom_objects(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < (sizeof tqsl_NIDs / sizeof tqsl_NIDs[0]); ++i)
|
|
if (OBJ_create(tqsl_NIDs[i][0], tqsl_NIDs[i][1], NULL) == 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ssl_info_callback(SSL *ssl, int where, int ret)
|
|
{
|
|
struct client_t *c = SSL_get_ex_data(ssl, ssl_connection_index);
|
|
|
|
if (!c) {
|
|
hlog(LOG_ERR, "ssl_info_callback: no application data for connection");
|
|
return;
|
|
}
|
|
|
|
struct ssl_connection_t *ssl_conn = c->ssl_con;
|
|
|
|
if (!ssl_conn) {
|
|
hlog(LOG_ERR, "ssl_info_callback: no ssl_conn for connection");
|
|
return;
|
|
}
|
|
|
|
if (where & SSL_CB_HANDSHAKE_START) {
|
|
hlog(LOG_INFO, "%s/%d: SSL handshake start", c->addr_rem, c->fd);
|
|
if (ssl_conn->handshaked) {
|
|
ssl_conn->renegotiation = 1;
|
|
}
|
|
}
|
|
|
|
if (where & SSL_CB_HANDSHAKE_DONE) {
|
|
hlog(LOG_INFO, "%s/%d: SSL handshake done", c->addr_rem, c->fd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize SSL
|
|
*/
|
|
|
|
int ssl_init(void)
|
|
{
|
|
hlog(LOG_INFO, "Initializing OpenSSL, built against %s ...", OPENSSL_VERSION_TEXT);
|
|
|
|
OPENSSL_config(NULL);
|
|
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
|
|
ssl_thread_setup();
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
load_tqsl_custom_objects();
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
|
|
#ifndef SSL_OP_NO_COMPRESSION
|
|
{
|
|
/*
|
|
* Disable gzip compression in OpenSSL prior to 1.0.0 version,
|
|
* this saves about 522K per connection.
|
|
*/
|
|
int n;
|
|
STACK_OF(SSL_COMP) *ssl_comp_methods;
|
|
|
|
ssl_comp_methods = SSL_COMP_get_compression_methods();
|
|
n = sk_SSL_COMP_num(ssl_comp_methods);
|
|
|
|
while (n--) {
|
|
(void) sk_SSL_COMP_pop(ssl_comp_methods);
|
|
}
|
|
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
|
|
|
if (ssl_connection_index == -1) {
|
|
ssl_error(LOG_ERR, "SSL_get_ex_new_index for connection");
|
|
return -1;
|
|
}
|
|
|
|
ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
|
|
|
if (ssl_server_conf_index == -1) {
|
|
ssl_error(LOG_ERR, "SSL_CTX_get_ex_new_index for conf");
|
|
return -1;
|
|
}
|
|
|
|
ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
|
|
|
if (ssl_session_cache_index == -1) {
|
|
ssl_error(LOG_ERR, "SSL_CTX_get_ex_new_index for session cache");
|
|
return -1;
|
|
}
|
|
|
|
ssl_available = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ssl_atend(void)
|
|
{
|
|
ssl_thread_cleanup();
|
|
}
|
|
|
|
struct ssl_t *ssl_alloc(void)
|
|
{
|
|
struct ssl_t *ssl;
|
|
|
|
ssl = hmalloc(sizeof(*ssl));
|
|
memset(ssl, 0, sizeof(*ssl));
|
|
|
|
return ssl;
|
|
}
|
|
|
|
void ssl_free(struct ssl_t *ssl)
|
|
{
|
|
if (ssl->ctx)
|
|
SSL_CTX_free(ssl->ctx);
|
|
|
|
hfree(ssl);
|
|
}
|
|
|
|
int ssl_create(struct ssl_t *ssl, void *data)
|
|
{
|
|
ssl->ctx = SSL_CTX_new(SSLv23_method());
|
|
|
|
if (ssl->ctx == NULL) {
|
|
ssl_error(LOG_ERR, "ssl_create SSL_CTX_new failed");
|
|
return -1;
|
|
}
|
|
|
|
if (SSL_CTX_set_ex_data(ssl->ctx, ssl_server_conf_index, data) == 0) {
|
|
ssl_error(LOG_ERR, "ssl_create SSL_CTX_set_ex_data failed");
|
|
return -1;
|
|
}
|
|
|
|
/* client side options */
|
|
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
|
|
|
|
/* server side options */
|
|
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
|
|
|
|
/* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING);
|
|
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
|
|
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
|
|
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);
|
|
|
|
/* SSL protocols not configurable for now */
|
|
int protocols = SSL_PROTOCOLS;
|
|
|
|
if (!(protocols & NGX_SSL_SSLv2)) {
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);
|
|
}
|
|
if (!(protocols & NGX_SSL_SSLv3)) {
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv3);
|
|
}
|
|
if (!(protocols & NGX_SSL_TLSv1)) {
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);
|
|
}
|
|
|
|
#ifdef SSL_OP_NO_TLSv1_1
|
|
if (!(protocols & NGX_SSL_TLSv1_1)) {
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
|
|
}
|
|
#endif
|
|
#ifdef SSL_OP_NO_TLSv1_2
|
|
if (!(protocols & NGX_SSL_TLSv1_2)) {
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_COMPRESSION
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
|
|
#endif
|
|
|
|
#ifdef SSL_MODE_RELEASE_BUFFERS
|
|
SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
|
|
#endif
|
|
|
|
SSL_CTX_set_mode(ssl->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
|
|
|
SSL_CTX_set_read_ahead(ssl->ctx, 1);
|
|
|
|
SSL_CTX_set_info_callback(ssl->ctx, (void *)ssl_info_callback);
|
|
|
|
if (SSL_CTX_set_cipher_list(ssl->ctx, SSL_DEFAULT_CIPHERS) == 0) {
|
|
ssl_error(LOG_ERR, "ssl_create SSL_CTX_set_cipher_list failed");
|
|
return -1;
|
|
}
|
|
|
|
/* prefer server-selected ciphers */
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Load server key and certificate
|
|
*/
|
|
|
|
int ssl_certificate(struct ssl_t *ssl, const char *certfile, const char *keyfile)
|
|
{
|
|
if (SSL_CTX_use_certificate_chain_file(ssl->ctx, certfile) == 0) {
|
|
hlog(LOG_ERR, "Error while loading SSL certificate chain file \"%s\"", certfile);
|
|
ssl_error(LOG_ERR, "SSL_CTX_use_certificate_chain_file");
|
|
return -1;
|
|
}
|
|
|
|
|
|
if (SSL_CTX_use_PrivateKey_file(ssl->ctx, keyfile, SSL_FILETYPE_PEM) == 0) {
|
|
hlog(LOG_ERR, "Error while loading SSL private key file \"%s\"", keyfile);
|
|
ssl_error(LOG_ERR, "SSL_CTX_use_PrivateKey_file");
|
|
return -1;
|
|
}
|
|
|
|
if (!SSL_CTX_check_private_key(ssl->ctx)) {
|
|
hlog(LOG_ERR, "SSL private key (%s) does not work with this certificate (%s)", keyfile, certfile);
|
|
ssl_error(LOG_ERR, "SSL_CTX_check_private_key");
|
|
return -1;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
|
|
{
|
|
hlog(LOG_DEBUG, "ssl_verify_callback, ok: %d", ok);
|
|
|
|
#if (NGX_DEBUG)
|
|
char *subject, *issuer;
|
|
int err, depth;
|
|
X509 *cert;
|
|
X509_NAME *sname, *iname;
|
|
ngx_connection_t *c;
|
|
ngx_ssl_conn_t *ssl_conn;
|
|
|
|
ssl_conn = X509_STORE_CTX_get_ex_data(x509_store,
|
|
SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
|
|
c = ngx_ssl_get_connection(ssl_conn);
|
|
|
|
cert = X509_STORE_CTX_get_current_cert(x509_store);
|
|
err = X509_STORE_CTX_get_error(x509_store);
|
|
depth = X509_STORE_CTX_get_error_depth(x509_store);
|
|
|
|
sname = X509_get_subject_name(cert);
|
|
subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)";
|
|
|
|
iname = X509_get_issuer_name(cert);
|
|
issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
|
|
|
|
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
|
"verify:%d, error:%d, depth:%d, "
|
|
"subject:\"%s\",issuer: \"%s\"",
|
|
ok, err, depth, subject, issuer);
|
|
|
|
if (sname) {
|
|
OPENSSL_free(subject);
|
|
}
|
|
|
|
if (iname) {
|
|
OPENSSL_free(issuer);
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Load trusted CA certs for verifying our peers
|
|
*/
|
|
|
|
int ssl_ca_certificate(struct ssl_t *ssl, const char *cafile, int depth)
|
|
{
|
|
STACK_OF(X509_NAME) *list;
|
|
|
|
SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ssl_verify_callback);
|
|
SSL_CTX_set_verify_depth(ssl->ctx, depth);
|
|
|
|
if (SSL_CTX_load_verify_locations(ssl->ctx, cafile, NULL) == 0) {
|
|
hlog(LOG_ERR, "Failed to load trusted CA list from \"%s\"", cafile);
|
|
ssl_error(LOG_ERR, "SSL_CTX_load_verify_locations");
|
|
return -1;
|
|
}
|
|
|
|
list = SSL_load_client_CA_file(cafile);
|
|
|
|
if (list == NULL) {
|
|
hlog(LOG_ERR, "Failed to load client CA file from \"%s\"", cafile);
|
|
ssl_error(LOG_ERR, "SSL_load_client_CA_file");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* before 0.9.7h and 0.9.8 SSL_load_client_CA_file()
|
|
* always leaved an error in the error queue
|
|
*/
|
|
|
|
ERR_clear_error();
|
|
|
|
SSL_CTX_set_client_CA_list(ssl->ctx, list);
|
|
|
|
ssl->validate = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create a connect
|
|
*/
|
|
|
|
int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client)
|
|
{
|
|
struct ssl_connection_t *sc;
|
|
|
|
sc = hmalloc(sizeof(*sc));
|
|
sc->connection = SSL_new(ssl->ctx);
|
|
|
|
if (sc->connection == NULL) {
|
|
ssl_error(LOG_ERR, "SSL_new failed");
|
|
hfree(sc);
|
|
return -1;
|
|
}
|
|
|
|
if (SSL_set_fd(sc->connection, c->fd) == 0) {
|
|
ssl_error(LOG_ERR, "SSL_set_fd failed");
|
|
SSL_free(sc->connection);
|
|
hfree(sc);
|
|
return -1;
|
|
}
|
|
|
|
if (i_am_client) {
|
|
SSL_set_connect_state(sc->connection);
|
|
} else {
|
|
SSL_set_accept_state(sc->connection);
|
|
}
|
|
|
|
if (SSL_set_ex_data(sc->connection, ssl_connection_index, c) == 0) {
|
|
ssl_error(LOG_ERR, "SSL_set_ex_data failed");
|
|
SSL_free(sc->connection);
|
|
hfree(sc);
|
|
return -1;
|
|
}
|
|
|
|
sc->validate = ssl->validate;
|
|
c->ssl_con = sc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ssl_free_connection(struct client_t *c)
|
|
{
|
|
if (!c->ssl_con)
|
|
return;
|
|
|
|
SSL_free(c->ssl_con->connection);
|
|
hfree(c->ssl_con);
|
|
c->ssl_con = NULL;
|
|
}
|
|
|
|
int ssl_cert_callsign_match(const char *subj_call, const char *username)
|
|
{
|
|
if (subj_call == NULL || username == NULL)
|
|
return 0;
|
|
|
|
while (*username != '-' && *username != 0 && *subj_call != 0) {
|
|
if (toupper(*username) != toupper(*subj_call))
|
|
return 0; /* mismatch */
|
|
|
|
subj_call++;
|
|
username++;
|
|
}
|
|
|
|
if (*subj_call != 0)
|
|
return 0; /* if username is shorter than subject callsign, we fail */
|
|
|
|
if (*username != '-' && *username != 0)
|
|
return 0; /* if we ran to end of subject callsign but not to end of username or start of SSID, we fail */
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Validate client certificate
|
|
*/
|
|
|
|
int ssl_validate_peer_cert_phase1(struct client_t *c)
|
|
{
|
|
X509 *cert;
|
|
|
|
int rc = SSL_get_verify_result(c->ssl_con->connection);
|
|
|
|
if (rc != X509_V_OK) {
|
|
/* client gave a certificate, but it's not valid */
|
|
hlog(LOG_DEBUG, "%s/%s: Peer SSL certificate verification error %d: %s",
|
|
c->addr_rem, c->username, rc, X509_verify_cert_error_string(rc));
|
|
c->ssl_con->ssl_err_code = rc;
|
|
return SSL_VALIDATE_CLIENT_CERT_UNVERIFIED;
|
|
}
|
|
|
|
cert = SSL_get_peer_certificate(c->ssl_con->connection);
|
|
|
|
if (cert == NULL) {
|
|
/* client did not give a certificate */
|
|
return SSL_VALIDATE_NO_CLIENT_CERT;
|
|
}
|
|
|
|
X509_free(cert);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ssl_validate_peer_cert_phase2(struct client_t *c)
|
|
{
|
|
int ret = -1;
|
|
X509 *cert;
|
|
X509_NAME *sname, *iname;
|
|
char *subject, *issuer;
|
|
char *subj_cn = NULL;
|
|
char *subj_call = NULL;
|
|
int nid, idx;
|
|
X509_NAME_ENTRY *entry;
|
|
ASN1_STRING *edata;
|
|
|
|
cert = SSL_get_peer_certificate(c->ssl_con->connection);
|
|
|
|
if (cert == NULL) {
|
|
/* client did not give a certificate */
|
|
return SSL_VALIDATE_NO_CLIENT_CERT;
|
|
}
|
|
|
|
/* ok, we have a cert, find subject */
|
|
sname = X509_get_subject_name(cert);
|
|
if (!sname) {
|
|
ret = SSL_VALIDATE_CERT_NO_SUBJECT;
|
|
goto fail;
|
|
}
|
|
subject = X509_NAME_oneline(sname, NULL, 0);
|
|
|
|
/* find tqsl callsign */
|
|
nid = OBJ_txt2nid("AROcallsign");
|
|
if (nid == NID_undef) {
|
|
hlog(LOG_ERR, "OBJ_txt2nid could not find NID for AROcallsign");
|
|
ret = SSL_VALIDATE_INTERNAL_ERROR;
|
|
goto fail;
|
|
}
|
|
|
|
idx = X509_NAME_get_index_by_NID(sname, nid, -1);
|
|
if (idx == -1) {
|
|
hlog(LOG_DEBUG, "%s/%s: peer certificate has no callsign: %s", c->addr_rem, c->username, subject);
|
|
ret = SSL_VALIDATE_CERT_NO_CALLSIGN;
|
|
goto fail;
|
|
}
|
|
|
|
entry = X509_NAME_get_entry(sname, idx);
|
|
if (entry != NULL) {
|
|
edata = X509_NAME_ENTRY_get_data(entry);
|
|
if (edata != NULL)
|
|
ASN1_STRING_to_UTF8((unsigned char **)&subj_call, edata);
|
|
}
|
|
|
|
/* find CN of subject */
|
|
idx = X509_NAME_get_index_by_NID(sname, NID_commonName, -1);
|
|
if (idx == -1) {
|
|
hlog(LOG_DEBUG, "%s/%s: peer certificate has no CN: %s", c->addr_rem, c->username, subject);
|
|
} else {
|
|
entry = X509_NAME_get_entry(sname, idx);
|
|
if (entry != NULL) {
|
|
edata = X509_NAME_ENTRY_get_data(entry);
|
|
if (edata != NULL)
|
|
ASN1_STRING_to_UTF8((unsigned char **)&subj_cn, edata);
|
|
}
|
|
}
|
|
|
|
if (!subj_call) {
|
|
hlog(LOG_DEBUG, "%s/%s: peer certificate callsign conversion failed: %s", c->addr_rem, c->username, subject);
|
|
ret = SSL_VALIDATE_CERT_NO_CALLSIGN;
|
|
goto fail;
|
|
}
|
|
|
|
if (!ssl_cert_callsign_match(subj_call, c->username)) {
|
|
ret = SSL_VALIDATE_CERT_CALLSIGN_MISMATCH;
|
|
goto fail;
|
|
}
|
|
|
|
/* find issuer */
|
|
iname = X509_get_issuer_name(cert);
|
|
issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
|
|
|
|
ret = 0;
|
|
hlog(LOG_INFO, "%s/%s: Peer validated using SSL certificate: subject '%s' callsign '%s' CN '%s' issuer '%s'",
|
|
c->addr_rem, c->username, subject, subj_call, (subj_cn) ? subj_cn : "(none)", issuer);
|
|
|
|
/* store copies of cert subject and issuer */
|
|
strncpy(c->cert_subject, subject, sizeof(c->cert_subject));
|
|
c->cert_subject[sizeof(c->cert_subject)-1] = 0;
|
|
strncpy(c->cert_issuer, issuer, sizeof(c->cert_issuer));
|
|
c->cert_issuer[sizeof(c->cert_issuer)-1] = 0;
|
|
|
|
fail:
|
|
/* free up whatever we allocated */
|
|
X509_free(cert);
|
|
|
|
if (subj_call)
|
|
OPENSSL_free(subj_call);
|
|
if (subj_cn)
|
|
OPENSSL_free(subj_cn);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Write data to an SSL socket
|
|
*/
|
|
|
|
int ssl_write(struct worker_t *self, struct client_t *c)
|
|
{
|
|
int n;
|
|
int sslerr;
|
|
int err;
|
|
int to_write;
|
|
|
|
to_write = c->obuf_end - c->obuf_start;
|
|
|
|
//hlog(LOG_DEBUG, "ssl_write fd %d of %d bytes", c->fd, to_write);
|
|
ssl_clear_error();
|
|
|
|
n = SSL_write(c->ssl_con->connection, c->obuf + c->obuf_start, to_write);
|
|
|
|
//hlog(LOG_DEBUG, "SSL_write fd %d returned %d", c->fd, n);
|
|
|
|
if (n > 0) {
|
|
/* ok, we wrote some */
|
|
c->obuf_start += n;
|
|
c->obuf_wtime = tick;
|
|
|
|
/* All done ? */
|
|
if (c->obuf_start >= c->obuf_end) {
|
|
//hlog(LOG_DEBUG, "ssl_write fd %d (%s) obuf empty", c->fd, c->addr_rem);
|
|
c->obuf_start = 0;
|
|
c->obuf_end = 0;
|
|
|
|
/* tell the poller that we have no outgoing data */
|
|
xpoll_outgoing(&self->xp, c->xfd, 0);
|
|
return n;
|
|
}
|
|
|
|
xpoll_outgoing(&self->xp, c->xfd, 1);
|
|
|
|
return n;
|
|
}
|
|
|
|
sslerr = SSL_get_error(c->ssl_con->connection, n);
|
|
err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;
|
|
|
|
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
|
//hlog(LOG_DEBUG, "ssl_write fd %d: SSL_write wants to write again, marking socket for write events", c->fd);
|
|
|
|
/* tell the poller that we have outgoing data */
|
|
xpoll_outgoing(&self->xp, c->xfd, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (sslerr == SSL_ERROR_WANT_READ) {
|
|
/* This happens regularly when our SSL library wants to hear back from the
|
|
* other end before transmitting more, for example when we're an SSL server,
|
|
* we have the initial server-side login prompt to write, but we're still
|
|
* waiting for the client SSL hello to appear. The login prompt is in obuf,
|
|
* so the worker tries to write it every now and then until the handshake
|
|
* is done.
|
|
*/
|
|
//hlog(LOG_DEBUG, "ssl_write fd %d: SSL_write wants to read, returning 0", c->fd);
|
|
|
|
/* tell the poller that we won't be writing now, until we've read... */
|
|
xpoll_outgoing(&self->xp, c->xfd, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (err) {
|
|
hlog(LOG_DEBUG, "ssl_write fd %d: I/O syscall error: %s", c->fd, strerror(err));
|
|
} else {
|
|
char ebuf[255];
|
|
|
|
ERR_error_string_n(sslerr, ebuf, sizeof(ebuf));
|
|
hlog(LOG_INFO, "ssl_write fd %d failed with ret %d sslerr %u errno %d: %s (%s)",
|
|
c->fd, n, sslerr, err, ebuf, ERR_reason_error_string(sslerr));
|
|
}
|
|
|
|
c->ssl_con->no_wait_shutdown = 1;
|
|
c->ssl_con->no_send_shutdown = 1;
|
|
|
|
hlog(LOG_DEBUG, "ssl_write fd %d: SSL_write() failed", c->fd);
|
|
client_close(self, c, err);
|
|
|
|
return -13;
|
|
}
|
|
|
|
int ssl_writable(struct worker_t *self, struct client_t *c)
|
|
{
|
|
int to_write;
|
|
|
|
to_write = c->obuf_end - c->obuf_start;
|
|
|
|
//hlog(LOG_DEBUG, "ssl_writable fd %d, %d available for writing", c->fd, to_write);
|
|
|
|
/* SSL_write does not appreciate writing a 0-length buffer */
|
|
if (to_write == 0) {
|
|
/* tell the poller that we have no outgoing data */
|
|
xpoll_outgoing(&self->xp, c->xfd, 0);
|
|
return 0;
|
|
}
|
|
|
|
return ssl_write(self, c);
|
|
}
|
|
|
|
int ssl_readable(struct worker_t *self, struct client_t *c)
|
|
{
|
|
int r;
|
|
int sslerr, err;
|
|
|
|
//hlog(LOG_DEBUG, "ssl_readable fd %d", c->fd);
|
|
|
|
ssl_clear_error();
|
|
|
|
r = SSL_read(c->ssl_con->connection, c->ibuf + c->ibuf_end, c->ibuf_size - c->ibuf_end - 1);
|
|
|
|
if (r > 0) {
|
|
/* we got some data... process */
|
|
//hlog(LOG_DEBUG, "SSL_read fd %d returned %d bytes of data", c->fd, r);
|
|
|
|
/* TODO: whatever the client_readable does */
|
|
return client_postread(self, c, r);
|
|
}
|
|
|
|
sslerr = SSL_get_error(c->ssl_con->connection, r);
|
|
err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;
|
|
|
|
if (sslerr == SSL_ERROR_WANT_READ) {
|
|
hlog(LOG_DEBUG, "ssl_readable fd %d: SSL_read wants to read again, doing it later", c->fd);
|
|
|
|
if (c->obuf_end - c->obuf_start > 0) {
|
|
/* tell the poller that we have outgoing data */
|
|
xpoll_outgoing(&self->xp, c->xfd, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
|
hlog(LOG_INFO, "ssl_readable fd %d: SSL_read wants to write (peer starts SSL renegotiation?), calling ssl_write", c->fd);
|
|
return ssl_write(self, c);
|
|
}
|
|
|
|
c->ssl_con->no_wait_shutdown = 1;
|
|
c->ssl_con->no_send_shutdown = 1;
|
|
|
|
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
|
|
hlog(LOG_DEBUG, "ssl_readable fd %d: peer shutdown SSL cleanly", c->fd);
|
|
client_close(self, c, CLIERR_EOF);
|
|
return -1;
|
|
}
|
|
|
|
if (err) {
|
|
hlog(LOG_DEBUG, "ssl_readable fd %d: I/O syscall error: %s", c->fd, strerror(err));
|
|
} else {
|
|
char ebuf[255];
|
|
|
|
ERR_error_string_n(sslerr, ebuf, sizeof(ebuf));
|
|
hlog(LOG_INFO, "ssl_readable fd %d failed with ret %d sslerr %d errno %d: %s (%s)",
|
|
c->fd, r, sslerr, err, ebuf, ERR_reason_error_string(sslerr));
|
|
}
|
|
|
|
client_close(self, c, err);
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif
|
|
|