aboutsummaryrefslogtreecommitdiff
path: root/doc/src/crypt-key.md
blob: 9fb199b5a81043ad6fdf4febb2cb98752a575944 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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])}"`