certifix/README.md

126 lines
5.0 KiB
Markdown
Raw Normal View History

2024-09-25 20:34:27 +00:00
# Certifix
Not an Asterix character.
2024-09-25 20:30:32 +00:00
A small HTTPS API modelled on the description of the
[Puppet CA "Policy-based autosigning" functionality](
https://www.puppet.com/docs/puppet/7/ssl_attributes_extensions#csr_custom_attributes-recommended-oids-custom-attributes),
that accepts X509 CSRs and automatically signs them without human
interaction if they have a custom `challengePassword` attribute
containing a pre-agreed value.
2024-09-25 20:30:32 +00:00
What's it for? I have a bunch of small devices on my LAN that may or
may not be able to retain persistent state across reboots. I would
like them to be able to talk securely to services on the network using
standard TLS with client authentication, and (because "[zero trust]
(https://en.wikipedia.org/wiki/Zero_trust_security_model)")
without having to rely on network firewall rules to prevent the rest
of the world also being able to talk to the service.
2024-09-25 20:30:32 +00:00
## To try it out
_This is alpha-quality code which was written by someone with only the
most passing familiarity with TLS or cryptography in general, and
has not been audited. Try it at your own risk._
2024-09-25 20:30:32 +00:00
It's written in [Fennel](https://www.fennel-lang.org). To build it
either use Nix or read [package.nix](package.nix) and figure out how
to replicate the steps manually. Note that it requires a patch to the
luaossl module.
2024-09-25 20:30:32 +00:00
### CA key and cert
Create the CA key and the certificate used for signing. You will be
asked a bunch of questions that will be incorporated into the
certificate: when prompted for "Common Name", say "Certificate
Authority" or something like that
```
2024-10-04 17:20:03 +00:00
openssl genrsa -out private/ca.key 4096
openssl req -addext basicConstraints=critical,CA:TRUE,pathlen:0 --x509 -new -nodes -key private/ca.key -sha256 -days 3650 -out certs/ca.crt
```
### Server key and cert
The certifix service is exposed over HTTPS, so it needs its own
certificate signed by the CA. Use your hostname when prompted for
2024-10-04 17:20:03 +00:00
Common Name. If your server host is reachable using different
names from different clients then you need to provide _all_ of them
as `subjectAltName` - as in the example below.
```
2024-10-04 17:20:03 +00:00
# it's important to list all the hostnames of your server machine in the
# subjectAltName field
openssl req -newkey rsa:2048 -addext "subjectAltName = DNS:loaclhost.lan,DNS:localhost,DNS:loaclhost.telent.net" -nodes -keyout private/server.key --out certs/server.csr
openssl x509 -req -in certs/server.csr -days 365 -CA certs/ca.crt -CAkey private/ca.key -CAcreateserial -copy_extensions copyall -out certs/server.crt
```
### Build and start the server
The server needs to be told of all of the preceding files, _plus_ a
file containing the expected value of the pre-shared key that you want
it to check client certificate requests against.
2024-09-25 20:30:32 +00:00
```
echo 'loves labours lost' > psk
2024-09-25 20:30:32 +00:00
chmod 0700 psk
nix-build
2024-10-04 17:20:03 +00:00
result/bin/certifix --challenge-password psk --ca-certificate certs/ca.crt --ca-private-key private/ca.key --server-certificate certs/server.crt --server-private-key private/server.key localhost:19613
```
2024-09-25 20:30:32 +00:00
2024-10-04 17:20:03 +00:00
* To listen on all interfaces, use a wildcard address instead of
`localhost` - e.g. `::0:19613' will (at least on my machine) accept
IPv4 and IPv6 connections on any interface
### Try it and see if it works
2024-09-25 20:30:32 +00:00
To set the `challengePassword` attribute in a CSR using OpenSSL, you
need to create a configuration file. Copy `openssl.cnf.example` to
`openssl.cnf` and edit it for your setup.
* the values in `req_distinguished_name` should match your organisation
* the `challengePassword` attribute must match whatever you told the
service to expect (`psk` file in the preceding step)
2024-09-27 18:47:11 +00:00
```
# make CSR
2024-10-04 17:20:03 +00:00
CN=mydevice openssl req -config openssl.cnf -addext "subjectAltName = DNS:mydevice.lan" -newkey rsa:2048 -nodes -keyout client.key -out client.csr
2024-09-25 20:30:32 +00:00
# send it to certifix, should get a certificate in response
2024-10-04 17:20:03 +00:00
curl --cacert certs/ca.crt -v -H 'content-type: application/x-pem-file' --data-binary @client.csr https://localhost:19613/sign
2024-09-25 20:30:32 +00:00
```
## Reasons this is not secure
* the CA key is readable by and present in the memory of the process
that reads and parses network requests. Bearing in mind the the whole
point is to automate signing we can only do so much about this, but at
least we could move the actual signing to a separate process which is
only invoked once an acceptable request has been received.
* there is no intermediate key - the requests are signed directly by the root CA
* I haven't checked that the protocols or the ciphers are restricted
to modern and sensible defaults
* doesn't set 4.2.1.6. Subject Alternative Name
* doesn't set Key Usage extension (https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3)
* probably has wrong basicConstraints in CA cert
2024-10-04 17:20:03 +00:00
* doesn't call SSL shutdown at the end of the response
* likewise other TLS best practices
2024-09-25 20:30:32 +00:00
## Background
2024-09-25 20:30:32 +00:00
* [RFC 5967 - spec for a CSR](https://datatracker.ietf.org/doc/html/rfc5967)
* [A gentle introduction to ASN1. and DER](https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/)
2024-10-04 17:20:03 +00:00
openssl req -newkey rsa:2048 -addext "subjectAltName = DNS:loaclhost.lan,DNS:localhost,DNS:loaclhost.telent.net" -nodes -keyout private/server.key --out certs/server.csr