diff options
author | Alexander Færøy <ahf@0x90.dk> | 2016-10-16 14:39:00 +0200 |
---|---|---|
committer | Alexander Færøy <ahf@0x90.dk> | 2016-10-22 22:01:50 +0200 |
commit | 5146ce96318bc9a2513d104ea76ab3277a540074 (patch) | |
tree | 0a0e049374b482766f0503d221b6176feb7ccc62 /src/core | |
parent | c6c2e795376e4659810360deaa3dd28475d0f7c7 (diff) | |
download | irssi-5146ce96318bc9a2513d104ea76ab3277a540074.zip |
Add x509 certificate and public key pinning support.
This patch adds two new options to /CONNECT and /SERVER to let the user
pin either an x509 certificate and/or the public key of a given server.
It is possible to fetch the certificate outside of Irssi itself to
verify the checksum. To fetch the certificate call:
$ openssl s_client -connect chat.freenode.net:6697 < /dev/null 2>/dev/null | \
openssl x509 > freenode.cert
This will download chat.freenode.net:6697's TLS certificate and put it into the
file freenode.cert.
-tls_pinned_cert
----------------
This option allows you to specify the SHA-256 hash of the x509
certificate. When succesfully connected to the server, irssi will verify
that the given server certificate matches the pin set by the user.
The SHA-256 hash of a given certificate can be verified outside of irssi
using the OpenSSL command line tool:
$ openssl x509 -in freenode.cert -fingerprint -sha256 -noout
-tls_pinned_pubkey
------------------
This option allows you to specify the SHA-256 hash of the subject public key
information section of the server certificate. This section contains both the
cryptographic parameters for the public key, but also information about the
algorithm used together with the public key parameters.
When succesfully connected to the server, irssi will verify that the
given public key matches the pin set by the user.
The SHA-256 hash of a public key can be verified outside of irssi using
the OpenSSL command line tool:
$ openssl x509 -in freenode.cert -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -c | \
tr a-z A-Z
It is possible to specify both -tls_pinned_cert and -tls_pinned_pubkey
together.
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/chat-commands.c | 6 | ||||
-rw-r--r-- | src/core/network-openssl.c | 20 | ||||
-rw-r--r-- | src/core/server-connect-rec.h | 2 | ||||
-rw-r--r-- | src/core/server-setup-rec.h | 2 | ||||
-rw-r--r-- | src/core/servers-reconnect.c | 2 | ||||
-rw-r--r-- | src/core/servers-setup.c | 18 | ||||
-rw-r--r-- | src/core/servers.c | 2 | ||||
-rw-r--r-- | src/core/session.c | 2 |
8 files changed, 53 insertions, 1 deletions
diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index db60e46f..c737b810 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -115,6 +115,10 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, conn->tls_capath = g_strdup(tmp); if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL) conn->tls_ciphers = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL) + conn->tls_pinned_cert = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL) + conn->tls_pinned_pubkey = g_strdup(tmp); if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0') || (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0')) conn->tls_verify = TRUE; @@ -494,7 +498,7 @@ void chat_commands_init(void) signal_add("default command server", (SIGNAL_FUNC) sig_default_command_server); signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg); - command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +host noproxy -rawlog noautosendcmd"); + command_set_options("connect", "4 6 !! -network ssl +ssl_cert +ssl_pkey +ssl_pass ssl_verify +ssl_cafile +ssl_capath +ssl_ciphers +ssl_pinned_cert +ssl_pinned_pubkey tls +tls_cert +tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd"); command_set_options("msg", "channel nick"); } diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c index c221624f..13db6e25 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -753,6 +753,8 @@ int irssi_ssl_handshake(GIOChannel *handle) unsigned int pubkey_fingerprint_size; unsigned char cert_fingerprint[EVP_MAX_MD_SIZE]; unsigned int cert_fingerprint_size; + const char *pinned_cert_fingerprint = chan->server->connrec->tls_pinned_cert; + const char *pinned_pubkey_fingerprint = chan->server->connrec->tls_pinned_pubkey; TLS_REC *tls = NULL; ERR_clear_error(); @@ -814,6 +816,24 @@ int irssi_ssl_handshake(GIOChannel *handle) ret = 1; do { + if (pinned_cert_fingerprint != NULL && pinned_cert_fingerprint[0] != '\0') { + ret = g_ascii_strcasecmp(pinned_cert_fingerprint, tls->certificate_fingerprint) == 0; + + if (! ret) { + g_warning(" Pinned certificate mismatch"); + continue; + } + } + + if (pinned_pubkey_fingerprint != NULL && pinned_pubkey_fingerprint[0] != '\0') { + ret = g_ascii_strcasecmp(pinned_pubkey_fingerprint, tls->public_key_fingerprint) == 0; + + if (! ret) { + g_warning(" Pinned public key mismatch"); + continue; + } + } + if (chan->verify) { ret = irssi_ssl_verify(chan->ssl, chan->ctx, chan->server->connrec->address, chan->port, cert, chan->server, tls); diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h index 35577fd4..fa348769 100644 --- a/src/core/server-connect-rec.h +++ b/src/core/server-connect-rec.h @@ -29,6 +29,8 @@ char *tls_pass; char *tls_cafile; char *tls_capath; char *tls_ciphers; +char *tls_pinned_cert; +char *tls_pinned_pubkey; GIOChannel *connect_handle; /* connect using this handle */ diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h index 22876d4e..e6b0431c 100644 --- a/src/core/server-setup-rec.h +++ b/src/core/server-setup-rec.h @@ -17,6 +17,8 @@ char *tls_pass; char *tls_cafile; char *tls_capath; char *tls_ciphers; +char *tls_pinned_cert; +char *tls_pinned_pubkey; 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 16ec1fac..1727704c 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -199,6 +199,8 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) dest->tls_cafile = g_strdup(src->tls_cafile); dest->tls_capath = g_strdup(src->tls_capath); dest->tls_ciphers = g_strdup(src->tls_ciphers); + dest->tls_pinned_cert = g_strdup(src->tls_pinned_cert); + dest->tls_pinned_pubkey = g_strdup(src->tls_pinned_pubkey); return dest; } diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index 01a36e1c..9492c58c 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -181,6 +181,10 @@ static void server_setup_fill_server(SERVER_CONNECT_REC *conn, conn->tls_capath = g_strdup(sserver->tls_capath); if (conn->tls_ciphers == NULL && sserver->tls_ciphers != NULL && sserver->tls_ciphers[0] != '\0') conn->tls_ciphers = g_strdup(sserver->tls_ciphers); + if (conn->tls_pinned_cert == NULL && sserver->tls_pinned_cert != NULL && sserver->tls_pinned_cert[0] != '\0') + conn->tls_pinned_cert = g_strdup(sserver->tls_pinned_cert); + if (conn->tls_pinned_pubkey == NULL && sserver->tls_pinned_pubkey != NULL && sserver->tls_pinned_pubkey[0] != '\0') + conn->tls_pinned_pubkey = g_strdup(sserver->tls_pinned_pubkey); server_setup_fill_reconn(conn, sserver); @@ -435,6 +439,16 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) value = config_node_get_str(node, "ssl_ciphers", NULL); rec->tls_ciphers = g_strdup(value); + value = config_node_get_str(node, "tls_pinned_cert", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pinned_cert", NULL); + rec->tls_pinned_cert = g_strdup(value); + + value = config_node_get_str(node, "tls_pinned_pubkey", NULL); + if (value == NULL) + value = config_node_get_str(node, "ssl_pinned_pubkey", NULL); + rec->tls_pinned_pubkey = g_strdup(value); + if (rec->tls_cafile || rec->tls_capath) rec->tls_verify = TRUE; if (rec->tls_cert != NULL || rec->tls_verify) @@ -500,6 +514,8 @@ static void server_setup_save(SERVER_SETUP_REC *rec) iconfig_node_set_str(node, "tls_cafile", rec->tls_cafile); iconfig_node_set_str(node, "tls_capath", rec->tls_capath); iconfig_node_set_str(node, "tls_ciphers", rec->tls_ciphers); + iconfig_node_set_str(node, "tls_pinned_cert", rec->tls_pinned_cert); + iconfig_node_set_str(node, "tls_pinned_pubkey", rec->tls_pinned_pubkey); iconfig_node_set_str(node, "own_host", rec->own_host); @@ -550,6 +566,8 @@ static void server_setup_destroy(SERVER_SETUP_REC *rec) g_free_not_null(rec->tls_cafile); g_free_not_null(rec->tls_capath); g_free_not_null(rec->tls_ciphers); + g_free_not_null(rec->tls_pinned_cert); + g_free_not_null(rec->tls_pinned_pubkey); g_free(rec->address); g_free(rec); } diff --git a/src/core/servers.c b/src/core/servers.c index 2a14d510..b9faab81 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -633,6 +633,8 @@ void server_connect_unref(SERVER_CONNECT_REC *conn) g_free_not_null(conn->tls_cafile); g_free_not_null(conn->tls_capath); g_free_not_null(conn->tls_ciphers); + g_free_not_null(conn->tls_pinned_cert); + g_free_not_null(conn->tls_pinned_pubkey); 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 5b3303bb..34190c52 100644 --- a/src/core/session.c +++ b/src/core/session.c @@ -165,6 +165,8 @@ static void session_save_server(SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "tls_cafile", server->connrec->tls_cafile); config_node_set_str(config, node, "tls_capath", server->connrec->tls_capath); config_node_set_str(config, node, "tls_ciphers", server->connrec->tls_ciphers); + config_node_set_str(config, node, "tls_pinned_cert", server->connrec->tls_pinned_cert); + config_node_set_str(config, node, "tls_pinned_pubkey", server->connrec->tls_pinned_pubkey); handle = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle)); config_node_set_int(config, node, "handle", handle); |