This system provides a signing + verifying server that can sign an arbitrary data passed POSTed to /sign
, as well as verify the signed response when POSTed to /verify
.
-
Install with
pip install -r requirements.txt
-
Copy
config.sample.yaml
toconfig.yaml
and fill in the config options, in particular, theemail
and thedomain
-
Start server by using the
./run.sh
script.
To support signing, the following data is created in the ./data
directory by default:
- private-key.pem - an ECDSA private key
- cert.pem - the domain certificate (obtained via lets encrypt) created via private-key.pem
- cs-cert.pem - a 'cross-signing' certificate, also created with private-key.pem but with a custom CA.
The key and the cert(s) are rotated every 48 hours.
Signing is done by making a POST request to /sign
containing the data to sign and the creation date.
The data is POSTed as JSON ojbect: {"hash": "...", "created": "..."}
For additional security, an auth token can be configured in the config.yaml
or via an AUTH_TOKEN
environment variable. The auth token
will guard all signing requests, and needs to be passed to the client outside of the app.
The authsigner also supports an optional 'cross-signing' CA, that can generate a certificate signed with the same private key as the domain (Lets Encrypt) certificate, using a privately created certificate authority. This allows for a backup validation to domain ownership, separate from LE.
To enable this, a csca-cert
and csca-private-key
fields should be set in the YAML config, pointing to a Certificate Authority that will be used for cross-signing.
The cross-singing cert chain is then also included in the response.
The signed response includes a JSON with the following fields:
hash
: original datacreated
: the created date passed in.software
: the tool used to create the signature, would beauthsign <version>
where<version>
is the current version of this package.signature
: signature of hash with private key from certdomainCert
: PEM-encoded cert chain of the domain certificate (including CA)domain
: the FQDN of the observer domaincrossSignedCert
: PEM-encoded cert chain signed with same key asdomainCert
but using the cross-signing CA (optional)timeSignature
: a signature of previous 'signature' using the timestamp via timestamping servertimestampCert
: PEM-encoded cert chain of the timestamp certificate (including CA)
Note: By design, the creation date must be close to the current date of the timestamping server. Signing data 'too old' or wrong date will be rejected.
The verification API includes POSTing the signed JSON response to the /verify
endpoint. (No auth token is required to verify).
The verification checks include:
- checking the signature using the public key from domain cert (and optionally, the cross-signing cert)
- checking the timeSignature is valid for the timestamp using the timestampCert
- checking that the signed created timestamp is within ten minutes of claimed signing time, and the domain cert was issued within 48-hours of signing time.
- checking that PEM cert chains for domain cert and timestamp cert are valid
- checking that the fingerprints of the root domain cert and timestamp cert are trusted.
To indicate the trusted domain and timestamp certs, the authsigner/trusted/roots.yaml
file includes the fingerprints (sha-256 hashes) of valid roots.
Currently, this includes the Lets Encrypt CA root and the root for the timestamping server (freetsa.org)
Additional trusted roots can be added as needed. A different trusted roots yaml can be specified in the YAML config as well (see config.sample.yaml for more info)
The system uses the ACME protocol from LetsEncrypt to request a staging or production cert. For this to work, port 80 must be available as a temp server is started on port 80 to obtain the port. The actual signing API should run on a different port.
Port 80 is a priviliged port and requires running this tool as root. Alternatively, a front-end like nginx can be configured to forward from port 80 to the HTTP verification server. For this reason, it is possible to configure the port
in the config.yaml
to be other than 80.
To run the test suite, run py.test --domain <your domain> [--check-port 80]
. The tests must be run on a server that can be verified by ACME HTTP verification (via port 80), but auth server can be started on different port if running behind a proxy.
Note: running tests too often may result in rate limiting from Lets Encrypt. The tests use the Lets Encrypt staging servers to generate the certs and trust the staging certificate roots in a custom trust file for verification.