From 5802b7206a96d5d55ba7408d05151a1887b21d28 Mon Sep 17 00:00:00 2001
From: jsing <>
Date: Fri, 12 Aug 2016 15:10:59 +0000
Subject: Add ALPN support to libtls.

ok beck@ doug@
---
 src/lib/libtls/tls.c          | 10 ++++++-
 src/lib/libtls/tls.h          |  8 +++--
 src/lib/libtls/tls_config.c   | 69 ++++++++++++++++++++++++++++++++++++++++++-
 src/lib/libtls/tls_conninfo.c | 35 +++++++++++++++++++++-
 src/lib/libtls/tls_init.3     | 36 +++++++++++++++++-----
 src/lib/libtls/tls_internal.h |  6 +++-
 src/lib/libtls/tls_server.c   | 20 ++++++++++++-
 7 files changed, 168 insertions(+), 16 deletions(-)

(limited to 'src/lib')

diff --git a/src/lib/libtls/tls.c b/src/lib/libtls/tls.c
index ddf847d390..4d4910d128 100644
--- a/src/lib/libtls/tls.c
+++ b/src/lib/libtls/tls.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls.c,v 1.43 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls.c,v 1.44 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -310,6 +310,14 @@ tls_configure_ssl(struct tls *ctx)
 	if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_2) == 0)
 		SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1_2);
 
