Support for sending SSL certificate to server and optionally verify server's

certificate. See the -ssl_* options for /SERVER and /SERVER ADD. Patch by
Joel Eriksson <je-irssi@bitnux.com>.


git-svn-id: http://svn.irssi.org/repos/irssi/trunk@3146 dbcabf3a-b0e7-0310-adc4-f8d773084564
This commit is contained in:
Timo Sirainen 2003-11-16 17:53:55 +00:00 committed by cras
commit 43b0d36ee1
11 changed files with 275 additions and 30 deletions

View file

@ -20,6 +20,7 @@
#include "module.h"
#include "network.h"
#include "misc.h"
#ifdef HAVE_OPENSSL
@ -36,14 +37,20 @@ typedef struct
gint fd;
GIOChannel *giochan;
SSL *ssl;
X509 *cert;
SSL_CTX *ctx;
unsigned int got_cert:1;
unsigned int verify:1;
} GIOSSLChannel;
static SSL_CTX *ssl_ctx = NULL;
static void irssi_ssl_free(GIOChannel *handle)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
g_io_channel_unref(chan->giochan);
SSL_free(chan->ssl);
if (chan->ctx != ssl_ctx)
SSL_CTX_free(chan->ctx);
g_free(chan);
}
@ -70,17 +77,63 @@ GIOError ssl_errno(gint e)
return G_IO_ERROR_INVAL;
}
static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, X509 *cert)
{
if (SSL_get_verify_result(ssl) != X509_V_OK) {
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int n;
char *str;
g_warning("Could not verify SSL servers certificate:");
if ((str = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) == NULL)
g_warning(" Could not get subject-name from peer certificate");
else {
g_warning(" Subject : %s", str);
free(str);
}
if ((str = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0)) == NULL)
g_warning(" Could not get issuer-name from peer certificate");
else {
g_warning(" Issuer : %s", str);
free(str);
}
if (! X509_digest(cert, EVP_md5(), md, &n))
g_warning(" Could not get fingerprint from peer certificate");
else {
char hex[] = "0123456789ABCDEF";
char fp[EVP_MAX_MD_SIZE*3];
if (n < sizeof(fp)) {
unsigned int i;
for (i = 0; i < n; i++) {
fp[i*3+0] = hex[(md[i] >> 4) & 0xF];
fp[i*3+1] = hex[(md[i] >> 0) & 0xF];
fp[i*3+2] = i == n - 1 ? '\0' : ':';
}
g_warning(" MD5 Fingerprint : %s", fp);
}
}
return FALSE;
}
return TRUE;
}
static GIOError irssi_ssl_cert_step(GIOSSLChannel *chan)
{
X509 *cert;
gint err;
switch(err = SSL_do_handshake(chan->ssl))
{
case 1:
if(!(chan->cert = SSL_get_peer_certificate(chan->ssl)))
if(!(cert = SSL_get_peer_certificate(chan->ssl)))
{
g_warning("SSL server supplied no certificate");
return G_IO_ERROR_INVAL;
}
if (chan->verify && ! irssi_ssl_verify(chan->ssl, chan->ctx, cert)) {
X509_free(cert);
return G_IO_ERROR_INVAL;
}
X509_free(cert);
return G_IO_ERROR_NONE;
default:
if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
@ -96,7 +149,7 @@ static GIOError irssi_ssl_read(GIOChannel *handle, gchar *buf, guint len, guint
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
gint err;
if(chan->cert == NULL)
if(! chan->got_cert)
{
gint cert_err = irssi_ssl_cert_step(chan);
if(cert_err != G_IO_ERROR_NONE)
@ -125,7 +178,7 @@ static GIOError irssi_ssl_write(GIOChannel *handle, gchar *buf, guint len, guint
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
gint err;
if(chan->cert == NULL)
if(chan->got_cert)
{
gint cert_err = irssi_ssl_cert_step(chan);
if(cert_err != G_IO_ERROR_NONE)
@ -208,15 +261,21 @@ GIOStatus ssl_errno(gint e)
static GIOStatus irssi_ssl_cert_step(GIOSSLChannel *chan)
{
X509 *cert;
gint err;
switch(err = SSL_do_handshake(chan->ssl))
{
case 1:
if(!(chan->cert = SSL_get_peer_certificate(chan->ssl)))
if(!(cert = SSL_get_peer_certificate(chan->ssl)))
{
g_warning("SSL server supplied no certificate");
return G_IO_STATUS_ERROR;
}
if (chan->verify && ! irssi_ssl_verify(chan->ssl, chan->ctx, cert)) {
X509_free(cert);
return G_IO_STATUS_ERROR;
}
X509_free(cert);
return G_IO_STATUS_NORMAL;
default:
if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
@ -232,7 +291,7 @@ static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, guint len, guint
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
gint err;
if(chan->cert == NULL)
if(! chan->got_cert)
{
gint cert_err = irssi_ssl_cert_step(chan);
if(cert_err != G_IO_STATUS_NORMAL)
@ -261,7 +320,7 @@ static GIOStatus irssi_ssl_write(GIOChannel *handle, const gchar *buf, gsize len
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
gint err;
if(chan->cert == NULL)
if(! chan->got_cert)
{
gint cert_err = irssi_ssl_cert_step(chan);
if(cert_err != G_IO_STATUS_NORMAL)
@ -335,8 +394,6 @@ static GIOFuncs irssi_ssl_channel_funcs = {
#endif
static SSL_CTX *ssl_ctx = NULL;
static gboolean irssi_ssl_init(void)
{
SSL_library_init();
@ -353,13 +410,14 @@ static gboolean irssi_ssl_init(void)
}
static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle)
static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
{
GIOSSLChannel *chan;
GIOChannel *gchan;
int err, fd;
SSL *ssl;
X509 *cert = NULL;
SSL_CTX *ctx = NULL;
g_return_val_if_fail(handle != NULL, NULL);
@ -369,7 +427,52 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle)
if(!(fd = g_io_channel_unix_get_fd(handle)))
return NULL;
if(!(ssl = SSL_new(ssl_ctx)))
if (mycert && *mycert) {
char *scert = NULL, *spkey = NULL;
if ((ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
g_error("Could not allocate memory for SSL context");
return NULL;
}
scert = convert_home(mycert);
if (mypkey && *mypkey)
spkey = convert_home(mypkey);
if (! SSL_CTX_use_certificate_file(ctx, scert, SSL_FILETYPE_PEM))
g_warning("Loading of client certificate '%s' failed", mycert);
else if (! SSL_CTX_use_PrivateKey_file(ctx, spkey ? spkey : scert, SSL_FILETYPE_PEM))
g_warning("Loading of private key '%s' failed", mypkey ? mypkey : mycert);
else if (! SSL_CTX_check_private_key(ctx))
g_warning("Private key does not match the certificate");
g_free(scert);
g_free(spkey);
}
if ((cafile && *cafile) || (capath && *capath)) {
char *scafile = NULL;
char *scapath = NULL;
if (! ctx && (ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
g_error("Could not allocate memory for SSL context");
return NULL;
}
if (cafile && *cafile)
scafile = convert_home(cafile);
if (capath && *capath)
scapath = convert_home(capath);
if (! SSL_CTX_load_verify_locations(ctx, scafile, scapath)) {
g_warning("Could not load CA list for verifying SSL server certificate");
g_free(scafile);
g_free(scapath);
SSL_CTX_free(ctx);
return NULL;
}
g_free(scafile);
g_free(scapath);
verify = TRUE;
}
if (ctx == NULL)
ctx = ssl_ctx;
if(!(ssl = SSL_new(ctx)))
{
g_warning("Failed to allocate SSL structure");
return NULL;
@ -378,6 +481,9 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle)
if(!(err = SSL_set_fd(ssl, fd)))
{
g_warning("Failed to associate socket to SSL stream");
SSL_free(ssl);
if (ctx != ssl_ctx)
SSL_CTX_free(ctx);
return NULL;
}
@ -391,22 +497,38 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle)
case SSL_ERROR_WANT_WRITE:
break;
default:
SSL_free(ssl);
if (ctx != ssl_ctx)
SSL_CTX_free(ctx);
return NULL;
}
}
else if(!(cert = SSL_get_peer_certificate(ssl)))
{
g_warning("SSL server supplied no certificate");
if (ctx != ssl_ctx)
SSL_CTX_free(ctx);
SSL_free(ssl);
return NULL;
}
else
{
if (verify && ! irssi_ssl_verify(ssl, ctx, cert)) {
SSL_free(ssl);
if (ctx != ssl_ctx)
SSL_CTX_free(ctx);
return NULL;
}
X509_free(cert);
}
chan = g_new0(GIOSSLChannel, 1);
chan->fd = fd;
chan->giochan = handle;
chan->ssl = ssl;
chan->cert = cert;
chan->ctx = ctx;
chan->got_cert = cert != NULL;
chan->verify = verify;
g_io_channel_ref(handle);
gchan = (GIOChannel *)chan;
@ -416,10 +538,10 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle)
return gchan;
}
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip)
GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
{
GIOChannel *gret = net_connect_ip(ip, port, my_ip);
gret = irssi_ssl_get_iochannel(gret);
gret = irssi_ssl_get_iochannel(gret, cert, pkey, cafile, capath, verify);
return gret;
}