aprsc/src/ssl.c

632 lines
16 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 <pthread.h>
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define SSL_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
#define SSL_PROTOCOLS (NGX_SSL_SSLv3|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)
/* 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)
{
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(mutex_buf[n]);
else
MUTEX_UNLOCK(mutex_buf[n]);
}
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;
}
int ssl_init(void)
{
hlog(LOG_INFO, "Initializing OpenSSL...");
OPENSSL_config(NULL);
SSL_library_init();
SSL_load_error_strings();
ssl_thread_setup();
OpenSSL_add_all_algorithms();
#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) {
hlog(LOG_ERR, "SSL_get_ex_new_index() failed");
return -1;
}
ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
if (ssl_server_conf_index == -1) {
hlog(LOG_ERR, "SSL_CTX_get_ex_new_index() failed");
return -1;
}
ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
if (ssl_session_cache_index == -1) {
hlog(LOG_ERR, "SSL_CTX_get_ex_new_index() failed");
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) {
hlog(LOG_ERR, "SSL_CTX_new() failed");
return -1;
}
if (SSL_CTX_set_ex_data(ssl->ctx, ssl_server_conf_index, data) == 0) {
hlog(LOG_ERR, "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_read_ahead(ssl->ctx, 1);
//SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);
if (SSL_CTX_set_cipher_list(ssl->ctx, SSL_DEFAULT_CIPHERS) == 0) {
hlog(LOG_ERR, "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, "SSL_CTX_use_certificate_chain_file(\"%s\") failed", certfile);
return -1;
}
if (SSL_CTX_use_PrivateKey_file(ssl->ctx, keyfile, SSL_FILETYPE_PEM) == 0) {
hlog(LOG_ERR, "SSL_CTX_use_PrivateKey_file(\"%s\") failed", keyfile);
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
*/
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, "SSL_CTX_load_verify_locations(\"%s\") failed", cafile);
return -1;
}
list = SSL_load_client_CA_file(cafile);
if (list == NULL) {
hlog(LOG_ERR, "SSL_load_client_CA_file(\"%s\") failed", cafile);
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->buffer = ((flags & NGX_SSL_BUFFER) != 0);
sc->connection = SSL_new(ssl->ctx);
if (sc->connection == NULL) {
hlog(LOG_ERR, "SSL_new() failed (fd %d)", c->fd);
return -1;
}
if (SSL_set_fd(sc->connection, c->fd) == 0) {
hlog(LOG_ERR, "SSL_set_fd() failed (fd %d)", c->fd);
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) {
hlog(LOG_ERR, "SSL_set_ex_data() failed (fd %d)", c->fd);
return -1;
}
sc->validate = ssl->validate;
c->ssl_con = sc;
return 0;
}
/*
* Validate client certificate
*/
int ssl_validate_client_cert(struct client_t *c)
{
long rc;
X509 *cert;
rc = SSL_get_verify_result(c->ssl_con->connection);
if (rc != X509_V_OK) {
hlog(LOG_INFO, "%s/%s: client SSL certificate verify error: (%l:%s)", c->addr_rem, c->username, rc, X509_verify_cert_error_string(rc));
return -1;
}
cert = SSL_get_peer_certificate(c->ssl_con->connection);
if (cert == NULL) {
hlog(LOG_INFO, "%s/%s: client sent no required SSL certificate", c->addr_rem, c->username);
return -1;
}
hlog(LOG_INFO, "%s/%s: Login validated using SSL client certificate");
X509_free(cert);
return 0;
}
/*
* Write data to an SSL socket
*/
int ssl_write(struct worker_t *self, struct client_t *c)
{
int n;
unsigned long sslerr;
int err;
int to_write;
to_write = c->obuf_end - c->obuf_start;
/* 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;
}
//hlog(LOG_DEBUG, "ssl_write fd %d of %d bytes", c->fd, to_write);
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 (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_DEBUG, "ssl_write fd %d: SSL_get_error %u: %s (%s)", c->fd, sslerr, ebuf, ERR_reason_error_string(sslerr));
}
if (sslerr == SSL_ERROR_WANT_WRITE) {
hlog(LOG_INFO, "ssl_write fd %d: says SSL_ERROR_WANT_WRITE, 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) {
hlog(LOG_INFO, "ssl_write fd %d: says SSL_ERROR_WANT_READ, returning 0", c->fd);
return 0;
}
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, errno);
return -13;
}
int ssl_writeable(struct worker_t *self, struct client_t *c)
{
//hlog(LOG_DEBUG, "ssl_writeable fd %d", c->fd);
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);
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;
hlog(LOG_DEBUG, "ssl_readable fd %d: SSL_get_error: %d", c->fd, sslerr);
if (sslerr == SSL_ERROR_WANT_READ) {
hlog(LOG_DEBUG, "ssl_readable fd %d: SSL_read says SSL_ERROR_WANT_READ, 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 says SSL_ERROR_WANT_WRITE (peer starts SSL renegotiation?), calling ssl_writeable", c->fd);
return ssl_writeable(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;
}
hlog(LOG_DEBUG, "ssl_readable fd %d: SSL_read() failed", c->fd);
client_close(self, c, errno);
return -1;
}
#endif