Go to file
Daniel Barlow e0e0e0f7cc expand the insecurities section 2024-10-04 23:27:41 +01:00
certs fix pathnames in README 2024-10-04 18:48:28 +01:00
private fix pathnames in README 2024-10-04 18:48:28 +01:00
Makefile initial commit 2024-09-25 10:20:14 +01:00
README.md expand the insecurities section 2024-10-04 23:27:41 +01:00
default.nix initial commit 2024-09-25 10:20:14 +01:00
main.fnl add key usage/extended key usage extensions 2024-10-04 23:20:20 +01:00
openssl.cnf.example polish the README and remove my local config from it 2024-10-02 18:04:42 +01:00
package.nix upate lusossl patch 2024-10-02 00:26:21 +01:00
shell.nix add openssl in shell environment 2024-09-25 21:17:42 +01:00

README.md

Certifix

Not an Asterix character.

A small HTTPS API modelled on the description of the Puppet CA "Policy-based autosigning" functionality, that accepts X509 CSRs and automatically signs them without human interaction if they have a custom challengePassword attribute containing a pre-agreed value.

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.

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.

It's written in Fennel. To build it either use Nix or read package.nix and figure out how to replicate the steps manually. Note that it requires a patch to the luaossl module.

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

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 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.

# 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.

echo 'loves labours lost' > psk
chmod 0700 psk
nix-build
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
  • 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

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)
# make CSR
CN=mydevice openssl req -config openssl.cnf -addext "subjectAltName = DNS:mydevice.lan" -newkey rsa:2048 -nodes -keyout client.key -out client.csr

# send it to certifix, should get a certificate in response
curl --cacert certs/ca.crt   -v -H 'content-type: application/x-pem-file' --data-binary @client.csr  https://localhost:19613/sign

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.

  • on that note, in the setup that this README describes there is no intermediate key - the requests are signed directly by the root CA. For a robust setup you might consider ignoring my instructions for generating a self-signed root cert, and instead create a CA signed by an existing (offline?) root CA

  • I haven't checked that the protocols or the ciphers are restricted to modern and sensible defaults

  • doesn't call SSL shutdown at the end of the response, so you may get "unexpected EOF" errors

  • it has not been reviewed by anyone who knows about TLS best practices. If you are that person, please look at the new-crt function in main.fnl and let me know :-)

Background