kike: implement SSL client cert. auth.
This commit is contained in:
parent
6382ecb016
commit
34967973aa
6
README
6
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
|
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.
|
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
|
Disclaimer
|
||||||
----------
|
----------
|
||||||
I am not an antisemitist, I'm just being an offensive asshole with the naming.
|
I am not an antisemitist, I'm just being an offensive asshole with the naming.
|
||||||
|
|
79
src/kike.c
79
src/kike.c
|
@ -38,6 +38,8 @@ static struct config_item g_config_table[] =
|
||||||
{ "ssl_cert", NULL, "Server SSL certificate (PEM)" },
|
{ "ssl_cert", NULL, "Server SSL certificate (PEM)" },
|
||||||
{ "ssl_key", NULL, "Server SSL private key (PEM)" },
|
{ "ssl_key", NULL, "Server SSL private key (PEM)" },
|
||||||
|
|
||||||
|
{ "operators", NULL, "IRCop SSL cert. fingerprints" },
|
||||||
|
|
||||||
{ "max_connections", "0", "Global connection limit" },
|
{ "max_connections", "0", "Global connection limit" },
|
||||||
{ "ping_interval", "180", "Interval between PING's (sec)" },
|
{ "ping_interval", "180", "Interval between PING's (sec)" },
|
||||||
{ NULL, NULL, NULL }
|
{ NULL, NULL, NULL }
|
||||||
|
@ -222,6 +224,12 @@ irc_is_valid_key (const char *key)
|
||||||
#undef LE
|
#undef LE
|
||||||
#undef SP
|
#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 --------------------------------------------------------
|
// --- Application data --------------------------------------------------------
|
||||||
|
|
||||||
#define IRC_SUPPORTED_USER_MODES "aiwros"
|
#define IRC_SUPPORTED_USER_MODES "aiwros"
|
||||||
|
@ -251,6 +259,7 @@ struct client
|
||||||
bool ssl_rx_want_tx; ///< SSL_read() wants to write
|
bool ssl_rx_want_tx; ///< SSL_read() wants to write
|
||||||
bool ssl_tx_want_rx; ///< SSL_write() wants to read
|
bool ssl_tx_want_rx; ///< SSL_write() wants to read
|
||||||
SSL *ssl; ///< SSL connection
|
SSL *ssl; ///< SSL connection
|
||||||
|
char *ssl_cert_fingerprint; ///< Client certificate fingerprint
|
||||||
|
|
||||||
char *nickname; ///< IRC nickname (main identifier)
|
char *nickname; ///< IRC nickname (main identifier)
|
||||||
char *username; ///< IRC username
|
char *username; ///< IRC username
|
||||||
|
@ -435,6 +444,7 @@ struct server_context
|
||||||
unsigned max_connections; ///< Max. connections allowed or 0
|
unsigned max_connections; ///< Max. connections allowed or 0
|
||||||
struct str_vector motd; ///< MOTD (none if empty)
|
struct str_vector motd; ///< MOTD (none if empty)
|
||||||
nl_catd catalog; ///< Message catalog for server msgs
|
nl_catd catalog; ///< Message catalog for server msgs
|
||||||
|
struct str_map operators; ///< SSL cert. fingerprints for IRCops
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -465,6 +475,9 @@ server_context_init (struct server_context *self)
|
||||||
self->max_connections = 0;
|
self->max_connections = 0;
|
||||||
str_vector_init (&self->motd);
|
str_vector_init (&self->motd);
|
||||||
self->catalog = (nl_catd) -1;
|
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
|
static void
|
||||||
|
@ -494,6 +507,7 @@ server_context_free (struct server_context *self)
|
||||||
str_vector_free (&self->motd);
|
str_vector_free (&self->motd);
|
||||||
if (self->catalog != (nl_catd) -1)
|
if (self->catalog != (nl_catd) -1)
|
||||||
catclose (self->catalog);
|
catclose (self->catalog);
|
||||||
|
str_map_free (&self->operators);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -759,6 +773,35 @@ irc_initiate_quit (struct server_context *ctx)
|
||||||
irc_try_finish_quit (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 ------------------------------------------------------------------
|
// --- Timers ------------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1076,6 +1119,12 @@ irc_try_finish_registration (struct client *c)
|
||||||
if (*mode)
|
if (*mode)
|
||||||
irc_send (c, ":%s MODE %s :+%s", c->nickname, c->nickname, mode);
|
irc_send (c, ":%s MODE %s :+%s", c->nickname, c->nickname, mode);
|
||||||
free (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
|
static void
|
||||||
|
@ -1367,7 +1416,13 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string)
|
||||||
case 'o':
|
case 'o':
|
||||||
if (!adding)
|
if (!adding)
|
||||||
irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, false);
|
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;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
irc_modify_mode (&new_mode, IRC_USER_MODE_RX_SERVER_NOTICES, adding);
|
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 (ping_interval, 1, UINT_MAX);
|
||||||
PARSE_UNSIGNED (max_connections, 0, 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
|
static bool
|
||||||
|
|
Loading…
Reference in New Issue