repo: ngircd
action: commit
revision: 
path_from: 
revision_from: 1254d315b9d33010035aaf7eba61ac4e8e1cc98f:
path_to: 
revision_to: 
git.thebackupbox.net
ngircd
git clone git://git.thebackupbox.net/ngircd
commit 1254d315b9d33010035aaf7eba61ac4e8e1cc98f
Author: Federico G. Schwindt 
Date:   Fri Aug 2 01:47:06 2013 +0100

    Add certificate fingerprint support

diff --git a/doc/Protocol.txt b/doc/Protocol.txt
index 39c5730bd4a76da18380af3f44562c806b1e005e..
index ..59fa617afaaa91f0f0b6b6338785e11c4c240bf8 100644
--- a/doc/Protocol.txt
+++ b/doc/Protocol.txt
@@ -228,6 +228,7 @@ The following  names are defined:
  - "cloakhost": the cloaked hostname of a client
  - "info": info text ("real name") of a client
  - "user": the user name of a client (can't be empty)
+ - "certfp": the cert fingerprint of a client


 III. Numerics used by IRC+ Protocol
diff --git a/src/ngircd/conf-ssl.h b/src/ngircd/conf-ssl.h
index 22897ef51356aed7136a2a662398a9c44aa3254e..
index ..439298c6736cd6b8dce93c546cfa01ebe3923fb1 100644
--- a/src/ngircd/conf-ssl.h
+++ b/src/ngircd/conf-ssl.h
@@ -37,6 +37,7 @@ struct ConnSSL_State {
 	void *cookie;		/* pointer to server configuration structure
 				   (for outgoing connections), or NULL. */
 #endif
+	char *fingerprint;
 };

 #endif
diff --git a/src/ngircd/conn-ssl.c b/src/ngircd/conn-ssl.c
index 45e6458a19d5805d48b13a11f27c639cd3ee3963..
index ..7141eaca6ebb4bf523459e7465075bb0a01ae207 100644
--- a/src/ngircd/conn-ssl.c
+++ b/src/ngircd/conn-ssl.c
@@ -54,11 +54,15 @@ static bool ConnSSL_LoadServerKey_openssl PARAMS(( SSL_CTX *c ));
 #define DH_BITS 2048
 #define DH_BITS_MIN 1024

+#define MAX_HASH_SIZE	64	/* from gnutls-int.h */
+
 static gnutls_certificate_credentials_t x509_cred;
 static gnutls_dh_params_t dh_params;
 static bool ConnSSL_LoadServerKey_gnutls PARAMS(( void ));
 #endif

+#define CERTFP_LEN	(20 * 2 + 1)
+
 static bool ConnSSL_Init_SSL PARAMS(( CONNECTION *c ));
 static int ConnectAccept PARAMS(( CONNECTION *c, bool connect ));
 static int ConnSSL_HandleError PARAMS(( CONNECTION *c, const int code, const char *fname ));
@@ -145,6 +149,13 @@ pem_passwd_cb(char *buf, int size, int rwflag, void *password)
 	memcpy(buf, (char *)(array_start(pass)), size);
 	return size;
 }
+
+
+static int
+Verify_openssl(UNUSED int preverify_ok, UNUSED X509_STORE_CTX *x509_ctx)
+{
+	return 1;
+}
 #endif


@@ -223,6 +234,10 @@ void ConnSSL_Free(CONNECTION *c)
 		SSL_shutdown(ssl);
 		SSL_free(ssl);
 		c->ssl_state.ssl = NULL;
+		if (c->ssl_state.fingerprint) {
+			free(c->ssl_state.fingerprint);
+			c->ssl_state.fingerprint = NULL;
+		}
 	}
 #endif
 #ifdef HAVE_LIBGNUTLS
@@ -277,6 +292,7 @@ ConnSSL_InitLibrary( void )

 	SSL_CTX_set_options(newctx, SSL_OP_SINGLE_DH_USE|SSL_OP_NO_SSLv2);
 	SSL_CTX_set_mode(newctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+	SSL_CTX_set_verify(newctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, Verify_openssl);
 	SSL_CTX_free(ssl_ctx);
 	ssl_ctx = newctx;
 	Log(LOG_INFO, "%s initialized.", SSLeay_version(SSLEAY_VERSION));
@@ -404,6 +420,7 @@ ConnSSL_Init_SSL(CONNECTION *c)
 		return false;
 	}
 	assert(c->ssl_state.ssl == NULL);
