From d6d9ca2a047df5a1db77210edcf48aa463e760e3 Mon Sep 17 00:00:00 2001 From: Heikki Hannikainen Date: Mon, 4 Mar 2013 23:38:04 +0200 Subject: [PATCH] Just about working client cert validation --- src/accept.c | 11 ++++- src/login.c | 31 +++++++++++-- src/ssl.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/ssl.h | 19 +++++--- 4 files changed, 171 insertions(+), 11 deletions(-) diff --git a/src/accept.c b/src/accept.c index 9cd8936..fdf6d94 100644 --- a/src/accept.c +++ b/src/accept.c @@ -322,7 +322,16 @@ static int open_listener(struct listen_config_t *lc) return -1; } - hlog(LOG_INFO, "SSL initialized for '%s': %s", lc->name, l->addr_s); + /* optional client cert validation */ + if (lc->cafile) { + if (ssl_ca_certificate(l->ssl, lc->cafile, 2)) { + hlog(LOG_ERR, "Failed to load trusted SSL CA certificates for '%s*': %s", lc->name, l->addr_s); + listener_free(l); + return -1; + } + } + + hlog(LOG_INFO, "SSL initialized for '%s': %s%s", lc->name, l->addr_s, (lc->cafile) ? " (client validation enabled)" : ""); } #endif diff --git a/src/login.c b/src/login.c index 84446f3..eb2f061 100644 --- a/src/login.c +++ b/src/login.c @@ -21,6 +21,7 @@ #include "filter.h" #include "clientlist.h" #include "parse_qc.h" +#include "ssl.h" /* a static list of usernames which are not allowed to log in */ static const char *disallow_login_usernames[] = { @@ -237,6 +238,7 @@ int login_handler(struct worker_t *self, struct client_t *c, int l4proto, char * goto failed_login; } + /* ok, it's somewhat valid, write it down */ strncpy(c->username, username, sizeof(c->username)); c->username[sizeof(c->username)-1] = 0; c->username_len = strlen(c->username); @@ -257,6 +259,25 @@ int login_handler(struct worker_t *self, struct client_t *c, int l4proto, char * goto failed_login; } + /* if SSL client cert verification is enabled, check it */ + int ssl_validated = 0; +#ifdef USE_SSL + if (c->ssl_con && c->ssl_con->validate) { + hlog(LOG_DEBUG, "%s/%s: login: doing SSL client cert validation", c->addr_rem, c->username); + int ssl_res = ssl_validate_client_cert(c); + if (ssl_res) { + hlog(LOG_WARNING, "%s/%s: SSL client cert validation failed", c->addr_rem, c->username); + rc = client_printf(self, c, "# Client certificate validation failed\r\n"); + c->failed_cmds = 10; /* bail out right away for a HTTP client */ + goto failed_login; + } + hlog(LOG_INFO, "%s/%s: Login validated using SSL client certificate"); + c->validated = 1; + ssl_validated = 1; + } +#endif + + int given_passcode = -1; for (i = 2; i < argc; i++) { @@ -266,10 +287,12 @@ int login_handler(struct worker_t *self, struct client_t *c, int l4proto, char * break; } - given_passcode = atoi(argv[i]); - if (given_passcode >= 0) - if (given_passcode == aprs_passcode(c->username)) - c->validated = 1; + if (!ssl_validated) { + given_passcode = atoi(argv[i]); + if (given_passcode >= 0) + if (given_passcode == aprs_passcode(c->username)) + c->validated = 1; + } } else if (strcasecmp(argv[i], "vers") == 0) { /* Collect application name and version separately. * Some clients only give out application name but diff --git a/src/ssl.c b/src/ssl.c index 708d7b5..389d131 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -330,8 +330,91 @@ int ssl_certificate(struct ssl_t *ssl, const char *certfile, const char *keyfile return 0; } +static int ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) +{ + hlog(LOG_DEBUG, "ssl_verify_callback, ok: %d", ok); + +#if (NGX_DEBUG) + char *subject, *issuer; + int err, depth; + X509 *cert; + X509_NAME *sname, *iname; + ngx_connection_t *c; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + c = ngx_ssl_get_connection(ssl_conn); + + cert = X509_STORE_CTX_get_current_cert(x509_store); + err = X509_STORE_CTX_get_error(x509_store); + depth = X509_STORE_CTX_get_error_depth(x509_store); + + sname = X509_get_subject_name(cert); + subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)"; + + iname = X509_get_issuer_name(cert); + issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)"; + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "verify:%d, error:%d, depth:%d, " + "subject:\"%s\",issuer: \"%s\"", + ok, err, depth, subject, issuer); + + if (sname) { + OPENSSL_free(subject); + } + + if (iname) { + OPENSSL_free(issuer); + } +#endif + + return 1; +} + + /* - * Create a connect */ + * Load trusted CA certs + */ + +int ssl_ca_certificate(struct ssl_t *ssl, const char *cafile, int depth) +{ + STACK_OF(X509_NAME) *list; + + SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ssl_verify_callback); + SSL_CTX_set_verify_depth(ssl->ctx, depth); + + if (SSL_CTX_load_verify_locations(ssl->ctx, cafile, NULL) == 0) { + hlog(LOG_ERR, "SSL_CTX_load_verify_locations(\"%s\") failed", cafile); + return -1; + } + + list = SSL_load_client_CA_file(cafile); + + if (list == NULL) { + hlog(LOG_ERR, "SSL_load_client_CA_file(\"%s\") failed", cafile); + return -1; + } + + /* + * before 0.9.7h and 0.9.8 SSL_load_client_CA_file() + * always leaved an error in the error queue + */ + + ERR_clear_error(); + + SSL_CTX_set_client_CA_list(ssl->ctx, list); + + ssl->validate = 1; + + return 0; +} + +/* + * Create a connect + */ int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client) { @@ -363,11 +446,47 @@ int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client return -1; } + sc->validate = ssl->validate; + c->ssl_con = sc; return 0; } +/* + * Validate client certificate + */ + +int ssl_validate_client_cert(struct client_t *c) +{ + long rc; + X509 *cert; + + rc = SSL_get_verify_result(c->ssl_con->connection); + + if (rc != X509_V_OK) { + hlog(LOG_INFO, "%s/%s: client SSL certificate verify error: (%l:%s)", c->addr_rem, c->username, rc, X509_verify_cert_error_string(rc)); + return -1; + } + + + cert = SSL_get_peer_certificate(c->ssl_con->connection); + + if (cert == NULL) { + hlog(LOG_INFO, "%s/%s: client sent no required SSL certificate", c->addr_rem, c->username); + return -1; + } + + hlog(LOG_INFO, "%s/%s: Login validated using SSL client certificate"); + + X509_free(cert); + + return 0; +} + +/* + * Write data to an SSL socket + */ int ssl_write(struct worker_t *self, struct client_t *c) { diff --git a/src/ssl.h b/src/ssl.h index 727f1d3..dc4b5a4 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -21,6 +21,8 @@ struct worker_t; struct ssl_t { SSL_CTX *ctx; + + unsigned validate; }; struct ssl_connection_t { @@ -34,11 +36,14 @@ struct ssl_connection_t { ngx_event_handler_pt saved_read_handler; ngx_event_handler_pt saved_write_handler; */ - unsigned handshaked:1; - unsigned renegotiation:1; - unsigned buffer:1; - unsigned no_wait_shutdown:1; - unsigned no_send_shutdown:1; + unsigned handshaked:1; + + unsigned renegotiation:1; + unsigned buffer:1; + unsigned no_wait_shutdown:1; + unsigned no_send_shutdown:1; + + unsigned validate; }; #define NGX_SSL_SSLv2 0x0002 @@ -64,10 +69,14 @@ extern void ssl_free(struct ssl_t *ssl); /* create context for listener, load certs */ extern int ssl_create(struct ssl_t *ssl, void *data); extern int ssl_certificate(struct ssl_t *ssl, const char *certfile, const char *keyfile); +extern int ssl_ca_certificate(struct ssl_t *ssl, const char *cafile, int depth); /* create / free connection */ extern int ssl_create_connection(struct ssl_t *ssl, struct client_t *c, int i_am_client); +/* validate a client certificate */ +extern int ssl_validate_client_cert(struct client_t *c); + extern int ssl_write(struct worker_t *self, struct client_t *c); extern int ssl_writeable(struct worker_t *self, struct client_t *c); extern int ssl_readable(struct worker_t *self, struct client_t *c);