/*
* websSSL.c -- SSL envrionment creation
*
* Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved.
*
* See the file "license.txt" for usage and redistribution license requirements
*
* $Id$
*/
/******************************** Description *********************************/
/*
* This module implements a patch into SSL implementations for the webs
* module.
*/
/********************************* Includes ***********************************/
#include "wsIntrn.h"
#include "webs.h"
#include "websSSL.h"
/******************************* Definitions **********************************/
#define DEFAULT_CERT_FILE "./server.pem"
#define DEFAULT_KEY_FILE "./certs/cakey.pem"
#define DEFAULT_CA_FILE "./certs/cacert.pem"
#define DEFAULT_CA_PATH "./certs/"
#define SSL_PORT 443
/*
* Define the components of the apps_startup() macro
*/
#ifdef SIGPIPE
#define do_pipe_sig() signal(SIGPIPE,SIG_IGN)
#else
#define do_pipe_sig()
#endif
#ifdef OPENSSL
#define SSLC_add_all_algorithms() SSLeay_add_all_algorithms()
#else
extern void SSLC_add_all_algorithms(void);
#endif
/*
* Define the apps_startup() macro
*/
# if defined(MSDOS) || defined(WIN16) || defined(WIN32)
# ifdef _O_BINARY
# define apps_startup() \
_fmode=_O_BINARY; do_pipe_sig(); CRYPTO_malloc_init(); \
SSLC_add_all_algorithms()
# else
# define apps_startup() \
_fmode=O_BINARY; do_pipe_sig(); CRYPTO_malloc_init(); \
SSLC_add_all_algorithms()
# endif
# else
# define apps_startup() do_pipe_sig(); SSLC_add_all_algorithms();
# endif
/*************************** Forward Declarations *****************************/
static int websSSLSetCertStuff(SSL_CTX *ctx,
char *cert_file,
char *key_file);
static int websSSLVerifyCallback(int ok, X509_STORE_CTX *ctx);
static RSA *websSSLTempRSACallback(SSL *s, int is_export, int keylength);
static int websSSLReadEvent (webs_t wp);
static int websSSLAccept(int sid, char *ipaddr, int port, int listenSid);
static void websSSLSocketEvent(int sid, int mask, int data);
/*********************************** Locals ***********************************/
static int sslListenSock = -1; /* Listen socket */
static SSL_CTX *sslctx = NULL;
/******************************************************************************/
/*
* Start up the SSL Context for the application, and start a listen on the
* SSL port (usually 443, and defined by SSL_PORT)
* Return 0 on success, -1 on failure.
*/
int websSSLOpen()
{
char *certFile, *keyFile, *CApath, *CAfile;
SSL_METHOD *meth;
/*
* Install and initialize the SSL library
*/
apps_startup();
trace(7, T("SSL: Initializing SSL\n"));
#ifdef SSLC
SSL_library_init();
#endif
SSL_load_error_strings();
#ifdef OPENSSL
SSLeay_add_ssl_algorithms();
#endif
/*
* Important! Enable both SSL versions 2 and 3
*/
meth = SSLv23_server_method();
sslctx = SSL_CTX_new(meth);
a_assert(sslctx);
if (sslctx == NULL) {
trace(2, T("SSL: Unable to create SSL context!\n"));
return -1;
}
/*
* Adjust some SSL Context variables
*/
SSL_CTX_set_quiet_shutdown(sslctx, 1);
SSL_CTX_set_options(sslctx, 0);
SSL_CTX_sess_set_cache_size(sslctx, 128);
/*
* Set the certificate verification locations
*/
CApath = DEFAULT_CA_PATH;
CAfile = DEFAULT_CA_FILE;
if ((!SSL_CTX_load_verify_locations(sslctx, CAfile, CApath)) ||
(!SSL_CTX_set_default_verify_paths(sslctx))) {
trace(2, T("SSL: Unable to set cert verification locations!\n"));
websSSLClose();
return -1;
}
/*
* Set the certificate and key files for the SSL context
*/
certFile = DEFAULT_CERT_FILE;
keyFile = NULL;
if (websSSLSetCertStuff(sslctx, certFile, keyFile) != 0) {
websSSLClose();
return -1;
}
/*
* Set the RSA callback for the SSL context
*/
SSL_CTX_set_tmp_rsa_callback(sslctx, websSSLTempRSACallback);
/*
* Set the verification callback for the SSL context
*/
SSL_CTX_set_verify(sslctx, SSL_VERIFY_NONE, websSSLVerifyCallback);
/*
* Set the certificate authority list for the client
*/
SSL_CTX_set_client_CA_list(sslctx, SSL_load_client_CA_file(CAfile));
/*
* Open the socket
*/
sslListenSock = socketOpenConnection(NULL, SSL_PORT,
websSSLAccept, SOCKET_BLOCK);
if (sslListenSock < 0) {
trace(2, T("SSL: Unable to open SSL socket on port <%d>!\n"),
SSL_PORT);
return -1;
}
return 0;
}
/******************************************************************************/
/*
* Return TRUE if websSSL has been opened
*/
int websSSLIsOpen()
{
return (sslListenSock != -1);
}
/******************************************************************************/
/*
* Stops the SSL
*/
void websSSLClose()
{
trace(7, T("SSL: Closing SSL\n"));
if (sslctx != NULL) {
SSL_CTX_free(sslctx);
sslctx = NULL;
}
if (sslListenSock != -1) {
socketCloseConnection(sslListenSock);
sslListenSock = -1;
}
#ifdef SSLC
SSL_library_cleanup();
#endif
}
/******************************************************************************/
/*
* Accept a connection
*/
int websSSLAccept(int sid, char *ipaddr, int port, int listenSid)
{
webs_t wp;
int wid;
a_assert(ipaddr && *ipaddr);
a_assert(sid >= 0);
a_assert(port >= 0);
/*
* Allocate a new handle for this accepted connection. This will allocate
* a webs_t structure in the webs[] list
*/
if ((wid = websAlloc(sid)) < 0) {
return -1;
}
wp = webs[wid];
a_assert(wp);
wp->listenSid = listenSid;
ascToUni(wp->ipaddr, ipaddr, min(sizeof(wp->ipaddr), strlen(ipaddr)+1));
/*
* Check if this is a request from a browser on this system. This is useful
* to know for permitting administrative operations only for local access
*/
if (gstrcmp(wp->ipaddr, T("127.0.0.1")) == 0 ||
gstrcmp(wp->ipaddr, websIpaddr) == 0 ||
gstrcmp(wp->ipaddr, websHost) == 0) {
wp->flags |= WEBS_LOCAL_REQUEST;
}
/*
* Since the acceptance came in on this channel, it must be secure
*/
wp->flags |= WEBS_SECURE;
/*
* Arrange for websSocketEvent to be called when read data is available
*/
socketCreateHandler(sid, SOCKET_READABLE, websSSLSocketEvent, (int) wp);
/*
* Arrange for a timeout to kill hung requests
*/
wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout, (void *) wp);
trace(8, T("webs: accept request\n"));
return 0;
}
/******************************************************************************/
/*
* The webs socket handler. Called in response to I/O. We just pass control
* to the relevant read or write handler. A pointer to the webs structure
* is passed as an (int) in iwp.
*/
static void websSSLSocketEvent(int sid, int mask, int iwp)
{
webs_t wp;
wp = (webs_t) iwp;
a_assert(wp);
if (! websValid(wp)) {
return;
}
if (mask & SOCKET_READABLE) {
websSSLReadEvent(wp);
}
if (mask & SOCKET_WRITABLE) {
if (wp->writeSocket) {
(*wp->writeSocket)(wp);
}
}
}
/******************************************************************************/
/*
* Handler for SSL Read Events
*/
static int websSSLReadEvent (webs_t wp)
{
int ret, sock;
socket_t *sptr;
SSL *ssl;
BIO *bio, *bioSSL, *bioSock;
#ifdef DEV
const char *ciphers;
#endif
a_assert (wp);
a_assert(websValid(wp));
sptr = socketPtr(wp->sid);
a_assert(sptr);
sock = sptr->sock;
/*
* Create a new BIO and SSL session for this web request
*/
bio = BIO_new(BIO_f_buffer());
a_assert(bio);
if (!BIO_set_write_buffer_size(bio, 128)) {
return -1;
}
ssl = (SSL *) SSL_new(sslctx);
a_assert(ssl);
if (ssl == NULL) {
return -1;
}
SSL_set_session(ssl, NULL);
bioSSL = BIO_new(BIO_f_ssl());
a_assert(bioSSL);
bioSock = BIO_new_socket(sock, BIO_NOCLOSE);
a_assert(bioSock);
SSL_set_bio(ssl, bioSock, bioSock);
SSL_set_accept_state(ssl);
ret = BIO_set_ssl(bioSSL, ssl, BIO_CLOSE);
BIO_push(bio, bioSSL);
#ifdef DEV
ciphers = SSL_get_cipher_list(ssl, 10);
#endif
/*
* Create the SSL data structure in the wp.
*/
#ifdef WEBS_SSL_SUPPORT
wp->wsp = balloc(B_L, sizeof(websSSL_t));
a_assert (wp->wsp);
(wp->wsp)->bio = bio;
(wp->wsp)->ssl = ssl;
#endif
/*
* Call the default Read Event
*/
websReadEvent(wp);
return ret;
}
/******************************************************************************/
/*
* SSL Verification Callback
*/
static int sslVerifyDepth = 0;
static int sslVerifyError = X509_V_OK;
int websSSLVerifyCallback(int ok, X509_STORE_CTX *ctx)
{
char buf[256];
X509 *errCert;
int err;
int depth;
errCert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
X509_NAME_oneline(X509_get_subject_name(errCert), buf, 256);
if (!ok) {
if (sslVerifyDepth >= depth) {
ok = 1;
sslVerifyError = X509_V_OK;
} else {
ok=0;
sslVerifyError = X509_V_ERR_CERT_CHAIN_TOO_LONG;
}
}
switch (err) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
#ifdef OPENSSL
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
#endif
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
break;
}
return ok;
}
/******************************************************************************/
/*
* Set the SSL certificate and key for the SSL context
*/
int websSSLSetCertStuff(SSL_CTX *ctx, char *certFile, char *keyFile)
{
a_assert (ctx);
a_assert (certFile);
if (certFile != NULL) {
if (SSL_CTX_use_certificate_file(ctx, certFile,
SSL_FILETYPE_PEM) <= 0) {
trace(2, T("SSL: Unable to set certificate file <%s>\n"),
certFile);
return -1;
}
if (keyFile == NULL) {
keyFile = certFile;
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyFile, SSL_FILETYPE_PEM) <= 0) {
trace(2, T("SSL: Unable to set private key file <%s>\n"),
keyFile);
return -1;
}
/*
* Now we know that a key and cert have been set against
* the SSL context
*/
if (!SSL_CTX_check_private_key(ctx)) {
trace(2, T("SSL: Check of private key file <%s> FAILED!\n"),
keyFile);
return -1;
}
}
return 0;
}
/******************************************************************************/
/*
* Set certificate file for SSL context
*/
int websSSLSetCertFile(char_t *certFile)
{
a_assert (sslctx);
a_assert (certFile);
if (sslctx == NULL) {
return -1;
}
if (SSL_CTX_use_certificate_file(sslctx, certFile,
SSL_FILETYPE_PEM) <= 0) {
return -1;
}
/*
* Confirm that the certificate and the private key jive.
*/
if (!SSL_CTX_check_private_key(sslctx)) {
return -1;
}
return 0;
}
/******************************************************************************/
/*
* Set key file for SSL context
*/
int websSSLSetKeyFile(char_t *keyFile)
{
a_assert (sslctx);
a_assert (keyFile);
if (sslctx == NULL) {
return -1;
}
if (SSL_CTX_use_PrivateKey_file(sslctx, keyFile, SSL_FILETYPE_PEM) <= 0) {
return -1;
}
/*
* Confirm that the certificate and the private key jive.
*/
if (!SSL_CTX_check_private_key(sslctx)) {
return -1;
}
return 0;
}
#ifdef SSLC
extern RSA *RSA_new(void);
#endif
/******************************************************************************/
/*
* the Temporary RSA callback
*/
static RSA *websSSLTempRSACallback(SSL *ssl, int isExport, int keyLength)
{
static RSA *rsaTemp = NULL;
if (rsaTemp == NULL) {
#ifdef OPENSSL
rsaTemp = RSA_generate_key(keyLength, RSA_F4, NULL, NULL);
#endif
#ifdef SSLC
rsaTemp = RSA_new();
#endif
}
return rsaTemp;
}
/******************************************************************************/
/*
* Free SSL resources
*/
int websSSLFree(websSSL_t *wsp)
{
if (wsp == NULL) {
return -1;
}
/*
* Make sure we re-use sessions
*/
if (wsp->ssl != NULL) {
SSL_set_shutdown(wsp->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
}
if (wsp->bio != NULL) {
BIO_free_all(wsp->bio);
}
bfree(B_L, wsp);
return 0;
}
/******************************************************************************/
/*
* Return Eof for the SSL BIO
*/
int websSSLEof(websSSL_t *wsp)
{
a_assert(wsp);
if ((wsp == NULL) || (wsp->bio == NULL)) {
return -1;
}
return BIO_eof(wsp->bio);
}
/******************************************************************************/
/*
* Perform a read of the SSL BIO
*/
int websSSLRead(websSSL_t *wsp, char_t *buf, int len)
{
a_assert(wsp);
a_assert(buf);
if ((wsp == NULL) || (wsp->bio == NULL)) {
return -1;
}
return BIO_read(wsp->bio, buf, len);
}
/******************************************************************************/
/*
* Perform a gets of the SSL BIO, returning an balloc'ed string
*/
#define BUF_BLOCK 256
int websSSLGets(websSSL_t *wsp, char_t **buf)
{
int rc, len, lenBuf;
char c;
a_assert(wsp);
a_assert(buf);
lenBuf = 0;
len = 0;
if ((wsp == NULL) || (wsp->bio == NULL)) {
return -1;
}
while (1) {
if ((rc = BIO_read(wsp->bio, &c, 1)) < 0) {
return rc;
}
if (rc == 0) {
/*
* If there is a partial line and we are at EOF, pretend we saw a '\n'
*/
if (len > 0 && BIO_eof(wsp->bio)) {
c = '\n';
} else {
return -1;
}
}
/*
* If a newline is seen, return the data excluding the new line to the
* caller. If carriage return is seen, just eat it.
*/
if (c == '\n') {
if ((len > 0) && (len < lenBuf)) {
(*buf)[len] = 0;
}
return len;
} else if (c == '\r') {
continue;
}
/*
* Append character to buf
*/
if (len >= lenBuf) {
lenBuf += BUF_BLOCK;
*buf = brealloc(B_L, *buf, lenBuf);
}
a_assert(*buf);
(*buf)[len] = c;
len++;
}
}
/******************************************************************************/
/*
* Perform a write to the SSL BIO
*/
int websSSLWrite(websSSL_t *wsp, char_t *buf, int len)
{
a_assert(wsp);
a_assert(buf);
if ((wsp == NULL) || (wsp->bio == NULL)) {
return -1;
}
return BIO_write(wsp->bio, buf, len);
}
/******************************************************************************/
/*
* Perform a flush of the SSL BIO
*/
int websSSLFlush(websSSL_t *wsp)
{
a_assert(wsp);
if ((wsp == NULL) || (wsp->bio == NULL)) {
return -1;
}
return BIO_flush(wsp->bio);
}
/******************************************************************************/