+	assert(c->ssl_state.fingerprint == NULL);

 	c->ssl_state.ssl = SSL_new(ssl_ctx);
 	if (!c->ssl_state.ssl) {
@@ -432,6 +449,7 @@ ConnSSL_Init_SSL(CONNECTION *c)
 	 * http://www.mail-archive.com/help-gnutls@gnu.org/msg00286.html
 	 */
 	gnutls_transport_set_ptr(c->ssl_state.gnutls_session, (gnutls_transport_ptr_t) (long) c->sock);
+	gnutls_certificate_server_set_request(c->ssl_state.gnutls_session, GNUTLS_CERT_REQUEST);
 	ret = gnutls_credentials_set(c->ssl_state.gnutls_session, GNUTLS_CRD_CERTIFICATE, x509_cred);
 	if (ret < 0) {
 		Log(LOG_ERR, "gnutls_credentials_set: %s", gnutls_strerror(ret));
@@ -614,6 +632,77 @@ ConnSSL_Connect( CONNECTION *c )
 	return ConnectAccept(c, true);
 }

+static int
+ConnSSL_InitFingerprint( CONNECTION *c )
+{
+	const char hex[] = "0123456789abcdef";
+	int i;
+
+#ifdef HAVE_LIBSSL
+	unsigned char digest[EVP_MAX_MD_SIZE];
+	unsigned int digest_size;
+	X509 *cert;
+
+	cert = SSL_get_peer_certificate(c->ssl_state.ssl);
+	if (!cert)
+		return 0;
+
+	if (!X509_digest(cert, EVP_sha1(), digest, &digest_size)) {
+		X509_free(cert);
+		return 0;
+	}
+
+	X509_free(cert);
+#endif /* HAVE_LIBSSL */
+#ifdef HAVE_LIBGNUTLS
+	gnutls_x509_crt_t cert;
+	unsigned int cert_list_size;
+	const gnutls_datum_t *cert_list;
+	unsigned char digest[MAX_HASH_SIZE];
+	size_t digest_size;
+
+	if (gnutls_certificate_type_get(c->ssl_state.gnutls_session) != GNUTLS_CRT_X509)
+		return 0;
+
+	if (gnutls_x509_crt_init(&cert) != GNUTLS_E_SUCCESS)
+		return 0;
+
+	cert_list_size = 0;
+	cert_list = gnutls_certificate_get_peers(c->ssl_state.gnutls_session,
+						 &cert_list_size);
+	if (!cert_list) {
+		gnutls_x509_crt_deinit(cert);
+		return 0;
+	}
+	
+	if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) != GNUTLS_E_SUCCESS) {
+		gnutls_x509_crt_deinit(cert);
+		return 0;
+	}
+
+	digest_size = sizeof(digest);
+	if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, digest, &digest_size)) {
+		gnutls_x509_crt_deinit(cert);
+		return 0;
+	}
+
+	gnutls_x509_crt_deinit(cert);
+#endif /* HAVE_LIBGNUTLS */
+
+	assert(c->ssl_state.fingerprint == NULL);
+
+	c->ssl_state.fingerprint = malloc(CERTFP_LEN);
+	if (!c->ssl_state.fingerprint)
+		return 0;
+
+	for (i = 0; i < (int)digest_size; i++) {
+		c->ssl_state.fingerprint[i * 2] = hex[digest[i] / 16];
+		c->ssl_state.fingerprint[i * 2 + 1] = hex[digest[i] % 16];
+	}
+	c->ssl_state.fingerprint[i * 2] = '\0';
+
+	return 1;
+}

 /* accept/connect wrapper. if connect is true, connect to peer, otherwise wait for incoming connection */
 static int
@@ -634,6 +723,8 @@ ConnectAccept( CONNECTION *c, bool connect)
 	if (ret)
 		return ConnSSL_HandleError(c, ret, "gnutls_handshake");
 #endif /* _GNUTLS */
+	(void)ConnSSL_InitFingerprint(c);
+
 	Conn_OPTION_DEL(c, (CONN_SSL_WANT_WRITE|CONN_SSL_WANT_READ|CONN_SSL_CONNECT));
 	ConnSSL_LogCertInfo(c);

@@ -725,6 +816,19 @@ ConnSSL_GetCipherInfo(CONNECTION *c, char *buf, size_t len)
 #endif
 }

+char *
+ConnSSL_GetFingerprint(CONNECTION *c)
+{
+	return c->ssl_state.fingerprint;
+}
+
+bool
+ConnSSL_SetFingerprint(CONNECTION *c, const char *fingerprint)
+{
+	assert (c != NULL);
+	c->ssl_state.fingerprint = strdup(fingerprint);
+	return c->ssl_state.fingerprint != NULL;
+}
 #else

 bool
