Skip to content

Commit 605f339

Browse files
authored
Merge pull request #1946 from uhoreg/ssss
MSC1946: Secure Secret Storage and Sharing
2 parents 037894d + fc79355 commit 605f339

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
# Secure Secret Storage and Sharing
2+
3+
Some features may require clients to store encrypted data on the server so that
4+
it can be shared securely between clients. Clients may also wish to securely
5+
send such data directly to each other. For example, key backups
6+
([MSC1219](https://github.com/matrix-org/matrix-doc/issues/1219)) can store the
7+
decryption key for the backups on the server, or cross-signing
8+
([MSC1756](https://github.com/matrix-org/matrix-doc/pull/1756)) can store the
9+
signing keys. This proposal presents a standardized way of storing such data.
10+
11+
## Proposal
12+
13+
Secrets are data that clients need to use and that are sent through or stored
14+
on the server, but should not be visible to server operators. Secrets are
15+
plain strings -- if clients need to use more complicated data, they must be
16+
encoded as a string, such as by encoding as JSON.
17+
18+
### Storage
19+
20+
If secret data is stored on the server, it must be encrypted in order to
21+
prevent homeserver administrators from being able to read it. A user can have
22+
multiple keys used for encrypting data. This allows the user to selectively
23+
decrypt data on clients. For example, the user could have one key that can
24+
decrypt everything, and another key that can only decrypt their user-signing
25+
key for cross-signing.
26+
27+
Key descriptions and secret data are both stored in the user's account_data.
28+
29+
#### Key storage
30+
31+
Each key has an ID, and the description of the key is stored in the user's
32+
account_data using the event type `m.secret_storage.key.[key ID]`. The contents
33+
of the account data for the key will include an `algorithm` property, which
34+
indicates the encryption algorithm used, as well as a `name` property, which is
35+
a human-readable name. The contents will be signed as signed JSON using the
36+
user's master cross-signing key. Other properties depend on the encryption
37+
algorithm, and are described below.
38+
39+
Example:
40+
41+
A key with ID `abcdefg` is stored in `m.secret_storage.key.abcdefg`
42+
43+
```json
44+
{
45+
"name": "Some key",
46+
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
47+
// ... other properties according to algorithm
48+
}
49+
```
50+
51+
A key can be marked as the "default" key by setting the user's account_data
52+
with event type `m.secret_storage.default_key` to an object that has the ID of
53+
the key as its `key` property. The default key will be used to encrypt all
54+
secrets that the user would expect to be available on all their clients.
55+
Unless the user specifies otherwise, clients will try to use the default key to
56+
decrypt secrets.
57+
58+
Clients MUST ensure that the key is trusted before using it to encrypt secrets.
59+
One way to do that is to have the client that creates the key sign the key
60+
description (as signed JSON) using the user's master cross-signing key.
61+
Another way to do that is to prompt the user to enter the passphrase used to
62+
generate the encryption key and ensure that the generated private key
63+
corresponds to the public key.
64+
65+
#### Secret storage
66+
67+
Encrypted data is stored in the user's account_data using the event type
68+
defined by the feature that uses the data. For example, decryption keys for
69+
key backups could be stored under the type `m.megolm_backup.v1.recovery_key`,
70+
or the self-signing key for cross-signing could be stored under the type
71+
`m.cross_signing.self_signing`.
72+
73+
The account_data will have an `encrypted` property that is a map from key ID
74+
to an object. The algorithm from the `m.secret_storage.key.[key ID]` data for
75+
the given key defines how the other properties are interpreted, though it's
76+
expected that most encryption schemes would have `ciphertext` and `mac`
77+
properties, where the `ciphertext` property is the unpadded base64-encoded
78+
ciphertext, and the `mac` is used to ensure the integrity of the data.
79+
80+
Example:
81+
82+
Some secret is encrypted using keys with ID `key_id_1` and `key_id_2`:
83+
84+
`org.example.some.secret`:
85+
86+
```json
87+
{
88+
"encrypted": {
89+
"key_id_1": {
90+
"ciphertext": "base64+encoded+encrypted+data",
91+
"mac": "base64+encoded+mac",
92+
// ... other properties according to algorithm property in
93+
// m.secret_storage.key.key_id_1
94+
},
95+
"key_id_2": {
96+
// ...
97+
}
98+
}
99+
}
100+
```
101+
102+
and the key descriptions for the keys would be:
103+
104+
`m.secret_storage.key.key_id_1`:
105+
106+
```json
107+
{
108+
"name": "Some key",
109+
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
110+
// ... other properties according to algorithm
111+
}
112+
```
113+
114+
`m.secret_storage.key.key_id_2`:
115+
116+
```json
117+
{
118+
"name": "Some other key",
119+
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
120+
// ... other properties according to algorithm
121+
}
122+
```
123+
124+
#### Encryption algorithms
125+
126+
##### `m.secret_storage.v1.curve25519-aes-sha2`
127+
128+
The public key is stored in the `pubkey` property of the `m.secret_storage.key.[key
129+
ID]` account_data as a base64-encoded string.
130+
131+
The data is encrypted and MACed as follows:
132+
133+
1. Generate an ephemeral curve25519 key, and perform an ECDH with the ephemeral
134+
key and the public key to generate a shared secret. The public half of the
135+
ephemeral key, encoded using base64, becomes the `ephemeral` property.
136+
2. Using the shared secret, generate 80 bytes by performing an HKDF using
137+
SHA-256 as the hash, with a salt of 32 bytes of 0, and with the empty string
138+
as the info. The first 32 bytes are used as the AES key, the next 32 bytes
139+
are used as the MAC key, and the last 16 bytes are used as the AES
140+
initialization vector.
141+
4. Encrypt the data using AES-CBC-256 with PKCS#7 padding. This encrypted
142+
data, encoded using base64, becomes the `ciphertext` property.
143+
5. Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256
144+
using the MAC key generated above. The first 8 bytes of the resulting MAC
145+
are base64-encoded, and become the `mac` property.
146+
147+
(The key HKDF, AES, and HMAC steps are the same as what are used for encryption
148+
in olm and megolm.)
149+
150+
For example, the `m.secret_storage.key.[key ID]` for a key using this algorithm
151+
could look like:
152+
153+
```json
154+
{
155+
"name": "m.default",
156+
"algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
157+
"pubkey": "base64+public+key"
158+
}
159+
```
160+
161+
and data encrypted using this algorithm could look like this:
162+
163+
```json
164+
{
165+
"encrypted": {
166+
"key_id": {
167+
"ciphertext": "base64+encoded+encrypted+data",
168+
"ephemeral": "base64+ephemeral+key",
169+
"mac": "base64+encoded+mac"
170+
}
171+
}
172+
}
173+
```
174+
175+
###### Keys
176+
177+
When a user is given a raw key for `m.secret_storage.v1.curve25519-aes-sha2`,
178+
it will be encoded as follows (this is the same as what is proposed in MSC1703):
179+
180+
* prepend the two bytes 0x8b and 0x01 to the key
181+
* compute a parity byte by XORing all bytes of the resulting string, and append
182+
the parity byte to the string
183+
* base58-encode the resulting byte string with the alphabet
184+
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.
185+
* format the resulting ASCII string into groups of 4 characters separated by
186+
spaces.
187+
188+
When decoding a raw key, the process should be reversed, with the exception
189+
that whitespace is insignificant in the user's ASCII input.
190+
191+
###### Passphrase
192+
193+
A user may wish to use a chosen passphrase rather than a randomly generated
194+
key. In this case, information on how to generate the key from a passphrase
195+
will be stored in the `passphrase` property of the `m.secret_storage.key.[key
196+
ID]` account-data:
197+
198+
```json
199+
{
200+
"passphrase": {
201+
"algorithm": "m.pbkdf2",
202+
"salt": "MmMsAlty",
203+
"iterations": 100000
204+
},
205+
...
206+
}
207+
```
208+
209+
**`m.pbkdf2`**
210+
211+
The key is generated using PBKDF2 using the salt given in the `salt` parameter,
212+
and the number of iterations given in the `iterations` parameter.
213+
214+
### Sharing
215+
216+
Rather than (or in addition to) storing secrets on the server encrypted by a
217+
shared key, devices can send secrets to each other, encrypted using olm.
218+
219+
To request a secret, a client sends a `m.secret.request` device event with `action`
220+
set to `request` to other devices, and `name` set to the name of the secret
221+
that it wishes to retrieve. A device that wishes to share the secret will
222+
reply with a `m.secret.send` event, encrypted using olm. When the original
223+
client obtains the secret, it sends a `m.secret.request` event with `action`
224+
set to `request_cancellation` to all devices other than the one that it received the
225+
secret from. Clients should ignore `m.secret.send` events received from
226+
devices that it did not send an `m.secret.request` event to.
227+
228+
Clients MUST ensure that they only share secrets with other devices that are
229+
allowed to see them. For example, clients SHOULD only share secrets with
230+
the user’s own devices that are verified and MAY prompt the user to confirm sharing the
231+
secret.
232+
233+
If a feature allows secrets to be stored or shared, then for consistency it
234+
SHOULD use the same name for both the account_data event type and the `name` in
235+
the `m.secret.request`.
236+
237+
#### Event definitions
238+
239+
##### `m.secret.request`
240+
241+
Sent by a client to request a secret from another device. It is sent as an
242+
unencrypted to-device event.
243+
244+
- `name`: (string) Required if `action` is `request`. The name of the secret
245+
that is being requested.
246+
- `action`: (enum) Required. One of ["request", "request_cancellation"].
247+
- `requesting_device_id`: (string) Required. ID of the device requesting the
248+
secret.
249+
- `request_id`: (string) Required. A random string uniquely identifying the
250+
request for a secret. If the secret is requested multiple times, it should be
251+
reused. It should also reused in order to cancel a request.
252+
253+
##### `m.secret.send`
254+
255+
Sent by a client to share a secret with another device, in response to an
256+
`m.secret.request` event. It MUST be encrypted as an `m.room.encrypted` event,
257+
then sent as a to-device event.
258+
259+
- `request_id`: (string) Required. The ID of the request that this a response to.
260+
- `secret`: (string) Required. The contents of the secret.
261+
262+
## Tradeoffs
263+
264+
Currently, only a public/private key mechanism is defined. It may be useful to
265+
also define a secret key mechanism.
266+
267+
## Potential issues
268+
269+
Keeping all the data and keys in account data means that it may clutter up
270+
`/sync` requests. However, clients can filter out the data that they are not interested
271+
in. One possibility for addressing this would be to add a flag to the account
272+
data to indicate whether it should come down the `/sync` or not.
273+
274+
## Security considerations
275+
276+
By storing information encrypted on the server, this allows the server operator
277+
to read the information if they manage to get hold of the decryption keys.
278+
In particular, if the key is based on a passphrase and the passphrase can be
279+
guessed, then the secrets could be compromised. In order to help protect the
280+
secrets, clients should provide feedback to the user when their chosen
281+
passphrase is considered weak, and may also wish to prevent the user from
282+
reusing their login password.
283+
284+
## Conclusion
285+
286+
This proposal presents a common way for bits of encrypted data to be stored on
287+
a user's homeserver for use by various features.

0 commit comments

Comments
 (0)