summaryrefslogtreecommitdiffstats
path: root/tcpd/libcouriertls.c
diff options
context:
space:
mode:
Diffstat (limited to 'tcpd/libcouriertls.c')
-rw-r--r--tcpd/libcouriertls.c1560
1 files changed, 1560 insertions, 0 deletions
diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c
new file mode 100644
index 0000000..6eee8b0
--- /dev/null
+++ b/tcpd/libcouriertls.c
@@ -0,0 +1,1560 @@
+/*
+** Copyright 2000-2009 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+#include "config.h"
+#include "argparse.h"
+#include "spipe.h"
+#define COURIERTCPD_EXPOSE_OPENSSL 1
+#include "libcouriertls.h"
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include "tlscache.h"
+#include "rfc1035/rfc1035.h"
+#include "soxwrap/soxwrap.h"
+#include "random128/random128.h"
+#ifdef getc
+#undef getc
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <netdb.h>
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#define dirent direct
+#define NAMLEN(dirent) (dirent)->d_namlen
+#if HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#if HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#if HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <errno.h>
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <sys/time.h>
+
+/***** TODO *****/
+
+/* #define TLSCACHEDEBUG */
+
+static const char *safe_getenv(const struct tls_info *info, const char *n)
+{
+ const char *v=(*info->getconfigvar)(n, info->app_data);
+
+ if (!v) v="";
+ return (v);
+}
+
+static int get_peer_verify_level(const struct tls_info *info)
+{
+ int peer_verify_level=SSL_VERIFY_PEER;
+ /* SSL_VERIFY_NONE */
+ /* SSL_VERIFY_PEER */
+ /* SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT */
+ const char *s=safe_getenv(info, "TLS_VERIFYPEER");
+
+ if (info->peer_verify_domain)
+ return SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+
+ switch (*s) {
+ case 'n':
+ case 'N': /* NONE */
+ peer_verify_level=SSL_VERIFY_NONE;
+ break;
+ case 'p':
+ case 'P': /* PEER */
+ peer_verify_level=SSL_VERIFY_PEER;
+ break;
+ case 'r':
+ case 'R': /* REQUIREPEER */
+ peer_verify_level=
+ SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ break;
+ }
+ return (peer_verify_level);
+}
+
+static int ssl_verify_callback(int goodcert, X509_STORE_CTX *x509)
+{
+ SSL *ssl=
+ X509_STORE_CTX_get_ex_data(x509,
+ SSL_get_ex_data_X509_STORE_CTX_idx()
+ );
+ struct tls_info *info=SSL_get_app_data(ssl);
+
+ if (info->peer_verify_domain || get_peer_verify_level(info))
+ {
+ if (!goodcert)
+ return (0);
+
+ info->certificate_verified=1;
+ }
+
+ return (1);
+}
+
+static int verifypeer(const struct tls_info *info, SSL *ssl)
+{
+ X509 *x=NULL;
+ X509_NAME *subj=NULL;
+ int nentries, j;
+ char domain[256];
+ char *p;
+ char errmsg[1000];
+
+ if (!info->peer_verify_domain)
+ return (1);
+
+ if (info->isserver)
+ {
+ x=SSL_get_peer_certificate(ssl);
+
+ if (x)
+ subj=X509_get_subject_name(x);
+ }
+ else
+ {
+ STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl);
+
+ if (peer_cert_chain && peer_cert_chain->stack.num > 0)
+ {
+ X509 *xx=(X509 *)peer_cert_chain->stack.data[0];
+
+ if (xx)
+ subj=X509_get_subject_name(xx);
+ }
+ }
+
+
+ nentries=0;
+ if (subj)
+ nentries=X509_NAME_entry_count(subj);
+
+ domain[0]=0;
+ for (j=0; j<nentries; j++)
+ {
+ const char *obj_name;
+ X509_NAME_ENTRY *e;
+ ASN1_OBJECT *o;
+ ASN1_STRING *d;
+
+ int dlen;
+ unsigned char *ddata;
+
+ e=X509_NAME_get_entry(subj, j);
+ if (!e)
+ continue;
+
+ o=X509_NAME_ENTRY_get_object(e);
+ d=X509_NAME_ENTRY_get_data(e);
+
+ if (!o || !d)
+ continue;
+
+ obj_name=OBJ_nid2sn(OBJ_obj2nid(o));
+
+ dlen=ASN1_STRING_length(d);
+ ddata=ASN1_STRING_data(d);
+
+ if (strcasecmp(obj_name, "CN") == 0)
+ {
+ if (dlen >= sizeof(domain)-1)
+ dlen=sizeof(domain)-1;
+
+ memcpy(domain, ddata, dlen);
+ domain[dlen]=0;
+ }
+ }
+
+ if (x)
+ X509_free(x);
+ p=domain;
+
+ if (*p == '*')
+ {
+ int pl, l;
+
+ pl=strlen(++p);
+ l=strlen(info->peer_verify_domain);
+
+ if (*p == '.' && pl <= l &&
+ strcasecmp(info->peer_verify_domain+l-pl, p) == 0)
+ return (1);
+ }
+ else if (strcasecmp(info->peer_verify_domain, p) == 0)
+ return (1);
+
+ strcpy(errmsg, "couriertls: Mismatched SSL certificate: CN=");
+ strcat(errmsg, domain);
+ strcat(errmsg, " (expected ");
+ strncat(errmsg, info->peer_verify_domain, 256);
+ strcat(errmsg, ")");
+ (*info->tls_err_msg)(errmsg, info->app_data);
+ return (0);
+}
+
+#ifndef NO_RSA
+
+static RSA *rsa_callback(SSL *s, int export, int keylength)
+{
+ return (RSA_generate_key(keylength,RSA_F4,NULL,NULL));
+}
+
+#endif
+
+static void nonsslerror(const struct tls_info *info, const char *pfix)
+{
+ char errmsg[256];
+
+ strcpy(errmsg, "couriertls: ");
+ strncat(errmsg, pfix, 200);
+ strcat(errmsg, ": ");
+ strncat(errmsg, strerror(errno), 255 - strlen(errmsg));
+
+ (*info->tls_err_msg)(errmsg, info->app_data);
+}
+
+static void sslerror(const struct tls_info *info, const char *pfix, int rc)
+{
+ char errmsg[256];
+ char errmsgbuf2[300];
+ int errnum=ERR_get_error();
+
+ if (errnum == 0)
+ {
+ if (rc == 0)
+ {
+ (*info->tls_err_msg)("DEBUG: Unexpected SSL connection shutdown.",
+ info->app_data);
+ return;
+ }
+
+ nonsslerror(info, pfix);
+ return;
+ }
+
+ ERR_error_string_n(errnum, errmsg, sizeof(errmsg)-1);
+
+ errmsg[sizeof(errmsg)-1]=0;
+
+ strcpy(errmsgbuf2, "couriertls: ");
+ strncat(errmsgbuf2, pfix, 200);
+ strcat(errmsgbuf2, ": ");
+ strncat(errmsgbuf2, errmsg, 299 - strlen(errmsgbuf2));
+
+ (*info->tls_err_msg)(errmsgbuf2, info->app_data);
+}
+
+static void init_session_cache(struct tls_info *, SSL_CTX *);
+
+static int process_rsacertfile(SSL_CTX *ctx, const char *filename)
+{
+#ifndef NO_RSA
+
+ const struct tls_info *info=SSL_CTX_get_app_data(ctx);
+
+ SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+
+ if(!SSL_CTX_use_certificate_chain_file(ctx, filename))
+ {
+ sslerror(info, filename, -1);
+ return (0);
+ }
+
+ if(!SSL_CTX_use_RSAPrivateKey_file(ctx, filename, SSL_FILETYPE_PEM))
+ {
+ sslerror(info, filename, -1);
+ return (0);
+ }
+#endif
+ return (1);
+}
+
+
+static int process_dhcertfile(SSL_CTX *ctx, const char *filename)
+{
+#ifndef NO_DH
+
+ const struct tls_info *info=SSL_CTX_get_app_data(ctx);
+ BIO *bio;
+ DH *dh;
+ int cert_done=0;
+
+ if(!SSL_CTX_use_certificate_chain_file(ctx, filename))
+ {
+ sslerror(info, filename, -1);
+ return (0);
+ }
+
+ if ((bio=BIO_new_file(filename, "r")) != 0)
+ {
+ if ((dh=PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) != 0)
+ {
+ SSL_CTX_set_tmp_dh(ctx, dh);
+ cert_done=1;
+ DH_free(dh);
+ }
+ else
+ sslerror(info, filename, -1);
+ BIO_free(bio);
+ }
+ else
+ sslerror(info, filename, -1);
+
+ if (!cert_done)
+ {
+ (*info->tls_err_msg)("couriertls: DH init failed!",
+ info->app_data);
+
+ return (0);
+ }
+
+ if(!SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM))
+ {
+ sslerror(info, filename, -1);
+ return (0);
+ }
+#endif
+ return (1);
+}
+
+static int process_certfile(SSL_CTX *ctx, const char *certfile, const char *ip,
+ int (*func)(SSL_CTX *, const char *))
+{
+ if (ip && *ip)
+ {
+ char *test_file;
+
+ if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.'))
+ return (process_certfile(ctx, certfile, ip+7, func));
+
+ test_file= malloc(strlen(certfile)+strlen(ip)+2);
+
+ strcpy(test_file, certfile);
+ strcat(test_file, ".");
+ strcat(test_file, ip);
+
+ if (access(test_file, R_OK) == 0)
+ {
+ int rc= (*func)(ctx, test_file);
+
+ free(test_file);
+ return rc;
+ }
+ free(test_file);
+ }
+
+ return (*func)(ctx, certfile);
+}
+
+static int client_cert_cb(ssl_handle ssl, X509 **x509, EVP_PKEY **pkey)
+{
+ struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl);
+ int i;
+ const char *pem_cert;
+ size_t pem_cert_size;
+ STACK_OF(X509_NAME) *client_cas;
+ int cert_num=0;
+ int rc;
+
+ if (info->getpemclientcert4ca == NULL)
+ return 0;
+
+ rc=0;
+ client_cas=SSL_get_client_CA_list(ssl);
+
+ if (info->loadpemclientcert4ca)
+ (*info->loadpemclientcert4ca)(info->app_data);
+
+ for (cert_num=0; (*info->getpemclientcert4ca)(cert_num, &pem_cert,
+ &pem_cert_size,
+ info->app_data);
+ ++cert_num)
+ {
+ BIO *certbio;
+ int err;
+ X509 *x;
+
+ ERR_clear_error();
+
+ certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size);
+
+ if (!certbio)
+ {
+ rc= -1;
+ break;
+ }
+
+ x=PEM_read_bio_X509(certbio, x509, NULL, NULL);
+
+ if (!x)
+ {
+ BIO_free(certbio);
+ continue;
+ }
+
+ for (i=0; client_cas && i<client_cas->stack.num; i++)
+ {
+ X509_NAME *cert=(X509_NAME *)client_cas->stack.data[i];
+
+ if (X509_NAME_cmp(cert,
+ x->cert_info->issuer) == 0)
+ break;
+ }
+
+ if (!client_cas || i >= client_cas->stack.num)
+ {
+ BIO_free(certbio);
+ continue;
+ }
+
+ while ((x=PEM_read_bio_X509(certbio, NULL,
+ NULL, 0)) != NULL)
+ {
+ if (SSL_CTX_add_extra_chain_cert(SSL_get_SSL_CTX(ssl),
+ x)
+ != 1)
+ {
+ X509_free(x);
+ rc= -1;
+ break;
+ }
+ }
+
+ err = ERR_peek_last_error();
+ if (rc || ERR_GET_LIB(err) != ERR_LIB_PEM ||
+ ERR_GET_REASON(err) != PEM_R_NO_START_LINE)
+ {
+ BIO_free(certbio);
+ continue;
+ }
+ BIO_free(certbio);
+
+ ERR_clear_error();
+
+ certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size);
+
+ if (!certbio)
+ {
+ rc= -1;
+ break;
+ }
+
+ if (!PEM_read_bio_PrivateKey(certbio, pkey, NULL, NULL))
+ {
+ BIO_free(certbio);
+ continue;
+ }
+
+ BIO_free(certbio);
+ rc=1;
+ break;
+ }
+ ERR_clear_error();
+ (*info->releasepemclientcert4ca)(info->app_data);
+ return rc;
+}
+
+SSL_CTX *tls_create(int isserver, const struct tls_info *info)
+{
+ SSL_CTX *ctx;
+ const char *protocol=safe_getenv(info, "TLS_PROTOCOL");
+ const char *ssl_cipher_list=safe_getenv(info, "TLS_CIPHER_LIST");
+ int session_timeout=atoi(safe_getenv(info, "TLS_TIMEOUT"));
+ const char *dhcertfile=safe_getenv(info, "TLS_DHCERTFILE");
+ const char *certfile=safe_getenv(info, "TLS_CERTFILE");
+ const char *s;
+ struct stat stat_buf;
+ const char *peer_cert_dir=NULL;
+ const char *peer_cert_file=NULL;
+ int n;
+ struct tls_info *info_copy;
+
+ if (!*ssl_cipher_list)
+ ssl_cipher_list=NULL;
+
+ if (!*dhcertfile)
+ dhcertfile=NULL;
+
+ if (!*certfile)
+ certfile=NULL;
+
+ s=safe_getenv(info, "TLS_TRUSTCERTS");
+ if (s && stat(s, &stat_buf) == 0)
+ {
+ if (S_ISDIR(stat_buf.st_mode))
+ peer_cert_dir=s;
+ else
+ peer_cert_file=s;
+ }
+ else if (info->peer_verify_domain)
+ {
+ errno=ENOENT;
+ nonsslerror(info, "TLS_TRUSTCERTS not set");
+ return (NULL);
+ }
+
+ {
+ static int first=1;
+
+ if (first)
+ {
+ first=0;
+ SSL_load_error_strings();
+ SSLeay_add_ssl_algorithms();
+
+ while (RAND_status() != 1)
+ {
+ const char *p=random128();
+ size_t l=strlen(p);
+
+ RAND_add(p, l, l/16);
+ }
+ }
+ }
+
+
+ info_copy=malloc(sizeof(struct tls_info));
+
+ if (info_copy == NULL)
+ {
+ nonsslerror(info, "malloc");
+ return (NULL);
+ }
+
+ memcpy(info_copy, info, sizeof(*info_copy));
+ info_copy->isserver=isserver;
+ info_copy->certificate_verified=0;
+
+ if (!protocol || !*protocol)
+ protocol="SSL23";
+
+ ctx=SSL_CTX_new(protocol && strcmp(protocol, "SSL3") == 0
+ ? SSLv3_method():
+ protocol && strcmp(protocol, "SSL23") == 0
+ ? SSLv23_method():
+ TLSv1_method());
+
+ if (!ctx)
+ {
+ free(info_copy);
+ nonsslerror(info, "SSL_CTX_NEW");
+ return (0);
+ }
+ SSL_CTX_set_app_data(ctx, info_copy);
+ SSL_CTX_set_options(ctx, SSL_OP_ALL);
+
+ if (!ssl_cipher_list)
+ ssl_cipher_list="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH";
+
+ SSL_CTX_set_cipher_list(ctx, ssl_cipher_list);
+ SSL_CTX_set_timeout(ctx, session_timeout);
+
+ info_copy->tlscache=NULL;
+ init_session_cache(info_copy, ctx);
+
+
+ s = safe_getenv(info, "TCPLOCALIP");
+
+ if (certfile && !process_certfile(ctx, certfile, s,
+ process_rsacertfile))
+ {
+ tls_destroy(ctx);
+ return (NULL);
+ }
+
+ if (dhcertfile && !process_certfile(ctx, dhcertfile, s,
+ process_dhcertfile))
+ {
+ tls_destroy(ctx);
+ return (NULL);
+ }
+
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
+
+ n=atoi(safe_getenv(info, "TLS_INTCACHESIZE"));
+
+ if (n > 0)
+ SSL_CTX_sess_set_cache_size(ctx, n);
+
+ if (peer_cert_dir || peer_cert_file)
+ {
+ if ((!SSL_CTX_set_default_verify_paths(ctx))
+ || (!SSL_CTX_load_verify_locations(ctx, peer_cert_file,
+ peer_cert_dir)))
+ {
+ sslerror(info, peer_cert_file ?
+ peer_cert_file:peer_cert_dir, -1);
+ tls_destroy(ctx);
+ return (0);
+ }
+
+ if (isserver && peer_cert_file)
+ {
+ SSL_CTX_set_client_CA_list(ctx,
+ SSL_load_client_CA_file
+ (peer_cert_file));
+ }
+
+ if (isserver && peer_cert_dir)
+ {
+ DIR *dirp;
+ struct dirent *de;
+ X509 *x;
+
+ dirp=opendir(peer_cert_dir);
+ while (dirp && (de=readdir(dirp)) != NULL)
+ {
+ const char *p;
+ char *q;
+ FILE *fp;
+
+ p=strrchr(de->d_name, '.');
+ if (!p || !p[1])
+ continue;
+ while (*++p)
+ {
+ if (strchr("0123456789", *p) == NULL)
+ break;
+ }
+ if (*p)
+ continue;
+
+ q=malloc(strlen(peer_cert_dir)
+ +strlen(de->d_name) + 4);
+ if (!q)
+ {
+ nonsslerror(info, "malloc");
+ exit(1);
+ }
+
+ strcat(strcat(strcpy(q, peer_cert_dir),
+ "/"), de->d_name);
+
+ fp=fopen(q, "r");
+ if (!fp)
+ {
+ nonsslerror(info, q);
+ exit(1);
+ }
+ free(q);
+
+ while ((x=PEM_read_X509(fp, NULL, NULL, NULL)))
+ {
+ SSL_CTX_add_client_CA(ctx,x);
+ X509_free(x);
+ }
+ fclose(fp);
+ }
+ if (dirp)
+ closedir(dirp);
+ }
+ }
+ SSL_CTX_set_verify(ctx, get_peer_verify_level(info),
+ ssl_verify_callback);
+ if (!isserver)
+ SSL_CTX_set_client_cert_cb(ctx, client_cert_cb);
+ return (ctx);
+}
+
+void tls_destroy(SSL_CTX *ctx)
+{
+ struct tls_info *info=SSL_CTX_get_app_data(ctx);
+
+ SSL_CTX_flush_sessions(ctx, 0); /* OpenSSL bug, 2002-08-07 */
+
+ SSL_CTX_free(ctx);
+
+ if (info->tlscache)
+ {
+ tls_cache_close(info->tlscache);
+ info->tlscache=NULL;
+ }
+ free(info);
+}
+
+static int cache_add(SSL *ssl, SSL_SESSION *sess);
+
+static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len,
+ int *copyflag);
+static void cache_del(SSL_CTX *ctx, SSL_SESSION *ssl);
+
+static void init_session_cache(struct tls_info *info, SSL_CTX *ctx)
+{
+ const char *filename=safe_getenv(info, "TLS_CACHEFILE");
+ const char *cachesize=safe_getenv(info, "TLS_CACHESIZE");
+ off_t cachesize_l;
+
+ if (!filename || !*filename)
+ {
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+ return;
+ }
+
+ if (info->tlscache == NULL)
+ {
+ cachesize_l= cachesize ? (off_t)atol(cachesize):0;
+
+ if (cachesize_l <= 0)
+ cachesize_l=512L * 1024;
+ if ((info->tlscache=tls_cache_open(filename, cachesize_l))
+ == NULL)
+ {
+ nonsslerror(info, filename);
+ return;
+ }
+ }
+
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
+ SSL_CTX_sess_set_new_cb(ctx, cache_add);
+ SSL_CTX_sess_set_get_cb(ctx, cache_get);
+ SSL_CTX_sess_set_remove_cb(ctx, cache_del);
+}
+
+static int cache_add(SSL *ssl, SSL_SESSION *sess)
+{
+ struct tls_info *info=SSL_get_app_data(ssl);
+ unsigned char buffer[BUFSIZ];
+ unsigned char *ucp;
+ time_t timeout= (time_t)SSL_SESSION_get_time(sess)
+ + SSL_SESSION_get_timeout(sess);
+ void *session_id=(void *)sess->session_id;
+ size_t session_id_len=sess->session_id_length;
+ size_t sess_len=i2d_SSL_SESSION(sess, NULL);
+
+ if (sizeof(timeout) + sizeof(session_id_len) + session_id_len +
+ sess_len > sizeof(buffer))
+ {
+ fprintf(stderr, "WARN: starttls.c: buffer not big enough to cache SSL_SESSION\n");
+ return (0); /* Too big */
+ }
+
+ memcpy(buffer, &timeout, sizeof(timeout));
+ memcpy(buffer+sizeof(timeout), &session_id_len,
+ sizeof(session_id_len));
+ memcpy(buffer+sizeof(timeout)+sizeof(session_id_len),
+ session_id, session_id_len);
+ ucp=buffer+sizeof(timeout)+
+ sizeof(session_id_len)+session_id_len;
+
+ i2d_SSL_SESSION(sess, &ucp);
+ if (tls_cache_add(info->tlscache, (char *)buffer,
+ (size_t)(sizeof(timeout) +
+ sizeof(session_id_len) +
+ session_id_len + sess_len)))
+ perror("ALERT: tls_cache_add: ");
+
+#ifdef TLSCACHEDEBUG
+ fprintf(stderr, "INFO: TLSCACHE: added\n");
+#endif
+ return 0;
+}
+
+struct walk_info {
+ unsigned char *id;
+ int id_len;
+ int *copyflag;
+ SSL_SESSION *ret;
+ time_t now;
+};
+
+static int get_func(void *rec, size_t recsize,
+ int *doupdate, void *arg);
+
+static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len,
+ int *copyflag)
+{
+ const struct tls_info *info=SSL_get_app_data(ssl);
+ struct walk_info wi;
+
+ wi.id=id;
+ wi.id_len=id_len;
+ wi.copyflag=copyflag;
+ wi.ret=NULL;
+ time(&wi.now);
+ if (tls_cache_walk(info->tlscache, get_func, &wi) < 0)
+ perror("ALERT: tls_cache_walk: ");
+
+#ifdef TLSCACHEDEBUG
+ fprintf(stderr, "INFO: TLSCACHE: session %s\n",
+ wi.ret ? "found":"not found");
+#endif
+ if (wi.ret)
+ SSL_set_session_id_context(ssl, id, id_len);
+ return wi.ret;
+}
+
+static int get_func(void *rec, size_t recsize,
+ int *doupdate, void *arg)
+{
+ unsigned char *recp=(unsigned char *)rec;
+ struct walk_info *wi=(struct walk_info *)arg;
+ time_t timeout;
+ size_t session_id_len;
+
+ unsigned char *sess;
+
+ if (recsize < sizeof(timeout)+sizeof(session_id_len))
+ return (0);
+
+ memcpy(&timeout, recp, sizeof(timeout));
+
+ if (timeout <= wi->now)
+ return (0);
+
+ memcpy(&session_id_len, recp + sizeof(timeout),
+ sizeof(session_id_len));
+
+ if (session_id_len != (size_t)wi->id_len ||
+ memcmp(recp + sizeof(timeout) + sizeof(session_id_len),
+ wi->id, session_id_len))
+ return (0);
+
+ sess=recp + sizeof(timeout) + sizeof(session_id_len) + session_id_len;
+
+ wi->ret=d2i_SSL_SESSION(NULL, (const unsigned char **)
+ &sess, recsize - sizeof(timeout) -
+ sizeof(session_id_len) - session_id_len);
+
+ *wi->copyflag=0;
+ return 1;
+}
+
+static int del_func(void *rec, size_t recsize,
+ int *doupdate, void *arg);
+
+static void cache_del(SSL_CTX *ctx, SSL_SESSION *sess)
+{
+ const struct tls_info *info=SSL_CTX_get_app_data(ctx);
+ struct walk_info wi;
+
+ wi.now=0;
+
+ wi.id=(unsigned char *)sess->session_id;
+ wi.id_len=sess->session_id_length;
+ if (tls_cache_walk(info->tlscache, del_func, &wi) < 0)
+ perror("ALERT: tls_cache_walk: ");
+}
+
+static int del_func(void *rec, size_t recsize,
+ int *doupdate, void *arg)
+{
+ unsigned char *recp=(unsigned char *)rec;
+ struct walk_info *wi=(struct walk_info *)arg;
+ time_t timeout;
+ size_t session_id_len;
+
+ if (recsize < sizeof(timeout)+sizeof(session_id_len))
+ return (0);
+
+ memcpy(&timeout, recp, sizeof(timeout));
+
+ if (timeout <= wi->now)
+ return (0);
+
+ memcpy(&session_id_len, recp + sizeof(timeout),
+ sizeof(session_id_len));
+
+ if (session_id_len != (size_t)wi->id_len ||
+ memcmp(recp + sizeof(timeout) + sizeof(session_id_len),
+ wi->id, session_id_len))
+ return (0);
+
+ timeout=0;
+ memcpy(recp, &timeout, sizeof(timeout));
+ *doupdate=1;
+#ifdef TLSCACHEDEBUG
+ fprintf(stderr, "INFO: TLSCACHE: deleted\n");
+#endif
+ return (1);
+}
+
+
+/* ----------------------------------------------------------------- */
+
+SSL *tls_connect(SSL_CTX *ctx, int fd)
+{
+ struct tls_info *info=SSL_CTX_get_app_data(ctx);
+ SSL *ssl;
+ int rc;
+
+ /*
+ ** Initialize a tls_transfer_info object.
+ */
+
+ if (fcntl(fd, F_SETFL, O_NONBLOCK))
+ {
+ nonsslerror(info, "fcntl");
+ return (NULL);
+ }
+
+#ifdef SO_KEEPALIVE
+
+ {
+ int dummy;
+
+ dummy=1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const char *)&dummy, sizeof(dummy)) < 0)
+ {
+ nonsslerror(info, "setsockopt");
+ return (NULL);
+ }
+ }
+#endif
+
+#ifdef SO_LINGER
+ {
+ struct linger l;
+
+ l.l_onoff=0;
+ l.l_linger=0;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER,
+ (const char *)&l, sizeof(l)) < 0)
+ {
+ nonsslerror(info, "setsockopt");
+ return (NULL);
+ }
+ }
+#endif
+
+ if (!(ssl=SSL_new(ctx)))
+ {
+ sslerror(info, "SSL_new", -1);
+ return (NULL);
+ }
+
+ SSL_set_app_data(ssl, info);
+
+ SSL_set_fd(ssl, fd);
+ info->accept_interrupted=0;
+ info->connect_interrupted=0;
+
+ if (info->isserver)
+ {
+ SSL_set_accept_state(ssl);
+ if ((rc=SSL_accept(ssl)) > 0)
+ {
+ if (!verifypeer(info, ssl))
+ {
+ tls_disconnect(ssl, fd);
+ return (NULL);
+ }
+
+ if (info->connect_callback != NULL &&
+ !(*info->connect_callback)(ssl, info->app_data))
+ {
+ tls_disconnect(ssl, fd);
+ return (NULL);
+ }
+
+ return ssl;
+ }
+ info->accept_interrupted=1;
+ }
+ else
+ {
+ SSL_set_connect_state(ssl);
+
+ if ((rc=SSL_connect(ssl)) > 0)
+ {
+ if (!verifypeer(info, ssl))
+ {
+ tls_disconnect(ssl, fd);
+ return (NULL);
+ }
+
+ if (info->connect_callback != NULL &&
+ !(*info->connect_callback)(ssl, info->app_data))
+ {
+ tls_disconnect(ssl, fd);
+ return (NULL);
+ }
+ return (ssl);
+ }
+ info->connect_interrupted=1;
+ }
+
+ switch (SSL_get_error(ssl, rc)) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ break;
+ default:
+ sslerror(info, "connect", rc);
+ tls_disconnect(ssl, fd);
+ return NULL;
+ }
+
+ return (ssl);
+}
+
+void tls_disconnect(SSL *ssl, int fd)
+{
+ fcntl(fd, F_SETFL, 0);
+ SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
+ SSL_free(ssl);
+ ERR_remove_state(0);
+}
+
+/* --------------------------------------- */
+
+int tls_transfer(struct tls_transfer_info *t, SSL *ssl, int fd,
+ fd_set *r, fd_set *w)
+{
+ struct tls_info *info=SSL_get_app_data(ssl);
+ int n;
+
+ if (info->connect_interrupted)
+ {
+ n=SSL_connect(ssl);
+
+ switch (SSL_get_error(ssl, n)) {
+ case SSL_ERROR_NONE:
+ info->connect_interrupted=0;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ FD_SET(fd, w);
+ return (1);
+ case SSL_ERROR_WANT_READ:
+ FD_SET(fd, r);
+ return (1);
+ default:
+ info->connect_interrupted=0;
+ t->shutdown=1;
+ sslerror(info, "connect", n);
+ return (-1);
+ }
+
+ if (!verifypeer(info, ssl))
+ {
+ info->connect_interrupted=0;
+ t->shutdown=1;
+ return (-1);
+ }
+ if (info->connect_callback != NULL &&
+ !(*info->connect_callback)(ssl, info->app_data))
+ {
+ info->connect_interrupted=0;
+ t->shutdown=1;
+ return (-1);
+ }
+ }
+ else if (info->accept_interrupted)
+ {
+ n=SSL_accept(ssl);
+
+ switch (SSL_get_error(ssl, n)) {
+ case SSL_ERROR_NONE:
+ info->accept_interrupted=0;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ FD_SET(fd, w);
+ return (1);
+ case SSL_ERROR_WANT_READ:
+ FD_SET(fd, r);
+ return (1);
+ default:
+ info->accept_interrupted=0;
+ t->shutdown=1;
+ sslerror(info, "accept", n);
+ return (-1);
+ }
+
+ if (!verifypeer(info, ssl))
+ {
+ info->accept_interrupted=0;
+ t->shutdown=1;
+ return (-1);
+ }
+
+ if (info->connect_callback != NULL &&
+ !(*info->connect_callback)(ssl, info->app_data))
+ {
+ info->accept_interrupted=0;
+ t->shutdown=1;
+ return (-1);
+ }
+ }
+
+ if (t->shutdown)
+ return -1;
+
+ if (t->shutdown_interrupted && !t->read_interrupted &&
+ !t->write_interrupted)
+ {
+ n=SSL_shutdown(ssl);
+ if (n > 0)
+ {
+ t->shutdown_interrupted=0;
+ t->shutdown=1;
+ return -1;
+ }
+
+ switch (SSL_get_error(ssl, n)) {
+ case SSL_ERROR_WANT_WRITE:
+ FD_SET(fd, w);
+ break;
+ case SSL_ERROR_WANT_READ:
+ FD_SET(fd, r);
+ break;
+ default:
+ t->shutdown_interrupted=0;
+ t->shutdown= -1;
+ return -1;
+ }
+ return 1;
+ }
+
+ if (!t->write_interrupted && t->readleft > 0)
+ {
+ n=SSL_read(ssl, t->readptr, t->readleft);
+
+ switch (SSL_get_error(ssl, n)) {
+ case SSL_ERROR_NONE:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ t->read_interrupted=1;
+ FD_SET(fd, w);
+ return (1);
+ case SSL_ERROR_WANT_READ:
+ FD_SET(fd, r);
+ n=0;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ n=0;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ t->shutdown=1;
+ return (-1);
+ default:
+ sslerror(info, "read", n);
+ return (-1);
+ }
+ t->read_interrupted=0;
+ t->readptr += n;
+ t->readleft -= n;
+
+ if (n > 0)
+ return (0);
+ }
+
+ if (!t->read_interrupted && t->writeleft > 0)
+ {
+ n=SSL_write(ssl, t->writeptr, t->writeleft);
+
+ switch (SSL_get_error(ssl, n)) {
+ case SSL_ERROR_NONE:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ FD_SET(fd, w);
+ n=0;
+ break;
+ case SSL_ERROR_WANT_READ:
+ t->write_interrupted=1;
+ FD_SET(fd, r);
+ return (1);
+ case SSL_ERROR_ZERO_RETURN:
+ t->shutdown=1;
+ return (-1);
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ n=0;
+ break;
+ default:
+ return (-1);
+ }
+ t->write_interrupted=0;
+ t->writeptr += n;
+ t->writeleft -= n;
+
+ if (n > 0)
+ return (0);
+ }
+
+ return (1);
+}
+
+int tls_connecting(SSL *ssl)
+{
+ struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl);
+
+ return info->accept_interrupted || info->connect_interrupted;
+}
+
+int tls_certificate_verified(ssl_handle ssl)
+{
+ struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl);
+
+ return info->certificate_verified;
+}
+
+#define MAXDOMAINSIZE 256
+
+static time_t asn1toTime(ASN1_TIME *asn1Time)
+{
+ struct tm tm;
+ int offset;
+
+ if (asn1Time == NULL || asn1Time->length < 13)
+ return 0;
+
+ memset(&tm, 0, sizeof(tm));
+
+#define N2(n) ((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0')
+
+#define CPY(f,n) (tm.f=N2(n))
+
+ CPY(tm_year,0);
+
+ if(tm.tm_year < 50)
+ tm.tm_year += 100; /* Sux */
+
+ CPY(tm_mon, 2);
+ --tm.tm_mon;
+ CPY(tm_mday, 4);
+ CPY(tm_hour, 6);
+ CPY(tm_min, 8);
+ CPY(tm_sec, 10);
+
+ offset=0;
+
+ if (asn1Time->data[12] != 'Z')
+ {
+ if (asn1Time->length < 17)
+ return 0;
+
+ offset=N2(13)*3600+N2(15)*60;
+
+ if (asn1Time->data[12] == '-')
+ offset= -offset;
+ }
+
+#undef N2
+#undef CPY
+
+ return mktime(&tm)-offset;
+}
+
+
+static void dump_x509(X509 *x509,
+ void (*dump_func)(const char *, int cnt, void *),
+ void *dump_arg)
+{
+ X509_NAME *subj=X509_get_subject_name(x509);
+ int nentries, j;
+ time_t timestamp;
+ static const char gcc_shutup[]="%Y-%m-%d %H:%M:%S";
+
+ if (!subj)
+ return;
+
+ (*dump_func)("Subject:\n", -1, dump_arg);
+
+ nentries=X509_NAME_entry_count(subj);
+ for (j=0; j<nentries; j++)
+ {
+ const char *obj_name;
+ X509_NAME_ENTRY *e;
+ ASN1_OBJECT *o;
+ ASN1_STRING *d;
+
+ int dlen;
+ unsigned char *ddata;
+
+ e=X509_NAME_get_entry(subj, j);
+ if (!e)
+ continue;
+
+ o=X509_NAME_ENTRY_get_object(e);
+ d=X509_NAME_ENTRY_get_data(e);
+
+ if (!o || !d)
+ continue;
+
+ obj_name=OBJ_nid2sn(OBJ_obj2nid(o));
+
+ dlen=ASN1_STRING_length(d);
+ ddata=ASN1_STRING_data(d);
+
+ (*dump_func)(" ", -1, dump_arg);
+ (*dump_func)(obj_name, -1, dump_arg);
+ (*dump_func)("=", 1, dump_arg);
+ (*dump_func)((const char *)ddata, dlen, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+
+ }
+ (*dump_func)("\n", 1, dump_arg);
+
+ timestamp=asn1toTime(X509_get_notBefore(x509));
+
+ if (timestamp)
+ {
+ struct tm *tm=localtime(&timestamp);
+ char buffer[500];
+
+ buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup,
+ tm)]=0;
+
+ (*dump_func)("Not-Before: ", -1, dump_arg);
+ (*dump_func)(buffer, -1, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+ }
+
+ timestamp=asn1toTime(X509_get_notAfter(x509));
+ if (timestamp)
+ {
+ struct tm *tm=localtime(&timestamp);
+ char buffer[500];
+
+ buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup,
+ tm)]=0;
+
+ (*dump_func)("Not-After: ", -1, dump_arg);
+ (*dump_func)(buffer, -1, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+ }
+}
+
+void tls_dump_connection_info(ssl_handle ssl,
+ int server,
+ void (*dump_func)(const char *, int cnt, void *),
+ void *dump_arg)
+{
+ const SSL_CIPHER *cipher;
+
+ {
+ STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl);
+ int i;
+
+ if (server)
+ {
+ X509 *x=SSL_get_peer_certificate(ssl);
+
+ if (x)
+ {
+ dump_x509(x, dump_func, dump_arg);
+ X509_free(x);
+ }
+ }
+
+ for (i=0; peer_cert_chain && i<peer_cert_chain->stack.num; i++)
+ dump_x509((X509 *)peer_cert_chain->stack.data[i],
+ dump_func, dump_arg);
+ }
+
+ cipher=SSL_get_current_cipher(ssl);
+
+ if (cipher)
+ {
+ const char *c;
+
+ c=SSL_CIPHER_get_version(cipher);
+ if (c)
+ {
+ (*dump_func)("Version: ", -1, dump_arg);
+ (*dump_func)(c, -1, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+ }
+
+ {
+ char buf[10];
+
+ (*dump_func)("Bits: ", -1, dump_arg);
+
+ snprintf(buf, sizeof(buf), "%d",
+ SSL_CIPHER_get_bits(cipher, NULL));
+ buf[sizeof(buf)-1]=0;
+
+ (*dump_func)(buf, -1, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+ }
+
+ c=SSL_CIPHER_get_name(cipher);
+
+ if (c)
+ {
+ (*dump_func)("Cipher: ", -1, dump_arg);
+ (*dump_func)(c, -1, dump_arg);
+ (*dump_func)("\n", 1, dump_arg);
+ }
+ }
+}
+
+char *tls_get_encryption_desc(ssl_handle ssl)
+{
+ char protocolbuf[256];
+ const SSL_CIPHER *cipher;
+ const char *c, *d;
+
+ cipher=SSL_get_current_cipher(ssl);
+
+ c=cipher ? SSL_CIPHER_get_version(cipher):NULL;
+ d=cipher ? SSL_CIPHER_get_name(cipher):NULL;
+
+ snprintf(protocolbuf, sizeof(protocolbuf),
+ "%s,%dbits,%s",
+ c ? c:"unknown",
+ cipher ? SSL_CIPHER_get_bits(cipher, NULL):0,
+ d ? d:"unknown");
+ protocolbuf[sizeof(protocolbuf)-1]=0;
+ return strdup(protocolbuf);
+}
+
+
+/* ------------------- */
+
+int tls_validate_pem_cert(const char *buf, size_t buf_size)
+{
+ int rc;
+ BIO *certbio;
+ int err;
+ EVP_PKEY *pk;
+ X509 *x;
+
+ ERR_clear_error();
+
+ rc=0;
+ certbio=BIO_new_mem_buf((void *)buf, buf_size);
+
+ if (!certbio)
+ return (0);
+
+ x=PEM_read_bio_X509(certbio, NULL, NULL, NULL);
+
+ if (x)
+ {
+ X509_free(x);
+
+ while ((x=PEM_read_bio_X509(certbio, NULL, NULL, NULL)) != NULL)
+ X509_free(x);
+
+ err = ERR_peek_last_error();
+ if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
+ ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
+ {
+ rc=1;
+ }
+ }
+
+ ERR_clear_error();
+ BIO_free(certbio);
+
+ certbio=BIO_new_mem_buf((void *)buf, buf_size);
+
+ if (!certbio)
+ return (0);
+
+ if (!(pk=PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL)))
+ {
+ BIO_free(certbio);
+ ERR_clear_error();
+ return 0;
+ }
+
+ EVP_PKEY_free(pk);
+ return rc;
+}
+
+static size_t conv_name_to_rfc2553(const char *p, char *q)
+{
+#define PUTC(c) if (q) *q++=(c); ++n
+
+ size_t n=0;
+ const char *sep="";
+
+ while (*p)
+ {
+ if (*p == '/')
+ {
+ ++p;
+ continue;
+ }
+
+ while (*sep)
+ {
+ PUTC(*sep);
+ ++sep;
+ }
+ sep=",";
+
+ while (*p && *p != '/')
+ {
+ if (*p == '\\' && p[1])
+ ++p;
+ if (*p == '\\' || *p == ',')
+ {
+ PUTC('\\');
+ }
+ PUTC(*p);
+ ++p;
+ }
+ }
+ PUTC(0);
+#undef PUTC
+
+ return n;
+}
+
+char *tls_cert_name(const char *buf, size_t buf_size)
+{
+ BIO *certbio;
+ char *p, *q;
+ X509 *x;
+ size_t cnt;
+
+ certbio=BIO_new_mem_buf((void *)buf, buf_size);
+
+ if (!certbio)
+ {
+ ERR_clear_error();
+ return (0);
+ }
+
+ x=PEM_read_bio_X509(certbio, NULL, NULL, NULL);
+ p=0;
+ q=0;
+
+ if (x)
+ {
+ p=X509_NAME_oneline(x->cert_info->subject, NULL, 0);
+ X509_free(x);
+ }
+ ERR_clear_error();
+ BIO_free(certbio);
+
+ if (p)
+ {
+ cnt=conv_name_to_rfc2553(p, NULL);
+
+ q=malloc(cnt);
+
+ if (q)
+ conv_name_to_rfc2553(p, q);
+ free(p);
+ }
+
+ return q;
+}