diff --git a/src/ngircd/conn-ssl.h b/src/ngircd/conn-ssl.h
index aea0846f46cb2036b15cff074678e7df482a953e..
index ..fc705f13b9e3b2c91a0a90c485decf04c06b5a5c 100644
--- a/src/ngircd/conn-ssl.h
+++ b/src/ngircd/conn-ssl.h
@@ -26,6 +26,9 @@ GLOBAL ssize_t ConnSSL_Write PARAMS(( CONNECTION *c, const void *buf, size_t cou
 GLOBAL ssize_t ConnSSL_Read PARAMS(( CONNECTION *c, void *buf, size_t count));

 GLOBAL bool ConnSSL_GetCipherInfo PARAMS(( CONNECTION *c, char *buf, size_t len ));
+GLOBAL char *ConnSSL_GetFingerprint PARAMS(( CONNECTION *c ));
+GLOBAL bool ConnSSL_SetFingerprint PARAMS(( CONNECTION *c, const char *fingerprint ));
+
 #endif /* SSL_SUPPORT */
 #endif /* conn_ssl_h */

diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c
index 087f5fc86de5f12c7c80395062e92c8fd675bb9a..
index ..9c6baef2676dd35aab47eaca13b8bf3ef39946f5 100644
--- a/src/ngircd/conn.c
+++ b/src/ngircd/conn.c
@@ -2611,6 +2611,45 @@ Conn_UsesSSL(CONN_ID Idx)
 	return Conn_OPTION_ISSET(&My_Connections[Idx], CONN_SSL);
 }

+
+GLOBAL char *
+Conn_GetFingerprint(CONN_ID Idx)
+{
+	if (Idx < 0)
+		return NULL;
+	assert(Idx < (int) array_length(&My_ConnArray, sizeof(CONNECTION)));
+	return ConnSSL_GetFingerprint(&My_Connections[Idx]);
+}
+
+
+GLOBAL bool
+Conn_SetFingerprint(CONN_ID Idx, const char *fingerprint)
+{
+	if (Idx < 0)
+		return false;
+	assert(Idx < (int) array_length(&My_ConnArray, sizeof(CONNECTION)));
+	return ConnSSL_SetFingerprint(&My_Connections[Idx], fingerprint);
+}
+#else
+GLOBAL bool
+Conn_UsesSSL(UNUSED CONN_ID Idx)
+{
+	return false;
+}
+
+
+GLOBAL char *
+Conn_GetFingerprint(UNUSED CONN_ID Idx)
+{
+	return NULL;
+}
+
+
+GLOBAL bool
+Conn_SetFingerprint(UNUSED CONN_ID Idx, UNUSED const char *fingerprint)
+{
+	return true;
+}
 #endif


diff --git a/src/ngircd/conn.h b/src/ngircd/conn.h
index 9236c58ba7767a7be858a6a331abeedc78e02221..
index ..a6cf53a47be4f3695e66f9fd74c76fcf56480a31 100644
--- a/src/ngircd/conn.h
+++ b/src/ngircd/conn.h
@@ -139,13 +139,12 @@ GLOBAL CONN_ID Conn_GetFromProc PARAMS((int fd));
 GLOBAL CLIENT* Conn_GetClient PARAMS((CONN_ID i));
 GLOBAL PROC_STAT* Conn_GetProcStat PARAMS((CONN_ID i));

+GLOBAL char *Conn_GetFingerprint PARAMS((CONN_ID Idx));
+GLOBAL bool Conn_SetFingerprint PARAMS((CONN_ID Idx, const char *fingerprint));
+GLOBAL bool Conn_UsesSSL PARAMS((CONN_ID Idx));
+
 #ifdef SSL_SUPPORT
 GLOBAL bool Conn_GetCipherInfo PARAMS((CONN_ID Idx, char *buf, size_t len));
-GLOBAL bool Conn_UsesSSL PARAMS((CONN_ID Idx));
-#else
-static inline bool
-Conn_UsesSSL(UNUSED CONN_ID Idx)
-{ return false; }
 #endif

 GLOBAL const char *Conn_GetIPAInfo PARAMS((CONN_ID Idx));
diff --git a/src/ngircd/irc-info.c b/src/ngircd/irc-info.c
index 046648fdfa02cab53369ccbf81f33de40801de0f..
index ..ad0404083a638ec92b8f0923152212c9a3ce0768 100644
--- a/src/ngircd/irc-info.c
+++ b/src/ngircd/irc-info.c
@@ -381,10 +381,19 @@ IRC_WHOIS_SendReply(CLIENT *Client, CLIENT *from, CLIENT *c)
 		return DISCONNECTED;

 	/* Connected using SSL? */
-	if (Conn_UsesSSL(Client_Conn(c)) &&
-	    !IRC_WriteStrClient(from, RPL_WHOISSSL_MSG, Client_ID(from),
-				Client_ID(c)))
-		return DISCONNECTED;
+	if (Conn_UsesSSL(Client_Conn(c))) {
+		if (!IRC_WriteStrClient(from, RPL_WHOISSSL_MSG, Client_ID(from),
+					Client_ID(c)))
+			return DISCONNECTED;
+
+		/* Certificate fingerprint? */
+		if (Conn_GetFingerprint(Client_Conn(c)) &&
+		    from == c &&
+		    !IRC_WriteStrClient(from, RPL_WHOISCERTFP_MSG,
+					Client_ID(from), Client_ID(c),
+					Conn_GetFingerprint(Client_Conn(c))))
+			return DISCONNECTED;
+	}

 	/* Registered nickname? */
 	if (Client_HasMode(c, 'R') &&
@@ -469,16 +478,26 @@ Show_MOTD_End(CLIENT *Client)
 #ifdef SSL_SUPPORT
 static bool Show_MOTD_SSLInfo(CLIENT *Client)
 {
-	bool ret = true;
-	char buf[COMMAND_LEN] = "Connected using Cipher ";
-
-	if (!Conn_GetCipherInfo(Client_Conn(Client), buf + 23, sizeof buf - 23))
-		return true;
+	char buf[COMMAND_LEN];
+	char c_str[128];
+
+	if (Conn_GetCipherInfo(Client_Conn(Client), c_str, sizeof(c_str))) {
+		snprintf(buf, sizeof(buf), "Connected using Cipher %s", c_str);
+		if (!IRC_WriteStrClient(Client, RPL_MOTD_MSG,
+					Client_ID(Client), buf))
+			return false;
+	}

-	if (!Show_MOTD_Sendline(Client, buf))
-		ret = false;
+	if (Conn_GetFingerprint(Client_Conn(Client))) {
+		snprintf(buf, sizeof(buf),
+			 "Your client certificate fingerprint is: %s",
+			 Conn_GetFingerprint(Client_Conn(Client)));
+		if (!IRC_WriteStrClient(Client, RPL_MOTD_MSG,
+					Client_ID(Client), buf))
+			return false;
+	}

-	return ret;
+	return true;
 }
 #else
 static inline bool
diff --git a/src/ngircd/irc-metadata.c b/src/ngircd/irc-metadata.c
index 308a7157ac4bad8c969391fe30a32b5be7a04cb6..
index ..d64ffb2178c8fff634e377a1c82964135cde9956 100644
--- a/src/ngircd/irc-metadata.c
+++ b/src/ngircd/irc-metadata.c
@@ -96,6 +96,8 @@ IRC_METADATA(CLIENT *Client, REQUEST *Req)
 		Client_SetInfo(target, Req->argv[2]);
 	else if (*Req->argv[2] && strcasecmp(Req->argv[1], "user") == 0)
 		Client_SetUser(target, Req->argv[2], true);
+	else if (*Req->argv[2] && strcasecmp(Req->argv[1], "certfp") == 0)
+		Conn_SetFingerprint(Client_Conn(target), Req->argv[2]);
 	else
 		Log(LOG_WARNING,
 		    "Ignored metadata update from \"%s\" for client \"%s\": \"%s=%s\" - unknown key!",
diff --git a/src/ngircd/messages.h b/src/ngircd/messages.h
index 371abc262d7dc6f708a7cee156884d6d2186efa5..
index ..3a91c1838998ec1a85dba3f10978ca2a82a21563 100644
--- a/src/ngircd/messages.h
+++ b/src/ngircd/messages.h
@@ -49,6 +49,7 @@
 #define RPL_NETUSERS_MSG		"266 %s %lu %lu :Current global users: %lu, Max: %lu"
 #define RPL_STATSCONN_MSG		"250 %s :Highest connection count: %lu (%lu connections received)"
 #define RPL_WHOISSSL_MSG		"275 %s %s :is connected via SSL (secure link)"
+#define RPL_WHOISCERTFP_MSG		"276 %s %s :has client certificate fingerprint %s"

 #define RPL_AWAY_MSG			"301 %s %s :%s"
 #define RPL_USERHOST_MSG		"302 %s :"
diff --git a/src/ngircd/numeric.c b/src/ngircd/numeric.c
index 9b8240bdca0227f7e9862d4fb6d5e0646d36ce7a..
index ..f7f3ac919b8c64e1061896fffacef6056dac3167 100644
--- a/src/ngircd/numeric.c
+++ b/src/ngircd/numeric.c
@@ -212,6 +212,14 @@ Announce_User(CLIENT * Client, CLIENT * User)
 		}
 	}

+	if (Conn_GetFingerprint(conn)) {
+		if (!IRC_WriteStrClient(Client,
+					"METADATA %s certfp :%s",
+					Client_ID(User),
+					Conn_GetFingerprint(conn)))
+			return DISCONNECTED;
+	}
+
 	return CONNECTED;
 } /* Announce_User */

-----END OF PAGE-----