/**
* \file myssl.c
*
* \brief Code to support SSL connections in PennMUSH.
*
* This file contains nearly all of the code that interacts with the
* OpenSSL libraries to suppose SSL connections in PennMUSH.
*
* Lots of stuff here taken from Eric Rescorla's 2001 Linux Journal
* articles "An Introduction to OpenSSL Programming"
*/
#include "copyrite.h"
#include "config.h"
#ifdef HAS_OPENSSL
#include <stdio.h>
#include <stdarg.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#ifdef WIN32
#define FD_SETSIZE 256
#include <windows.h>
#include <winsock.h>
#include <io.h>
#define EWOULDBLOCK WSAEWOULDBLOCK
#define MAXHOSTNAMELEN 32
#define LC_MESSAGES 6
void shutdown_checkpoint(void);
#else /* !WIN32 */
#ifdef I_SYS_FILE
#include <sys/file.h>
#endif
#ifdef I_SYS_TIME
#include <sys/time.h>
#endif
#include <sys/ioctl.h>
#include <errno.h>
#ifdef I_SYS_SOCKET
#include <sys/socket.h>
#endif
#ifdef I_NETINET_IN
#include <netinet/in.h>
#endif
#ifdef I_NETDB
#include <netdb.h>
#endif
#ifdef I_SYS_PARAM
#include <sys/param.h>
#endif
#ifdef I_SYS_STAT
#include <sys/stat.h>
#endif
#endif /* !WIN32 */
#include <time.h>
#ifdef I_SYS_WAIT
#include <sys/wait.h>
#endif
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#ifdef I_SYS_SELECT
#include <sys/select.h>
#endif
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
#include <openssl/evp.h>
#include "conf.h"
#include "mysocket.h"
#include "externs.h"
#include "myssl.h"
#include "log.h"
#include "parse.h"
#include "confmagic.h"
#define MYSSL_RB 0x1 /**< Read blocked (on read) */
#define MYSSL_WB 0x2 /**< Write blocked (on write) */
#define MYSSL_RBOW 0x4 /**< Read blocked (on write) */
#define MYSSL_WBOR 0x8 /**< Write blocked (on read) */
#define MYSSL_ACCEPT 0x10 /**< We need to call SSL_accept (again) */
#define MYSSL_VERIFIED 0x20 /**< This is an authenticated connection */
#define MYSSL_HANDSHAKE 0x40 /**< We need to call SSL_do_handshake */
#undef MYSSL_DEBUG
#ifdef MYSSL_DEBUG
#define ssl_debugdump(x) ssl_errordump(x)
#else
#define ssl_debugdump(x)
#endif
static void ssl_errordump(const char *msg);
static int client_verify_callback(int preverify_ok, X509_STORE_CTX * x509_ctx);
static DH *get_dh1024(void);
static BIO *bio_err = NULL;
static SSL_CTX *ctx = NULL;
/** Initialize the SSL context.
* \return pointer to SSL context object.
*/
SSL_CTX *
ssl_init(void)
{
SSL_METHOD *meth;
unsigned char context[128];
if (!bio_err) {
if (!SSL_library_init())
return NULL;
SSL_load_error_strings();
/* Error write context */
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
}
#ifndef HAS_DEV_URANDOM
/* We need to seed the RNG with RAND_seed() or RAND_egd() here.
* Where are we going to get an unpredictable seed?
*/
#endif
/* Set up SIGPIPE handler here? */
/* Create context */
meth = SSLv23_server_method();
ctx = SSL_CTX_new(meth);
/* Load keys/certs */
if (*options.ssl_private_key_file) {
if (!SSL_CTX_use_certificate_chain_file(ctx, options.ssl_private_key_file)) {
ssl_errordump
("Unable to load server certificate - only anonymous ciphers supported.");
}
if (!SSL_CTX_use_PrivateKey_file
(ctx, options.ssl_private_key_file, SSL_FILETYPE_PEM)) {
ssl_errordump
("Unable to load private key - only anonymous ciphers supported.");
}
}
/* Load trusted CAs */
if (*options.ssl_ca_file) {
if (!SSL_CTX_load_verify_locations(ctx, options.ssl_ca_file, NULL)) {
ssl_errordump("Unable to load CA certificates");
} else {
if (options.ssl_require_client_cert)
SSL_CTX_set_verify(ctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
client_verify_callback);
else
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, client_verify_callback);
#if (OPENSSL_VERSION_NUMBER < 0x0090600fL)
SSL_CTX_set_verify_depth(ctx, 1);
#endif
}
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_ALL);
SSL_CTX_set_mode(ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
/* Set up DH callback */
SSL_CTX_set_tmp_dh(ctx, get_dh1024());
/* Set the cipher list to the usual default list, except that
* we'll allow anonymous diffie-hellman, too.
*/
SSL_CTX_set_cipher_list(ctx, "ALL:ADH:RC4+RSA:+SSLv2:@STRENGTH");
/* Set up session cache if we can */
strncpy(context, MUDNAME, 128);
SSL_CTX_set_session_id_context(ctx, context, u_strlen(context));
/* Load hash algorithms */
OpenSSL_add_all_digests();
return ctx;
}
static int
client_verify_callback(int preverify_ok, X509_STORE_CTX * x509_ctx)
{
char buf[256];
X509 *err_cert;
int err, depth;
err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
err = X509_STORE_CTX_get_error(x509_ctx);
depth = X509_STORE_CTX_get_error_depth(x509_ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
if (!preverify_ok) {
do_log(LT_ERR, 0, 0, "verify error:num=%d:%s:depth=%d:%s\n", err,
X509_verify_cert_error_string(err), depth, buf);
if (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) {
X509_NAME_oneline(X509_get_issuer_name(x509_ctx->current_cert), buf, 256);
do_log(LT_ERR, 0, 0, "issuer= %s\n", buf);
}
return preverify_ok;
}
/* They've passed the preverification */
/* if there are contents of the cert we wanted to verify, we'd do it here.
*/
return preverify_ok;
}
static DH *
get_dh1024(void)
{
static unsigned char dh1024_p[] = {
0xB6, 0xBC, 0x30, 0x5B, 0xB4, 0xE5, 0x96, 0x62, 0x3F, 0x85, 0x5B, 0x1F,
0x88, 0xD1, 0x12, 0xE1, 0x1D, 0x27, 0x69, 0x63, 0xAD, 0xB3, 0x4D, 0x23,
0xB8, 0x4B, 0x1A, 0x90, 0xA6, 0x89, 0xD8, 0x5D, 0xFA, 0xF5, 0x8F, 0xFF,
0xFF, 0xF4, 0x54, 0x3B, 0xCD, 0x5C, 0xAA, 0x79, 0x8B, 0x14, 0xBB, 0x84,
0xAC, 0xEE, 0x94, 0x47, 0x76, 0xEC, 0x46, 0x75, 0x26, 0x48, 0x8C, 0x06,
0x55, 0x27, 0x7F, 0xC0, 0xF1, 0xE8, 0x1F, 0xD2, 0xE4, 0x55, 0xAE, 0x78,
0x11, 0x6E, 0xF1, 0x3B, 0xCD, 0x55, 0xE8, 0x17, 0xE9, 0x15, 0x7B, 0x05,
0x91, 0x28, 0x9D, 0xD3, 0x40, 0x2E, 0x34, 0x03, 0x04, 0x2B, 0x2D, 0xC5,
0x5C, 0x67, 0xC5, 0xF4, 0x28, 0x8E, 0x16, 0xAC, 0xDD, 0x68, 0x43, 0x66,
0x51, 0xC1, 0x6F, 0x54, 0xB9, 0x22, 0xD8, 0x1A, 0x39, 0x6B, 0x0A, 0xC1,
0x20, 0x5A, 0x9D, 0x31, 0x30, 0xE4, 0x0B, 0xC3,
};
static unsigned char dh1024_g[] = {
0x02,
};
DH *dh;
if ((dh = DH_new()) == NULL)
return (NULL);
dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
if ((dh->p == NULL) || (dh->g == NULL)) {
DH_free(dh);
return (NULL);
}
return (dh);
}
/** Associate an SSL object with a socket and return it.
* \param sock socket descriptor to associate with an SSL object.
* \return pointer to SSL object.
*/
SSL *
ssl_setup_socket(int sock)
{
SSL *ssl;
BIO *bio;
ssl = SSL_new(ctx);
bio = BIO_new_socket(sock, BIO_NOCLOSE);
BIO_set_nbio(bio, 1);
SSL_set_bio(ssl, bio, bio);
return ssl;
}
/** Close down an SSL connection and free the object.
* \param ssl pointer to SSL object to close down.
* Technically, this function sends a shutdown notification
* and then frees the object without waiting for acknowledgement
* of the shutdown. If there were a good way to do that, it would
* be better.
*/
void
ssl_close_connection(SSL * ssl)
{
SSL_shutdown(ssl);
SSL_free(ssl);
}
/** Given an accepted connection on the listening socket, set up SSL.
* \param sock an accepted socket (returned by accept())
* \param state pointer to place to return connection state.
* \return an SSL object to associate with the listen end of this connection.
*/
SSL *
ssl_listen(int sock, int *state)
{
SSL *ssl;
ssl = ssl_setup_socket(sock);
*state = ssl_accept(ssl);
return ssl;
}
/** Given an accepted connection on the listening socket, resume SSL.
* \param sock an accepted socket (returned by accept())
* \param state pointer to place to return connection state.
* \return an SSL object to associate with the listen end of this connection.
*/
SSL *
ssl_resume(int sock, int *state)
{
SSL *ssl;
ssl = ssl_setup_socket(sock);
SSL_set_accept_state(ssl);
*state = ssl_handshake(ssl);
return ssl;
}
/** Perform an SSL handshake.
* In some cases, a handshake may block, so we may have to call this
* function again. Accordingly, we return state information that
* tells us if we need to do that.
* \param ssl pointer to an SSL object.
* \return resulting state of the object.
*/
int
ssl_handshake(SSL * ssl)
{
int ret;
int state = 0;
int err;
if ((ret = SSL_do_handshake(ssl)) <= 0) {
switch (err = SSL_get_error(ssl, ret)) {
case SSL_ERROR_WANT_READ:
/* We must want for the socket to be readable, and then repeat
* the call.
*/
ssl_debugdump("SSL_do_handshake wants read");
state = MYSSL_RB | MYSSL_HANDSHAKE;
break;
case SSL_ERROR_WANT_WRITE:
/* We must want for the socket to be writable, and then repeat
* the call.
*/
ssl_debugdump("SSL_do_handshake wants write");
state = MYSSL_WB | MYSSL_HANDSHAKE;
break;
default:
/* Oops, don't know what's wrong */
ssl_errordump("Error in ssl_handshake");
state = -1;
}
} else {
state = ssl_accept(ssl);
}
return state;
}
/** Given connection state, determine if an SSL_accept needs to be
* performed. This is a just a wrapper so we don't have to expose
* our internal state management stuff.
* \param state an ssl connection state.
* \return 0 if no ssl_accept is needed, non-zero otherwise.
*/
int
ssl_need_accept(int state)
{
return (state & MYSSL_ACCEPT);
}
/** Given connection state, determine if an SSL_handshake needs to be
* performed. This is a just a wrapper so we don't have to expose
* our internal state management stuff.
* \param state an ssl connection state.
* \return 0 if no ssl_handshake is needed, non-zero otherwise.
*/
int
ssl_need_handshake(int state)
{
return (state & MYSSL_HANDSHAKE);
}
/** Given connection state, determine if it's blocked on write.
* This is a just a wrapper so we don't have to expose
* our internal state management stuff.
* \param state an ssl connection state.
* \return 0 if no ssl_handshake is needed, non-zero otherwise.
*/
int
ssl_want_write(int state)
{
return (state & MYSSL_WB);
}
/** Call SSL_accept and return the connection state.
* \param ssl pointer to an SSL object.
* \return ssl state flags indicating success, pending, or failure.
*/
int
ssl_accept(SSL * ssl)
{
int ret;
int state = 0;
X509 *peer;
char buf[256];
if ((ret = SSL_accept(ssl)) <= 0) {
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_WANT_READ:
/* We must want for the socket to be readable, and then repeat
* the call.
*/
ssl_debugdump("SSL_accept wants read");
state = MYSSL_RB | MYSSL_ACCEPT;
break;
case SSL_ERROR_WANT_WRITE:
/* We must want for the socket to be writable, and then repeat
* the call.
*/
ssl_debugdump("SSL_accept wants write");
state = MYSSL_WB | MYSSL_ACCEPT;
break;
default:
/* Oops, don't know what's wrong */
ssl_errordump("Error accepting connection");
return -1;
}
} else {
/* Successful accept - report it */
if ((peer = SSL_get_peer_certificate(ssl))) {
if (SSL_get_verify_result(ssl) == X509_V_OK) {
/* The client sent a certificate which verified OK */
X509_NAME_oneline(X509_get_subject_name(peer), buf, 256);
do_log(LT_CONN, 0, 0, "SSL client certificate accepted: %s", buf);
state |= MYSSL_VERIFIED;
}
}
}
return state;
}
/** Given an SSL object and its last known state, attempt to read from it.
* \param ssl pointer to SSL object.
* \param state saved state of SSL object.
* \param net_read_ready 1 if the underlying socket is ready for read.
* \param net_write_ready 1 if the underlying socket is ready for write.
* \param buf buffer to read into.
* \param bufsize number of bytes to read.
* \param bytes_read pointer to return the number of bytes read.
* \return new state of SSL object, or -1 if the connection closed.
*/
int
ssl_read(SSL * ssl, int state, int net_read_ready, int net_write_ready,
char *buf, int bufsize, int *bytes_read)
{
if ((net_read_ready && !(state & MYSSL_WBOR)) ||
(net_write_ready && (state & MYSSL_RBOW))) {
do {
state &= ~(MYSSL_RB | MYSSL_RBOW);
*bytes_read = SSL_read(ssl, buf, bufsize);
switch (SSL_get_error(ssl, *bytes_read)) {
case SSL_ERROR_NONE:
/* Yay */
return state;
case SSL_ERROR_ZERO_RETURN:
/* End of data on this socket */
return -1;
case SSL_ERROR_WANT_READ:
/* More needs to be read from the underlying socket */
ssl_debugdump("SSL_read wants read");
state |= MYSSL_RB;
break;
case SSL_ERROR_WANT_WRITE:
/* More needs to be written to the underlying socket.
* This can happen during a rehandshake.
*/
ssl_debugdump("SSL_read wants write");
state |= MYSSL_RBOW;
break;
default:
/* Should never happen */
ssl_errordump("Unknown ssl_read failure!");
return -1;
}
} while (SSL_pending(ssl) && !(state & MYSSL_RB));
}
return state;
}
/** Given an SSL object and its last known state, attempt to write to it.
* \param ssl pointer to SSL object.
* \param state saved state of SSL object.
* \param net_read_ready 1 if the underlying socket is ready for read.
* \param net_write_ready 1 if the underlying socket is ready for write.
* \param buf buffer to write.
* \param bufsize length of buffer to write.
* \param offset pointer to offset into buffer marking where to write next.
* \return new state of SSL object, or -1 if the connection closed.
*/
int
ssl_write(SSL * ssl, int state, int net_read_ready, int net_write_ready,
unsigned char *buf, int bufsize, int *offset)
{
int r;
if ((net_write_ready && bufsize) || (net_read_ready && !(state & MYSSL_WBOR))) {
state &= ~(MYSSL_WBOR | MYSSL_WB);
r = SSL_write(ssl, buf + *offset, bufsize);
switch (SSL_get_error(ssl, r)) {
case SSL_ERROR_NONE:
/* We wrote something, but maybe not all */
bufsize -= r;
*offset += r;
break;
case SSL_ERROR_WANT_WRITE:
/* Underlying socket isn't ready to be written to. */
ssl_debugdump("SSL_write wants write");
state |= MYSSL_WB;
break;
case SSL_ERROR_WANT_READ:
/* More needs to be read from the underlying socket first.
* This can happen during a rehandshake.
*/
ssl_debugdump("SSL_write wants read");
state |= MYSSL_WBOR;
break;
default:
/* Should never happen */
ssl_errordump("Unknown ssl_write failure!");
}
}
return state;
}
static void
ssl_errordump(const char *msg)
{
fprintf(stderr, "%s\n", msg);
ERR_print_errors(bio_err);
}
#ifdef BROKEN
/* The below were various attempts to serialize and save/restore
* SSL objects. It ain't pretty, and it don't work.
*/
void
ssl_write_session(FILE * fp, SSL * ssl)
{
SSL_SESSION *s;
s = SSL_get_session(ssl);
PEM_write_SSL_SESSION(fp, s);
}
void
ssl_read_session(FILE * fp)
{
SSL_SESSION s;
PEM_read_SSL_SESSION(fp, &s, NULL, NULL);
SSL_CTX_add_session(ctx, &s);
}
void
ssl_write_ssl(FILE * fp, SSL * ssl)
{
BIO *bio;
SSL_CIPHER *cipher;
bio = SSL_get_rbio(ssl);
cipher = SSL_CIPHER_get_current_cipher(ssl);
fwrite(bio, sizeof(BIO), 1, fp);
fwrite(ssl->version, sizeof(ssl->version), 1, fp);
fwrite(ssl->type, sizeof(ssl->type), 1, fp);
fwrite(ssl->rwstate, sizeof(ssl->type), 1, fp);
fwrite(ssl->rstate, sizeof(ssl->type), 1, fp);
fwrite(ssl->state, sizeof(ssl->type), 1, fp);
fwrite(cipher, sizeof(cipher), 1, fp);
}
SSL *
ssl_read_ssl(FILE * fp, int sock)
{
SSL *ssl;
BIO *bio;
bio = BIO_new(BIO_s_socket());
fread(bio, sizeof(BIO), 1, fp);
ssl = SSL_new(ctx);
fread(ssl, sizeof(SSL), 1, fp);
SSL_set_ssl_method(ssl, SSLv23_server_method());
SSL_set_bio(ssl, bio, bio);
return ssl;
}
#endif /* BROKEN */
#endif /* HAS_OPENSSL */