ref: 27c94cd34892ffd8443bfb596e69704624b30595
parent: fe1ec8fd63507f3972c7d89ceba694477d570be4
author: igor <igor@mux>
date: Sat Jan 20 20:13:38 EST 2024
First prototype supporting multiple SSL certificates selected via SNI.
--- a/README
+++ b/README
@@ -1,73 +1,119 @@
-This is WIP attempting to extend tlssrv to support the Server Name
-Identifier (SNI) extension. With this extension it is possible to use
-multiple SSL certificates with a single IP address.
+A variant of tlssrv(8) supporting the Server Name Identifier (SNI)
+extension. With this extension it is possible to use multiple SSL
+certificates with a single IP address.
-# Key Functions
+# Example Setup
-tlshand.c:/^tlsClientExtensions
-tlshand.c:/^checkClientExtensions
-tlshand.c:/^tlsConnectionFree
+The easiest way to fetch and renew a **TLS** certificate on
+[9front](http://9front.org) is via
+[acmed(8)](http://man.9front.org/8/acmed). We are going to
+create two SSL certificates, that is mux.9lab.org and
+mux.bytelabs.org, for the same IP address.
-# Data Types
+Initially an account key must be generated:
-tlshand.c:/^typedef struct TlsConnection
-/sys/include/libsec.h:/^typedef struct TLSconn
+```
+% ramfs -p ; cd /tmp
+% auth/rsagen -t \
+ 'service=acme role=sign hash=sha256 acct=igor@9lab.org' \
+ >account.key
+% auth/rsa2jwk account.key \
+ >/sys/lib/tls/acmed/igor@9lab.org.pub
+```
-# Approach
+The `account.key` must be loaded into
+[factotum(4)](http://man.9front.org/4/factotum); however, it is best
+to store it in [secstore(1)](http://man.9front.org/1/secstore) instead
+of storing it unencrypted on the file system:
-The function tlshand.c:/^checkClientExtensions must
-be extended to decode the server name indication. For this
-the 'tlshand.c:/^typedef struct TlsConnection' needs to
-be extended to hold the server name.
+```
+% auth/secstore -g factotum
+secstore password:
+% cat account.key >> factotum
+% auth/secstore -p factotum
+secstore password:
+% read -m factotum > /mnt/factotum/ctl
+```
-tlshand.c:/^tlsConnectionFree needs to free any potential
-`TlsConnection.serverName`.
+Please consult the [secstore(1)](http://man.9front.org/1/secstore) man
+page as well as the excellent [9front FQA secstore
+documentation](http://fqa.9front.org/fqa8.html#8.4.7) if the above
+doesn't make sense or doesn't work for you.
-We are going to allow multiple certificates to be specified
-to tlssrv. A certificate encodes its domain in its subject:
+Next, generate [rsa(8)](http://man.9front.org/8/rsa) key (i.e.
+`certificate.key`) and certificate signing request (i.e.
+`mux.9lab.org.csr`, `mux.bytelabs.org`) files:
- % auth/pemdecode 'CERTIFICATE' /sys/lib/tls/acmed/mux.9lab.org.crt | auth/x5092pub
-key proto=rsa size=2048 ek=10001 n=BEF7549C333817BBE366CD58B2A60D3D2E3C1924ADF9E9B6BDB7FB74B5FF0CF1FF5A4D7A6F7202B7155D854396E925ADEAAF27AF8A118E0360470D6FD01BBF710051B02A53ECBFA8A8BB0FADCD4C7E320296CC6E82DE71B3B9D0378B982DB0359E4C8FD2AF9CB9BC3B6CBC0931E6638F74E716AA7B7AAF9E0873DDA676073B9C4F499EE57D96BAD2C2FB156CC25C289EAAC962CE785C82D2CFD78CC2A493B0965224EAFE94AB0A14441EF0CE4BBC0BF1E4034CAD051131A08120221F56E7000CAAF2244D5B111F7764CF04BE1A4BC866721FC4F8C9BD35F4EB71BFF33982F7BEAB3246794610666896C01D572D674E9A27B2CBD2D2D9311DAD675BAF230B82BF subject=mux.9lab.org
+```
+% auth/rsagen -t 'service=tls role=client owner=*' \
+ >certificate.key
+% auth/rsa2csr 'CN=mux.9lab.org' certificate.key \
+ >/sys/lib/tls/acmed/mux.9lab.org.csr
+% auth/rsa2csr 'CN=mux.bytelabs.org' certificate.key \
+ >/sys/lib/tls/acmed/mux.bytelabs.org.csr
+```
-Sample code that shows how to extract the domain from the
-subject can be found in /sys/src/cmd/auth/acmed.c:/^getcert
+Again, you are urged to store the
+[rsa(8)](http://man.9front.org/8/rsa) key in
+[secstore(1)](http://man.9front.org/1/secstore):
-static void
-getcert(char *csrpath)
-{
- char *csr, *dom[64], subj[2048];
- uchar *der;
- int nder, ndom, fd;
- RSApub *rsa;
- Hdr loc = { "location" };
- JSON *o;
+```
+% auth/secstore -g factotum
+secstore password:
+% cat certificate.key >> factotum
+% auth/secstore -p factotum
+secstore password:
+% read -m factotum > /mnt/factotum/ctl
+```
- if((fd = open(csrpath, OREAD|OCEXEC)) == -1)
- sysfatal("open %s: %r", csrpath);
- if((der = slurp(fd, &nder)) == nil)
- sysfatal("read %s: %r", csrpath);
- close(fd);
+See [rsa(8)](http://man.9front.org/8/rsa) and
+[tlssrv(8)](http://man.9front.org/8/tlssrv) for more examples on how
+to use RSA keys.
- if((rsa = X509reqtoRSApub(der, nder, subj, sizeof(subj))) == nil)
- sysfatal("decode csr: %r");
- rsapubfree(rsa);
- if((csr = encurl64(der, nder)) == nil)
- sysfatal("encode %s: %r", csrpath);
- free(der);
+Finally, the certificate for your domain can now be fetched. This
+requires [webfs(4)](http://man.9front.org/4/webfs) to be mounted as
+the [ACME
+protocol](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment)
+uses HTTP to talk to the provider.
- dprint("subject: %s\n", subj);
- if((ndom = getfields(subj, dom, nelem(dom), 1, ", ")) == nelem(dom))
- sysfatal("too man domains");
+```
+% webfs
+% auth/acmed igor@9lab.org /sys/lib/tls/acmed/mux.9lab.org.csr \
+ >/sys/lib/tls/acmed/mux.9lab.org.crt
+% auth/acmed igor@9lab.org /sys/lib/tls/acmed/mux.bytelabs.org.csr \
+ >/sys/lib/tls/acmed/mux.bytelabs.org.crt
+```
- if((o = submitorder(dom, ndom, &loc)) == nil)
- sysfatal("order: %r");
- if(dochallenges(dom, ndom, o) == -1)
- sysfatal("challenge: %r");
+The above incantation is also used to *renew* the certificate.
- if(submitcsr(o, csr) == -1)
- sysfatal("signing cert: %r");
- if(fetchcert(loc.val) == -1)
- sysfatal("saving cert: %r");
+Let's display the certificate:
- free(csr);
-}
\ No newline at end of file
+```
+% auth/pemdecode 'CERTIFICATE' /sys/lib/tls/acmed/mux.9lab.org.crt | auth/x5092pub
+key proto=rsa size=2048 ek=… n=… subject=mux.9lab.org
+```
+
+At last, to [listen(8)](http://man.9front.org/8/listen) on https port
+443 for requests modify the file `/bin/service/tcp443` as follows:
+
+```
+% cat /bin/service/tcp443
+#!/bin/rc
+exec /bin/tlssrv -c/sys/lib/tls/acmed/mux.9lab.org.crt -ltcp80 -r`{cat $3/remote} /bin/tcp80 `{cat $3/remote}>>[2]/sys/log/tcp80
+```
+
+# Caveats
+
+A main or fallback certificate can be specified to tlssrv(8)
+via the `-c` option. If an SNI is provided we try to find it
+as follows:
+
+```
+ snprint(path, sizeof(path), "/sys/lib/tls/acmed/%s.crt", c->serverName);
+```
+
+That means the certificate has to be present in the `/sys/lib/tls/acmed/`
+folder with the name `SNI.crt` where SNI is the the server name indicator
+provided by the client.
+
+It is not yet possible to override the path to certificates.
--- a/tlshand.c
+++ b/tlshand.c
@@ -746,11 +746,19 @@
}
if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
goto Err;
- if(trace)
- trace("ClientHello server name indicator %s\n", c->serverName);
- // TODO: implement function that selects the right certificate
- // from an array of certificate names given the SNI
if(certlen > 0){
+ /* override default server certificate with SNI (@igor) */
+ if(c->serverName){
+ char path[512];
+ snprint(path, sizeof(path), "/sys/lib/tls/acmed/%s.crt", c->serverName);
+ if(trace)
+ trace("ClientHello server name indicator %s using %s\n", c->serverName, path);
+ PEMChain *chain = readcertchain(path);
+ if (chain){
+ cert = chain->pem;
+ certlen = chain->pemlen;
+ }
+ }
/* server certificate */
c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0);
if(c->sec->rsapub == nil){