An interesting way of enabling asymmetric cryptography without having to carry around certificate or key files.
- the client obtains a username & password, e.g. via a login form
- from these, it computes a master keypair using the argon2id algorithm:
// inputs username := "alice" password := "hunter2" domain := "example.org" // standard values, set by the crypto library SEEDBYTES := 32 // required by sign_seed_kp SALTBYTES := 16 // required by argon2id OPSLIMIT_INTERACTIVE := 2 MEMLIMIT_INTERACTIVE := 67108864 // 64 MiB seed := argon2id( SEEDBYTES, // output length password, hash(SALTBYTES, username+domain), // salt OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE, ) kp := sign_seed_kp(seed)
- server: exposes a
/capka/login
endpoint- client: GETs the endpoint:
- this returns a “nonce” (can be any random string)
- server keeps all nones in an “active” cache for a short amount of time (e.g. 5 seconds)
- the client POSTs to the same endpoint the following structure:
{ "user": "<the given username>", "nonce": "<nonce returned by GET /capka/login>", "signature": "<nonce signed with the master keypair>" }
- the server then validates both the nonce (is it still in the active cache?) and the signature (user’s public keys are stored in the server DB)
In step 4, the signature
is computed on user+nonce
instead of just the nonce.
In step 5, if the server wants to return some kind of session identifier, it encrypts it to the client’s public key instead of sending it in plaintext.
The previous extension misuses the client’s keypair by assigning it two functions: signature verification and data encryption. Instead, the following changes are made:
- in step 2, the client also creates a random, ephemeral keypair
- in step 4, the public key of that keypair is added to the structure (field
ephkey
) and the signature is now computed over all three values (user, nonce, ephkey) - in step 5, the server encrypts the session ID to the ephemeral pubkey instead of the client master pubkey
This version of the protocol should be completely secure even on untrusted (not HTTPS) channels (of course the client can’t trust the server under those circumstances).
Attackers can’t replace any message or component without triggering some kind of validation error on the server. And since nonces don’t carry intrinsic ownership information, even a complete replacement of the nonce with another valid one doesn’t give the attacker any way of injecting their own user or obtaining the session ID.
- a Go library for embedding the login logic into an application (DONE)
- serverside:
- clientside:
- a JavaScript library for the browser
- a CLI tool for quickly generating client pubkeys (DONE)
- everything will be based on libsodium: