Browse Source

implement optional SSL certificate validation (including CN host check)

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Felix Fietkau 10 years ago
parent
commit
25e44fc1e6
5 changed files with 117 additions and 20 deletions
  1. 1 1
      uclient-backend.h
  2. 51 6
      uclient-example.c
  3. 48 3
      uclient-http.c
  4. 7 7
      uclient.c
  5. 10 3
      uclient.h

+ 1 - 1
uclient-backend.h

@@ -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);

+ 51 - 6
uclient-example.c

@@ -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;
 }

+ 48 - 3
uclient-http.c

@@ -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;
 }

+ 7 - 7
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);
 }

+ 10 - 3
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