From 34967973aa1d6f6d4d69335a519966c2436bcd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Fri, 8 Aug 2014 01:26:56 +0200 Subject: [PATCH] kike: implement SSL client cert. auth. --- README | 6 +++++ src/kike.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README b/README index 00ef915..40894f4 100644 --- a/README +++ b/README @@ -44,6 +44,12 @@ ZyklonB stays running in the foreground, so I recommend launching it inside a Screen or tmux session. kike, on the other hand, immediately forks into the background. Use something like `killall' if you want to terminate it. +Client Certificates +------------------- +`kike' uses SHA1 fingerprints of SSL client certificates to authenticate users. +To get the fingerprint from a certificate file, use: + $ openssl x509 -noout -in cert.pem -sha1 -fingerprint + Disclaimer ---------- I am not an antisemitist, I'm just being an offensive asshole with the naming. diff --git a/src/kike.c b/src/kike.c index cdb20ca..75537d6 100644 --- a/src/kike.c +++ b/src/kike.c @@ -38,6 +38,8 @@ static struct config_item g_config_table[] = { "ssl_cert", NULL, "Server SSL certificate (PEM)" }, { "ssl_key", NULL, "Server SSL private key (PEM)" }, + { "operators", NULL, "IRCop SSL cert. fingerprints" }, + { "max_connections", "0", "Global connection limit" }, { "ping_interval", "180", "Interval between PING's (sec)" }, { NULL, NULL, NULL } @@ -222,6 +224,12 @@ irc_is_valid_key (const char *key) #undef LE #undef SP +static bool +irc_is_valid_fingerprint (const char *fp) +{ + return irc_regex_match ("^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){20}$", fp); +} + // --- Application data -------------------------------------------------------- #define IRC_SUPPORTED_USER_MODES "aiwros" @@ -251,6 +259,7 @@ struct client bool ssl_rx_want_tx; ///< SSL_read() wants to write bool ssl_tx_want_rx; ///< SSL_write() wants to read SSL *ssl; ///< SSL connection + char *ssl_cert_fingerprint; ///< Client certificate fingerprint char *nickname; ///< IRC nickname (main identifier) char *username; ///< IRC username @@ -435,6 +444,7 @@ struct server_context unsigned max_connections; ///< Max. connections allowed or 0 struct str_vector motd; ///< MOTD (none if empty) nl_catd catalog; ///< Message catalog for server msgs + struct str_map operators; ///< SSL cert. fingerprints for IRCops }; static void @@ -465,6 +475,9 @@ server_context_init (struct server_context *self) self->max_connections = 0; str_vector_init (&self->motd); self->catalog = (nl_catd) -1; + str_map_init (&self->operators); + // The regular irc_strxfrm() is sufficient for fingerprints + self->operators.key_xfrm = irc_strxfrm; } static void @@ -494,6 +507,7 @@ server_context_free (struct server_context *self) str_vector_free (&self->motd); if (self->catalog != (nl_catd) -1) catclose (self->catalog); + str_map_free (&self->operators); } static void @@ -759,6 +773,35 @@ irc_initiate_quit (struct server_context *ctx) irc_try_finish_quit (ctx); } +static char * +client_get_ssl_cert_fingerprint (struct client *c) +{ + if (!c->ssl) + return NULL; + + X509 *peer_cert = SSL_get_peer_certificate (c->ssl); + if (!peer_cert) + return NULL; + + int cert_len = i2d_X509 (peer_cert, NULL); + if (cert_len < 0) + return NULL; + + unsigned char cert[cert_len], *p = cert; + if (i2d_X509 (peer_cert, &p) < 0) + return NULL; + + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA1 (cert, cert_len, hash); + + struct str fingerprint; + str_init (&fingerprint); + str_append_printf (&fingerprint, "%02X", hash[0]); + for (size_t i = 0; i < sizeof hash; i++) + str_append_printf (&fingerprint, ":%02X", hash[i]); + return str_steal (&fingerprint); +} + // --- Timers ------------------------------------------------------------------ static void @@ -1076,6 +1119,12 @@ irc_try_finish_registration (struct client *c) if (*mode) irc_send (c, ":%s MODE %s :+%s", c->nickname, c->nickname, mode); free (mode); + + hard_assert (c->ssl_cert_fingerprint == NULL); + if ((c->ssl_cert_fingerprint = client_get_ssl_cert_fingerprint (c))) + irc_send (c, ":%s NOTICE %s :" + "Your SSL client certificate fingerprint is %s", + ctx->server_name, c->nickname, c->ssl_cert_fingerprint); } static void @@ -1367,7 +1416,13 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string) case 'o': if (!adding) irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, false); - // TODO: check public key fingerprint when adding + else if (c->ssl_cert_fingerprint + && str_map_find (&c->ctx->operators, c->ssl_cert_fingerprint)) + irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, true); + else + irc_send (c, ":%s NOTICE %s :Either you're not using an SSL" + " client certificate, or the fingerprint doesn't match", + c->ctx->server_name, c->nickname); break; case 's': irc_modify_mode (&new_mode, IRC_USER_MODE_RX_SERVER_NOTICES, adding); @@ -2657,7 +2712,27 @@ irc_parse_config (struct server_context *ctx, struct error **e) PARSE_UNSIGNED (ping_interval, 1, UINT_MAX); PARSE_UNSIGNED (max_connections, 0, UINT_MAX); - return true; + + bool result = true; + struct str_vector fingerprints; + str_vector_init (&fingerprints); + const char *operators = str_map_find (&ctx->config, "operators"); + if (operators) + split_str_ignore_empty (operators, ',', &fingerprints); + for (size_t i = 0; i < fingerprints.len; i++) + { + const char *key = fingerprints.vector[i]; + if (!irc_is_valid_fingerprint (key)) + { + error_set (e, "invalid configuration value for `%s': %s", + "operators", "invalid fingerprint value"); + result = false; + break; + } + str_map_set (&ctx->operators, key, (void *) 1); + } + str_vector_free (&fingerprints); + return result; } static bool