Currently, the only way to use a self-signed certificate for an IRC server (or bouncer) is to add the certificate to the root certificate list or to disable certificate verification all together.
Adding it to the root CA list means that other certificate signed with the matching private key will also be accepted, which is not desirable since it could be used to forge certificates for existing servers when compromised. Additionally, there is only one global setting for which root CA list to use, and it can't be set per server. That means that you'd have to either add the certificate to your distribution's CA list or keep a copy in in sync somewhere with the added certificate. Adding it to your distribution's CA list would even compromise https and other applications if the private key was ever stolen, so this would be a *very bad idea*™. Keeping a copy in sync is just a hassle, although it can no doubt be automated. Disabling certificate verification entirely means you are vulnerable to man-in-the-middle attacks again, which means the whole purpose of SSL/TLS is kind of defeated. Sure, the traffic is encrypted, but with enough effort it can still be eavesdropped on. A much better option, in my opinion, is to allow the user to specify exactly which certificate is allowed for a specific server. That way you can use a self-signed certificate without fear of compromising traffic to other server and without being susceptible to man-in-the-middle attacks. To keep things easy (for the implementation and for the user) I think that a sha1 fingerprint of the certificate is enough to identify the certificate uniquely and safely. I added an option irc.server.*.ssl_fingerprint . When set and not an empty string, the only certificate accepted for the server is the one with that fingerprint. It should be the SHA1 hash of the certificate without separators between the bytes, exactly in the format as shown when connecting to the server. Otherwise valid certificates that have been signed by a trusted CA will not be accepted if this option is non-empty, unless of course the fingerprint matches. I attached the patch. I hope I followed the coding style. Any comments or remarks are welcome.
From 210daef5cfbbb4aefcd83c9376589c11c8a35f03 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Haha <[email protected]> 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
_______________________________________________ Weechat-dev mailing list [email protected] https://lists.nongnu.org/mailman/listinfo/weechat-dev
