9front - general discussion about 9front
 help / color / mirror / Atom feed
From: igor@9lab.org
To: 9front@9front.org
Cc: igor@9lab.org
Subject: [9front] [PATCH] libsec, pushtls, tlssrv: add support for Server Name Indication (SNI) extension
Date: Thu, 25 Jan 2024 12:32:45 +0100	[thread overview]
Message-ID: <229D405D66A4143A9080A3D8FD494A0F@9lab.org> (raw)

[-- Attachment #1: Type: text/plain, Size: 4923 bytes --]

This patch extends libsec to support the Server Name Indication (SNI)
extension.

Server Name Indication (SNI) is an extension to the Transport Layer
Security (TLS) computer networking protocol by which a client
indicates which hostname it is attempting to connect to at the start
of the handshaking process.  The extension allows a server to present
one of multiple possible certificates on the same IP address and TCP
port number and hence allows multiple secure (HTTPS) websites (or any
other service over TLS) to be served by the same IP address without
requiring all those sites to use the same certificate.

A real world example is outlined here:
https://9lab.org/plan9/tlssrv8-with-server-name-indication-sni-support/

Please let me know what you think and whether you would like additional
input on why this extension is useful after having glanced at the
example above.

One open item that is not addressed by the attached patch is where
and how to document this feature. The best place seems to be the
pushtls(2) man page with additional references from tlssrv(8) and
friends. I plan to address this once the core implementation of SNI
gets past the approval stage.

Cheers,
Igor

---
diff c2a290b8fe17f06370bc748552f2af1d58f50a71 eff99a21434474625fbdd60e68fb1b4cab389238
--- a/sys/src/libsec/port/tlshand.c
+++ b/sys/src/libsec/port/tlshand.c
@@ -98,6 +98,8 @@
 	char *digest;	// name of digest algorithm to use
 	char *enc;	// name of encryption algorithm to use
 
+	char *serverName;	// server name indication; extension
+
 	// for finished messages
 	HandshakeHash	handhash;
 	Finished	finished;
@@ -355,7 +357,7 @@
 };
 
 static TlsConnection *tlsServer2(int ctl, int hand,
-	uchar *cert, int certlen,
+	uchar **cert, int certlen,
 	char *pskid, uchar *psk, int psklen,
 	int (*trace)(char*fmt, ...), PEMChain *chain);
 static TlsConnection *tlsClient2(int ctl, int hand,
@@ -456,7 +458,7 @@
 	data = -1;
 	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
 	tls = tlsServer2(ctl, hand,
-		conn->cert, conn->certlen,
+		&(conn->cert), conn->certlen,
 		conn->pskID, conn->psk, conn->psklen,
 		conn->trace, conn->chain);
 	if(tls != nil){
@@ -659,9 +661,23 @@
 		if(e-p < 4)
 			goto Short;
 		p += 4;
-		if(e-p < (n = get16(p-2)))
+		if(e-p < (n = get16(p-2)))	/* Length */
 			goto Short;
-		switch(get16(p-4)){
+		switch(get16(p-4)){			/* Type */
+		case Extsni:
+			if(n < 4 || get16(p) != (n -= 2))
+				goto Short;
+			if(*(p+2) != 0)			/* Server Name Type: host_name */
+				break;
+			p += 2+1+2;
+			if(e-p < (n = get16(p-2)))
+				goto Short;
+			if(n > 255)				/* DNS name can not exceed 255 bytes RFC1035 */
+				break;
+			c->serverName = emalloc(n+1);
+			memmove(c->serverName, p, n);
+			c->serverName[n] = 0;
+			break;
 		case Extec:
 			if(n < 4 || n % 2 || get16(p) != (n -= 2))
 				goto Short;
@@ -717,7 +733,7 @@
 
 static TlsConnection *
 tlsServer2(int ctl, int hand,
-	uchar *cert, int certlen,
+	uchar **cert, int certlen,
 	char *pskid, uchar *psk, int psklen,
 	int (*trace)(char*fmt, ...), PEMChain *chp)
 {
@@ -765,9 +781,26 @@
 		c->sec->psk = psk;
 		c->sec->psklen = psklen;
 	}
+	if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
+		goto Err;
 	if(certlen > 0){
+		/* override default certificate using Server Name Indication (SNI) extension */
+		if(c->serverName){
+			char path[512];
+			PEMChain *chain;
+
+			snprint(path, sizeof(path), "/sys/lib/tls/acmed/%s.crt", c->serverName);
+			if(trace)
+				trace("ClientHello extension server name identifier selects %s\n", path);
+			chain = readcertchain(path);
+			if (chain){
+				free(*cert);
+				*cert = chain->pem;
+				certlen = chain->pemlen;
+			}
+		}
 		/* server certificate */
-		c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0);
+		c->sec->rsapub = X509toRSApub(*cert, certlen, nil, 0);
 		if(c->sec->rsapub == nil){
 			tlsError(c, EHandshakeFailure, "invalid X509/rsa certificate");
 			goto Err;
@@ -780,8 +813,6 @@
 	}
 	if(lookupid(m.u.clientHello.ciphers, TLS_EMPTY_RENEGOTIATION_INFO_SCSV) >= 0)
 		c->sec->reneg = 1;
-	if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
-		goto Err;
 	cipher = okCipher(m.u.clientHello.ciphers, psklen > 0, c->sec->nc != nil);
 	if(cipher < 0 || !setAlgs(c, cipher)) {
 		tlsError(c, EHandshakeFailure, "no matching cipher suite");
@@ -813,7 +844,7 @@
 		numcerts = countchain(chp);
 		m.u.certificate.ncert = 1 + numcerts;
 		m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes*));
-		m.u.certificate.certs[0] = makebytes(cert, certlen);
+		m.u.certificate.certs[0] = makebytes(*cert, certlen);
 		for (i = 0; i < numcerts && chp; i++, chp = chp->next)
 			m.u.certificate.certs[i+1] = makebytes(chp->pem, chp->pemlen);
 		if(!msgSend(c, &m, AQueue))
@@ -2113,6 +2144,8 @@
 	factotum_rsa_close(c->sec->rpc);
 	rsapubfree(c->sec->rsapub);
 	freebytes(c->cert);
+
+	free(c->serverName);
 
 	memset(c, 0, sizeof(*c));
 	free(c);

[-- Attachment #2: tlssrv.sni.patch --]
[-- Type: text/plain, Size: 4544 bytes --]

From: Igor Böhm <igor@9lab.org>
Date: Thu, 25 Jan 2024 11:20:51 +0000
Subject: [PATCH] libsec, pushtls, tlssrv: add support for Server Name Indication (SNI) extension


Server Name Indication (SNI) is an extension to the Transport Layer
Security (TLS) computer networking protocol by which a client
indicates which hostname it is attempting to connect to at the start
of the handshaking process.  The extension allows a server to present
one of multiple possible certificates on the same IP address and TCP
port number and hence allows multiple secure (HTTPS) websites (or any
other service over TLS) to be served by the same IP address without
requiring all those sites to use the same certificate.

A real world example is outlined here:
https://9lab.org/plan9/tlssrv8-with-server-name-indication-sni-support/
---
diff c2a290b8fe17f06370bc748552f2af1d58f50a71 eff99a21434474625fbdd60e68fb1b4cab389238
--- a/sys/src/libsec/port/tlshand.c
+++ b/sys/src/libsec/port/tlshand.c
@@ -98,6 +98,8 @@
 	char *digest;	// name of digest algorithm to use
 	char *enc;	// name of encryption algorithm to use
 
+	char *serverName;	// server name indication; extension
+
 	// for finished messages
 	HandshakeHash	handhash;
 	Finished	finished;
@@ -355,7 +357,7 @@
 };
 
 static TlsConnection *tlsServer2(int ctl, int hand,
-	uchar *cert, int certlen,
+	uchar **cert, int certlen,
 	char *pskid, uchar *psk, int psklen,
 	int (*trace)(char*fmt, ...), PEMChain *chain);
 static TlsConnection *tlsClient2(int ctl, int hand,
@@ -456,7 +458,7 @@
 	data = -1;
 	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
 	tls = tlsServer2(ctl, hand,
-		conn->cert, conn->certlen,
+		&(conn->cert), conn->certlen,
 		conn->pskID, conn->psk, conn->psklen,
 		conn->trace, conn->chain);
 	if(tls != nil){
@@ -659,9 +661,23 @@
 		if(e-p < 4)
 			goto Short;
 		p += 4;
-		if(e-p < (n = get16(p-2)))
+		if(e-p < (n = get16(p-2)))	/* Length */
 			goto Short;
-		switch(get16(p-4)){
+		switch(get16(p-4)){			/* Type */
+		case Extsni:
+			if(n < 4 || get16(p) != (n -= 2))
+				goto Short;
+			if(*(p+2) != 0)			/* Server Name Type: host_name */
+				break;
+			p += 2+1+2;
+			if(e-p < (n = get16(p-2)))
+				goto Short;
+			if(n > 255)				/* DNS name can not exceed 255 bytes RFC1035 */
+				break;
+			c->serverName = emalloc(n+1);
+			memmove(c->serverName, p, n);
+			c->serverName[n] = 0;
+			break;
 		case Extec:
 			if(n < 4 || n % 2 || get16(p) != (n -= 2))
 				goto Short;
@@ -717,7 +733,7 @@
 
 static TlsConnection *
 tlsServer2(int ctl, int hand,
-	uchar *cert, int certlen,
+	uchar **cert, int certlen,
 	char *pskid, uchar *psk, int psklen,
 	int (*trace)(char*fmt, ...), PEMChain *chp)
 {
@@ -765,9 +781,26 @@
 		c->sec->psk = psk;
 		c->sec->psklen = psklen;
 	}
+	if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
+		goto Err;
 	if(certlen > 0){
+		/* override default certificate using Server Name Indication (SNI) extension */
+		if(c->serverName){
+			char path[512];
+			PEMChain *chain;
+
+			snprint(path, sizeof(path), "/sys/lib/tls/acmed/%s.crt", c->serverName);
+			if(trace)
+				trace("ClientHello extension server name identifier selects %s\n", path);
+			chain = readcertchain(path);
+			if (chain){
+				free(*cert);
+				*cert = chain->pem;
+				certlen = chain->pemlen;
+			}
+		}
 		/* server certificate */
-		c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0);
+		c->sec->rsapub = X509toRSApub(*cert, certlen, nil, 0);
 		if(c->sec->rsapub == nil){
 			tlsError(c, EHandshakeFailure, "invalid X509/rsa certificate");
 			goto Err;
@@ -780,8 +813,6 @@
 	}
 	if(lookupid(m.u.clientHello.ciphers, TLS_EMPTY_RENEGOTIATION_INFO_SCSV) >= 0)
 		c->sec->reneg = 1;
-	if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
-		goto Err;
 	cipher = okCipher(m.u.clientHello.ciphers, psklen > 0, c->sec->nc != nil);
 	if(cipher < 0 || !setAlgs(c, cipher)) {
 		tlsError(c, EHandshakeFailure, "no matching cipher suite");
@@ -813,7 +844,7 @@
 		numcerts = countchain(chp);
 		m.u.certificate.ncert = 1 + numcerts;
 		m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes*));
-		m.u.certificate.certs[0] = makebytes(cert, certlen);
+		m.u.certificate.certs[0] = makebytes(*cert, certlen);
 		for (i = 0; i < numcerts && chp; i++, chp = chp->next)
 			m.u.certificate.certs[i+1] = makebytes(chp->pem, chp->pemlen);
 		if(!msgSend(c, &m, AQueue))
@@ -2113,6 +2144,8 @@
 	factotum_rsa_close(c->sec->rpc);
 	rsapubfree(c->sec->rsapub);
 	freebytes(c->cert);
+
+	free(c->serverName);
 
 	memset(c, 0, sizeof(*c));
 	free(c);

             reply	other threads:[~2024-01-25 11:34 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-25 11:32 igor [this message]
2024-01-25 12:11 ` cinap_lenrek
2024-01-25 12:51   ` igor
2024-01-25 15:14     ` cinap_lenrek
2024-01-26  7:21       ` igor

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=229D405D66A4143A9080A3D8FD494A0F@9lab.org \
    --to=igor@9lab.org \
    --cc=9front@9front.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).