diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2022-08-01 10:35:29 +0200 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2022-08-01 10:35:29 +0200 |
commit | 112e63b5d7feb9476b6bd1852dc276bb3de2d0bd (patch) | |
tree | 4cae7617506bbd3fe387ebd63b26b3e63bf14f11 /doc/src/crypt-key.md | |
parent | 441730e1f7a27541d52d1aaccc59a8204a96d079 (diff) | |
download | aerogramme-112e63b5d7feb9476b6bd1852dc276bb3de2d0bd.tar.gz aerogramme-112e63b5d7feb9476b6bd1852dc276bb3de2d0bd.zip |
First iteration on documentation
Diffstat (limited to 'doc/src/crypt-key.md')
-rw-r--r-- | doc/src/crypt-key.md | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/doc/src/crypt-key.md b/doc/src/crypt-key.md new file mode 100644 index 0000000..9fb199b --- /dev/null +++ b/doc/src/crypt-key.md @@ -0,0 +1,82 @@ +# Cryptography & key management + +Keys that are used: + +- master secret key (for indexes) +- curve25519 public/private key pair (for incoming mail) + +Keys that are stored in K2V under PK `keys`: + +- `public`: the public curve25519 key (plain text) +- `salt`: the 32-byte salt `S` used to calculate digests that index keys below +- if a password is used, `password:<truncated(128bit) argon2 digest of password using salt S>`: + - a 32-byte salt `Skey` + - followed a secret box + - that is encrypted with a strong argon2 digest of the password (using the salt `Skey`) and a user secret (see below) + - that contains the master secret key and the curve25519 private key + +User secret: an additionnal secret that is added to the password when deriving the encryption key for the secret box. +This additionnal secret should not be stored in K2V/S3, so that just knowing a user's password isn't enough to be able +to decrypt their mailbox (supposing the attacker has a dump of their K2V/S3 bucket). +This user secret should typically be stored in the LDAP database or just in the configuration file when using +the static login provider. + +Operations: + +- **Initialize**(`user_secret`, `password`): + - if `"salt"` or `"public"` already exist, BAIL + - generate salt `S` (32 random bytes) + - generate `public`, `private` (curve25519 keypair) + - generate `master` (secretbox secret key) + - calculate `digest = argon2_S(password)` + - generate salt `Skey` (32 random bytes) + - calculate `key = argon2_Skey(user_secret + password)` + - serialize `box_contents = (private, master)` + - seal box `blob = seal_key(box_contents)` + - write `S` at `"salt"` + - write `concat(Skey, blob)` at `"password:{hex(digest[..16])}"` + - write `public` at `"public"` + +- **InitializeWithoutPassword**(`private`, `master`): + - if `"salt"` or `"public"` already exist, BAIL + - generate salt `S` (32 random bytes) + - write `S` at `"salt"` + - calculate `public` the public key associated with `private` + - write `public` at `"public"` + +- **Open**(`user_secret`, `password`): + - load `S = read("salt")` + - calculate `digest = argon2_S(password)` + - load `blob = read("password:{hex(digest[..16])}") + - set `Skey = blob[..32]` + - calculate `key = argon2_Skey(user_secret + password)` + - open secret box `box_contents = open_key(blob[32..])` + - retrieve `master` and `private` from `box_contents` + - retrieve `public = read("public")` + +- **OpenWithoutPassword**(`private`, `master`): + - load `public = read("public")` + - check that `public` is the correct public key associated with `private` + +- **AddPassword**(`user_secret`, `existing_password`, `new_password`): + - load `S = read("salt")` + - calculate `digest = argon2_S(existing_password)` + - load `blob = read("existing_password:{hex(digest[..16])}") + - set `Skey = blob[..32]` + - calculate `key = argon2_Skey(user_secret + existing_password)` + - open secret box `box_contents = open_key(blob[32..])` + - retrieve `master` and `private` from `box_contents` + + - calculate `digest_new = argon2_S(new_password)` + - generate salt `Skeynew` (32 random bytes) + - calculate `key_new = argon2_Skeynew(user_secret + new_password)` + - serialize `box_contents_new = (private, master)` + - seal box `blob_new = seal_key_new(box_contents_new)` + - write `concat(Skeynew, blob_new)` at `"new_password:{hex(digest_new[..16])}"` + +- **RemovePassword**(`password`): + - load `S = read("salt")` + - calculate `digest = argon2_S(existing_password)` + - check that `"password:{hex(digest[..16])}"` exists + - check that other passwords exist ?? (or not) + - delete `"password:{hex(digest[..16])}"` |