implement optional SSL certificate validation (including CN host check)
authorFelix Fietkau <nbd@openwrt.org>
Tue, 25 Mar 2014 14:39:58 +0000 (15:39 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Tue, 25 Mar 2014 14:39:58 +0000 (15:39 +0100)
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
uclient-backend.h
uclient-example.c
uclient-http.c
uclient.c
uclient.h

index fdebf2618d0f2ad9361ac8dc85f59b8080a3776d..308cd575aabc9c8675fe7531f454836ce5a40d4a 100644 (file)
@@ -28,7 +28,7 @@ struct uclient_url {
        const char *auth;
 };
 
-void uclient_backend_set_error(struct uclient *cl);
+void uclient_backend_set_error(struct uclient *cl, int code);
 void uclient_backend_set_eof(struct uclient *cl);
 void uclient_backend_reset_state(struct uclient *cl);
 struct uclient_url *uclient_get_url(const char *url_str, const char *auth_str);
index c8f0b1b7847dcab44bba4df7e9e7af6f139b6e2f..9bfd241ad4a9ebb37e2dedf83fd330226878bddf 100644 (file)
@@ -47,7 +47,6 @@ static void example_request_sm(struct uclient *cl)
                uclient_request(cl);
                break;
        default:
-               uclient_free(cl);
                uloop_end();
                break;
        };
@@ -66,30 +65,76 @@ static void example_eof(struct uclient *cl)
        example_request_sm(cl);
 }
 
+static void example_error(struct uclient *cl, int code)
+{
+       fprintf(stderr, "Error %d!\n", code);
+       example_request_sm(cl);
+}
+
 static const struct uclient_cb cb = {
        .header_done = example_header_done,
        .data_read = example_read_data,
        .data_eof = example_eof,
+       .error = example_error,
 };
 
+static int usage(const char *progname)
+{
+       fprintf(stderr,
+               "Usage: %s [options] <hostname> <port>\n"
+               "Options:\n"
+               "       -c <cert>:         Load CA certificates from file <cert>\n"
+               "       -C:                Skip certificate CN verification against hostname\n"
+               "\n", progname);
+       return 1;
+}
+
+
 int main(int argc, char **argv)
 {
+       struct ustream_ssl_ctx *ctx;
+       const char *progname = argv[0];
        struct uclient *cl;
-
-       if (argc != 2) {
-               fprintf(stderr, "Usage: %s <URL>\n", argv[0]);
-               return 1;
+       bool verify = true;
+       int ch;
+
+       ctx = ustream_ssl_context_new(false);
+
+       while ((ch = getopt(argc, argv, "Cc:")) != -1) {
+               switch(ch) {
+               case 'c':
+                       ustream_ssl_context_add_ca_crt_file(ctx, optarg);
+                       break;
+               case 'C':
+                       verify = false;
+                       break;
+               default:
+                       return usage(progname);
+               }
        }
 
+       argv += optind;
+       argc -= optind;
+
+       if (argc != 1)
+               return usage(progname);
+
        uloop_init();
-       cl = uclient_new(argv[1], &cb);
+
+       cl = uclient_new(argv[0], &cb);
        if (!cl) {
                fprintf(stderr, "Failed to allocate uclient context\n");
                return 1;
        }
+
+       uclient_http_set_ssl_ctx(cl, ctx, verify);
        example_request_sm(cl);
        uloop_run();
        uloop_done();
 
+       uclient_free(cl);
+       ustream_ssl_context_free(ctx);
+
+
        return 0;
 }
index bff6d38ca861870109aa5b1edd65723cae53d0d5..8053be000f92a7588f6096463a99b0ebb194cc4b 100644 (file)
@@ -50,6 +50,7 @@ struct uclient_http {
        struct ustream_fd ufd;
        struct ustream_ssl ussl;
 
+       bool ssl_require_validation;
        bool ssl_ctx_ext;
        bool ssl;
        bool eof;
@@ -118,6 +119,14 @@ static void uclient_http_free_url_state(struct uclient *cl)
        uclient_http_disconnect(uh);
 }
 
+static void uclient_http_error(struct uclient_http *uh, int code)
+{
+       uh->state = HTTP_STATE_ERROR;
+       uh->us->eof = true;
+       ustream_state_change(uh->us);
+       uclient_backend_set_error(&uh->uc, code);
+}
+
 static void uclient_notify_eof(struct uclient_http *uh)
 {
        struct ustream *us = uh->us;
@@ -552,7 +561,7 @@ static void __uclient_notify_read(struct uclient_http *uh)
        char *data;
        int len;
 
-       if (uh->state < HTTP_STATE_REQUEST_DONE)
+       if (uh->state < HTTP_STATE_REQUEST_DONE || uh->state == HTTP_STATE_ERROR)
                return;
 
        data = ustream_get_read_buf(uh->us, &len);
@@ -642,6 +651,34 @@ static void uclient_ssl_notify_state(struct ustream *us)
        uclient_notify_eof(uh);
 }
 
+static void uclient_ssl_notify_error(struct ustream_ssl *ssl, int error, const char *str)
+{
+       struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl);
+
+       uclient_http_error(uh, UCLIENT_ERROR_CONNECT);
+}
+
+static void uclient_ssl_notify_verify_error(struct ustream_ssl *ssl, int error, const char *str)
+{
+       struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl);
+
+       if (!uh->ssl_require_validation)
+               return;
+
+       uclient_http_error(uh, UCLIENT_ERROR_SSL_INVALID_CERT);
+}
+
+static void uclient_ssl_notify_connected(struct ustream_ssl *ssl)
+{
+       struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl);
+
+       if (!uh->ssl_require_validation)
+               return;
+
+       if (!uh->ussl.valid_cn)
+               uclient_http_error(uh, UCLIENT_ERROR_SSL_CN_MISMATCH);
+}
+
 static int uclient_setup_https(struct uclient_http *uh)
 {
        struct ustream *us = &uh->ussl.stream;
@@ -660,7 +697,11 @@ static int uclient_setup_https(struct uclient_http *uh)
        us->string_data = true;
        us->notify_state = uclient_ssl_notify_state;
        us->notify_read = uclient_ssl_notify_read;
+       uh->ussl.notify_error = uclient_ssl_notify_error;
+       uh->ussl.notify_verify_error = uclient_ssl_notify_verify_error;
+       uh->ussl.notify_connected = uclient_ssl_notify_connected;
        ustream_ssl_init(&uh->ussl, &uh->ufd.stream, uh->ssl_ctx, false);
+       ustream_ssl_set_peer_cn(&uh->ussl, uh->uc.url->host);
 
        return 0;
 }
