commit 69ce65bacb0155be5fb9159a3dfc5c8e3390cc0d from: Alexander Barton date: Fri Aug 02 22:29:12 2013 UTC Merge remote-tracking branch 'fgsch/certfp' * fgsch/certfp: Simplify MOTD handling Add certificate fingerprint support commit - 4ec19c03323c872a576d6891b3c5fa3fe70261c4 commit + 69ce65bacb0155be5fb9159a3dfc5c8e3390cc0d blob - 39c5730bd4a76da18380af3f44562c806b1e005e blob + 59fa617afaaa91f0f0b6b6338785e11c4c240bf8 --- doc/Protocol.txt +++ 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 blob - 22897ef51356aed7136a2a662398a9c44aa3254e blob + 439298c6736cd6b8dce93c546cfa01ebe3923fb1 --- src/ngircd/conf-ssl.h +++ 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 blob - 45e6458a19d5805d48b13a11f27c639cd3ee3963 blob + 7141eaca6ebb4bf523459e7465075bb0a01ae207 --- src/ngircd/conn-ssl.c +++ src/ngircd/conn-ssl.c @@ -54,11 +54,15 @@ static bool ConnSSL_LoadServerKey_openssl PARAMS(( SSL #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 *p 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)); @@ -613,8 +631,79 @@ ConnSSL_Connect( CONNECTION *c ) assert(Conn_OPTION_ISSET(c, CONN_SSL)); 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 ConnectAccept( CONNECTION *c, bool connect) @@ -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 #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 blob - aea0846f46cb2036b15cff074678e7df482a953e blob + fc705f13b9e3b2c91a0a90c485decf04c06b5a5c --- src/ngircd/conn-ssl.h +++ src/ngircd/conn-ssl.h @@ -26,6 +26,9 @@ GLOBAL ssize_t ConnSSL_Write PARAMS(( CONNECTION *c, c 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 */ blob - 087f5fc86de5f12c7c80395062e92c8fd675bb9a blob + 9c6baef2676dd35aab47eaca13b8bf3ef39946f5 --- src/ngircd/conn.c +++ src/ngircd/conn.c @@ -2610,7 +2610,46 @@ Conn_UsesSSL(CONN_ID Idx) assert(Idx < (int) array_length(&My_ConnArray, sizeof(CONNECTION))); 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 blob - 9236c58ba7767a7be858a6a331abeedc78e02221 blob + a6cf53a47be4f3695e66f9fd74c76fcf56480a31 --- src/ngircd/conn.h +++ 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)); blob - 046648fdfa02cab53369ccbf81f33de40801de0f blob + 22284cb328c87b0e76a15cbd00c15cd8889bbec2 --- src/ngircd/irc-info.c +++ src/ngircd/irc-info.c @@ -381,10 +381,19 @@ IRC_WHOIS_SendReply(CLIENT *Client, CLIENT *from, CLIE 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') && @@ -439,51 +448,37 @@ WHOWAS_EntryWrite(CLIENT *prefix, WHOWAS *entry) entry->id, entry->server, t_str); } +#ifdef SSL_SUPPORT static bool -Show_MOTD_Start(CLIENT *Client) +Show_MOTD_SSLInfo(CLIENT *Client) { - return IRC_WriteStrClient(Client, RPL_MOTDSTART_MSG, - Client_ID( Client ), Client_ID( Client_ThisServer( ))); -} + char buf[COMMAND_LEN]; + char c_str[128]; -static bool -Show_MOTD_Sendline(CLIENT *Client, const char *msg) -{ - return IRC_WriteStrClient(Client, RPL_MOTD_MSG, Client_ID( Client ), msg); -} + 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; + } -static bool -Show_MOTD_End(CLIENT *Client) -{ - if (!IRC_WriteStrClient(Client, RPL_ENDOFMOTD_MSG, Client_ID(Client))) - return DISCONNECTED; - - if (*Conf_CloakHost) - return IRC_WriteStrClient(Client, RPL_HOSTHIDDEN_MSG, - Client_ID(Client), - Client_Hostname(Client)); - - return CONNECTED; -} + 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; + } -#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; - - if (!Show_MOTD_Sendline(Client, buf)) - ret = false; - - return ret; + return true; } #else -static inline bool +static bool Show_MOTD_SSLInfo(UNUSED CLIENT *c) -{ return true; } +{ + return true; +} #endif /* Global functions */ @@ -1477,7 +1472,8 @@ IRC_Show_MOTD( CLIENT *Client ) if (len_tot == 0 && !Conn_UsesSSL(Client_Conn(Client))) return IRC_WriteStrClient(Client, ERR_NOMOTD_MSG, Client_ID(Client)); - if (!Show_MOTD_Start(Client)) + if (!IRC_WriteStrClient(Client, RPL_MOTDSTART_MSG, Client_ID(Client), + Client_ID(Client_ThisServer()))) return DISCONNECTED; line = array_start(&Conf_Motd); @@ -1487,14 +1483,23 @@ IRC_Show_MOTD( CLIENT *Client ) assert(len_tot >= len_str); len_tot -= len_str; - if (!Show_MOTD_Sendline(Client, line)) + if (!IRC_WriteStrClient(Client, RPL_MOTD_MSG, Client_ID(Client), line)) return DISCONNECTED; line += len_str; } if (!Show_MOTD_SSLInfo(Client)) return DISCONNECTED; - return Show_MOTD_End(Client); + + if (!IRC_WriteStrClient(Client, RPL_ENDOFMOTD_MSG, Client_ID(Client))) + return DISCONNECTED; + + if (*Conf_CloakHost) + return IRC_WriteStrClient(Client, RPL_HOSTHIDDEN_MSG, + Client_ID(Client), + Client_Hostname(Client)); + + return CONNECTED; } /* IRC_Show_MOTD */ /** blob - 308a7157ac4bad8c969391fe30a32b5be7a04cb6 blob + d64ffb2178c8fff634e377a1c82964135cde9956 --- src/ngircd/irc-metadata.c +++ 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!", blob - 371abc262d7dc6f708a7cee156884d6d2186efa5 blob + 3a91c1838998ec1a85dba3f10978ca2a82a21563 --- src/ngircd/messages.h +++ 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 :" blob - 9b8240bdca0227f7e9862d4fb6d5e0646d36ce7a blob + f7f3ac919b8c64e1061896fffacef6056dac3167 --- src/ngircd/numeric.c +++ src/ngircd/numeric.c @@ -210,6 +210,14 @@ Announce_User(CLIENT * Client, CLIENT * User) Client_HostnameCloaked(User))) return DISCONNECTED; } + } + + if (Conn_GetFingerprint(conn)) { + if (!IRC_WriteStrClient(Client, + "METADATA %s certfp :%s", + Client_ID(User), + Conn_GetFingerprint(conn))) + return DISCONNECTED; } return CONNECTED;