90d9d492b7
In particular, work around a problem with gcc complaining about NULL format strings. And added additional system header files that might be required on some platforms. This should fix some of the problems reported when compiling on BSD-style systems. But we are still using SysV style session management code. This probably needs to be rewritten before ShellInABox can be run on BSD-style system. In particular, we rely on grantpt(), we use the utmpx API, and we access /dev/urandom. git-svn-id: https://shellinabox.googlecode.com/svn/trunk@55 0da03de8-d603-11dd-86c2-0f8696b7b6f9
705 lines
24 KiB
C
705 lines
24 KiB
C
// ssl.c -- Support functions that find and load SSL support, if available
|
|
// Copyright (C) 2008-2009 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 <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "libhttp/ssl.h"
|
|
#include "libhttp/httpconnection.h"
|
|
#include "logging/logging.h"
|
|
|
|
#if !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>
|
|
extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
|
|
extern int pthread_sigmask(int, const sigset_t *, sigset_t *)
|
|
__attribute__((weak));
|
|
|
|
#endif
|
|
|
|
// 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 *);
|
|
void (*ERR_clear_error)(void);
|
|
void (*ERR_clear_error)(void);
|
|
unsigned long (*ERR_peek_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_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);
|
|
|
|
|
|
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;
|
|
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)
|
|
static void *loadSymbol(const char *lib, const char *fn) {
|
|
void *dl = RTLD_DEFAULT;
|
|
void *rc = dlsym(dl, fn);
|
|
if (!rc) {
|
|
#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) {
|
|
rc = dlsym(dl, fn);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void loadSSL(void) {
|
|
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" },
|
|
{ { &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_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" }
|
|
};
|
|
for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
|
|
if (!(*symbols[i].var = loadSymbol("libssl.so", symbols[i].fn))) {
|
|
debug("Failed to load SSL support. Could not find \"%s\"",
|
|
symbols[i].fn);
|
|
for (int j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
|
|
*symbols[j].var = NULL;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
SSL_library_init();
|
|
dcheck(!ERR_peek_error());
|
|
debug("Loaded SSL suppport");
|
|
}
|
|
#endif
|
|
|
|
int serverSupportsSSL(void) {
|
|
#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.
|
|
#if defined(HAVE_PTHREAD_H)
|
|
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
|
|
}
|
|
|
|
static void sslGenerateCertificate(const char *certificate,
|
|
const char *serverName) {
|
|
#if defined(HAVE_OPENSSL)
|
|
debug("Auto-generating missing certificate \"%s\" for \"%s\"",
|
|
certificate, serverName);
|
|
char *cmd = stringPrintf(NULL,
|
|
"set -e; "
|
|
"exec 2>/dev/null </dev/null; "
|
|
"umask 0377; "
|
|
"PATH=/usr/bin "
|
|
"openssl req -x509 -nodes -days 7300 -newkey rsa:1024 -keyout /dev/stdout "
|
|
"-out /dev/stdout -subj '/CN=%s/' | cat>'%s'",
|
|
serverName, certificate);
|
|
if (system(cmd)) {
|
|
warn("Failed to generate self-signed certificate \"%s\"", certificate);
|
|
}
|
|
free(cmd);
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_TLSEXT
|
|
static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
|
|
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("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')) {
|
|
i++;
|
|
continue;
|
|
}
|
|
serverName[++i] = ch;
|
|
if (!ch) {
|
|
break;
|
|
}
|
|
}
|
|
if (!*serverName) {
|
|
free(serverName);
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
SSL_CTX *context = (SSL_CTX *)getFromTrie(&ssl->sniContexts,
|
|
serverName+1,
|
|
NULL);
|
|
if (context == NULL) {
|
|
check(context = SSL_CTX_new(SSLv23_server_method()));
|
|
check(ssl->sniCertificatePattern);
|
|
char *certificate = stringPrintf(NULL, ssl->sniCertificatePattern,
|
|
serverName);
|
|
if (!SSL_CTX_use_certificate_file(context, certificate, SSL_FILETYPE_PEM)||
|
|
!SSL_CTX_use_PrivateKey_file(context, certificate, SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_check_private_key(context)) {
|
|
if (ssl->generateMissing) {
|
|
sslGenerateCertificate(certificate, serverName + 1);
|
|
if (!SSL_CTX_use_certificate_file(context, certificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_use_PrivateKey_file(context, certificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_check_private_key(context)) {
|
|
goto certificate_missing;
|
|
}
|
|
} else {
|
|
certificate_missing:
|
|
warn("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) > 0);
|
|
}
|
|
check(!ERR_peek_error());
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
#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);
|
|
}
|
|
|
|
check(ssl->sslContext = SSL_CTX_new(SSLv23_server_method()));
|
|
if (autoGenerateMissing) {
|
|
if (!SSL_CTX_use_certificate_file(ssl->sslContext, defaultCertificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_use_PrivateKey_file(ssl->sslContext, defaultCertificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_check_private_key(ssl->sslContext)) {
|
|
char hostname[256], buf[4096];
|
|
check(!gethostname(hostname, sizeof(hostname)));
|
|
struct hostent he_buf, *he;
|
|
int h_err;
|
|
if (gethostbyname_r(hostname, &he_buf, buf, sizeof(buf),
|
|
&he, &h_err)) {
|
|
sslGenerateCertificate(defaultCertificate, hostname);
|
|
} else {
|
|
sslGenerateCertificate(defaultCertificate, he->h_name);
|
|
}
|
|
} else {
|
|
goto valid_certificate;
|
|
}
|
|
}
|
|
if (!SSL_CTX_use_certificate_file(ssl->sslContext, defaultCertificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_use_PrivateKey_file(ssl->sslContext, defaultCertificate,
|
|
SSL_FILETYPE_PEM) ||
|
|
!SSL_CTX_check_private_key(ssl->sslContext)) {
|
|
fatal("Cannot read valid certificate from \"%s\". "
|
|
"Check file permissions and file format.", defaultCertificate);
|
|
}
|
|
valid_certificate:
|
|
free(defaultCertificate);
|
|
|
|
#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
|
|
}
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
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);
|
|
size_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) {
|
|
*size = -1;
|
|
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);
|
|
free(marker);
|
|
if (!end) {
|
|
return NULL;
|
|
}
|
|
unsigned char *ret;
|
|
size_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;
|
|
}
|
|
#endif
|
|
|
|
void sslSetCertificateFd(struct SSLSupport *ssl, int fd) {
|
|
#ifdef HAVE_OPENSSL
|
|
check(serverSupportsSSL());
|
|
check(fd >= 0);
|
|
check(ssl->sslContext = SSL_CTX_new(SSLv23_server_method()));
|
|
const unsigned char *data = sslSecureReadASCIIFileToMem(fd);
|
|
check(!NOINTR(close(fd)));
|
|
long dataSize = (long)strlen((const char *)data);
|
|
long certSize, rsaSize, dsaSize, ecSize;
|
|
const unsigned char *cert = sslPEMtoASN1(data, "CERTIFICATE", &certSize);
|
|
const unsigned char *rsa = sslPEMtoASN1(data, "RSA PRIVATE KEY", &rsaSize);
|
|
const unsigned char *dsa = sslPEMtoASN1(data, "DSA PRIVATE KEY", &dsaSize);
|
|
const unsigned char *ec = sslPEMtoASN1(data, "EC PRIVATE KEY", &ecSize);
|
|
if (!certSize || !(rsaSize > 0 || dsaSize > 0
|
|
#ifdef EVP_PKEY_EC
|
|
|| ecSize > 0
|
|
#endif
|
|
) ||
|
|
!SSL_CTX_use_certificate_ASN1(ssl->sslContext, certSize, cert) ||
|
|
(rsaSize > 0 &&
|
|
!SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ssl->sslContext, rsa,
|
|
rsaSize)) ||
|
|
(dsaSize > 0 &&
|
|
!SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_DSA, ssl->sslContext, dsa,
|
|
dsaSize)) ||
|
|
#ifdef EVP_PKEY_EC
|
|
(ecSize > 0 &&
|
|
!SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, ssl->sslContext, ec,
|
|
ecSize)) ||
|
|
#endif
|
|
!SSL_CTX_check_private_key(ssl->sslContext)) {
|
|
fatal("Cannot read valid certificate from fd %d. Check file format.", fd);
|
|
}
|
|
dcheck(!ERR_peek_error());
|
|
ERR_clear_error();
|
|
memset((char *)data, 0, dataSize);
|
|
free((char *)data);
|
|
memset((char *)cert, 0, certSize);
|
|
free((char *)cert);
|
|
if (rsaSize > 0) {
|
|
memset((char *)rsa, 0, rsaSize);
|
|
free((char *)rsa);
|
|
}
|
|
if (dsaSize > 0) {
|
|
memset((char *)dsa, 0, dsaSize);
|
|
free((char *)dsa);
|
|
}
|
|
if (ecSize > 0) {
|
|
memset((char *)ec, 0, ecSize);
|
|
free((char *)ec);
|
|
}
|
|
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);
|
|
dcheck(!(&pthread_sigmask ? pthread_sigmask : sigprocmask)
|
|
(SIG_BLOCK, &set, NULL));
|
|
}
|
|
|
|
int sslUnblockSigPipe(void) {
|
|
int signum = 0;
|
|
sigset_t set;
|
|
check(!sigpending(&set));
|
|
if (sigismember(&set, SIGPIPE)) {
|
|
sigwait(&set, &signum);
|
|
}
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGPIPE);
|
|
check(!(&pthread_sigmask ? pthread_sigmask : 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("Unexpected corruption of OpenSSL data structures");
|
|
}
|
|
}
|
|
SSL_free(*sslHndl);
|
|
dcheck(!ERR_peek_error());
|
|
}
|
|
#endif
|
|
*sslHndl = NULL;
|
|
}
|