|
| 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