shellinabox/libhttp/ssl.c
KLuka 6c9f98bf34 Logging and debuging
* Added prefixes to all log messages. Prefix should describe source of
  message, like "config", "http", "ssl", "server", etc... This should
  give users more info to figure out what went wrong or what is going
  on. Prefixes also make automatic processing easier.
* Usage is not displayed by default when given command line options are
  incorrect. This way it is easier to notice actual error.
2015-08-23 19:25:36 +02:00

1064 lines
34 KiB
C

// ssl.c -- Support functions that find and load SSL support, if available
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// In addition to these license terms, the author grants the following
// additional rights:
//
// If you modify this program, or any covered work, by linking or
// combining it with the OpenSSL project's OpenSSL library (or a
// modified version of that library), containing parts covered by the
// terms of the OpenSSL or SSLeay licenses, the author
// grants you additional permission to convey the resulting work.
// Corresponding Source for a non-source form of such a combination
// shall include the source code for the parts of OpenSSL used as well
// as that of the covered work.
//
// You may at your option choose to remove this additional permission from
// the work, or from any part of it.
//
// It is possible to build this program in a way that it loads OpenSSL
// libraries at run-time. If doing so, the following notices are required
// by the OpenSSL and SSLeay licenses:
//
// This product includes software developed by the OpenSSL Project
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
//
// This product includes cryptographic software written by Eric Young
// (eay@cryptsoft.com)
//
//
// The most up-to-date version of this program is always available from
// http://shellinabox.com
#define _GNU_SOURCE
#include "config.h"
#define pthread_once x_pthread_once
#define pthread_sigmask x_pthread_sigmask
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "libhttp/ssl.h"
#include "libhttp/httpconnection.h"
#include "logging/logging.h"
#ifdef HAVE_UNUSED
#defined ATTR_UNUSED __attribute__((unused))
#defined UNUSED(x) do { } while (0)
#else
#define ATTR_UNUSED
#define UNUSED(x) do { (void)(x); } while (0)
#endif
#undef pthread_once
#undef pthread_sigmask
#if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_TLSEXT) && \
defined(TLSEXT_NAMETYPE_host_name) && defined(SSL_TLSEXT_ERR_OK)
#define HAVE_TLSEXT
#endif
#if defined(HAVE_PTHREAD_H)
// Pthread support is optional. Only enable it, if the library has been
// linked into the program
#include <pthread.h>
#if defined(__linux__)
extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
#endif
extern int pthread_sigmask(int, const sigset_t *, sigset_t *)
__attribute__((weak));
#endif
#if defined(HAVE_DLOPEN)
// SSL support is optional. Only enable it, if the library can be loaded.
long (*BIO_ctrl)(BIO *, int, long, void *);
BIO_METHOD * (*BIO_f_buffer)(void);
void (*BIO_free_all)(BIO *);
BIO * (*BIO_new)(BIO_METHOD *);
BIO * (*BIO_new_socket)(int, int);
BIO * (*BIO_pop)(BIO *);
BIO * (*BIO_push)(BIO *, BIO *);
#if defined(HAVE_OPENSSL_EC)
void (*EC_KEY_free)(EC_KEY *);
EC_KEY * (*EC_KEY_new_by_curve_name)(int);
#endif
void (*ERR_clear_error)(void);
unsigned long (*ERR_peek_error)(void);
long (*SSL_CTX_callback_ctrl)(SSL_CTX *, int, void (*)(void));
int (*SSL_CTX_check_private_key)(const SSL_CTX *);
long (*SSL_CTX_ctrl)(SSL_CTX *, int, long, void *);
void (*SSL_CTX_free)(SSL_CTX *);
SSL_CTX * (*SSL_CTX_new)(SSL_METHOD *);
int (*SSL_CTX_set_cipher_list)(SSL_CTX *, const char *);
void (*SSL_CTX_set_info_callback)(SSL_CTX *,
void (*)(const SSL *, int, int));
int (*SSL_CTX_use_PrivateKey_file)(SSL_CTX *, const char *, int);
int (*SSL_CTX_use_PrivateKey_ASN1)(int, SSL_CTX *,
const unsigned char *, long);
int (*SSL_CTX_use_certificate_file)(SSL_CTX *, const char *, int);
int (*SSL_CTX_use_certificate_ASN1)(SSL_CTX *, long,
const unsigned char *);
long (*SSL_ctrl)(SSL *, int, long, void *);
void (*SSL_free)(SSL *);
int (*SSL_get_error)(const SSL *, int);
void * (*SSL_get_ex_data)(const SSL *, int);
BIO * (*SSL_get_rbio)(const SSL *);
const char * (*SSL_get_servername)(const SSL *, int);
BIO * (*SSL_get_wbio)(const SSL *);
int (*SSL_library_init)(void);
SSL * (*SSL_new)(SSL_CTX *);
int (*SSL_read)(SSL *, void *, int);
SSL_CTX * (*SSL_set_SSL_CTX)(SSL *, SSL_CTX *);
void (*SSL_set_accept_state)(SSL *);
void (*SSL_set_bio)(SSL *, BIO *, BIO *);
int (*SSL_set_ex_data)(SSL *, int, void *);
int (*SSL_shutdown)(SSL *);
int (*SSL_write)(SSL *, const void *, int);
SSL_METHOD * (*SSLv23_server_method)(void);
X509 * (*d2i_X509)(X509 **px, const unsigned char **in, int len);
void (*X509_free)(X509 *a);
void (*x_sk_zero)(void *st);
void * (*x_SSL_COMP_get_compression_methods)(void);
#endif
static void sslDestroyCachedContext(void *ssl_, char *context_) {
struct SSLSupport *ssl = (struct SSLSupport *)ssl_;
SSL_CTX *context = (SSL_CTX *)context_;
#if defined(HAVE_OPENSSL)
if (context != ssl->sslContext) {
SSL_CTX_free(context);
}
#else
check(!context);
check(!ssl->sslContext);
#endif
}
struct SSLSupport *newSSL(void) {
struct SSLSupport *ssl;
check(ssl = malloc(sizeof(struct SSLSupport)));
initSSL(ssl);
return ssl;
}
void initSSL(struct SSLSupport *ssl) {
ssl->enabled = serverSupportsSSL();
ssl->sslContext = NULL;
ssl->sniCertificatePattern = NULL;
ssl->generateMissing = 0;
ssl->renegotiationCount = 0;
initTrie(&ssl->sniContexts, sslDestroyCachedContext, ssl);
}
void destroySSL(struct SSLSupport *ssl) {
if (ssl) {
free(ssl->sniCertificatePattern);
destroyTrie(&ssl->sniContexts);
#if defined(HAVE_OPENSSL)
if (ssl->sslContext) {
dcheck(!ERR_peek_error());
SSL_CTX_free(ssl->sslContext);
}
#else
check(!ssl->sslContext);
#endif
}
}
void deleteSSL(struct SSLSupport *ssl) {
destroySSL(ssl);
free(ssl);
}
#if defined(HAVE_OPENSSL) && defined(HAVE_DLOPEN)
static int maybeLoadCrypto(void) {
// Some operating systems cannot automatically load dependent dynamic
// libraries. As libssl.so can depend on libcrypto.so, we try to load
// it, iff we haven't tried loading it before and iff libssl.so does not
// work by itself.
static int crypto;
// SHELLINABOX_LIBCRYPTO_SO can be used to select the specific
// soname of libcrypto for systems where it is not libcrypto.so.
// The feature is currently disabled.
const char* path_libcrypto = NULL; // getenv ("SHELLINABOX_LIBCRYPTO_SO");
if (path_libcrypto == NULL)
path_libcrypto = "libcrypto.so";
if (!crypto++) {
#ifdef RTLD_NOLOAD
if (dlopen(path_libcrypto, RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD))
return 1;
else
#endif
if (dlopen(path_libcrypto, RTLD_LAZY|RTLD_GLOBAL))
return 1;
}
return 0;
}
static void *loadSymbol(const char *lib, const char *fn) {
int err = NOINTR(dup(2));
if (err > 2) {
int null = NOINTR(open("/dev/null", O_WRONLY));
if (null >= 0) {
NOINTR(dup2(null, 2));
NOINTR(close(null));
}
}
void *dl = RTLD_DEFAULT;
void *rc = dlsym(dl, fn);
if (!rc) {
for (int i = 0; i < 2; i++) {
#ifdef RTLD_NOLOAD
dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
#else
dl = NULL;
#endif
if (dl == NULL) {
dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL);
}
if (dl != NULL || !maybeLoadCrypto()) {
break;
}
}
if (dl != NULL) {
rc = dlsym(RTLD_DEFAULT, fn);
if (rc == NULL && maybeLoadCrypto()) {
rc = dlsym(RTLD_DEFAULT, fn);
}
}
}
if (err > 2) {
NOINTR(dup2(err, 2));
}
NOINTR(close(err));
return rc;
}
static void loadSSL(void) {
// SHELLINABOX_LIBSSL_SO can be used to select the specific
// soname of libssl for systems where it is not libssl.so.
// The feature is currently disabled.
const char* path_libssl = NULL; // = getenv ("SHELLINABOX_LIBSSL_SO");
if (path_libssl == NULL)
path_libssl = "libssl.so";
check(!SSL_library_init);
struct {
union {
void *avoid_gcc_warning_about_type_punning;
void **var;
};
const char *fn;
} symbols[] = {
{ { &BIO_ctrl }, "BIO_ctrl" },
{ { &BIO_f_buffer }, "BIO_f_buffer" },
{ { &BIO_free_all }, "BIO_free_all" },
{ { &BIO_new }, "BIO_new" },
{ { &BIO_new_socket }, "BIO_new_socket" },
{ { &BIO_pop }, "BIO_pop" },
{ { &BIO_push }, "BIO_push" },
{ { &ERR_clear_error }, "ERR_clear_error" },
{ { &ERR_clear_error }, "ERR_clear_error" },
{ { &ERR_peek_error }, "ERR_peek_error" },
{ { &ERR_peek_error }, "ERR_peek_error" },
#ifdef HAVE_OPENSSL_EC
{ { &EC_KEY_free }, "EC_KEY_free" },
{ { &EC_KEY_new_by_curve_name }, "EC_KEY_new_by_curve_name" },
#endif
{ { &SSL_CTX_callback_ctrl }, "SSL_CTX_callback_ctrl" },
{ { &SSL_CTX_check_private_key }, "SSL_CTX_check_private_key" },
{ { &SSL_CTX_ctrl }, "SSL_CTX_ctrl" },
{ { &SSL_CTX_free }, "SSL_CTX_free" },
{ { &SSL_CTX_new }, "SSL_CTX_new" },
{ { &SSL_CTX_set_cipher_list }, "SSL_CTX_set_cipher_list" },
{ { &SSL_CTX_set_info_callback }, "SSL_CTX_set_info_callback" },
{ { &SSL_CTX_use_PrivateKey_file }, "SSL_CTX_use_PrivateKey_file" },
{ { &SSL_CTX_use_PrivateKey_ASN1 }, "SSL_CTX_use_PrivateKey_ASN1" },
{ { &SSL_CTX_use_certificate_file },"SSL_CTX_use_certificate_file"},
{ { &SSL_CTX_use_certificate_ASN1 },"SSL_CTX_use_certificate_ASN1"},
{ { &SSL_ctrl }, "SSL_ctrl" },
{ { &SSL_free }, "SSL_free" },
{ { &SSL_get_error }, "SSL_get_error" },
{ { &SSL_get_ex_data }, "SSL_get_ex_data" },
{ { &SSL_get_rbio }, "SSL_get_rbio" },
#ifdef HAVE_TLSEXT
{ { &SSL_get_servername }, "SSL_get_servername" },
#endif
{ { &SSL_get_wbio }, "SSL_get_wbio" },
{ { &SSL_library_init }, "SSL_library_init" },
{ { &SSL_new }, "SSL_new" },
{ { &SSL_read }, "SSL_read" },
#ifdef HAVE_TLSEXT
{ { &SSL_set_SSL_CTX }, "SSL_set_SSL_CTX" },
#endif
{ { &SSL_set_accept_state }, "SSL_set_accept_state" },
{ { &SSL_set_bio }, "SSL_set_bio" },
{ { &SSL_set_ex_data }, "SSL_set_ex_data" },
{ { &SSL_shutdown }, "SSL_shutdown" },
{ { &SSL_write }, "SSL_write" },
{ { &SSLv23_server_method }, "SSLv23_server_method" },
{ { &d2i_X509 }, "d2i_X509" },
{ { &X509_free }, "X509_free" },
{ { &x_sk_zero }, "sk_zero" }
};
for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
if (!(*symbols[i].var = loadSymbol(path_libssl, symbols[i].fn))) {
debug("[ssl] Failed to load SSL support. Could not find \"%s\"!",
symbols[i].fn);
for (unsigned j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
*symbols[j].var = NULL;
}
return;
}
}
// These are optional
x_SSL_COMP_get_compression_methods = loadSymbol(path_libssl, "SSL_COMP_get_compression_methods");
// ends
SSL_library_init();
dcheck(!ERR_peek_error());
debug("[ssl] Loaded SSL suppport...");
}
#endif
int serverSupportsSSL(void) {
#if defined(HAVE_OPENSSL) && !defined(HAVE_DLOPEN)
return SSL_library_init();
#else
#if defined(HAVE_OPENSSL)
// We want to call loadSSL() exactly once. For single-threaded applications,
// this is straight-forward. For threaded applications, we need to call
// pthread_once(), instead. We perform run-time checks for whether we are
// single- or multi-threaded, so that the same code can be used.
// This currently only works on Linux.
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (!!&pthread_once) {
static pthread_once_t once = PTHREAD_ONCE_INIT;
pthread_once(&once, loadSSL);
} else
#endif
{
static int initialized;
if (!initialized) {
initialized = 1;
loadSSL();
}
}
return !!SSL_library_init;
#else
return 0;
#endif
#endif
}
#if defined(HAVE_OPENSSL)
static void sslGenerateCertificate(const char *certificate,
const char *serverName) {
info("[ssl] Auto-generating missing certificate \"%s\" for \"%s\"...",
certificate, serverName);
pid_t pid = fork();
if (pid == -1) {
warn("[ssl] Failed to generate self-signed certificate \"%s\"!", certificate);
} else if (pid == 0) {
int fd = NOINTR(open("/dev/null", O_RDONLY));
check(fd != -1);
check(NOINTR(dup2(fd, STDERR_FILENO)) == STDERR_FILENO);
check(NOINTR(close(fd)) == 0);
fd = NOINTR(open("/dev/null", O_WRONLY));
check(fd != -1);
check(NOINTR(dup2(fd, STDIN_FILENO)) == STDIN_FILENO);
check(NOINTR(close(fd)) == 0);
umask(077);
check(setenv("PATH", "/usr/bin:/usr/sbin", 1) == 0);
char *subject;
check(subject = stringPrintf(NULL, "/CN=%s/", serverName));
if (execlp("openssl", "openssl", "req", "-x509", "-nodes", "-days", "7300",
"-newkey", "rsa:2048", "-keyout", certificate, "-out", certificate,
"-subj", subject, (char *)NULL) < 0) {
warn("[ssl] Failed to generate self-signed certificate \"%s\"!", certificate);
free(subject);
}
} else {
int status;
check(NOINTR(waitpid(pid, &status, 0)) == pid);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
warn("[ssl] Failed to generate self-signed certificate \"%s\"!", certificate);
} else {
info("[ssl] Certificate succesfully generated.");
}
}
}
static const unsigned char *sslSecureReadASCIIFileToMem(int fd) {
size_t inc = 16384;
size_t bufSize = inc;
size_t len = 0;
unsigned char *buf;
check((buf = malloc(bufSize)) != NULL);
for (;;) {
check(len < bufSize - 1);
ssize_t readLen = bufSize - len - 1;
ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
if (bytesRead > 0) {
len += bytesRead;
}
if (bytesRead != readLen) {
break;
}
// Instead of calling realloc(), allocate a new buffer, copy the data,
// and then clear the old buffer. This way, we are not accidentally
// leaving key material in memory.
unsigned char *newBuf;
check((newBuf = malloc(bufSize + inc)) != NULL);
memcpy(newBuf, buf, len);
memset(buf, 0, bufSize);
free(buf);
buf = newBuf;
bufSize += inc;
}
check(len < bufSize);
buf[len] = '\000';
return buf;
}
static const unsigned char *sslPEMtoASN1(const unsigned char *pem,
const char *record,
long *size,
const unsigned char **eor) {
if (eor) {
*eor = NULL;
}
*size = 0;
char *marker;
check((marker = stringPrintf(NULL, "-----BEGIN %s-----",record))!=NULL);
unsigned char *ptr = (unsigned char *)strstr((char *)pem, marker);
if (!ptr) {
free(marker);
return NULL;
} else {
ptr += strlen(marker);
}
*marker = '\000';
check((marker = stringPrintf(marker, "-----END %s-----",record))!=NULL);
unsigned char *end = (unsigned char *)strstr((char *)ptr, marker);
if (eor) {
*eor = end + strlen(marker);
}
free(marker);
if (!end) {
return NULL;
}
unsigned char *ret;
ssize_t maxSize = (((end - ptr)*6)+7)/8;
check((ret = malloc(maxSize)) != NULL);
unsigned char *out = ret;
unsigned bits = 0;
int count = 0;
while (ptr < end) {
unsigned char ch = *ptr++;
if (ch >= 'A' && ch <= 'Z') {
ch -= 'A';
} else if (ch >= 'a' && ch <= 'z') {
ch -= 'a' - 26;
} else if (ch >= '0' && ch <= '9') {
ch += 52 - '0';
} else if (ch == '+') {
ch += 62 - '+';
} else if (ch == '/') {
ch += 63 - '/';
} else if (ch == '=') {
while (ptr < end) {
if ((ch = *ptr++) != '=' && ch > ' ') {
goto err;
}
}
break;
} else if (ch <= ' ') {
continue;
} else {
err:
free(ret);
return NULL;
}
check(ch <= 63);
check(count >= 0);
check(count <= 6);
bits = (bits << 6) | ch;
count += 6;
if (count >= 8) {
*out++ = (bits >> (count -= 8)) & 0xFF;
}
}
check(out - ret <= maxSize);
*size = out - ret;
return ret;
}
static int sslSetCertificateFromFd(SSL_CTX *context, int fd) {
int rc = 0;
check(serverSupportsSSL());
check(fd >= 0);
const unsigned char *data = sslSecureReadASCIIFileToMem(fd);
check(!NOINTR(close(fd)));
long dataSize = (long)strlen((const char *)data);
long certSize, rsaSize, dsaSize, ecSize, notypeSize;
const unsigned char *record;
const unsigned char *cert = sslPEMtoASN1(data, "CERTIFICATE", &certSize,
&record);
const unsigned char *rsa = sslPEMtoASN1(data, "RSA PRIVATE KEY",&rsaSize,
NULL);
const unsigned char *dsa = sslPEMtoASN1(data, "DSA PRIVATE KEY",&dsaSize,
NULL);
const unsigned char *ec = sslPEMtoASN1(data, "EC PRIVATE KEY", &ecSize,
NULL);
const unsigned char *notype = sslPEMtoASN1(data, "PRIVATE KEY", &notypeSize,
NULL);
if (certSize && (rsaSize || dsaSize
#ifdef EVP_PKEY_EC
|| ecSize
#endif
|| notypeSize) &&
SSL_CTX_use_certificate_ASN1(context, certSize, cert) &&
(!rsaSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, context, rsa, rsaSize)) &&
(!dsaSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_DSA, context, dsa, dsaSize)) &&
#ifdef EVP_PKEY_EC
(!ecSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, context, ec, ecSize)) &&
#endif
// Assume a private key is RSA if the header does not specify a type.
// (e.g. BEGIN PRIVATE KEY)
(!notypeSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, context, notype, notypeSize))
) {
memset((char *)cert, 0, certSize);
free((char *)cert);
while (record) {
cert = sslPEMtoASN1(record, "CERTIFICATE", &certSize,
&record);
if (cert) {
X509 *x509;
const unsigned char *c = cert;
check(x509 = d2i_X509(NULL, &c, certSize));
memset((char *)cert, 0, certSize);
free((char *)cert);
if (!SSL_CTX_add_extra_chain_cert(context, x509)) {
X509_free(x509);
break;
}
}
}
if (!record && SSL_CTX_check_private_key(context)) {
rc = 1;
}
dcheck(!ERR_peek_error());
ERR_clear_error();
} else {
memset((char *)cert, 0, certSize);
free((char *)cert);
}
memset((char *)data, 0, dataSize);
free((char *)data);
memset((char *)rsa, 0, rsaSize);
free((char *)rsa);
memset((char *)dsa, 0, dsaSize);
free((char *)dsa);
memset((char *)ec, 0, ecSize);
free((char *)ec);
memset((char *)notype, 0, notypeSize);
free((char *)notype);
return rc;
}
static int sslSetCertificateFromFile(SSL_CTX *context,
const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
return -1;
}
int rc = sslSetCertificateFromFd(context, fd);
return rc;
}
static void sslInfoCallback(const SSL *sslHndl, int type, int val) {
// Count the number of renegotiations for each SSL session.
if (type & SSL_CB_HANDSHAKE_START) {
struct HttpConnection *http =
(struct HttpConnection *) SSL_get_app_data(sslHndl);
http->ssl->renegotiationCount += 1;
}
}
static SSL_CTX *sslMakeContext(void) {
SSL_CTX *context;
check(context = SSL_CTX_new(SSLv23_server_method()));
long options = SSL_OP_ALL;
options |= SSL_OP_NO_SSLv2;
options |= SSL_OP_NO_SSLv3;
options |= SSL_OP_SINGLE_DH_USE;
#ifdef SSL_OP_NO_COMPRESSION
options |= SSL_OP_NO_COMPRESSION;
#endif
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
#endif
// Set default SSL options.
SSL_CTX_set_options(context, options);
// Workaround for SSL_OP_NO_COMPRESSION with older OpenSSL versions.
#ifdef HAVE_DLOPEN
if (SSL_COMP_get_compression_methods) {
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
}
#elif OPENSSL_VERSION_NUMBER >= 0x00908000L
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#endif
// For Perfect Forward Secrecy (PFS) support we need to enable some additional
// SSL options, provide eliptic curve key object for handshake and add chipers
// suits with ECDHE handshake on top of the ciper list.
#ifdef HAVE_OPENSSL_EC
SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE);
EC_KEY *ecKey;
check(ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
SSL_CTX_set_tmp_ecdh(context, ecKey);
EC_KEY_free(ecKey);
debug("[ssl] Support for PFS enabled...");
#endif
check(SSL_CTX_set_cipher_list(context,
"ECDHE-RSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES128-GCM-SHA256:"
"ECDHE-RSA-AES256-SHA384:"
"ECDHE-RSA-AES128-SHA256:"
"ECDHE-RSA-AES256-SHA:"
"ECDHE-RSA-AES128-SHA:"
"ECDHE-RSA-DES-CBC3-SHA:"
"HIGH:MEDIUM:!RC4:!aNULL:!MD5"));
SSL_CTX_set_info_callback(context, sslInfoCallback);
debug("[ssl] Server context succesfully initialized...");
return context;
}
#endif
#ifdef HAVE_TLSEXT
static int sslSNICallback(SSL *sslHndl, int *al ATTR_UNUSED,
struct SSLSupport *ssl) {
UNUSED(al);
check(!ERR_peek_error());
const char *name = SSL_get_servername(sslHndl,
TLSEXT_NAMETYPE_host_name);
if (name == NULL || !*name) {
return SSL_TLSEXT_ERR_OK;
}
struct HttpConnection *http =
(struct HttpConnection *)SSL_get_app_data(sslHndl);
debug("[ssl] Received SNI callback for virtual host \"%s\" from \"%s:%d\"...",
name, httpGetPeerName(http), httpGetPort(http));
char *serverName;
check(serverName = malloc(strlen(name)+2));
serverName[0] = '-';
for (int i = 0;;) {
char ch = name[i];
if (ch >= 'A' && ch <= 'Z') {
ch |= 0x20;
} else if (ch != '\000' && ch != '.' && ch != '-' &&
(ch < '0' ||(ch > '9' && ch < 'A') || (ch > 'Z' &&
ch < 'a')|| ch > 'z')) {
free(serverName);
return SSL_TLSEXT_ERR_OK;
}
serverName[++i] = ch;
if (!ch) {
break;
}
}
SSL_CTX *context = (SSL_CTX *)getFromTrie(&ssl->sniContexts,
serverName+1,
NULL);
if (context == NULL) {
context = sslMakeContext();
check(ssl->sniCertificatePattern);
char *certificate = stringPrintfUnchecked(NULL,
ssl->sniCertificatePattern,
serverName);
if (sslSetCertificateFromFile(context, certificate) < 0) {
if (ssl->generateMissing) {
sslGenerateCertificate(certificate, serverName + 1);
// No need to check the certificate. If we fail to set it, we will use
// the default certificate, instead.
sslSetCertificateFromFile(context, certificate);
} else {
warn("[ssl] Could not find matching certificate \"%s\" for \"%s\"",
certificate, serverName + 1);
SSL_CTX_free(context);
context = ssl->sslContext;
}
}
ERR_clear_error();
free(certificate);
addToTrie(&ssl->sniContexts, serverName+1, (char *)context);
}
free(serverName);
if (context != ssl->sslContext) {
check(SSL_set_SSL_CTX(sslHndl, context));
}
check(!ERR_peek_error());
return SSL_TLSEXT_ERR_OK;
}
#endif
#if defined(HAVE_OPENSSL) && !defined(HAVE_GETHOSTBYNAME_R)
// This is a not-thread-safe replacement for gethostbyname_r()
#define gethostbyname_r x_gethostbyname_r
static int gethostbyname_r(const char *name, struct hostent *ret,
char *buf ATTR_UNUSED, size_t buflen ATTR_UNUSED,
struct hostent **result, int *h_errnop) {
UNUSED(buf);
UNUSED(buflen);
if (result) {
*result = NULL;
}
if (h_errnop) {
*h_errnop = ERANGE;
}
if (!ret) {
return -1;
}
struct hostent *he = gethostbyname(name);
if (he) {
*ret = *he;
if (result) {
*result = ret;
}
}
if (h_errnop) {
*h_errnop = h_errno;
}
return he ? 0 : -1;
}
#endif
void sslSetCertificate(struct SSLSupport *ssl, const char *filename,
int autoGenerateMissing) {
#if defined(HAVE_OPENSSL)
check(serverSupportsSSL());
char *defaultCertificate;
check(defaultCertificate = strdup(filename));
char *ptr = strchr(defaultCertificate, '%');
if (ptr != NULL) {
check(!strchr(ptr+1, '%'));
check(ptr[1] == 's');
memmove(ptr, ptr + 2, strlen(ptr)-1);
}
// Try to set the default certificate. If necessary, (re-)generate it.
ssl->sslContext = sslMakeContext();
if (autoGenerateMissing) {
if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
char hostname[256], buf[4096];
check(!gethostname(hostname, sizeof(hostname)));
struct hostent he_buf, *he;
int h_err = 0;
int ret = gethostbyname_r(hostname, &he_buf, buf, sizeof(buf), &he, &h_err);
if (!ret && he && he->h_name) {
sslGenerateCertificate(defaultCertificate, he->h_name);
} else {
if (h_err) {
warn("[ssl] Error getting host information: \"%s\".", hstrerror(h_err));
}
sslGenerateCertificate(defaultCertificate, hostname);
}
} else {
goto valid_certificate;
}
}
if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
fatal("[ssl] Cannot read valid certificate from \"%s\"! "
"Check file permissions and file format.", defaultCertificate);
}
valid_certificate:
free(defaultCertificate);
// Enable SNI support so that we can set a different certificate, if the
// client asked for it.
#ifdef HAVE_TLSEXT
if (ptr != NULL) {
check(ssl->sniCertificatePattern = strdup(filename));
check(SSL_CTX_set_tlsext_servername_callback(ssl->sslContext,
sslSNICallback));
check(SSL_CTX_set_tlsext_servername_arg(ssl->sslContext, ssl));
}
#endif
dcheck(!ERR_peek_error());
ERR_clear_error();
ssl->generateMissing = autoGenerateMissing;
#endif
}
// Convert the file descriptor to a human-readable format. Attempts to
// retrieve the original file name where possible.
#ifdef HAVE_OPENSSL
static char *sslFdToFilename(int fd) {
char *proc, *buf;
int len = 128;
check(proc = stringPrintf(NULL, "/proc/self/fd/%d", fd));
check(buf = malloc(len));
for (;;) {
ssize_t i;
if ((i = readlink(proc, buf + 1, len-3)) < 0) {
free(proc);
free(buf);
check(buf = stringPrintf(NULL, "fd %d", fd));
return buf;
} else if (i >= len-3) {
len += 512;
check(buf = realloc(buf, len));
} else {
free(proc);
check(i >= 0 && i < len);
buf[i+1] = '\000';
struct stat sb;
if (!stat(buf + 1, &sb) && S_ISREG(sb.st_mode)) {
*buf = '"';
buf[i + 1] = '"';
buf[i + 2] = '\000';
return buf;
} else {
free(buf);
check(buf = stringPrintf(NULL, "fd %d", fd));
return buf;
}
}
}
}
#endif
void sslSetCertificateFd(struct SSLSupport *ssl, int fd) {
#ifdef HAVE_OPENSSL
ssl->sslContext = sslMakeContext();
char *filename = sslFdToFilename(fd);
if (!sslSetCertificateFromFd(ssl->sslContext, fd)) {
fatal("[ssl] Cannot read valid certificate from %s. Check file format.",
filename);
}
free(filename);
ssl->generateMissing = 0;
#endif
}
int sslEnable(struct SSLSupport *ssl, int enabled) {
int old = ssl->enabled;
ssl->enabled = enabled;
return old;
}
void sslBlockSigPipe(void) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_BLOCK, &set, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &set, NULL));
}
}
#ifndef HAVE_SIGWAIT
// This is a non-thread-safe replacement for sigwait()
static int dummysignalno;
static void dummysignal(int signo) {
dummysignalno = signo;
}
#define sigwait x_sigwait
static int sigwait(const sigset_t *set, int *sig) {
sigset_t mask, old_mask;
sigfillset(&mask);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_BLOCK, &mask, &old_mask));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &mask, &old_mask));
}
#ifndef NSIG
#define NSIG 32
#endif
struct sigaction sa[NSIG];
memset(sa, 0, sizeof(sa));
sa->sa_handler = dummysignal;
for (int i = 1; i <= NSIG; i++) {
if (sigismember(set, i)) {
sigdelset(&mask, i);
sigaction(i, sa, sa + i);
}
}
dummysignalno = -1;
sigsuspend(&mask);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_SETMASK, &old_mask, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &old_mask, NULL));
}
return dummysignalno;
}
#endif
int sslUnblockSigPipe(void) {
int signum = 0;
sigset_t set;
check(!sigpending(&set));
if (sigismember(&set, SIGPIPE)) {
sigwait(&set, &signum);
}
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_UNBLOCK, &set, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_UNBLOCK, &set, NULL));
}
return signum;
}
int sslPromoteToSSL(struct SSLSupport *ssl, SSL **sslHndl, int fd,
const char *buf, int len) {
#if defined(HAVE_OPENSSL)
sslBlockSigPipe();
int rc = 0;
check(!*sslHndl);
dcheck(!ERR_peek_error());
dcheck(*sslHndl = SSL_new(ssl->sslContext));
if (*sslHndl == NULL) {
ERR_clear_error();
errno = EINVAL;
rc = -1;
} else {
SSL_set_mode(*sslHndl, SSL_MODE_ENABLE_PARTIAL_WRITE);
BIO *writeBIO = BIO_new_socket(fd, 0);
BIO *readBIO = writeBIO;
if (len > 0) {
readBIO = BIO_new(BIO_f_buffer());
BIO_push(readBIO, writeBIO);
check(BIO_set_buffer_read_data(readBIO, (char *)buf, len));
}
SSL_set_bio(*sslHndl, readBIO, writeBIO);
SSL_set_accept_state(*sslHndl);
dcheck(!ERR_peek_error());
}
sslUnblockSigPipe();
return rc;
#else
errno = EINVAL;
return -1;
#endif
}
void sslFreeHndl(SSL **sslHndl) {
#if defined(HAVE_OPENSSL)
if (*sslHndl) {
// OpenSSL does not always correctly perform reference counting for stacked
// BIOs. This is particularly a problem if an SSL connection has two
// different BIOs for the read and the write end, with one being a stacked
// derivative of the other. Unfortunately, this is exactly the scenario
// that we set up.
// As a work-around, we un-stack the BIOs prior to freeing the SSL
// connection.
ERR_clear_error();
BIO *writeBIO, *readBIO;
check(writeBIO = SSL_get_wbio(*sslHndl));
check(readBIO = SSL_get_rbio(*sslHndl));
if (writeBIO != readBIO) {
if (readBIO->next_bio == writeBIO) {
// OK, that's exactly the bug we are looking for. We know how to
// fix it.
check(BIO_pop(readBIO) == writeBIO);
check(readBIO->references == 1);
check(writeBIO->references == 1);
check(!readBIO->next_bio);
check(!writeBIO->prev_bio);
} else if (readBIO->next_bio == writeBIO->next_bio &&
writeBIO->next_bio->prev_bio == writeBIO) {
// Things get even more confused, if the SSL handshake is aborted
// prematurely.
// OpenSSL appears to internally stack a BIO onto the read end that
// does not get removed afterwards. We end up with the original
// socket BIO having two different BIOs prepended to it (one for
// reading and one for writing). In this case, not only is the
// reference count wrong, but the chain of next_bio/prev_bio pairs
// is corrupted, too.
BIO *sockBIO;
check(sockBIO = BIO_pop(readBIO));
check(sockBIO == BIO_pop(writeBIO));
check(readBIO->references == 1);
check(writeBIO->references == 1);
check(sockBIO->references == 1);
check(!readBIO->next_bio);
check(!writeBIO->next_bio);
check(!sockBIO->prev_bio);
BIO_free_all(sockBIO);
} else {
// We do not know, how to fix this situation. Something must have
// changed in the OpenSSL internals. Either, this is a new bug, or
// somebody fixed the code in a way that we did not anticipate.
fatal("[ssl] Unexpected corruption of OpenSSL data structures");
}
}
SSL_free(*sslHndl);
dcheck(!ERR_peek_error());
}
#endif
*sslHndl = NULL;
}