[PATCH 3/4] Implement ssl_sha256 option

noname <[email protected]> Mon, 10 Nov 2014 16:33:11 +0000
Newsgroups gmane.network.irc.irssi.devel
Message-ID <20141110163311.GA15790@darkstar>
---
 src/core/network-openssl.c    | 26 +++++++++++++++++++++++++-
 src/core/server-connect-rec.h |  1 +
 src/core/server-setup-rec.h   |  1 +
 src/core/servers-reconnect.c  |  1 +
 src/core/servers-setup.c      |  7 ++++++-
 src/core/servers.c            |  1 +
 src/core/session.c            |  1 +
 7 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c
index efb7525..6daa612 100644
--- a/src/core/network-openssl.c
+++ b/src/core/network-openssl.c
@@ -282,6 +282,29 @@ static gboolean irssi_ssl_verify(SSL *ssl, const char *hostname, int port, X509
 	return TRUE;
 }
 
+static gboolean match_fingerprint(X509 *cert, const char *fingerprint)
+{
+	unsigned char md[EVP_MAX_MD_SIZE];
+	unsigned int i, n;
+	char fp[EVP_MAX_MD_SIZE*3];
+
+	if (! X509_digest(cert, EVP_sha256(), md, &n)) {
+		g_warning("Could not get fingerprint from peer certificate");
+		return FALSE;
+	}
+
+	for (i = 0; i < n; i++)
+		sprintf(fp + 3 * i, "%02X%c", md[i], i + 1 == n ? '\0' : ':');
+
+	if (strcmp(fp, fingerprint) != 0) {
+		g_warning("Fingerprint mismatch");
+		g_warning("Got %s", fp);
+		g_warning("Need %s", fingerprint);
+		return FALSE;
+	}
+	return TRUE;
+}
+
 static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
@@ -601,7 +624,8 @@ int irssi_ssl_handshake(GIOChannel *handle)
 		g_warning("SSL server supplied no certificate");
 		return -1;
 	}
-	ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->server->connrec->address, chan->port, cert, chan->server);
+	ret = (!chan->verify || irssi_ssl_verify(chan->ssl, chan->server->connrec->address, chan->port, cert, chan->server)) &&
+	      (chan->server->connrec->ssl_sha256 == NULL || match_fingerprint(cert, chan->server->connrec->ssl_sha256));
 	X509_free(cert);
 	return ret ? 0 : -1;
 }
diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h
index 1753750..04ad047 100644
--- a/src/core/server-connect-rec.h
+++ b/src/core/server-connect-rec.h
@@ -28,6 +28,7 @@ char *ssl_pkey;
 char *ssl_pass;
 char *ssl_cafile;
 char *ssl_capath;
+char *ssl_sha256;
 
 GIOChannel *connect_handle; /* connect using this handle */
 
diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h
index ae79755..53e5978 100644
--- a/src/core/server-setup-rec.h
+++ b/src/core/server-setup-rec.h
@@ -13,6 +13,7 @@ char *ssl_pkey;
 char *ssl_pass;
 char *ssl_cafile;
 char *ssl_capath;
+char *ssl_sha256;
 
 char *own_host; /* address to use when connecting this server */
 IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c
index 0a08b46..069bbf4 100644
--- a/src/core/servers-reconnect.c
+++ b/src/core/servers-reconnect.c
@@ -197,6 +197,7 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
 	dest->ssl_verify = src->ssl_verify;
 	dest->ssl_cafile = g_strdup(src->ssl_cafile);
 	dest->ssl_capath = g_strdup(src->ssl_capath);
+	dest->ssl_sha256 = g_strdup(src->ssl_sha256);
 
 	return dest;
 }
diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c
index 0819ff1..42c6b3a 100644
--- a/src/core/servers-setup.c
+++ b/src/core/servers-setup.c
@@ -176,6 +176,8 @@ static void server_setup_fill_server(SERVER_CONNECT_REC *conn,
 		conn->ssl_cafile = g_strdup(sserver->ssl_cafile);
 	if (conn->ssl_capath == NULL && sserver->ssl_capath != NULL && sserver->ssl_capath[0] != '\0')
 		conn->ssl_capath = g_strdup(sserver->ssl_capath);
+	if (conn->ssl_sha256 == NULL && sserver->ssl_sha256 != NULL && sserver->ssl_sha256[0] != '\0')
+		conn->ssl_sha256 = g_strdup(sserver->ssl_sha256);
 
 	server_setup_fill_reconn(conn, sserver);
 
@@ -402,9 +404,10 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node)
 	rec->ssl_verify = config_node_get_bool(node, "ssl_verify", FALSE);
 	rec->ssl_cafile = g_strdup(config_node_get_str(node, "ssl_cafile", NULL));
 	rec->ssl_capath = g_strdup(config_node_get_str(node, "ssl_capath", NULL));
+	rec->ssl_sha256 = g_strdup(config_node_get_str(node, "ssl_sha256", NULL));
 	if (rec->ssl_cafile || rec->ssl_capath)
 		rec->ssl_verify = TRUE;
-	if (rec->ssl_cert != NULL || rec->ssl_verify)
+	if (rec->ssl_cert != NULL || rec->ssl_verify || rec->ssl_sha256 != NULL)
 		rec->use_ssl = TRUE;
 	rec->port = port;
 	rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
@@ -442,6 +445,7 @@ static void server_setup_save(SERVER_SETUP_REC *rec)
 	iconfig_node_set_bool(node, "ssl_verify", rec->ssl_verify);
 	iconfig_node_set_str(node, "ssl_cafile", rec->ssl_cafile);
 	iconfig_node_set_str(node, "ssl_capath", rec->ssl_capath);
+	iconfig_node_set_str(node, "ssl_sha256", rec->ssl_sha256);
 	iconfig_node_set_str(node, "own_host", rec->own_host);
 
 	iconfig_node_set_str(node, "family",
@@ -483,6 +487,7 @@ static void server_setup_destroy(SERVER_SETUP_REC *rec)
 	g_free_not_null(rec->ssl_pass);
 	g_free_not_null(rec->ssl_cafile);
 	g_free_not_null(rec->ssl_capath);
+	g_free_not_null(rec->ssl_sha256);
 	g_free(rec->address);
 	g_free(rec);
 }
diff --git a/src/core/servers.c b/src/core/servers.c
index 6eaad19..5457242 100644
--- a/src/core/servers.c
+++ b/src/core/servers.c
@@ -636,6 +636,7 @@ void server_connect_unref(SERVER_CONNECT_REC *conn)
 	g_free_not_null(conn->ssl_pass);
 	g_free_not_null(conn->ssl_cafile);
 	g_free_not_null(conn->ssl_capath);
+	g_free_not_null(conn->ssl_sha256);
 
 	g_free_not_null(conn->channels);
         g_free_not_null(conn->away_reason);
diff --git a/src/core/session.c b/src/core/session.c
index b300263..4159bad 100644
--- a/src/core/session.c
+++ b/src/core/session.c
@@ -165,6 +165,7 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config,
 	config_node_set_bool(config, node, "ssl_verify", server->connrec->ssl_verify);
 	config_node_set_str(config, node, "ssl_cafile", server->connrec->ssl_cafile);
 	config_node_set_str(config, node, "ssl_capath", server->connrec->ssl_capath);
+	config_node_set_str(config, node, "ssl_sha256", server->connrec->ssl_sha256);
 
 	handle = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle));
 	config_node_set_int(config, node, "handle", handle);
-- 
1.8.4