From ba143b42606857ddf67bba94936484b4ff3f4cd8 Mon Sep 17 00:00:00 2001 From: TingPing Date: Sun, 1 Sep 2013 20:11:26 -0400 Subject: [PATCH] Add alerts for invalid ssl certs This patch was originally provided by dila in #xchat but modified heavily since. It will show an alert on invalid certs to add temp or permanent exceptions per server. --- src/common/fe.h | 1 + src/common/hexchat.c | 1 + src/common/hexchat.h | 2 + src/common/server.c | 267 ++++++++++++++---------------- src/common/ssl.c | 235 ++++++++++++++++++++++++-- src/common/ssl.h | 22 +++ src/fe-gtk/Makefile.am | 2 +- src/fe-gtk/fe-gtk.vcxproj | 1 + src/fe-gtk/fe-gtk.vcxproj.filters | 5 +- src/fe-gtk/servlistgui.c | 22 +-- src/fe-gtk/sslalert.c | 144 ++++++++++++++++ src/fe-text/fe-text.c | 5 + 12 files changed, 538 insertions(+), 169 deletions(-) create mode 100644 src/fe-gtk/sslalert.c diff --git a/src/common/fe.h b/src/common/fe.h index 2ca15c60..db58b747 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -103,6 +103,7 @@ void fe_dcc_remove (struct DCC *dcc); int fe_dcc_open_recv_win (int passive); int fe_dcc_open_send_win (int passive); int fe_dcc_open_chat_win (int passive); +void fe_sslalert_open (struct server *serv, void (*callback)(int, void *), void *callback_data); void fe_clear_channel (struct session *sess); void fe_session_callback (struct session *sess); void fe_server_callback (struct server *serv); diff --git a/src/common/hexchat.c b/src/common/hexchat.c index fde6d108..48732f9c 100644 --- a/src/common/hexchat.c +++ b/src/common/hexchat.c @@ -933,6 +933,7 @@ xchat_init (void) defaultconf_urlhandlers); servlist_init (); /* load server list */ + _SSL_certlist_init (); /* load known certificate fingerprints */ /* if we got a URL, don't open the server list GUI */ if (!prefs.hex_gui_slist_skip && !arg_url && !arg_urls) diff --git a/src/common/hexchat.h b/src/common/hexchat.h index 993a209e..c02c256f 100644 --- a/src/common/hexchat.h +++ b/src/common/hexchat.h @@ -55,6 +55,7 @@ #ifdef USE_OPENSSL #include /* SSL_() */ +#include "ssl.h" #endif #ifdef __EMX__ /* for o/s 2 */ @@ -597,6 +598,7 @@ typedef struct server #ifdef USE_OPENSSL unsigned int use_ssl:1; /* is server SSL capable? */ unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */ + struct cert_info *cert_info; #endif } server; diff --git a/src/common/server.c b/src/common/server.c index 6432a2b9..4370e509 100644 --- a/src/common/server.c +++ b/src/common/server.c @@ -587,43 +587,76 @@ server_stopconnecting (server * serv) static void ssl_cb_info (SSL * s, int where, int ret) { -/* char buf[128];*/ - - - return; /* FIXME: make debug level adjustable in serverlist or settings */ - -/* snprintf (buf, sizeof (buf), "%s (%d)", SSL_state_string_long (s), where); - if (g_sess) - EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); - else - fprintf (stderr, "%s\n", buf);*/ + return; } static int ssl_cb_verify (int ok, X509_STORE_CTX * ctx) { - char subject[256]; - char issuer[256]; - char buf[512]; + return TRUE; /* always ok */ +} +static void +ssl_do_connect_finish (server *serv, int success, int verify_error) +{ + /* + we land here after ssl_do_connect(). possible execution flows are: + ssl_do_connect -> ssl_do_connect_finish (with no user interaction) + ssl_do_connect -> sslalert.c -> ssl_do_connect_finish (prompt user to accept/reject certificate) + */ - X509_NAME_oneline (X509_get_subject_name (ctx->current_cert), subject, - sizeof (subject)); - X509_NAME_oneline (X509_get_issuer_name (ctx->current_cert), issuer, - sizeof (issuer)); + if (success) + { + /* connection has been established */ + server_stopconnecting (serv); + server_connected (serv); /* activate gtk poll */ + } + else + { + /* + the connection failed. most likely because the certificate is invalid + and the user hit the cancel button to reject the connection + */ + + char buf[128]; + snprintf (buf, sizeof (buf), "%s.? (%d)", + X509_verify_cert_error_string (verify_error), + verify_error); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL, + NULL, 0); + server_cleanup (serv); + } +} - snprintf (buf, sizeof (buf), "* Subject: %s", subject); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); - snprintf (buf, sizeof (buf), "* Issuer: %s", issuer); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); - - return (TRUE); /* always ok */ +static void +ssl_alert_cb (int user_action, void *callback_data) +{ + /* interpret the user response and possibly abandon the connection */ + ssl_alert_context *context = callback_data; + switch (user_action) + { + case SSLALERT_RESPONSE_ABORT: /* user wants to abandon connection */ + ssl_do_connect_finish (context->serv, FALSE, context->verify_error); + break; + case SSLALERT_RESPONSE_ACCEPT: /* user wants to accept the certificate ONLY this time */ + ssl_do_connect_finish (context->serv, TRUE, context->verify_error); + break; + case SSLALERT_RESPONSE_SAVE: /* user wants to accept the certificate AND remember it for next time */ + _SSL_certlist_cert_add (context->serv, &context->cert); + _SSL_certlist_save (); + ssl_do_connect_finish (context->serv, TRUE, context->verify_error); + break; + } + free (context); } static int ssl_do_connect (server * serv) { char buf[128]; + struct cert_info cert_info; + ssl_alert_context *cb_context; + int cert_error, verify_result; g_sess = serv->server_session; if (SSL_connect (serv->ssl) <= 0) @@ -647,132 +680,12 @@ ssl_do_connect (server * serv) if (prefs.hex_net_auto_reconnectonfail) auto_reconnect (serv, FALSE, -1); - return (0); /* remove it (0) */ + return 0; /* remove it (0) */ } } g_sess = NULL; - if (SSL_is_init_finished (serv->ssl)) - { - struct cert_info cert_info; - struct chiper_info *chiper_info; - int verify_error; - int i; - - if (!_SSL_get_cert_info (&cert_info, serv->ssl)) - { - snprintf (buf, sizeof (buf), "* Certification info:"); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - snprintf (buf, sizeof (buf), " Subject:"); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - for (i = 0; cert_info.subject_word[i]; i++) - { - snprintf (buf, sizeof (buf), " %s", cert_info.subject_word[i]); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - } - snprintf (buf, sizeof (buf), " Issuer:"); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - for (i = 0; cert_info.issuer_word[i]; i++) - { - snprintf (buf, sizeof (buf), " %s", cert_info.issuer_word[i]); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - } - snprintf (buf, sizeof (buf), " Public key algorithm: %s (%d bits)", - cert_info.algorithm, cert_info.algorithm_bits); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - /*if (cert_info.rsa_tmp_bits) - { - snprintf (buf, sizeof (buf), - " Public key algorithm uses ephemeral key with %d bits", - cert_info.rsa_tmp_bits); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - }*/ - snprintf (buf, sizeof (buf), " Sign algorithm %s", - cert_info.sign_algorithm/*, cert_info.sign_algorithm_bits*/); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - snprintf (buf, sizeof (buf), " Valid since %s to %s", - cert_info.notbefore, cert_info.notafter); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - } else - { - snprintf (buf, sizeof (buf), " * No Certificate"); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - } - - chiper_info = _SSL_get_cipher_info (serv->ssl); /* static buffer */ - snprintf (buf, sizeof (buf), "* Cipher info:"); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, - 0); - snprintf (buf, sizeof (buf), " Version: %s, cipher %s (%u bits)", - chiper_info->version, chiper_info->chiper, - chiper_info->chiper_bits); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, - 0); - - verify_error = SSL_get_verify_result (serv->ssl); - switch (verify_error) - { - case X509_V_OK: - { - X509 *cert = SSL_get_peer_certificate (serv->ssl); - int hostname_err; - if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0) - { - snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname? (%d)%s", - hostname_err, serv->accept_invalid_cert ? " -- Ignored" : ""); - if (serv->accept_invalid_cert) - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); - else - goto conn_fail; - } - break; - } - /* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */ - /* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */ - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - case X509_V_ERR_CERT_HAS_EXPIRED: - if (serv->accept_invalid_cert) - { - snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored", - X509_verify_cert_error_string (verify_error), - verify_error); - EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, - NULL, 0); - break; - } - default: - snprintf (buf, sizeof (buf), "%s.? (%d)", - X509_verify_cert_error_string (verify_error), - verify_error); -conn_fail: - EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL, - NULL, 0); - - server_cleanup (serv); - - return (0); - } - - server_stopconnecting (serv); - - /* activate gtk poll */ - server_connected (serv); - - return (0); /* remove it (0) */ - } else + if (!SSL_is_init_finished (serv->ssl)) { if (serv->ssl->session && serv->ssl->session->time + SSLTMOUT < time (NULL)) { @@ -784,11 +697,71 @@ conn_fail: if (prefs.hex_net_auto_reconnectonfail) auto_reconnect (serv, FALSE, -1); - return (0); /* remove it (0) */ + return 0; /* remove it (0) */ } - return (1); /* call it more (1) */ + return 1; /* call it more (1) */ } + + cert_error = _SSL_get_cert_info (&cert_info, serv->ssl); + serv->cert_info = &cert_info; + + /* at this point we check the certificate to make sure it is valid */ + + verify_result = SSL_get_verify_result (serv->ssl); + + switch (verify_result) + { + /* 1) certificate is valid. finish connecting */ + case X509_V_OK: + { + X509 *cert = SSL_get_peer_certificate (serv->ssl); + int hostname_err; + if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) == 0) + { + ssl_do_connect_finish (serv, TRUE, verify_result); + return 0; + } + break; + } + + /* 2) certificate has a problem but the user might want to accept it */ + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_CERT_HAS_EXPIRED: +#if 0 + if (serv->accept_invalid_cert) + { + ssl_do_connect_finish (serv, TRUE, verify_result); + return 0; + } +#endif + break; + + /* 3) certificate has a problem and we should disconnect */ + default: + ssl_do_connect_finish (serv, FALSE, verify_result); /* disconnect */ + return 0; + } + + /* check if this INVALID certificate is on the users whitelist */ + + if (_SSL_certlist_cert_check (serv, &cert_info)) + { + ssl_do_connect_finish (serv, TRUE, verify_result); + return 0; + } + + /* this INVALID certificate is not on the whitelist. ask the user what to do */ + + cb_context = malloc (sizeof (ssl_alert_context)); + cb_context->serv = serv; + memcpy (&cb_context->cert, &cert_info, sizeof (cert_info)); + cb_context->verify_error = verify_result; + fe_sslalert_open (serv, ssl_alert_cb, cb_context); /* bring up GUI alert */ + return 0; } #endif diff --git a/src/common/ssl.c b/src/common/ssl.c index 04cd6673..cc1c7aa7 100644 --- a/src/common/ssl.c +++ b/src/common/ssl.c @@ -32,6 +32,8 @@ #include "../../config.h" #include /* asctime() */ #include /* strncpy() */ +#include "hexchat.h" +#include "cfgfiles.h" #include "ssl.h" /* struct cert_info */ #include @@ -161,12 +163,15 @@ _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) /* EVP_PKEY *tmp_pkey; */ char notBefore[64]; char notAfter[64]; + unsigned char digest[EVP_MAX_MD_SIZE]; /* SHA-256 fingerprint of the certificate */ + int digest_length; int alg; int sign_alg; + int i; if (!(peer_cert = SSL_get_peer_certificate (ssl))) - return (1); /* FATAL? */ + return 1; /* FATAL? */ X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject, sizeof (cert_info->subject)); @@ -198,6 +203,17 @@ _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) EVP_PKEY_free (peer_pkey); + /* compute the fingerprint and make it pretty */ + X509_digest (peer_cert, EVP_sha256(), digest, &digest_length); + cert_info->fingerprint[0] = '\0'; + for (i = 0; i < digest_length; ++i) + { + char digits[4]; + g_snprintf (digits, sizeof(digits), (i?":%02x":"%02x"), digest[i]); + g_strlcat (cert_info->fingerprint, digits, sizeof(cert_info->fingerprint)); + } + + /* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */ /* if (ssl->session->sess_cert->peer_rsa_tmp) { @@ -212,7 +228,7 @@ _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) X509_free (peer_cert); - return (0); + return 0; } @@ -229,10 +245,62 @@ _SSL_get_cipher_info (SSL * ssl) sizeof (chiper_info.chiper)); SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits); - return (&chiper_info); + return &chiper_info; } +/* + * returns zero on success, non-zero on failure. + * "*.freenode.com" matches "ssl.freenode.com" and "irc.freenode.com" but not "chat.irc.freenode.com" + */ +int +_SSL_verify_cert_hostname (struct server *serv, struct cert_info *cert) +{ + int i; + for (i = 0; cert->subject_word[i]; i++) + { + char *cname = cert->subject_word[i]; + if (strstr (cname, "CN=") == cname) + { + char *host = serv->hostname; + cname += strlen ("CN="); + while (*host && *cname) + { + switch (*cname) + { + case '*': /* wildcard matching */ + switch (*host) + { + case '.': + cname++; /* wildcard ends */ + break; + default: + host++; /* wildcard continues */ + if (!*host) + { + cname++; /* wildcard ends */ + } + break; + } + break; + default: /* regular strcmp */ + if (*host++ != *cname++) + { + return 1; /* error: mismatch */ + } + break; + } + } + if (*host || *cname) + { + return 1; /* error: failed to process both strings completely */ + } + return 0; /* success: match */ + } + } + return 1; /* error: no CNAME field */ +} + int _SSL_send (SSL * ssl, char *buf, int len) { @@ -257,7 +325,7 @@ _SSL_send (SSL * ssl, char *buf, int len) break; } - return (num); + return num; } @@ -286,7 +354,7 @@ _SSL_recv (SSL * ssl, char *buf, int len) break; } - return (num); + return num; } @@ -306,7 +374,7 @@ _SSL_socket (SSL_CTX *ctx, int sd) else SSL_set_accept_state(ssl); - return (ssl); + return ssl; } @@ -316,7 +384,7 @@ _SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert) if (!SSL_CTX_set_default_verify_paths (ctx)) { __SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths"); - return (err_buf); + return err_buf; } /* if (cacert) @@ -324,13 +392,13 @@ _SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert) if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL)) { __SSL_fill_err_buf ("SSL_CTX_load_verify_locations"); - return (err_buf); + return err_buf; } } */ SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback); - return (NULL); + return NULL; } @@ -342,6 +410,7 @@ _SSL_close (SSL * ssl) ERR_remove_state (0); /* free state buffer */ } + /* Hostname validation code based on OpenBSD's libtls. */ static int @@ -541,4 +610,150 @@ _SSL_check_hostname (X509 *cert, const char *host) return rv; return _SSL_check_common_name (cert, host); -} \ No newline at end of file +} + + +/* Cert list */ + +static GSList *ssl_certlist = NULL; /* loaded at startup, saved on shutdown */ + +typedef struct ssl_certlist_item { + char *hostname; + char *fingerprint; +} ssl_certlist_item; + +static void +_SSL_certlist_item_free (ssl_certlist_item *item) +{ + g_return_if_fail (item != NULL); + + if (item->hostname) + g_free (item->hostname); + if (item->fingerprint) + g_free (item->fingerprint); + g_free (item); +} + +/* append a new hostname+fingerprint to the certificate list */ +static void +_SSL_certlist_item_add (char *hostname, char *fingerprint) +{ + int hn_length = strlen (hostname); + int fp_length = strlen (fingerprint); + ssl_certlist_item *item = g_malloc0 (sizeof (ssl_certlist_item)); + + if (item) + { + item->hostname = g_malloc0 (hn_length + 1); + item->fingerprint = g_malloc0 (fp_length + 1); + if (!item->hostname || !item->fingerprint) + { + _SSL_certlist_item_free (item); + return; + } + g_strlcpy (item->hostname, hostname, hn_length); + g_strlcpy (item->fingerprint, fingerprint, fp_length); + + ssl_certlist = g_slist_append (ssl_certlist, item); + } +} + +/* + * parse a simple new-line/whitepsace delimited text file of hostname+fingerprint combinations. + * it is not a problem if the file does not exist - we just end up with a empty list. + * it should be pretty safe against corrupted input. example file contents follows: + * + * irc.something.com 01:22:1a:c3:43:e6:35:ff:73:76:17:98:68:2f:2c:00:07:ae:1b:b8:81:a3:8d:0f:a6:a5:bd:dc:80:03:6c:33 + * ssl.someircserver.com 02:22:1a:c3:43:e6:35:ff:73:76:17:98:68:2f:2c:00:07:ae:1b:b8:81:a3:8d:0f:a6:a5:bd:dc:80:03:6c:33 + * another.com 03:22:1a:c3:43:e6:35:ff:73:76:17:98:68:2f:2c:00:07:ae:1b:b8:81:a3:8d:0f:a6:a5:bd:dc:80:03:6c:33 + */ +void +_SSL_certlist_init () +{ + char buf[1024]; + char *space, *host, *fp; + FILE *fh; + + fh = hexchat_fopen_file ("sslcerts.conf", "r", 0); + if (!fh) + return; + + while (fgets (buf, sizeof(buf), fh)) + { + + space = strchr (buf, ' '); + if (!space) + continue; + + *space = '\0'; + + host = buf; + fp = g_strchomp (space + 1); + + if (host[0] && fp[0]) + _SSL_certlist_item_add (host, fp); + } + + fclose (fh); +} + +void +_SSL_certlist_save () +{ + /* write the list back out to disk. if there are no items an empty file is created. */ + GSList *list; + ssl_certlist_item *item; + FILE *fh; + + fh = hexchat_fopen_file ("sslcerts.conf", "w", 0); + if (fh) + { + list = ssl_certlist; + while (list) + { + item = (ssl_certlist_item*)list->data; + + fprintf (fh, "%s %s\n", item->hostname, item->fingerprint); + + list = g_slist_next (list); + } + fclose (fh); + } +} + +/* + * a "computer" is a hostname + certificate combination. we extract these details + * from the input structures and make an O(n) (worst-case) pass over the list to find + * a match. if the computer is known to us we return 1, and 0 otherwise. + */ +int +_SSL_certlist_cert_check (struct server *serv, struct cert_info *cert) +{ + GSList *list; + ssl_certlist_item *item; + + if (serv && cert) + { + list = ssl_certlist; + while (list) + { + item = (ssl_certlist_item*)list->data; + + if (!g_ascii_strcasecmp (serv->hostname, item->hostname) + && !g_ascii_strcasecmp (cert->fingerprint, item->fingerprint)) + { + return 1; /* the user trusts this computer */ + } + list = g_slist_next (list); + } + } + return 0; /* the user does NOT trust this computer */ +} + +void +_SSL_certlist_cert_add (struct server *serv, struct cert_info *cert) +{ + /* called from server.c when the user decides that they want to remember a computer */ + _SSL_certlist_item_add (serv->hostname, cert->fingerprint); +} + diff --git a/src/common/ssl.h b/src/common/ssl.h index ce2f616c..d605864d 100644 --- a/src/common/ssl.h +++ b/src/common/ssl.h @@ -31,10 +31,25 @@ struct cert_info { int sign_algorithm_bits; char notbefore[32]; char notafter[32]; + char fingerprint[128]; int rsa_tmp_bits; }; +enum +{ + SSLALERT_RESPONSE_ABORT = 0, + SSLALERT_RESPONSE_ACCEPT = 1, + SSLALERT_RESPONSE_SAVE = 2 +}; + +typedef struct ssl_alert_context +{ + struct server *serv; + struct cert_info cert; + int verify_error; +} ssl_alert_context; + struct chiper_info { char version[16]; char chiper[48]; @@ -55,6 +70,7 @@ void _SSL_close (SSL * ssl); int _SSL_check_hostname(X509 *cert, const char *host); int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl); struct chiper_info *_SSL_get_cipher_info (SSL * ssl); +int _SSL_verify_cert_hostname (struct server *serv, struct cert_info *cert); /*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/ /*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/ @@ -82,4 +98,10 @@ int _SSL_recv (SSL * ssl, char *buf, int len); /*int _SSL_verify_x509(X509 *x509);*/ +/* functions for managing the SSL certificate/fingerprint cache */ +void _SSL_certlist_init (); +void _SSL_certlist_save (); +int _SSL_certlist_cert_check (struct server *serv, struct cert_info *cert); +void _SSL_certlist_cert_add (struct server *serv, struct cert_info *cert); + #endif diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am index a8f43ac5..52fbaac0 100644 --- a/src/fe-gtk/Makefile.am +++ b/src/fe-gtk/Makefile.am @@ -30,7 +30,7 @@ hexchat_SOURCES = ascii.c banlist.c chanlist.c chanview.c custom-list.c \ dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \ maingui.c notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \ rawlog.c resources.c servlistgui.c setup.c $(iso_codes_c) \ - sexy-spell-entry.c textgui.c urlgrab.c userlistgui.c xtext.c + sslalert.c sexy-spell-entry.c textgui.c urlgrab.c userlistgui.c xtext.c hexchat_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_builddir)/src/common resources.c: $(top_srcdir)/data/hexchat.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(top_srcdir)/data --generate-dependencies $(top_srcdir)/data/hexchat.gresource.xml) diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj index 59ab17c6..4471a0a7 100644 --- a/src/fe-gtk/fe-gtk.vcxproj +++ b/src/fe-gtk/fe-gtk.vcxproj @@ -161,6 +161,7 @@ powershell "Get-Content -Encoding UTF8 '$(SolutionDir)..\src\fe-gtk\hexchat.rc.u + diff --git a/src/fe-gtk/fe-gtk.vcxproj.filters b/src/fe-gtk/fe-gtk.vcxproj.filters index 4598b1f2..19bcd278 100644 --- a/src/fe-gtk/fe-gtk.vcxproj.filters +++ b/src/fe-gtk/fe-gtk.vcxproj.filters @@ -182,6 +182,9 @@ Source Files + + Source Files + @@ -202,4 +205,4 @@ - \ No newline at end of file + diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index f43a225a..2aa2bc09 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -1866,33 +1866,35 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) #ifndef USE_OPENSSL gtk_widget_set_sensitive (check, FALSE); #endif +#if 0 check = servlist_create_check (5, net->flags & FLAG_ALLOW_INVALID, table3, 4, 0, _("Accept invalid SSL certificates")); #ifndef USE_OPENSSL gtk_widget_set_sensitive (check, FALSE); #endif - servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, 5, 0, _("Use global user information")); +#endif + servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, 4, 0, _("Use global user information")); - edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 6, net->nick, &edit_label_nick, 0); - edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 7, net->nick2, &edit_label_nick2, 0); - edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, &edit_label_real, 0); - edit_entry_user = servlist_create_entry (table3, _("_User name:"), 9, net->user, &edit_label_user, 0); + edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 5, net->nick, &edit_label_nick, 0); + edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 6, net->nick2, &edit_label_nick2, 0); + edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 7, net->real, &edit_label_real, 0); + edit_entry_user = servlist_create_entry (table3, _("_User name:"), 8, net->user, &edit_label_user, 0); label_logintype = gtk_label_new (_("Login method:")); - gtk_table_attach (GTK_TABLE (table3), label_logintype, 0, 1, 10, 11, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING); + gtk_table_attach (GTK_TABLE (table3), label_logintype, 0, 1, 9, 10, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING); gtk_misc_set_alignment (GTK_MISC (label_logintype), 0, 0.5); combobox_logintypes = servlist_create_logintypecombo (notebook); - gtk_table_attach (GTK_TABLE (table3), combobox_logintypes, 1, 2, 10, 11, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_FILL), 4, 2); + gtk_table_attach (GTK_TABLE (table3), combobox_logintypes, 1, 2, 9, 10, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_FILL), 4, 2); - edit_entry_pass = servlist_create_entry (table3, _("Password:"), 11, net->pass, 0, _("Password used for login. If in doubt, leave blank.")); + edit_entry_pass = servlist_create_entry (table3, _("Password:"), 10, net->pass, 0, _("Password used for login. If in doubt, leave blank.")); gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL) gtk_widget_set_sensitive (edit_entry_pass, FALSE); label34 = gtk_label_new (_("Character set:")); - gtk_table_attach (GTK_TABLE (table3), label34, 0, 1, 12, 13, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING); + gtk_table_attach (GTK_TABLE (table3), label34, 0, 1, 11, 12, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING); gtk_misc_set_alignment (GTK_MISC (label34), 0, 0.5); comboboxentry_charset = servlist_create_charsetcombo (); - gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 1, 2, 12, 13, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_FILL), 4, 2); + gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 1, 2, 11, 12, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (GTK_FILL), 4, 2); /* Rule and Close button */ diff --git a/src/fe-gtk/sslalert.c b/src/fe-gtk/sslalert.c new file mode 100644 index 00000000..946ff560 --- /dev/null +++ b/src/fe-gtk/sslalert.c @@ -0,0 +1,144 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include "fe-gtk.h" +#include +#include "../common/hexchat.h" +#include "../common/util.h" +#include "palette.h" +#include "pixmaps.h" +#include "gtkutil.h" + +void (*server_callback)(int, void *) = 0; + +static void +sslalert_cb (GtkDialog *dialog, gint response, gpointer data) +{ + if (response < 0) /* Such as window deleted */ + server_callback (SSLALERT_RESPONSE_ABORT, data); + else + server_callback (response, data); + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +void +fe_sslalert_open (struct server *serv, void (*callback)(int, void *), void *callback_data) +{ + GtkWidget *sslalert; + GtkWidget *wid; + GtkWidget *dialog_vbox; + GtkWidget *expander; + GtkWidget *hbox1, *vbox1, *vbox2; + GtkWidget *img_vbox; + char *cert_buf; + char buf[256]; + char buf2[256]; + + server_callback = callback; + + sslalert = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (sslalert), _ (DISPLAY_NAME": Security Alert")); + gtk_window_set_type_hint (GTK_WINDOW (sslalert), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position (GTK_WINDOW (sslalert), GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_transient_for (GTK_WINDOW (sslalert), GTK_WINDOW (serv->front_session->gui->window)); + gtk_window_set_modal (GTK_WINDOW (sslalert), TRUE); + gtk_window_set_resizable (GTK_WINDOW (sslalert), FALSE); + + dialog_vbox = gtk_dialog_get_content_area (GTK_DIALOG (sslalert)); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (dialog_vbox), vbox1, TRUE, TRUE, 0); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); + + img_vbox = gtk_vbox_new (FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (img_vbox), 6); + gtk_box_pack_start (GTK_BOX (hbox1), img_vbox, TRUE, TRUE, 0); + + wid = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start (GTK_BOX (img_vbox), wid, FALSE, TRUE, 24); + gtk_misc_set_alignment (GTK_MISC (wid), 0.5f, 0.06f); + + vbox2 = gtk_vbox_new (FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 6); + gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0); + + snprintf (buf2, sizeof (buf2), _ ("Connecting to %s (+%d)"), + serv->hostname, serv->port); + snprintf (buf, sizeof (buf), "\n%s", buf2); + wid = gtk_label_new (buf); + gtk_box_pack_start (GTK_BOX (vbox2), wid, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (wid), TRUE); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + + wid = gtk_label_new (_ ("This server has presented an invalid certificate, and is self-signed, expired, or has another problem.")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (wid), TRUE); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + + wid = gtk_label_new (_ ("If you are certain that your connection is not being tampered with, you can continue and your connection will be secure.")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (wid), TRUE); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + + if (serv->cert_info) + { + char *subject; + char *issuer; + + expander = gtk_expander_new (_ ("More details:")); + gtk_widget_set_can_focus (expander, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (expander), 10); + gtk_box_pack_start (GTK_BOX (vbox1), expander, FALSE, FALSE, 0); + + wid = gtk_label_new (NULL); + gtk_label_set_use_markup (GTK_LABEL (wid), TRUE); + gtk_label_set_justify (GTK_LABEL (wid), GTK_JUSTIFY_LEFT); + gtk_container_add (GTK_CONTAINER (expander), wid); + + issuer = g_strjoinv ("\n\t\t", serv->cert_info->issuer_word); + subject = g_strjoinv ("\n\t\t", serv->cert_info->subject_word); + cert_buf = g_markup_printf_escaped ("Issuer:\t%s\n\n"\ + "Subject: %s\n\n"\ + "Valid:\tAfter: %s\n\t\tBefore: %s\n\n"\ + "Algorithm: %s (%d bits)", + issuer, subject, + serv->cert_info->notbefore, serv->cert_info->notafter, + serv->cert_info->algorithm, serv->cert_info->algorithm_bits); + + gtk_label_set_markup (GTK_LABEL (wid), cert_buf); + + g_free (cert_buf); + g_free (issuer); + g_free (subject); + } + + gtk_dialog_add_buttons (GTK_DIALOG (sslalert), _ ("Abort"), SSLALERT_RESPONSE_ABORT, + _("Accept Once"), SSLALERT_RESPONSE_ACCEPT, + _("Always Accept"), SSLALERT_RESPONSE_SAVE, NULL); + gtk_dialog_set_default_response (GTK_DIALOG (sslalert), SSLALERT_RESPONSE_ABORT); + + g_signal_connect (G_OBJECT (sslalert), "response", G_CALLBACK (sslalert_cb), callback_data); + + gtk_widget_show_all (sslalert); +} \ No newline at end of file diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c index c8b64ab0..56c297d7 100644 --- a/src/fe-text/fe-text.c +++ b/src/fe-text/fe-text.c @@ -922,3 +922,8 @@ fe_get_default_font (void) { return NULL; } +void +fe_sslalert_open (struct server *serv, void (*callback)(int, void *), void *callback_data) +{ + callback (SSLALERT_RESPONSE_ACCEPT, callback_data); +}