#include "module.h"
#include "network.h"
+#include "misc.h"
#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
-/* ssl read */
-GIOError irssi_ssl_read(GIOChannel *, gchar *, guint, guint *);
-/* ssl write */
-GIOError irssi_ssl_write(GIOChannel *, gchar *, guint, guint*);
-/* ssl seek */
-GIOError irssi_ssl_seek(GIOChannel *, gint, GSeekType);
-/* ssl close */
-void irssi_ssl_close(GIOChannel *);
-#if GLIB_MAJOR_VERSION < 2
-/* ssl create watch */
-guint irssi_ssl_create_watch(GIOChannel *, gint, GIOCondition, GIOFunc, gpointer, GDestroyNotify);
-#else
-GSource *irssi_ssl_create_watch(GIOChannel *, GIOCondition);
-#endif
-/* ssl free */
-void irssi_ssl_free(GIOChannel *);
-
/* ssl i/o channel object */
typedef struct
{
gint fd;
GIOChannel *giochan;
SSL *ssl;
- X509 *cert;
+ SSL_CTX *ctx;
+ unsigned int got_cert:1;
+ unsigned int verify:1;
} GIOSSLChannel;
-/* ssl function pointers */
-GIOFuncs irssi_ssl_channel_funcs =
+static SSL_CTX *ssl_ctx = NULL;
+
+static void irssi_ssl_free(GIOChannel *handle)
{
- irssi_ssl_read,
- irssi_ssl_write,
- irssi_ssl_seek,
- irssi_ssl_close,
- irssi_ssl_create_watch,
- irssi_ssl_free
-};
+ 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);
+}
-SSL_CTX *ssl_ctx = NULL;
+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;
+}
-#ifdef G_CAN_INLINE
-G_INLINE_FUNC
-#endif
-gint ssl_errno(gint e)
+
+#if GLIB_MAJOR_VERSION < 2
+
+static GIOError ssl_errno(gint e)
{
switch(e)
{
return G_IO_ERROR_INVAL;
}
/*UNREACH*/
- return -1;
+ return G_IO_ERROR_INVAL;
}
-gboolean irssi_ssl_cert_step(GIOSSLChannel *chan)
+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)
return ssl_errno(errno);
}
/*UNREACH*/
- return -1;
+ return G_IO_ERROR_INVAL;
}
-GIOError irssi_ssl_read(GIOChannel *handle, gchar *buf, guint len, guint *ret)
+static GIOError irssi_ssl_read(GIOChannel *handle, gchar *buf, guint len, guint *ret)
{
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)
return -1;
}
-GIOError irssi_ssl_write(GIOChannel *handle, gchar *buf, guint len, guint *ret)
+static GIOError irssi_ssl_write(GIOChannel *handle, gchar *buf, guint len, guint *ret)
{
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)
return G_IO_ERROR_NONE;
}
/*UNREACH*/
- return -1;
+ return G_IO_ERROR_INVAL;
}
-GIOError irssi_ssl_seek(GIOChannel *handle, gint offset, GSeekType type)
+static GIOError irssi_ssl_seek(GIOChannel *handle, gint offset, GSeekType type)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
GIOError e;
return (e == G_IO_ERROR_NONE) ? G_IO_ERROR_NONE : G_IO_ERROR_INVAL;
}
-void irssi_ssl_close(GIOChannel *handle)
+static void irssi_ssl_close(GIOChannel *handle)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
g_io_channel_close(chan->giochan);
}
-#if GLIB_MAJOR_VERSION < 2
-guint irssi_ssl_create_watch(GIOChannel *handle, gint priority, GIOCondition cond,
+static guint irssi_ssl_create_watch(GIOChannel *handle, gint priority, GIOCondition cond,
GIOFunc func, gpointer data, GDestroyNotify notify)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
return chan->giochan->funcs->io_add_watch(handle, priority, cond, func, data, notify);
}
-#else
-GSource *irssi_ssl_create_watch(GIOChannel *handle, GIOCondition cond)
+
+/* ssl function pointers */
+static GIOFuncs irssi_ssl_channel_funcs =
+{
+ irssi_ssl_read,
+ irssi_ssl_write,
+ irssi_ssl_seek,
+ irssi_ssl_close,
+ irssi_ssl_create_watch,
+ irssi_ssl_free
+};
+
+#else /* GLIB_MAJOR_VERSION < 2 */
+
+static GIOStatus ssl_errno(gint e)
+{
+ switch(e)
+ {
+ case EINVAL:
+ return G_IO_STATUS_ERROR;
+ case EINTR:
+ case EAGAIN:
+ return G_IO_STATUS_AGAIN;
+ default:
+ return G_IO_STATUS_ERROR;
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_cert_step(GIOSSLChannel *chan)
+{
+ X509 *cert;
+ gint err;
+ switch(err = SSL_do_handshake(chan->ssl))
+ {
+ case 1:
+ 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)
+ return G_IO_STATUS_AGAIN;
+ return ssl_errno(errno);
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ gint err;
+
+ if(! chan->got_cert)
+ {
+ gint cert_err = irssi_ssl_cert_step(chan);
+ if(cert_err != G_IO_STATUS_NORMAL)
+ return cert_err;
+ }
+
+ err = SSL_read(chan->ssl, buf, len);
+ if(err < 0)
+ {
+ *ret = 0;
+ if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
+ return G_IO_STATUS_AGAIN;
+ return ssl_errno(errno);
+ }
+ else
+ {
+ *ret = err;
+ return G_IO_STATUS_NORMAL;
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_write(GIOChannel *handle, const gchar *buf, gsize len, gsize *ret, GError **gerr)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ gint err;
- return chan->giochan->funcs->io_create_watch(handle, cond);
+ if(! chan->got_cert)
+ {
+ gint cert_err = irssi_ssl_cert_step(chan);
+ if(cert_err != G_IO_STATUS_NORMAL)
+ return cert_err;
+ }
+
+ err = SSL_write(chan->ssl, (const char *)buf, len);
+ if(err < 0)
+ {
+ *ret = 0;
+ if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
+ return G_IO_STATUS_AGAIN;
+ return ssl_errno(errno);
+ }
+ else
+ {
+ *ret = err;
+ return G_IO_STATUS_NORMAL;
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
}
-#endif
-void irssi_ssl_free(GIOChannel *handle)
+static GIOStatus irssi_ssl_seek(GIOChannel *handle, gint64 offset, GSeekType type, GError **gerr)
{
GIOSSLChannel *chan = (GIOSSLChannel *)handle;
- g_io_channel_unref(chan->giochan);
- SSL_free(chan->ssl);
- g_free(chan);
+ GIOError e;
+ e = g_io_channel_seek(chan->giochan, offset, type);
+ return (e == G_IO_ERROR_NONE) ? G_IO_STATUS_NORMAL : G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_close(GIOChannel *handle, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ g_io_channel_close(chan->giochan);
+
+ return G_IO_STATUS_NORMAL;
+}
+
+static GSource *irssi_ssl_create_watch(GIOChannel *handle, GIOCondition cond)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_create_watch(handle, cond);
+}
+
+static GIOStatus irssi_ssl_set_flags(GIOChannel *handle, GIOFlags flags, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_set_flags(handle, flags, gerr);
}
-gboolean irssi_ssl_init(void)
+static GIOFlags irssi_ssl_get_flags(GIOChannel *handle)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_get_flags(handle);
+}
+
+static GIOFuncs irssi_ssl_channel_funcs = {
+ irssi_ssl_read,
+ irssi_ssl_write,
+ irssi_ssl_seek,
+ irssi_ssl_close,
+ irssi_ssl_create_watch,
+ irssi_ssl_free,
+ irssi_ssl_set_flags,
+ irssi_ssl_get_flags
+};
+
+#endif
+
+static gboolean irssi_ssl_init(void)
{
SSL_library_init();
SSL_load_error_strings();
}
-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);
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;
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;
}
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;
- g_io_channel_ref(handle);
+ chan->ctx = ctx;
+ chan->got_cert = cert != NULL;
+ chan->verify = verify;
gchan = (GIOChannel *)chan;
gchan->funcs = &irssi_ssl_channel_funcs;
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);
- return gret;
+ GIOChannel *handle, *ssl_handle;
+
+ handle = net_connect_ip(ip, port, my_ip);
+ ssl_handle = irssi_ssl_get_iochannel(handle, cert, pkey, cafile, capath, verify);
+ if (ssl_handle == NULL)
+ g_io_channel_unref(handle);
+ return ssl_handle;
}
#else /* HAVE_OPENSSL */
-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)
{
g_warning("Connection failed: SSL support not enabled in this build.");
errno = ENOSYS;