mirror of
https://github.com/hexchat/hexchat.git
synced 2024-11-08 12:12:39 +01:00
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.
This commit is contained in:
parent
232096801b
commit
ba143b4260
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -55,6 +55,7 @@
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/ssl.h> /* 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;
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
235
src/common/ssl.c
235
src/common/ssl.c
@ -32,6 +32,8 @@
|
||||
#include "../../config.h"
|
||||
#include <time.h> /* asctime() */
|
||||
#include <string.h> /* strncpy() */
|
||||
#include "hexchat.h"
|
||||
#include "cfgfiles.h"
|
||||
#include "ssl.h" /* struct cert_info */
|
||||
|
||||
#include <glib.h>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -161,6 +161,7 @@ powershell "Get-Content -Encoding UTF8 '$(SolutionDir)..\src\fe-gtk\hexchat.rc.u
|
||||
<ClCompile Include="setup.c" />
|
||||
<ClCompile Include="sexy-iso-codes.c" />
|
||||
<ClCompile Include="sexy-spell-entry.c" />
|
||||
<ClCompile Include="sslalert.c" />
|
||||
<ClCompile Include="textgui.c" />
|
||||
<ClCompile Include="urlgrab.c" />
|
||||
<ClCompile Include="userlistgui.c" />
|
||||
|
@ -182,6 +182,9 @@
|
||||
<ClCompile Include="resources.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sslalert.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="hexchat.exe.manifest">
|
||||
@ -202,4 +205,4 @@
|
||||
<ItemGroup>
|
||||
<Xml Include="..\..\data\hexchat.gresource.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -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 */
|
||||
|
144
src/fe-gtk/sslalert.c
Normal file
144
src/fe-gtk/sslalert.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "fe-gtk.h"
|
||||
#include <gtk/gtk.h>
|
||||
#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<b>%s</b>", 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 ("<b>Issuer:</b>\t%s\n\n"\
|
||||
"<b>Subject:</b> %s\n\n"\
|
||||
"<b>Valid:</b>\tAfter: %s\n\t\tBefore: %s\n\n"\
|
||||
"<b>Algorithm:</b> %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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user