+	if (ctx->config->alpn != NULL) {
+		if (SSL_CTX_set_alpn_protos(ctx->ssl_ctx, ctx->config->alpn,
+		    ctx->config->alpn_len) != 0) {
+			tls_set_errorx(ctx, "failed to set alpn");
+			goto err;
+		}
+	}
+
 	if (ctx->config->ciphers != NULL) {
 		if (SSL_CTX_set_cipher_list(ctx->ssl_ctx,
 		    ctx->config->ciphers) != 1) {
diff --git a/src/lib/libtls/tls.h b/src/lib/libtls/tls.h
index 1497319611..13df43f046 100644
--- a/src/lib/libtls/tls.h
+++ b/src/lib/libtls/tls.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls.h,v 1.32 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls.h,v 1.33 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -27,7 +27,7 @@ extern "C" {
 #include <stddef.h>
 #include <stdint.h>
 
-#define TLS_API	20160801
+#define TLS_API	20160812
 
 #define TLS_PROTOCOL_TLSv1_0	(1 << 1)
 #define TLS_PROTOCOL_TLSv1_1	(1 << 2)
@@ -52,6 +52,7 @@ const char *tls_error(struct tls *_ctx);
 struct tls_config *tls_config_new(void);
 void tls_config_free(struct tls_config *_config);
 
+int tls_config_set_alpn(struct tls_config *_config, const char *_alpn);
 int tls_config_set_ca_file(struct tls_config *_config, const char *_ca_file);
 int tls_config_set_ca_path(struct tls_config *_config, const char *_ca_path);
 int tls_config_set_ca_mem(struct tls_config *_config, const uint8_t *_ca,
@@ -116,8 +117,9 @@ const char *tls_peer_cert_subject(struct tls *_ctx);
 time_t	tls_peer_cert_notbefore(struct tls *_ctx);
 time_t	tls_peer_cert_notafter(struct tls *_ctx);
 
-const char *tls_conn_version(struct tls *_ctx);
+const char *tls_conn_alpn_selected(struct tls *_ctx);
 const char *tls_conn_cipher(struct tls *_ctx);
+const char *tls_conn_version(struct tls *_ctx);
 
 uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password);
 
diff --git a/src/lib/libtls/tls_config.c b/src/lib/libtls/tls_config.c
index 63054ab1e9..e690b9ee76 100644
--- a/src/lib/libtls/tls_config.c
+++ b/src/lib/libtls/tls_config.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_config.c,v 1.24 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls_config.c,v 1.25 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -166,6 +166,7 @@ tls_config_free(struct tls_config *config)
 
 	free(config->error.msg);
 
+	free(config->alpn);
 	free((char *)config->ca_file);
 	free((char *)config->ca_mem);
 	free((char *)config->ca_path);
@@ -249,6 +250,72 @@ tls_config_parse_protocols(uint32_t *protocols, const char *protostr)
 	return (0);
 }
 
+static int
+tls_config_parse_alpn(struct tls_config *config, const char *alpn,
+    char **alpn_data, size_t *alpn_len)
+{
+	size_t buf_len, i, len;
+	char *buf = NULL;
+	char *s = NULL;
+	char *p, *q;
+
+	if ((buf_len = strlen(alpn) + 1) > 65535) {
+		tls_config_set_errorx(config, "alpn too large");
+		goto err;
+	}
+
+	if ((buf = malloc(buf_len)) == NULL) {
+		tls_config_set_errorx(config, "out of memory");
+		goto err;
+	}
+
+	if ((s = strdup(alpn)) == NULL) {
+		tls_config_set_errorx(config, "out of memory");
+		goto err;
+	}
+
+	i = 0;
+	q = s;
+	while ((p = strsep(&q, ",")) != NULL) {
+		if ((len = strlen(p)) == 0) {
+			tls_config_set_errorx(config,
+			    "alpn protocol with zero length");
+			goto err;
+		}
+		if (len > 255) {
+			tls_config_set_errorx(config,
+			    "alpn protocol too long");
+			goto err;
+		}
+		buf[i++] = len & 0xff;
+		memcpy(&buf[i], p, len);
+		i += len;
+	}
+
+	free(s);
+
+	*alpn_data = buf;
+	*alpn_len = buf_len;
+
+	return (0);
+
+ err:
+	free(buf);
+	free(s);
+
+	*alpn_data = NULL;
+	*alpn_len = 0;
+
+	return (-1);
+}
+
+int
+tls_config_set_alpn(struct tls_config *config, const char *alpn)
+{
+	return tls_config_parse_alpn(config, alpn, &config->alpn,
+	    &config->alpn_len);
+}
+
 int
 tls_config_set_ca_file(struct tls_config *config, const char *ca_file)
 {
diff --git a/src/lib/libtls/tls_conninfo.c b/src/lib/libtls/tls_conninfo.c
index 6caf655536..7888c919b0 100644
--- a/src/lib/libtls/tls_conninfo.c
+++ b/src/lib/libtls/tls_conninfo.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_conninfo.c,v 1.7 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls_conninfo.c,v 1.8 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
  * Copyright (c) 2015 Bob Beck <beck@openbsd.org>
@@ -150,6 +150,26 @@ tls_get_peer_cert_times(struct tls *ctx, time_t *notbefore, time_t *notafter)
 	return (rv);
 }
 
+static int
+tls_conninfo_alpn_proto(struct tls *ctx)
+{
+	const unsigned char *p;
+	unsigned int len;
+
+	free(ctx->conninfo->alpn);
+	ctx->conninfo->alpn = NULL;
+
+	SSL_get0_alpn_selected(ctx->ssl_conn, &p, &len);
+	if (len > 0) {
+		if ((ctx->conninfo->alpn = malloc(len + 1)) == NULL)
+			return (-1);
+		memcpy(ctx->conninfo->alpn, p, len);
+		ctx->conninfo->alpn[len] = '\0';
+	}
+
+	return (0);
+}
+
 int
 tls_get_conninfo(struct tls *ctx) {
 	const char * tmp;
@@ -175,6 +195,9 @@ tls_get_conninfo(struct tls *ctx) {
 	ctx->conninfo->cipher = strdup(tmp);
 	if (ctx->conninfo->cipher == NULL)
 		goto err;
+	if (tls_conninfo_alpn_proto(ctx) == -1)
+		goto err;
+
 	return (0);
 err:
 	tls_free_conninfo(ctx->conninfo);
@@ -184,6 +207,8 @@ err:
 void
 tls_free_conninfo(struct tls_conninfo *conninfo) {
 	if (conninfo != NULL) {
+		free(conninfo->alpn);
+		conninfo->alpn = NULL;
 		free(conninfo->hash);
 		conninfo->hash = NULL;
 		free(conninfo->subject);
@@ -197,6 +222,14 @@ tls_free_conninfo(struct tls_conninfo *conninfo) {
 	}
 }
 
+const char *
+tls_conn_alpn_selected(struct tls *ctx)
+{
+	if (ctx->conninfo == NULL)
+		return (NULL);
+	return (ctx->conninfo->alpn);
+}
+
 const char *
 tls_conn_cipher(struct tls *ctx)
 {
diff --git a/src/lib/libtls/tls_init.3 b/src/lib/libtls/tls_init.3
index 6ba2cb28be..e7f10ef556 100644
--- a/src/lib/libtls/tls_init.3
+++ b/src/lib/libtls/tls_init.3
@@ -1,4 +1,4 @@
-.\" $OpenBSD: tls_init.3,v 1.64 2016/08/02 07:47:11 jsing Exp $
+.\" $OpenBSD: tls_init.3,v 1.65 2016/08/12 15:10:59 jsing Exp $
 .\"
 .\" Copyright (c) 2014 Ted Unangst <tedu@openbsd.org>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 2 2016 $
+.Dd $Mdocdate: August 12 2016 $
 .Dt TLS_INIT 3
 .Os
 .Sh NAME
@@ -24,6 +24,7 @@
 .Nm tls_config_new ,
 .Nm tls_config_free ,
 .Nm tls_config_parse_protocols ,
+.Nm tls_config_set_alpn ,
 .Nm tls_config_set_ca_file ,
 .Nm tls_config_set_ca_path ,
 .Nm tls_config_set_ca_mem ,
@@ -54,8 +55,9 @@
 .Nm tls_peer_cert_hash ,
 .Nm tls_peer_cert_notbefore ,
 .Nm tls_peer_cert_notafter ,
-.Nm tls_conn_version ,
+.Nm tls_conn_alpn_selected ,
 .Nm tls_conn_cipher ,
+.Nm tls_conn_version ,
 .Nm tls_load_file ,
 .Nm tls_client ,
 .Nm tls_server ,
@@ -88,6 +90,8 @@
 .Ft "int"
 .Fn tls_config_parse_protocols "uint32_t *protocols" "const char *protostr"
 .Ft "int"
+.Fn tls_config_set_alpn "struct tls_config *config" "const char *alpn"
+.Ft "int"
 .Fn tls_config_set_ca_file "struct tls_config *config" "const char *ca_file"
 .Ft "int"
 .Fn tls_config_set_ca_path "struct tls_config *config" "const char *ca_path"
@@ -148,9 +152,11 @@
 .Ft "time_t"
 .Fn tls_peer_cert_notafter "struct tls *ctx"
 .Ft "const char *"
-.Fn tls_conn_version "struct tls *ctx"
+.Fn tls_conn_alpn_selected "struct tls *ctx"
 .Ft "const char *"
 .Fn tls_conn_cipher "struct tls *ctx"
+.Ft "const char *"
+.Fn tls_conn_version "struct tls *ctx"
 .Ft "uint8_t *"
 .Fn tls_load_file "const char *file" "size_t *len" "char *password"
 .Ft "struct tls *"
@@ -295,6 +301,11 @@ The following functions modify a configuration by setting parameters.
 Configuration options may apply to only clients or only servers or both.
 .Bl -bullet -offset four
 .It
+.Fn tls_config_set_alpn
+sets the ALPN protocols that are supported.
+The alpn string is a comma separated list of protocols, in order of preference.
+.Em (Client and Server)
+.It
 .Fn tls_config_set_ca_file
 sets the filename used to load a file
 containing the root certificates.
@@ -480,13 +491,14 @@ the peer certificate from
 will only succeed after the handshake is complete.
 .Em (Server and client)
 .It
-.Fn tls_conn_version
-returns a string
-corresponding to a TLS version negotiated with the peer
+.Fn tls_conn_alpn_selected
+returns a string that specifies the ALPN protocol selected for use with the peer
 connected to
 .Ar ctx .
-.Fn tls_conn_version
+If no protocol was selected then NULL is returned.
+.Fn tls_conn_alpn_selected
 will only succeed after the handshake is complete.
+.Em (Server and Client)
 .It
 .Fn tls_conn_cipher
 returns a string
@@ -497,6 +509,14 @@ connected to
 will only succeed after the handshake is complete.
 .Em (Server and client)
 .It
+.Fn tls_conn_version
+returns a string
+corresponding to a TLS version negotiated with the peer
+connected to
+.Ar ctx .
+.Fn tls_conn_version
+will only succeed after the handshake is complete.
+.It
 .Fn tls_load_file
 loads a certificate or key from disk into memory to be loaded with
 .Fn tls_config_set_ca_mem ,
diff --git a/src/lib/libtls/tls_internal.h b/src/lib/libtls/tls_internal.h
index be5d659e68..1ef95adb08 100644
--- a/src/lib/libtls/tls_internal.h
+++ b/src/lib/libtls/tls_internal.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_internal.h,v 1.34 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls_internal.h,v 1.35 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
@@ -55,6 +55,8 @@ struct tls_keypair {
 struct tls_config {
 	struct tls_error error;
 
+	char *alpn;
+	size_t alpn_len;
 	const char *ca_file;
 	const char *ca_path;
 	char *ca_mem;
@@ -73,6 +75,7 @@ struct tls_config {
 };
 
 struct tls_conninfo {
+	char *alpn;
 	char *issuer;
 	char *subject;
 	char *hash;
@@ -104,6 +107,7 @@ struct tls {
 	SSL *ssl_conn;
 	SSL_CTX *ssl_ctx;
 	X509 *ssl_peer_cert;
+
 	struct tls_conninfo *conninfo;
 };
 
diff --git a/src/lib/libtls/tls_server.c b/src/lib/libtls/tls_server.c
index bba15aae7e..690af32eaf 100644
--- a/src/lib/libtls/tls_server.c
+++ b/src/lib/libtls/tls_server.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tls_server.c,v 1.21 2016/08/02 07:47:11 jsing Exp $ */
+/* $OpenBSD: tls_server.c,v 1.22 2016/08/12 15:10:59 jsing Exp $ */
 /*
  * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
  *
@@ -48,6 +48,20 @@ tls_server_conn(struct tls *ctx)
 	return (conn_ctx);
 }
 
+static int
+tls_server_alpn_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+    const unsigned char *in, unsigned int inlen, void *arg)
+{
+	struct tls *ctx = arg;
+
+	if (SSL_select_next_proto((unsigned char**)out, outlen,
+	    ctx->config->alpn, ctx->config->alpn_len, in, inlen) ==
+	    OPENSSL_NPN_NEGOTIATED)
+		return (SSL_TLSEXT_ERR_OK);
+
+	return (SSL_TLSEXT_ERR_NOACK);
+}
+
 int
 tls_configure_server(struct tls *ctx)
 {
@@ -71,6 +85,10 @@ tls_configure_server(struct tls *ctx)
 			goto err;
 	}
 
+	if (ctx->config->alpn != NULL)
+		SSL_CTX_set_alpn_select_cb(ctx->ssl_ctx, tls_server_alpn_cb,
+		    ctx);
+
 	if (ctx->config->dheparams == -1)
 		SSL_CTX_set_dh_auto(ctx->ssl_ctx, 1);
 	else if (ctx->config->dheparams == 1024)
-- 
cgit v1.2.3-55-g6feb