README.md (5138B)
1 # Certifix 2 3 Not an Asterix character. 4 5 A small HTTPS API modelled on the description of the 6 [Puppet CA "Policy-based autosigning" functionality]( 7 https://www.puppet.com/docs/puppet/7/ssl_attributes_extensions#csr_custom_attributes-recommended-oids-custom-attributes), 8 that accepts X509 CSRs and automatically signs them without human 9 interaction if they have a custom `challengePassword` attribute 10 containing a pre-agreed value. 11 12 What's it for? I have a bunch of small devices on my LAN that may or 13 may not be able to retain persistent state across reboots. I would 14 like them to be able to talk securely to services on the network using 15 standard TLS with client authentication, and (because "[zero trust] 16 (https://en.wikipedia.org/wiki/Zero_trust_security_model)") 17 without having to rely on network firewall rules to prevent the rest 18 of the world also being able to talk to the service. 19 20 ## To try it out 21 22 _This is alpha-quality code which was written by someone with only the 23 most passing familiarity with TLS or cryptography in general, and 24 has not been audited. Try it at your own risk._ 25 26 It's written in [Fennel](https://www.fennel-lang.org). To build it 27 either use Nix or read [package.nix](package.nix) and figure out how 28 to replicate the steps manually. Note that it requires a patch to the 29 luaossl module. 30 31 ### CA key and cert 32 33 Create the CA key and the certificate used for signing. You will be 34 asked a bunch of questions that will be incorporated into the 35 certificate: when prompted for "Common Name", say "Certificate 36 Authority" or something like that 37 38 ``` 39 openssl genrsa -out private/ca.key 4096 40 openssl req -addext basicConstraints=critical,CA:TRUE,pathlen:0 --x509 -new -nodes -key private/ca.key -sha256 -days 3650 -out certs/ca.crt 41 ``` 42 43 ### Server key and cert 44 45 The certifix service is exposed over HTTPS, so it needs its own 46 certificate signed by the CA. Use your hostname when prompted for 47 Common Name. If your server host is reachable using different 48 names from different clients then you need to provide _all_ of them 49 as `subjectAltName` - as in the example below. 50 51 ``` 52 # it's important to list all the hostnames of your server machine in the 53 # subjectAltName field 54 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 55 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 56 57 ``` 58 59 ### Build and start the server 60 61 The server needs to be told of all of the preceding files, _plus_ a 62 file containing the expected value of the pre-shared key that you want 63 it to check client certificate requests against. 64 65 ``` 66 echo 'loves labours lost' > psk 67 chmod 0700 psk 68 nix-build 69 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 70 ``` 71 72 * To listen on all interfaces, use a wildcard address instead of 73 `localhost` - e.g. `::0:19613' will (at least on my machine) accept 74 IPv4 and IPv6 connections on any interface 75 76 77 ### Try it and see if it works 78 79 To set the `challengePassword` attribute in a CSR using OpenSSL, you 80 need to create a configuration file. Copy `openssl.cnf.example` to 81 `openssl.cnf` and edit it for your setup. 82 83 * the values in `req_distinguished_name` should match your organisation 84 * the `challengePassword` attribute must match whatever you told the 85 service to expect (`psk` file in the preceding step) 86 87 ``` 88 # make CSR 89 CN=mydevice openssl req -config openssl.cnf -addext "subjectAltName = DNS:mydevice.lan" -newkey rsa:2048 -nodes -keyout client.key -out client.csr 90 91 # send it to certifix, should get a certificate in response 92 curl --cacert certs/ca.crt -v -H 'content-type: application/x-pem-file' --data-binary @client.csr https://localhost:19613/sign 93 ``` 94 95 96 ## Reasons this is not secure 97 98 * the CA key is readable by and present in the memory of the process 99 that reads and parses network requests. Bearing in mind the the 100 whole point is to automate signing we can only do so much about 101 this, but at least we could move the actual signing to a separate 102 process which is only invoked once an acceptable request has been 103 received. 104 105 * on that note, in the setup that this README describes there is no 106 intermediate key - the requests are signed directly by the root 107 CA. For a robust setup you might consider ignoring my instructions 108 for generating a self-signed root cert, and instead create a CA 109 signed by an existing (offline?) root CA 110 111 * I haven't checked that the protocols or the ciphers are restricted 112 to modern and sensible defaults 113 114 * it has _not_ been reviewed by anyone who knows about TLS best 115 practices. If you are that person, please look at the new-crt 116 function in [main.fnl](main.fnl) and let me know :-) 117 118 119 ## Background 120 121 122 * [RFC 5967 - spec for a CSR](https://datatracker.ietf.org/doc/html/rfc5967) 123 * [A gentle introduction to ASN1. and DER](https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/)