implement certificate validation (including CN verification)
authorFelix Fietkau <nbd@openwrt.org>
Tue, 25 Mar 2014 08:02:39 +0000 (09:02 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Tue, 25 Mar 2014 08:02:39 +0000 (09:02 +0100)
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
ustream-example-client.c
ustream-internal.h
ustream-openssl.c
ustream-openssl.h
ustream-polarssl.c
ustream-polarssl.h
ustream-ssl.c
ustream-ssl.h

index a02815b..c3d0018 100644 (file)
@@ -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] <hostname> <port>\n", progname);
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
-       if (argc != 3) {
-               fprintf(stderr, "Usage: %s <hostname> <port>\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);
index 85d8b47..e0e1f50 100644 (file)
@@ -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);
index c826e4e..a45e2f4 100644 (file)
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <ctype.h>
+#include <openssl/x509v3.h>
 #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)
index 76d2bad..20c502d 100644 (file)
@@ -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
index 8516d7f..ef8360a 100644 (file)
@@ -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);
+}
index 1da2ff6..527c14a 100644 (file)
@@ -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);
 
index 346a53f..2728e00 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include <errno.h>
+#include <stdlib.h>
 #include <libubox/ustream.h>
 
 #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,
 };
index d2cdb69..b4317af 100644 (file)
@@ -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