@@ -683,7 +724,7 @@ static int uclient_http_connect(struct uclient *cl)
                ret = uclient_setup_http(uh);
 
        if (ret)
-               uh->state = HTTP_STATE_ERROR;
+               uclient_http_error(uh, UCLIENT_ERROR_CONNECT);
 
        return ret;
 }
@@ -904,15 +945,19 @@ bool uclient_http_redirect(struct uclient *cl)
        return true;
 }
 
-int uclient_http_set_ssl_ctx(struct uclient *cl, struct ustream_ssl_ctx *ctx)
+int uclient_http_set_ssl_ctx(struct uclient *cl, struct ustream_ssl_ctx *ctx, bool require_validation)
 {
        struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
 
+       if (cl->backend != &uclient_backend_http)
+               return -1;
+
        uclient_http_free_url_state(cl);
 
        uclient_http_free_ssl_ctx(uh);
        uh->ssl_ctx = ctx;
        uh->ssl_ctx_ext = !!ctx;
+       uh->ssl_require_validation = !!ctx && require_validation;
 
        return 0;
 }
index 4c85e800cd30de398c15ed2104fc20b24b4e4495..71da9359b491a4053aea9ecfaa03a82df76bcc29 100644 (file)
--- a/uclient.c
+++ b/uclient.c
@@ -178,8 +178,8 @@ static void __uclient_backend_change_state(struct uloop_timeout *timeout)
 {
        struct uclient *cl = container_of(timeout, struct uclient, timeout);
 
-       if (cl->error && cl->cb->error)
-               cl->cb->error(cl);
+       if (cl->error_code && cl->cb->error)
+               cl->cb->error(cl, cl->error_code);
        else if (cl->eof && cl->cb->data_eof)
                cl->cb->data_eof(cl);
 }
@@ -190,18 +190,18 @@ static void uclient_backend_change_state(struct uclient *cl)
        uloop_timeout_set(&cl->timeout, 1);
 }
 
-void __hidden uclient_backend_set_error(struct uclient *cl)
+void __hidden uclient_backend_set_error(struct uclient *cl, int code)
 {
-       if (cl->error)
+       if (cl->error_code)
                return;
 
-       cl->error = true;
+       cl->error_code = code;
        uclient_backend_change_state(cl);
 }
 
 void __hidden uclient_backend_set_eof(struct uclient *cl)
 {
-       if (cl->eof || cl->error)
+       if (cl->eof || cl->error_code)
                return;
 
        cl->eof = true;
@@ -210,7 +210,7 @@ void __hidden uclient_backend_set_eof(struct uclient *cl)
 
 void __hidden uclient_backend_reset_state(struct uclient *cl)
 {
-       cl->error = false;
        cl->eof = false;
+       cl->error_code = 0;
        uloop_timeout_cancel(&cl->timeout);
 }
index 11a19555e2df3a64318849522ade47f9bd104b81..79ec0b7e0185390f28a4de2e4afb9b82a5ccebcc 100644 (file)
--- a/uclient.h
+++ b/uclient.h
@@ -8,6 +8,13 @@
 struct uclient_cb;
 struct uclient_backend;
 
+enum uclient_error_code {
+       UCLIENT_ERROR_UNKNOWN,
+       UCLIENT_ERROR_CONNECT,
+       UCLIENT_ERROR_SSL_INVALID_CERT,
+       UCLIENT_ERROR_SSL_CN_MISMATCH,
+};
+
 struct uclient {
        const struct uclient_backend *backend;
        const struct uclient_cb *cb;
@@ -16,7 +23,7 @@ struct uclient {
        void *priv;
 
        bool eof;
-       bool error;
+       int error_code;
        int status_code;
        struct blob_attr *meta;
 
@@ -28,7 +35,7 @@ struct uclient_cb {
        void (*data_sent)(struct uclient *cl);
        void (*data_eof)(struct uclient *cl);
        void (*header_done)(struct uclient *cl);
-       void (*error)(struct uclient *cl);
+       void (*error)(struct uclient *cl, int code);
 };
 
 struct uclient *uclient_new(const char *url, const struct uclient_cb *cb);
@@ -54,6 +61,6 @@ int uclient_http_reset_headers(struct uclient *cl, const char *name, const char
 int uclient_http_set_request_type(struct uclient *cl, const char *type);
 bool uclient_http_redirect(struct uclient *cl);
 
-int uclient_http_set_ssl_ctx(struct uclient *cl, struct ustream_ssl_ctx *ctx);
+int uclient_http_set_ssl_ctx(struct uclient *cl, struct ustream_ssl_ctx *ctx, bool require_validation);
 
 #endif