From 210daef5cfbbb4aefcd83c9376589c11c8a35f03 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Haha Date: Mon, 20 Jan 2014 02:11:33 +0100 Subject: [PATCH] irc: add option to accept server certificate with fingerprint --- src/plugins/irc/irc-config.c | 13 +++++ src/plugins/irc/irc-server.c | 115 +++++++++++++++++++++++++++++++++++++++++-- src/plugins/irc/irc-server.h | 1 + 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/plugins/irc/irc-config.c b/src/plugins/irc/irc-config.c index 36dc8f4..9a026dc 100644 --- a/src/plugins/irc/irc-config.c +++ b/src/plugins/irc/irc-config.c @@ -1581,6 +1581,19 @@ irc_config_server_new_option (struct t_config_file *config_file, callback_change, callback_change_data, NULL, NULL); break; + case IRC_SERVER_OPTION_SSL_FINGERPRINT: + new_option = weechat_config_new_option ( + config_file, section, + option_name, "string", + N_("only accept the the server certificate if it has this SHA1 fingerprint" + "(the fingerprint must be encoded as a hexadecimal string without seperators between the bytes)"), + NULL, 0, 0, + default_value, value, + null_value_allowed, + callback_check_value, callback_check_value_data, + callback_change, callback_change_data, + NULL, NULL); + break; case IRC_SERVER_OPTION_PASSWORD: new_option = weechat_config_new_option ( config_file, section, diff --git a/src/plugins/irc/irc-server.c b/src/plugins/irc/irc-server.c index c058ba9..dd655d6 100644 --- a/src/plugins/irc/irc-server.c +++ b/src/plugins/irc/irc-server.c @@ -72,7 +72,7 @@ struct t_irc_message *irc_msgq_last_msg = NULL; char *irc_server_option_string[IRC_SERVER_NUM_OPTIONS] = { "addresses", "proxy", "ipv6", - "ssl", "ssl_cert", "ssl_priorities", "ssl_dhkey_size", "ssl_verify", + "ssl", "ssl_cert", "ssl_priorities", "ssl_dhkey_size", "ssl_verify", "ssl_fingerprint", "password", "capabilities", "sasl_mechanism", "sasl_username", "sasl_password", "sasl_timeout", "autoconnect", "autoreconnect", "autoreconnect_delay", @@ -87,7 +87,7 @@ char *irc_server_option_string[IRC_SERVER_NUM_OPTIONS] = char *irc_server_option_default[IRC_SERVER_NUM_OPTIONS] = { "", "", "on", - "off", "", "NORMAL", "2048", "on", + "off", "", "NORMAL", "2048", "on", "", "", "", "plain", "", "", "15", "off", "on", "10", @@ -3525,6 +3525,84 @@ irc_server_create_buffer (struct t_irc_server *server) } #ifdef HAVE_GNUTLS + +/* + * Check if a gnutls_session uses the certificate with the provided fingerprint. + */ +int +irc_server_test_certificate_fingerprint (gnutls_session_t session, const struct t_irc_server * server, const char * good_fingerprint) +{ + const gnutls_datum_t *chain; + unsigned int chain_size; + gnutls_x509_crt_t certificate; + char fingerprint[20]; + size_t fingerprint_size; + size_t i; + int buffer; + + // Get the certificate chain. + chain = gnutls_certificate_get_peers (session, &chain_size); + if (!chain || !chain_size) { + weechat_printf (server->buffer, + _("%sgnutls: no server certificate found"), + weechat_prefix("error")); + return 0; + } + + // Initalize the certificate structure. + if (gnutls_x509_crt_init (&certificate) != GNUTLS_E_SUCCESS) + { + weechat_printf (server->buffer, + _("%sgnutls: failed to initialize certificate structure"), + weechat_prefix("error")); + return 0; + } + + // Import the raw certificate data. + if (gnutls_x509_crt_import (certificate, + &chain[0], + GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) + { + gnutls_x509_crt_deinit (certificate); + weechat_printf (server->buffer, + _("%sgnutls: failed to import server certificate"), + weechat_prefix ("error")); + return 0; + } + + // Calculate the SHA1 fingerprint for the certificate. + fingerprint_size = sizeof (fingerprint); + if (gnutls_x509_crt_get_fingerprint (certificate, + GNUTLS_DIG_SHA1, + fingerprint, + &fingerprint_size) != GNUTLS_E_SUCCESS) + { + weechat_printf (server->buffer, + _("%sgnutls: failed to calculate server fingerprint"), + weechat_prefix ("error")); + gnutls_x509_crt_deinit (certificate); + return 0; + } + + // Clean up. + gnutls_x509_crt_deinit (certificate); + + // Compare the fingerprints. + for (i = 0; i < fingerprint_size; ++i) + { + // Make sure good_fingerprint doesn't end too early. + if (!good_fingerprint[i*2] || !good_fingerprint[i*2+1]) + return 0; + // Parse one byte and make sure it was parsed correctly. + if (sscanf (&good_fingerprint[i*2], "%02x", &buffer) != 1) + return 0; + // Make sure the parsed byte matches the actual fingerprint. + if ((char) buffer != fingerprint[i]) + return 0; + } + return 1; +} + /* * GnuTLS callback called during handshake. * @@ -3553,7 +3631,7 @@ irc_server_gnutls_callback (void *data, gnutls_session_t tls_session, unsigned int cert_list_len, status; time_t cert_time; char *cert_path0, *cert_path1, *cert_path2, *cert_str, *hostname; - const char *weechat_dir; + const char *weechat_dir, *fingerprint; int rc, ret, i, j, hostname_match; #if LIBGNUTLS_VERSION_NUMBER >= 0x010706 gnutls_datum_t cinfo; @@ -3583,7 +3661,26 @@ irc_server_gnutls_callback (void *data, gnutls_session_t tls_session, weechat_prefix ("network"), IRC_SERVER_OPTION_INTEGER (server, IRC_SERVER_OPTION_SSL_DHKEY_SIZE)); - if (gnutls_certificate_verify_peers2 (tls_session, &status) < 0) + + // Skip normal checks if ssl_fingerprint is set and just check that. + fingerprint = IRC_SERVER_OPTION_STRING (server, + IRC_SERVER_OPTION_SSL_FINGERPRINT); + if (fingerprint && fingerprint[0]) { + if (!irc_server_test_certificate_fingerprint (tls_session, server, fingerprint)) + { + rc = -1; + weechat_printf (server->buffer, + _("%sgnutls: server fingerprint did NOT match"), + weechat_prefix ("error")); + } + else + { + weechat_printf (server->buffer, + _("%sgnutls: server fingerprint matches"), + weechat_prefix ("network")); + } + } + else if (gnutls_certificate_verify_peers2 (tls_session, &status) < 0) { weechat_printf (server->buffer, _("%sgnutls: error while checking peer's certificate"), @@ -4748,6 +4845,9 @@ irc_server_add_to_infolist (struct t_infolist *infolist, if (!weechat_infolist_new_var_integer (ptr_item, "ssl_verify", IRC_SERVER_OPTION_BOOLEAN(server, IRC_SERVER_OPTION_SSL_VERIFY))) return 0; + if (!weechat_infolist_new_var_string (ptr_item, "ssl_fingerprint", + IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_SSL_FINGERPRINT))) + return 0; if (!weechat_infolist_new_var_string (ptr_item, "password", IRC_SERVER_OPTION_STRING(server, IRC_SERVER_OPTION_PASSWORD))) return 0; @@ -4977,6 +5077,13 @@ irc_server_print_log () weechat_log_printf (" ssl_verify . . . . . : %s", weechat_config_boolean (ptr_server->options[IRC_SERVER_OPTION_SSL_VERIFY]) ? "on" : "off"); + /* ssl_fingerprint */ + if (weechat_config_option_is_null (ptr_server->options[IRC_SERVER_OPTION_SSL_FINGERPRINT])) + weechat_log_printf (" ssl_fingerprint . . . : null ('%s')", + IRC_SERVER_OPTION_STRING(ptr_server, IRC_SERVER_OPTION_SSL_FINGERPRINT)); + else + weechat_log_printf (" ssl_fingerprint . . . : '%s'", + weechat_config_string (ptr_server->options[IRC_SERVER_OPTION_SSL_FINGERPRINT])); /* password */ if (weechat_config_option_is_null (ptr_server->options[IRC_SERVER_OPTION_PASSWORD])) weechat_log_printf (" password . . . . . . : null"); diff --git a/src/plugins/irc/irc-server.h b/src/plugins/irc/irc-server.h index e359176..9f59c6e 100644 --- a/src/plugins/irc/irc-server.h +++ b/src/plugins/irc/irc-server.h @@ -42,6 +42,7 @@ enum t_irc_server_option IRC_SERVER_OPTION_SSL_PRIORITIES, /* gnutls priorities */ IRC_SERVER_OPTION_SSL_DHKEY_SIZE, /* Diffie Hellman key size */ IRC_SERVER_OPTION_SSL_VERIFY, /* check if the connection is trusted */ + IRC_SERVER_OPTION_SSL_FINGERPRINT, /* server specific sha1 fingerprint */ IRC_SERVER_OPTION_PASSWORD, /* password for server */ IRC_SERVER_OPTION_CAPABILITIES, /* client capabilities to enable */ IRC_SERVER_OPTION_SASL_MECHANISM,/* mechanism for SASL authentication */ -- 1.8.5.2