From 34967973aa1d6f6d4d69335a519966c2436bcd98 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Fri, 8 Aug 2014 01:26:56 +0200
Subject: kike: implement SSL client cert. auth.
---
src/kike.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 2 deletions(-)
(limited to 'src')
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
--
cgit v1.2.3-70-g09d2