kike: implement SSL client cert. auth.
This commit is contained in:
		
							
								
								
									
										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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user