This tries to describe secrets' use of cryptography in a way that makes auditing easier.
All crypto is done with either libsodium via the Rust binding sodiumoxide (the vast majority) or openssl via the Rust binding rust-openssl (which is used for HTTPS authentication between the client and server).
- Sharing secret data with a small-to-medium team should be easy and secure.
- The server should never see plaintext. Plaintext should never leave the client and the server shouldn't be able to decrypt anything
- The server should be trustless. Even a malicious server should never compromise secret data
- It should be possible to revoke secrets and rotate them regularly without having to hunt down everyone that might know it
- Compromise attempts should be loud and obvious
- Auditing should be easy
The server is a store of users' public keys and opaque encrypted blobs. Secret data is encrypted from one user's public key to another user's private key.
Creating or rotating a service involves fetching each grantee's public keys, encrypting the secret to them, and uploading to the server only that encrypted data. Similarly, granting access to an existing service involves an existing grantee of that service.
Once a user has seen the password to an external service, they can't unknow it. So there is no way to simply remove a grant: you must rotate the password (change the password on the external service, and update the grants) while withholding the new value from that user. For this reason, disabling a user (secrets-server fire
) won't succeed until the given user holds no grants which can only be achieved by rotating every password they know.
This is the basic building block. The server and every client has a PeerInfo
. It is fully public to all users of the server. It has these properties:
cn
: the user's username (or the fqdn of the server). It's also the common name on their SSL certificatefingerprint
: the SHA256 of their SSL certificatepublic_key
: a libsodium box using thecrypto_box_curve25519xsalsa20poly1305
cipher suitepublic_sign
: a libsodium sign key using theed25519
signature scheme
In addition, the server and every client store these private data:
- their SSL certificate and private key
private_key
: the private side ofpublic_key
private_sign
: the private side ofpublic_sign
These private data are encrypted locally to the server or client. They are encrypted using libsodium secretbox using the crypto_secretbox_xsalsa20poly1305
cipher suite and a different random nonce per value. The key is generated from the store password (the argument to -p
) using libsodium pwhash with the crypto_pwhash_scryptsalsa208sha256
cipher suite and a different random salt per value.
When shown to a user, a PeerInfo
report looks like this:
=== secrets.vm ===
common name: secrets.vm
fingerprint: b957e10c998faa9909cff3ba4ec35485d04708c3ecc7481fe14d7f07bc0229cd
public key: c15e697e4807793ef8a9461a7b2c6cf2266d1ec1480a594e83b54e7b75e07702
public sign: f1db594eb55fe97657c57f2aa01afd1210a46d42d80d5552ac4d548162d4968e
mnemonic: AM ROBE KIT OMEN BATE ICY TROY RON WHAT HIP OMIT SUP LID CLAY AVER LEAR CAVE REEL CAN PAM FAN LUND RIFT ACME
does that look right? [y/n]
(mnemonic
is a rfc1751 human-readable display of the SHA256 of the other values)
This is the storage of the actual secret values. A Grant has
grantee
: who holds it, and to whose private key will decrypt itgrantor
: who granted it tograntee
, and whose private key encrypted itservice_name
: the service it representcreated
: the creation timestampciphertext
: the encrypted data, encrypted usinggrantors
's private key tograntee
's public keysignature
: a signature verifiable with thegrantor
'spublic_sign
that includes all of the above values. (This is included because thebox
's encryption ciphersuite doesn't provide non-repudiation so we provide it separately.)
When creating a service, adding a grant to a user, or rotating a service, the target's PeerInfo
is fetched and their public key is used to encrypt the Grant to them.
HTTPS is used between the client and server using openssl. Authentication is done in both directions using pinned SSL certificates.
secrets involves human checking of fingerprints to authenticate both a client and the server to each other.
In order for a client to authenticate to the server, the client must first "join" the server. secrets join
makes the client initialise its keys in its local database, connects to the server, and shows the user the server it connected to in a PeerInfoi
report. If they confirm, they are given a base64 summary of their own and the server's PeerInfo
data (a JoinRequest
blob) and instructed to send it to the admin of the server which must be transmitted using a side channel like email. This data is not encrypted or signed. (It contains only fully public data, and we haven't established any mutually trusted keys yet anyway.) When the server is given this summary via secrets-server accept-join
, the server double-checks that the server is in fact the server that the client thought they connected to, prompts the server admin to double-check the client values using the same report format (which should be double-checked via a side channel). If the server admin confirms, the user has joined the server and can now authenticate to it.
- The client refuses to connect to a server whose SSL certificate does not match the CN and fingerprint from when it joined.
- The server refuses to allow connections to URLs requiring authentication (more or less everything except the URL to fetch the server's PeerInfo) to clients whose CA and fingerprint don't match a client that has joined.
Certificate signing is not used. All certificates are self-signed and a CA is not used or required.
- Service: an external service, such as a bank
- A secret value: the secret data to be stored, such as the username and password to the bank account
- Grant: an instance of a secret value, encrypted from one user to another
- Grantor: the user giving access
- Grantee: a user that holds a Grant to a Service
- Server: an instance of
secrets-server
running somewhere - Client: an instance of
secrets
with an associated user - Rotation: changing the secret value by e.g. changing the password on the external service and updating secrets with the new value. This can add or remove grantees in the process
- Store password: the password used to encrypt private keys local to a client or server. This password is only used locally, it is not used for network communication or authentication