From ba34903f4163d0650118bee76f012de961951409 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 25 Mar 2014 09:02:39 +0100 Subject: [PATCH] implement certificate validation (including CN verification) Signed-off-by: Felix Fietkau --- ustream-example-client.c | 42 +++++++++-- ustream-internal.h | 1 + ustream-openssl.c | 149 +++++++++++++++++++++++++++++++++++++-- ustream-openssl.h | 4 ++ ustream-polarssl.c | 71 ++++++++++++++++--- ustream-polarssl.h | 3 +- ustream-ssl.c | 16 +++++ ustream-ssl.h | 10 +++ 8 files changed, 276 insertions(+), 20 deletions(-) diff --git a/ustream-example-client.c b/ustream-example-client.c index a02815b..c3d0018 100644 --- a/ustream-example-client.c +++ b/ustream-example-client.c @@ -10,6 +10,7 @@ static struct uloop_fd fd; static struct ustream_fd stream, s_input; static struct ustream_ssl ssl; +static const char *host, *port; static void *ctx; @@ -47,7 +48,7 @@ static void client_ssl_notify_read(struct ustream *s, int bytes) static void client_notify_connected(struct ustream_ssl *ssl) { - fprintf(stderr, "SSL connection established\n"); + fprintf(stderr, "SSL connection established (CN verified: %d)\n", ssl->valid_cn); s_input.stream.notify_read = client_input_notify_read; ustream_fd_init(&s_input, 0); } @@ -58,6 +59,11 @@ static void client_notify_error(struct ustream_ssl *ssl, int error, const char * client_teardown(); } +static void client_notify_verify_error(struct ustream_ssl *ssl, int error, const char *str) +{ + fprintf(stderr, "WARNING: SSL certificate error(%d): %s\n", error, str); +} + static void client_notify_state(struct ustream *us) { if (!us->write_error && !us->eof) @@ -72,12 +78,14 @@ static void example_connect_ssl(int fd) fprintf(stderr, "Starting SSL negnotiation\n"); ssl.notify_error = client_notify_error; + ssl.notify_verify_error = client_notify_verify_error; ssl.notify_connected = client_notify_connected; ssl.stream.notify_read = client_ssl_notify_read; ssl.stream.notify_state = client_notify_state; ustream_fd_init(&stream, fd); ustream_ssl_init(&ssl, &stream.stream, ctx, false); + ustream_ssl_set_peer_cn(&ssl, host); } static void example_connect_cb(struct uloop_fd *f, unsigned int events) @@ -93,23 +101,43 @@ static void example_connect_cb(struct uloop_fd *f, unsigned int events) example_connect_ssl(fd.fd); } -static void connect_client(const char *host, const char *port) +static void connect_client(void) { fd.fd = usock(USOCK_TCP | USOCK_NONBLOCK, host, port); fd.cb = example_connect_cb; uloop_fd_add(&fd, ULOOP_WRITE | ULOOP_EDGE_TRIGGER); } +static int usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options] \n", progname); + return 1; +} + int main(int argc, char **argv) { - if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } + int ch; ctx = ustream_ssl_context_new(false); + + while ((ch = getopt(argc, argv, "c:")) != -1) { + switch(ch) { + case 'c': + ustream_ssl_context_add_ca_crt_file(ctx, optarg); + break; + } + } + + argv += optind; + argc -= optind; + + if (argc != 2) + return usage(argv[0]); + uloop_init(); - connect_client(argv[1], argv[2]); + host = argv[0]; + port = argv[1]; + connect_client(); uloop_run(); close(fd.fd); diff --git a/ustream-internal.h b/ustream-internal.h index 85d8b47..e0e1f50 100644 --- a/ustream-internal.h +++ b/ustream-internal.h @@ -35,6 +35,7 @@ enum ssl_conn_status { void ustream_set_io(struct ustream_ssl_ctx *ctx, void *ssl, struct ustream *s); struct ustream_ssl_ctx *__ustream_ssl_context_new(bool server); +int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file); int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file); int __ustream_ssl_set_key_file(struct ustream_ssl_ctx *ctx, const char *file); void __ustream_ssl_context_free(struct ustream_ssl_ctx *ctx); diff --git a/ustream-openssl.c b/ustream-openssl.c index c826e4e..a45e2f4 100644 --- a/ustream-openssl.c +++ b/ustream-openssl.c @@ -16,6 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include #include "ustream-ssl.h" #include "ustream-internal.h" @@ -48,17 +50,27 @@ __ustream_ssl_context_new(bool server) if (!c) return NULL; - if (server) - SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_verify(c, SSL_VERIFY_NONE, NULL); return (void *) c; } +__hidden int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file) +{ + int ret; + + ret = SSL_CTX_load_verify_locations((void *) ctx, file, NULL); + if (ret < 1) + return -1; + + return 0; +} + __hidden int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file) { int ret; - ret = SSL_CTX_use_certificate_file((void *) ctx, file, SSL_FILETYPE_PEM); + ret = SSL_CTX_use_certificate_chain_file((void *) ctx, file); if (ret < 1) ret = SSL_CTX_use_certificate_file((void *) ctx, file, SSL_FILETYPE_ASN1); @@ -93,6 +105,133 @@ static void ustream_ssl_error(struct ustream_ssl *us, int ret) uloop_timeout_set(&us->error_timer, 0); } +static bool host_pattern_match(const unsigned char *pattern, const char *cn) +{ + char c; + + for (; (c = tolower(*pattern++)) != 0; cn++) { + if (c != '*') { + if (c != *cn) + return false; + continue; + } + + do { + c = tolower(*pattern++); + } while (c == '*'); + + while (*cn) { + if (c == tolower(*cn) && + host_pattern_match(pattern, cn)) + return true; + if (*cn == '.') + return false; + cn++; + } + + return !c; + } + return !*cn; +} + +static bool host_pattern_match_asn1(ASN1_STRING *asn1, const char *cn) +{ + unsigned char *pattern; + bool ret = false; + + if (ASN1_STRING_to_UTF8(&pattern, asn1) < 0) + return false; + + if (!pattern) + return false; + + if (strlen((char *) pattern) == ASN1_STRING_length(asn1)) + ret = host_pattern_match(pattern, cn); + + OPENSSL_free(pattern); + + return ret; +} + +static bool ustream_ssl_verify_cn_alt(struct ustream_ssl *us, X509 *cert) +{ + GENERAL_NAMES *alt_names; + int i, n_alt; + + alt_names = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); + if (!alt_names) + return false; + + n_alt = sk_GENERAL_NAME_num(alt_names); + for (i = 0; i < n_alt; i++) { + const GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i); + + if (!name) + continue; + + if (name->type != GEN_DNS) + continue; + + if (host_pattern_match_asn1(name->d.dNSName, us->peer_cn)) + return true; + } + + return false; +} + +static bool ustream_ssl_verify_cn(struct ustream_ssl *us, X509 *cert) +{ + ASN1_STRING *astr; + X509_NAME *xname; + int i, last; + + if (!us->peer_cn) + return false; + + if (ustream_ssl_verify_cn_alt(us, cert)) + return true; + + xname = X509_get_subject_name(cert); + + last = -1; + while (1) { + i = X509_NAME_get_index_by_NID(xname, NID_commonName, last); + if (i < 0) + break; + + last = i; + } + + if (last < 0) + return false; + + astr = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(xname, last)); + + return host_pattern_match_asn1(astr, us->peer_cn); +} + + +static void ustream_ssl_verify_cert(struct ustream_ssl *us) +{ + void *ssl = us->ssl; + X509 *cert; + int res; + + cert = SSL_get_peer_certificate(ssl); + if (!cert) + return; + + res = SSL_get_verify_result(ssl); + if (res != X509_V_OK) { + if (us->notify_verify_error) + us->notify_verify_error(us, res, X509_verify_cert_error_string(res)); + return; + } + + us->valid_cert = true; + us->valid_cn = ustream_ssl_verify_cn(us, cert); +} + __hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us) { void *ssl = us->ssl; @@ -103,8 +242,10 @@ __hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us) else r = SSL_connect(ssl); - if (r == 1) + if (r == 1) { + ustream_ssl_verify_cert(us); return U_SSL_OK; + } r = SSL_get_error(ssl, r); if (r == SSL_ERROR_WANT_READ || r == SSL_ERROR_WANT_WRITE) diff --git a/ustream-openssl.h b/ustream-openssl.h index 76d2bad..20c502d 100644 --- a/ustream-openssl.h +++ b/ustream-openssl.h @@ -39,4 +39,8 @@ static inline char *__ustream_ssl_strerror(int error, char *buffer, int len) return ERR_error_string(error, buffer); } +static inline void __ustream_ssl_update_peer_cn(struct ustream_ssl *us) +{ +} + #endif diff --git a/ustream-polarssl.c b/ustream-polarssl.c index 8516d7f..ef8360a 100644 --- a/ustream-polarssl.c +++ b/ustream-polarssl.c @@ -95,7 +95,6 @@ __ustream_ssl_context_new(bool server) if (!ctx) return NULL; - ctx->auth = SSL_VERIFY_NONE; ctx->server = server; #ifdef USE_VERSION_1_3 pk_init(&ctx->key); @@ -106,6 +105,21 @@ __ustream_ssl_context_new(bool server) return ctx; } +__hidden int __ustream_ssl_add_ca_crt_file(struct ustream_ssl_ctx *ctx, const char *file) +{ + int ret; + +#ifdef USE_VERSION_1_3 + ret = x509_crt_parse_file(&ctx->ca_cert, file); +#else + ret = x509parse_crtfile(&ctx->ca_cert, file); +#endif + if (ret) + return -1; + + return 0; +} + __hidden int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char *file) { int ret; @@ -118,9 +132,6 @@ __hidden int __ustream_ssl_set_crt_file(struct ustream_ssl_ctx *ctx, const char if (ret) return -1; - if (!ctx->server) - ctx->auth = SSL_VERIFY_OPTIONAL; - return 0; } @@ -168,14 +179,45 @@ static bool ssl_do_wait(int ret) } } +static void ustream_ssl_verify_cert(struct ustream_ssl *us) +{ + void *ssl = us->ssl; + const char *msg = NULL; + bool cn_mismatch; + int r; + + r = ssl_get_verify_result(ssl); + cn_mismatch = r & BADCERT_CN_MISMATCH; + r &= ~BADCERT_CN_MISMATCH; + + if (r & BADCERT_EXPIRED) + msg = "certificate has expired"; + else if (r & BADCERT_REVOKED) + msg = "certificate has been revoked"; + else if (r & BADCERT_NOT_TRUSTED) + msg = "certificate is self-signed or not signed by a trusted CA"; + else + msg = "unknown error"; + + if (r) { + us->notify_verify_error(us, r, msg); + return; + } + + if (!cn_mismatch) + us->valid_cn = true; +} + __hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us) { void *ssl = us->ssl; int r; r = ssl_handshake(ssl); - if (r == 0) + if (r == 0) { + ustream_ssl_verify_cert(us); return U_SSL_OK; + } if (ssl_do_wait(r)) return U_SSL_PENDING; @@ -260,6 +302,7 @@ static const int default_ciphersuites[] = __hidden void *__ustream_ssl_session_new(struct ustream_ssl_ctx *ctx) { ssl_context *ssl; + int auth; int ep; ssl = calloc(1, sizeof(ssl_context)); @@ -271,20 +314,25 @@ __hidden void *__ustream_ssl_session_new(struct ustream_ssl_ctx *ctx) return NULL; } - if (ctx->server) + if (ctx->server) { ep = SSL_IS_SERVER; - else + auth = SSL_VERIFY_NONE; + } else { ep = SSL_IS_CLIENT; + auth = SSL_VERIFY_OPTIONAL; + } ssl_set_ciphersuites(ssl, default_ciphersuites); ssl_set_endpoint(ssl, ep); - ssl_set_authmode(ssl, ctx->auth); + ssl_set_authmode(ssl, auth); ssl_set_rng(ssl, _urandom, NULL); if (ctx->server) { if (ctx->cert.next) ssl_set_ca_chain(ssl, ctx->cert.next, NULL, NULL); ssl_set_own_cert(ssl, &ctx->cert, &ctx->key); + } else { + ssl_set_ca_chain(ssl, &ctx->cert, NULL, NULL); } ssl_session_reset(ssl); @@ -297,3 +345,10 @@ __hidden void __ustream_ssl_session_free(void *ssl) ssl_free(ssl); free(ssl); } + +__hidden void __ustream_ssl_update_peer_cn(struct ustream_ssl *us) +{ + struct ustream_ssl_ctx *ctx = us->ctx; + + ssl_set_ca_chain(us->ssl, &ctx->ca_cert, NULL, us->peer_cn); +} diff --git a/ustream-polarssl.h b/ustream-polarssl.h index 1da2ff6..527c14a 100644 --- a/ustream-polarssl.h +++ b/ustream-polarssl.h @@ -39,8 +39,8 @@ struct ustream_ssl_ctx { #else rsa_context key; #endif + x509_crt ca_cert; x509_crt cert; - int auth; bool server; }; @@ -50,6 +50,7 @@ static inline char *__ustream_ssl_strerror(int error, char *buffer, int len) return buffer; } +void __ustream_ssl_update_peer_cn(struct ustream_ssl *us); void __ustream_ssl_session_free(void *ssl); void *__ustream_ssl_session_new(struct ustream_ssl_ctx *ctx); diff --git a/ustream-ssl.c b/ustream-ssl.c index 346a53f..2728e00 100644 --- a/ustream-ssl.c +++ b/ustream-ssl.c @@ -17,6 +17,7 @@ */ #include +#include #include #include "ustream-ssl.h" @@ -133,11 +134,16 @@ static void ustream_ssl_free(struct ustream *s) uloop_timeout_cancel(&us->error_timer); __ustream_ssl_session_free(us->ssl); + free(us->peer_cn); + us->ctx = NULL; us->ssl = NULL; us->conn = NULL; + us->peer_cn = NULL; us->connected = false; us->error = false; + us->valid_cert = false; + us->valid_cn = false; } static bool ustream_ssl_poll(struct ustream *s) @@ -184,10 +190,20 @@ static int _ustream_ssl_init(struct ustream_ssl *us, struct ustream *conn, struc return 0; } +static int _ustream_ssl_set_peer_cn(struct ustream_ssl *us, const char *name) +{ + us->peer_cn = strdup(name); + __ustream_ssl_update_peer_cn(us); + + return 0; +} + const struct ustream_ssl_ops ustream_ssl_ops = { .context_new = __ustream_ssl_context_new, .context_set_crt_file = __ustream_ssl_set_crt_file, .context_set_key_file = __ustream_ssl_set_key_file, + .context_add_ca_crt_file = __ustream_ssl_add_ca_crt_file, .context_free = __ustream_ssl_context_free, .init = _ustream_ssl_init, + .set_peer_cn = _ustream_ssl_set_peer_cn, }; diff --git a/ustream-ssl.h b/ustream-ssl.h index d2cdb69..b4317af 100644 --- a/ustream-ssl.h +++ b/ustream-ssl.h @@ -28,13 +28,19 @@ struct ustream_ssl { void (*notify_connected)(struct ustream_ssl *us); void (*notify_error)(struct ustream_ssl *us, int error, const char *str); + void (*notify_verify_error)(struct ustream_ssl *us, int error, const char *str); struct ustream_ssl_ctx *ctx; void *ssl; + char *peer_cn; + int error; bool connected; bool server; + + bool valid_cert; + bool valid_cn; }; struct ustream_ssl_ctx; @@ -44,9 +50,11 @@ struct ustream_ssl_ops { struct ustream_ssl_ctx *(*context_new)(bool server); int (*context_set_crt_file)(struct ustream_ssl_ctx *ctx, const char *file); int (*context_set_key_file)(struct ustream_ssl_ctx *ctx, const char *file); + int (*context_add_ca_crt_file)(struct ustream_ssl_ctx *ctx, const char *file); void (*context_free)(struct ustream_ssl_ctx *ctx); int (*init)(struct ustream_ssl *us, struct ustream *conn, struct ustream_ssl_ctx *ctx, bool server); + int (*set_peer_cn)(struct ustream_ssl *conn, const char *name); }; extern const struct ustream_ssl_ops ustream_ssl_ops; @@ -54,7 +62,9 @@ extern const struct ustream_ssl_ops ustream_ssl_ops; #define ustream_ssl_context_new ustream_ssl_ops.context_new #define ustream_ssl_context_set_crt_file ustream_ssl_ops.context_set_crt_file #define ustream_ssl_context_set_key_file ustream_ssl_ops.context_set_key_file +#define ustream_ssl_context_add_ca_crt_file ustream_ssl_ops.context_add_ca_crt_file #define ustream_ssl_context_free ustream_ssl_ops.context_free #define ustream_ssl_init ustream_ssl_ops.init +#define ustream_ssl_set_peer_cn ustream_ssl_ops.set_peer_cn #endif -- 2.30.2