Viewing file:
libcouriertls.c (29.13 KB) -rw-rw-rw-Select action/file-type:

(
+) |

(
+) |

(
+) |
Code (
+) |
Session (
+) |

(
+) |
SDB (
+) |

(
+) |

(
+) |

(
+) |

(
+) |

(
+) |
/*
** 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(×tamp);
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(×tamp);
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)
{
int rc;
BIO *certbio;
char *p, *q;
X509 *x;
size_t cnt;
rc=0;
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;
}