From e245735f7107119211251b26d8bc59f88a8470e1 Mon Sep 17 00:00:00 2001 From: nohar Date: Thu, 25 Aug 2005 08:17:10 +0000 Subject: [PATCH] merged YS' elite cert management patch --- Makefile.in | 51 +++++++++++---- samples/bip.conf | 21 ++++-- src/bip.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++- src/bip.h | 4 ++ src/connection.c | 70 ++++++++++++++------ src/connection.h | 1 + src/irc.c | 41 ++++++------ src/irc.h | 16 +++++ 8 files changed, 309 insertions(+), 58 deletions(-) diff --git a/Makefile.in b/Makefile.in index f695abc..07c73cd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -76,7 +76,8 @@ CONFIG_HEADER = ./src/config.h CONFIG_CLEAN_FILES = DIST_COMMON = README AUTHORS COPYING ChangeLog INSTALL Makefile.am \ Makefile.in NEWS TODO aclocal.m4 config.guess config.sub configure \ -configure.in install-sh missing mkinstalldirs +configure.in install-sh missing mkinstalldirs src/config.h.in \ +src/stamp-h.in DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) @@ -100,6 +101,34 @@ config.status: $(srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(srcdir)/configure: $(srcdir)/configure.in $(ACLOCAL_M4) $(CONFIGURE_DEPENDENCIES) cd $(srcdir) && $(AUTOCONF) +src/config.h: src/stamp-h + @if test ! -f $@; then \ + rm -f src/stamp-h; \ + $(MAKE) src/stamp-h; \ + else :; fi +src/stamp-h: $(srcdir)/src/config.h.in $(top_builddir)/config.status + cd $(top_builddir) \ + && CONFIG_FILES= CONFIG_HEADERS=src/config.h \ + $(SHELL) ./config.status + @echo timestamp > src/stamp-h 2> /dev/null +$(srcdir)/src/config.h.in: $(srcdir)/src/stamp-h.in + @if test ! -f $@; then \ + rm -f $(srcdir)/src/stamp-h.in; \ + $(MAKE) $(srcdir)/src/stamp-h.in; \ + else :; fi +$(srcdir)/src/stamp-h.in: $(top_srcdir)/configure.in $(ACLOCAL_M4) + cd $(top_srcdir) && $(AUTOHEADER) + @echo timestamp > $(srcdir)/src/stamp-h.in 2> /dev/null + +mostlyclean-hdr: + +clean-hdr: + +distclean-hdr: + -rm -f src/config.h + +maintainer-clean-hdr: + # This directory's subdirectories are mostly independent; you can cd # into them and run `make' without going through this Makefile. # To change the values of `make' variables: instead of editing Makefiles, @@ -288,32 +317,32 @@ distclean-generic: -rm -f config.cache config.log stamp-h stamp-h[0-9]* maintainer-clean-generic: -mostlyclean-am: mostlyclean-tags mostlyclean-generic +mostlyclean-am: mostlyclean-hdr mostlyclean-tags mostlyclean-generic mostlyclean: mostlyclean-recursive -clean-am: clean-tags clean-generic mostlyclean-am +clean-am: clean-hdr clean-tags clean-generic mostlyclean-am clean: clean-recursive -distclean-am: distclean-tags distclean-generic clean-am +distclean-am: distclean-hdr distclean-tags distclean-generic clean-am distclean: distclean-recursive -rm -f config.status -maintainer-clean-am: maintainer-clean-tags maintainer-clean-generic \ - distclean-am +maintainer-clean-am: maintainer-clean-hdr maintainer-clean-tags \ + maintainer-clean-generic distclean-am @echo "This command is intended for maintainers to use;" @echo "it deletes files that may require special tools to rebuild." maintainer-clean: maintainer-clean-recursive -rm -f config.status -.PHONY: install-data-recursive uninstall-data-recursive \ -install-exec-recursive uninstall-exec-recursive installdirs-recursive \ -uninstalldirs-recursive all-recursive check-recursive \ -installcheck-recursive info-recursive dvi-recursive \ -mostlyclean-recursive distclean-recursive clean-recursive \ +.PHONY: mostlyclean-hdr distclean-hdr clean-hdr maintainer-clean-hdr \ +install-data-recursive uninstall-data-recursive install-exec-recursive \ +uninstall-exec-recursive installdirs-recursive uninstalldirs-recursive \ +all-recursive check-recursive installcheck-recursive info-recursive \ +dvi-recursive mostlyclean-recursive distclean-recursive clean-recursive \ maintainer-clean-recursive tags tags-recursive mostlyclean-tags \ distclean-tags clean-tags maintainer-clean-tags distdir info-am info \ dvi-am dvi check check-am installcheck-am installcheck install-exec-am \ diff --git a/samples/bip.conf b/samples/bip.conf index b698569..a2ddf74 100644 --- a/samples/bip.conf +++ b/samples/bip.conf @@ -77,17 +77,24 @@ user { password = "3880f2b39b3b9cb507b052b695d2680859bfc327"; # SSL certificates checking mode for user: - # "none" to accept anything; - # "basic" to accept if the certificate is contained in the store below; - # "ca" to do a complete certificate chain checking with the objects + # - "none" to accept anything; + # - "basic" to accept if the certificate is contained in the store; + # In "basic" mode, encountered untrusted certificates can be added to + # the store interactively by connecting a client and "trusting" them. + # - "ca" to do a complete certificate chain checking with the objects # in the store below (you have to put in it every cert, CRL, up to the - # root CA). + # root CA). You have to build your store manually, so you may prefer + # using "basic" unless you're a crypto zealot... ssl_check_mode = "none"; # Location of the user's store for SSL certificate check - # Standard openssl store, you must put PEM objects with .pem extension - # and run `c_rehash .' in it - ssl_check_store = "/home/nohar/.bip/certstore"; + # In "basic" mode, that must point to a single file with all trusted + # certs concatenated together (the interactive "trust" appends to this + # file). + # In "ca" mode, it's a directory of a standard openssl store; you must + # put PEM objects (certificates, CRLs...) with .pem extension and run + # `c_rehash .' in it + ssl_check_store = "/home/nohar/.bip/trustedcerts.txt"; # These will be the default for each connections default_nick = "nohar"; diff --git a/src/bip.c b/src/bip.c index def4752..d2468ea 100644 --- a/src/bip.c +++ b/src/bip.c @@ -58,6 +58,9 @@ int conf_blreset_on_talk = 0; list_t *parse_conf(FILE *file); static void conf_die(char *fmt, ...); +#ifdef HAVE_LIBSSL +static int adm_trust(struct link_client *ic, struct line *line); +#endif static void hash_binary(char *hex, unsigned char **password, unsigned int *seed) { @@ -791,6 +794,11 @@ void ircize(list_t *ll) MAYFREE(link->s_password); MAYFREE(link->connect_nick); MAYFREE(link->vhost); +#ifdef HAVE_LIBSSL + MAYFREE(link->ssl_check_store); + sk_X509_free(link->untrusted_certs); +#endif + for (i = 0; i < link->serverc; i++) server_free(link->serverv[i]); @@ -843,6 +851,7 @@ void ircize(list_t *ll) link->s_ssl = c->network->ssl; link->ssl_check_mode = u->ssl_check_mode; link->ssl_check_store = strmaydup(u->ssl_check_store); + link->untrusted_certs = sk_X509_new_null(); #endif if (!link->user) @@ -985,6 +994,150 @@ void write_user_list(connection_t *c, char *dest) "end of bip user list"); } +#ifdef HAVE_LIBSSL +int link_add_untrusted(struct link_server *ls, X509 *cert) +{ + int i; + + /* Check whether the cert is already in the stack */ + for (i = 0; i < sk_X509_num(LINK(ls)->untrusted_certs); i++) { + if (!X509_cmp(cert, + sk_X509_value(LINK(ls)->untrusted_certs, i))) + return 1; + } + + return sk_X509_push(LINK(ls)->untrusted_certs, cert); +} + +int ssl_check_trust(struct link_client *ic) +{ + X509 *trustcert = NULL; + char subject[270]; + char issuer[270]; + unsigned char fp[EVP_MAX_MD_SIZE]; + char fpstr[EVP_MAX_MD_SIZE * 3 + 20]; + unsigned int fplen; + int i; + + if(!LINK(ic)->untrusted_certs || + sk_X509_num(LINK(ic)->untrusted_certs) <= 0) + return 0; + + trustcert = sk_X509_value(LINK(ic)->untrusted_certs, 0); + strcpy(subject, "Subject: "); + strcpy(issuer, "Issuer: "); + strcpy(fpstr, "MD5 fingerprint: "); + X509_NAME_oneline(X509_get_subject_name(trustcert), subject + 9, 256); + X509_NAME_oneline(X509_get_issuer_name(trustcert), issuer + 9, 256); + + X509_digest(trustcert, EVP_md5(), fp, &fplen); + for(i = 0; i < (int)fplen; i++) + sprintf(fpstr + 17 + (i * 3), "%02X%c", + fp[i], (i == (int)fplen - 1) ? '\0' : ':'); + + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", + "This server SSL certificate was not " + "accepted because it is not in your store " + "of trusted certificates:"); + + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", subject); + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", issuer); + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", fpstr); + + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", + "WARNING: if you've already trusted a " + "certificate for this server before, that " + "probably means it has changed."); + + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", + "If so, YOU MAY BE SUBJECT OF A " + "MAN-IN-THE-MIDDLE ATTACK! PLEASE DON'T TRUST " + "THIS CERTIFICATE IF YOU'RE NOT SURE THIS IS " + "NOT THE CASE."); + + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", + "Type /QUOTE BIP TRUST OK to trust this " + "certificate, /QUOTE BIP TRUST NO to discard it."); + + return 1; +} + +#if 0 +static int ssl_trust_next_cert(struct link_client *ic) +{ + (void)ic; +} + +static int ssl_discard_next_cert(struct link_client *ic) +{ + (void)ic; +} +#endif /* 0 */ +#endif + +#ifdef HAVE_LIBSSL +static int adm_trust(struct link_client *ic, struct line *line) +{ + if (ic->allow_trust != 1) { + mylog(LOG_ERROR, "User attempted TRUST command without " + "being allowed to!"); + unbind_from_link(ic); + return OK_CLOSE; + } + + if(!LINK(ic)->untrusted_certs || + sk_X509_num(LINK(ic)->untrusted_certs) <= 0) { + /* shouldn't have been asked to /QUOTE TRUST but well... */ + WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", + "No untrusted certificates."); + return ERR_PROTOCOL; + } + + if (line->elemc != 3) + return ERR_PROTOCOL; + + if (!strcasecmp(line->elemv[2], "OK")) { + /* OK, attempt to trust the cert! */ + BIO *bio = BIO_new_file(LINK(ic)->ssl_check_store, "a+"); + X509 *trustcert = sk_X509_shift(LINK(ic)->untrusted_certs); + + if(!bio || !trustcert || + PEM_write_bio_X509(bio, trustcert) <= 0) + write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " + ":==== Error while trusting test!\r\n"); + else + write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " + ":==== Certificate now trusted.\r\n"); + + BIO_free_all(bio); + X509_free(trustcert); + } else if (!strcasecmp(line->elemv[2], "NO")) { + /* NO, discard the cert! */ + write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " + ":==== Certificate discarded.\r\n"); + + X509_free(sk_X509_shift(LINK(ic)->untrusted_certs)); + } else + return ERR_PROTOCOL; + + if (!ssl_check_trust(ic)) { + write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " + ":No more certificates waiting awaiting " + "user trust, thanks!\r\n"); + write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " + ":If the certificate is trusted, bip should " + "be able to connect to the server on the " + "next retry. Please wait a while and try " + "connecting your client again.\r\n"); + + LINK(ic)->recon_timer = 1; /* Speed up reconnection... */ + unbind_from_link(ic); + return OK_CLOSE; + } + return OK_FORGET; +} +#endif + extern struct link_client *reloading_client; void adm_blreset(struct link_client *ic) { @@ -997,7 +1150,8 @@ void adm_blreset(struct link_client *ic) } } -void adm_bip(struct link_client *ic, struct line *line) +int adm_bip(struct link_client *ic, struct line *line); +int adm_bip(struct link_client *ic, struct line *line) { char *nick; if (LINK(ic)->l_server) @@ -1005,7 +1159,7 @@ void adm_bip(struct link_client *ic, struct line *line) else nick = LINK(ic)->prev_nick; if (line->elemc < 2) - return; + return OK_FORGET; if (strcasecmp(line->elemv[1], "RELOAD") == 0) { reloading_client = ic; @@ -1023,7 +1177,12 @@ void adm_bip(struct link_client *ic, struct line *line) } else if (strcasecmp(line->elemv[1], "HELP") == 0) { WRITE_LINE2(CONN(ic), P_IRCMASK, "PRIVMSG", nick, "/BIP (RELOAD|LIST|JUMP|BLRESET|HELP)"); +#ifdef HAVE_LIBSSL + } else if (strcasecmp(line->elemv[1], "TRUST") == 0) { + return adm_trust(ic, line); +#endif } + return OK_FORGET; } void free_conf(list_t *l) diff --git a/src/bip.h b/src/bip.h index 329f53f..69a806c 100644 --- a/src/bip.h +++ b/src/bip.h @@ -62,4 +62,8 @@ struct c_channel char *key; }; +int adm_bip(struct link_client *ic, struct line *line); +int ssl_check_trust(struct link_client *ic); +void adm_blreset(struct link_client *ic); + #endif diff --git a/src/connection.c b/src/connection.c index 8948138..83ae24c 100644 --- a/src/connection.c +++ b/src/connection.c @@ -25,6 +25,8 @@ static BIO *errbio = NULL; extern char *conf_ssl_certfile; static int SSLize(connection_t *cn, int *nc); static SSL_CTX *SSL_init_context(void); +/* SSH like trust management */ +int link_add_untrusted(void *ls, X509 *cert); #endif static int connection_timedout(connection_t *cn); @@ -341,6 +343,7 @@ list_t *read_lines(connection_t *cn, int *error) case CONN_ERROR: case CONN_DISCONN: case CONN_EXCEPT: + case CONN_UNTRUSTED: *error = 1; ret = NULL; break; @@ -488,6 +491,7 @@ int cn_is_new(connection_t *cn) case CONN_EXCEPT: case CONN_NEED_SSLIZE: case CONN_OK: + case CONN_UNTRUSTED: return 0; case CONN_NEW: case CONN_INPROGRESS: @@ -505,6 +509,7 @@ int cn_is_in_error(connection_t *cn) case CONN_ERROR: case CONN_DISCONN: case CONN_EXCEPT: + case CONN_UNTRUSTED: return 1; case CONN_NEW: case CONN_INPROGRESS: @@ -1184,6 +1189,8 @@ static int bip_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) "in store, rejecting it!"); err = X509_V_ERR_CERT_REJECTED; X509_STORE_CTX_set_error(ctx, err); + + link_add_untrusted(c->user_data, X509_dup(err_cert)); } } @@ -1226,30 +1233,40 @@ static int SSLize(connection_t *cn, int *nc) SSL_CIPHER *cipher; char buf[128]; int len; - int err; cipher = SSL_get_current_cipher(cn->ssl_h); SSL_CIPHER_description(cipher, buf, 128); len = strlen(buf); if (len > 0) - buf[len-1] = '\0'; - mylog(LOG_DEBUG, "Negociated cyphers: %s",buf); - - if (cn->ssl_check_mode > 0 && - (err = SSL_get_verify_result(cn->ssl_h)) - != X509_V_OK) { - mylog(LOG_ERROR, "Certificate check failed: %s (%d)!", - X509_verify_cert_error_string(err), - err); - cn->connected = CONN_ERROR; - return 1; - } + buf[len - 1] = '\0'; + mylog(LOG_DEBUG, "Negociated cyphers: %s", buf); cn->connected = CONN_OK; *nc = 1; return 0; } - + + switch (cn->ssl_check_mode) { + case SSL_CHECK_NONE: + break; + case SSL_CHECK_BASIC: + if((err = SSL_get_verify_result(cn->ssl_h)) != X509_V_OK) { + mylog(LOG_ERROR, "Certificate check failed: %s (%d)!", + X509_verify_cert_error_string(err), err); + cn->connected = CONN_UNTRUSTED; + return 1; + } + break; + case SSL_CHECK_CA: + if((err = SSL_get_verify_result(cn->ssl_h)) != X509_V_OK) { + mylog(LOG_ERROR, "Certificate check failed: %s (%d)!", + X509_verify_cert_error_string(err), err); + cn->connected = CONN_UNTRUSTED; + return 1; + } + break; + } + if (err2 == SSL_ERROR_SYSCALL) { /* socked died */ cn->connected = CONN_ERROR; @@ -1280,11 +1297,23 @@ static connection_t *_connection_new_SSL(char *dsthostname, char *dstport, conn->cert = NULL; conn->ssl_check_mode = check_mode; conn->ssl_check_store = check_store; - if (conn->ssl_check_mode != SSL_CHECK_NONE && - !SSL_CTX_load_verify_locations(conn->ssl_ctx_h, NULL, + + switch (conn->ssl_check_mode) { + case SSL_CHECK_BASIC: + if (!SSL_CTX_load_verify_locations(conn->ssl_ctx_h, check_store, + NULL)) { + mylog(LOG_ERROR, "Can't assign check store to " + "SSL connection! Proceeding without!"); + } + break; + case SSL_CHECK_CA: + if (!SSL_CTX_load_verify_locations(conn->ssl_ctx_h, NULL, check_store)) { - mylog(LOG_ERROR, "Can't assign check store to SSL connection!"); - return conn; + mylog(LOG_ERROR, "Can't assign check store to " + "SSL connection!"); + return conn; + } + break; } switch (conn->ssl_check_mode) { @@ -1331,6 +1360,11 @@ connection_t *connection_new(char *dsthostname, int dstport, char *srchostname, int timeout) { char dstportbuf[20], srcportbuf[20], *tmp; +#ifndef HAVE_LIBSSL + (void)ssl; + (void)ssl_check_mode; + (void)ssl_check_store; +#endif /* TODO: allow litteral service name in the function interface */ if (snprintf(dstportbuf, 20, "%d", dstport) >= 20) dstportbuf[19] = '\0'; diff --git a/src/connection.h b/src/connection.h index c4ded9f..952a25f 100644 --- a/src/connection.h +++ b/src/connection.h @@ -50,6 +50,7 @@ #define CONN_EXCEPT 6 #define CONN_NEW 7 #define CONN_NEED_SSLIZE 8 +#define CONN_UNTRUSTED 9 #define WRITE_OK 0 #define WRITE_ERROR -1 diff --git a/src/irc.c b/src/irc.c index a9c73eb..c5ee3fd 100644 --- a/src/irc.c +++ b/src/irc.c @@ -17,6 +17,7 @@ #include #include "util.h" #include "irc.h" +#include "bip.h" #include "log.h" #include "connection.h" #include "md5.h" @@ -63,13 +64,6 @@ static void irc_cli_make_join(struct link_client *ic); #define CONNECT_TIMEOUT 60 -#define ERR_PROTOCOL (-1) -#define ERR_AUTH (-2) -#define OK_COPY (1) -#define OK_FORGET (2) -#define OK_CLOSE (3) -#define OK_COPY_CLI (4) - struct channel *channel_new(const char *name) { struct channel *chan; @@ -298,7 +292,6 @@ static void irc_server_join(struct link_server *s) static void irc_server_connected(struct link_server *server) { - int i; LINK(server)->s_state = IRCS_CONNECTED; irc_server_join(server); log_connected(LINK(server)->log); @@ -310,11 +303,6 @@ static void irc_server_connected(struct link_server *server) write_line(CONN(server), str); free(str); } - -#if 0 - for (i = 0; i < LINK(server)->l_clientc; i++) - irc_cli_make_join(LINK(server)->l_clientv[i]); -#endif } /* @@ -527,7 +515,7 @@ static void bind_to_link(struct link *l, struct link_client *ic) l->l_clientv[i] = ic; } -static void unbind_from_link(struct link_client *ic) +void unbind_from_link(struct link_client *ic) { struct link *l = LINK(ic); int i; @@ -550,11 +538,9 @@ static void unbind_from_link(struct link_client *ic) fatal("realloc"); } -void adm_bip(struct link_client *ic, struct line *line); int irc_cli_bip(struct link_client *ic, struct line *line) { - adm_bip(ic, line); - return OK_FORGET; + return adm_bip(ic, line); } #define PASS_SEP ':' @@ -682,6 +668,17 @@ static int irc_cli_startup(struct link_client *ic, struct line *line, return ERR_AUTH; } + if (LINK(ic)->s_state != IRCS_CONNECTED) { +#ifdef HAVE_LIBSSL + /* Check if we have an untrusted certificate from the server */ + if (ssl_check_trust(ic)) { + ic->allow_trust = 1; + free(init_nick); + return OK_FORGET; + } +#endif + } + if (LINK(ic)->s_state == IRCS_NONE) { /* drop it if corresponding server hasn't connected at all. */ write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " @@ -693,7 +690,7 @@ static int irc_cli_startup(struct link_client *ic, struct line *line, } #if 0 - TODO code this + TODO code this -- OR NOT! if (!connection_check_source(ic, ic->client->src_ipl)) return ERR_AUTH; #endif @@ -798,7 +795,6 @@ static int irc_cli_quit(struct link_client *ic, struct line *line) return OK_CLOSE; } -void adm_blreset(struct link_client *ic); static int irc_cli_privmsg(struct link_client *ic, struct line *line) { log_cli_privmsg(LINK(ic)->log, LINK(ic)->l_server->nick, @@ -1847,8 +1843,13 @@ connection_t *irc_server_connect(struct link *link) conn = connection_new(link->serverv[link->cur_server]->host, link->serverv[link->cur_server]->port, link->vhost, link->bind_port, +#ifdef HAVE_LIBSSL link->s_ssl, link->ssl_check_mode, - link->ssl_check_store, CONNECT_TIMEOUT); + link->ssl_check_store, +#else + 0, 0, NULL, +#endif + CONNECT_TIMEOUT); if (!conn) fatal("connection_new"); diff --git a/src/irc.h b/src/irc.h index 1076c1b..f49436e 100644 --- a/src/irc.h +++ b/src/irc.h @@ -16,6 +16,14 @@ #include "connection.h" #include "line.h" + +#define ERR_PROTOCOL (-1) +#define ERR_AUTH (-2) +#define OK_COPY (1) +#define OK_FORGET (2) +#define OK_CLOSE (3) +#define OK_COPY_CLI (4) + #define P_SERV "bip.bip.bip" #define S_PING "BIPPING" #define P_IRCMASK "-bip!bip@bip.bip.bip" @@ -103,8 +111,11 @@ struct link { int bind_port; int s_ssl; +#ifdef HAVE_LIBSSL int ssl_check_mode; char *ssl_check_store; + STACK_OF(X509) *untrusted_certs; +#endif }; struct link_connection { @@ -134,6 +145,10 @@ struct link_client { char *init_pass; int state; int logging_timer; + +#ifdef HAVE_LIBSSL + int allow_trust; +#endif }; #define link_client_new() calloc(sizeof(struct link_client), 1) @@ -180,4 +195,5 @@ int ischannel(char p); void irc_client_close(struct link_client *); void irc_client_free(struct link_client *); struct link *irc_link_new(); +void unbind_from_link(struct link_client *ic); #endif