diff --git a/changelogs/client_server/newsfragments/1420.feature b/changelogs/client_server/newsfragments/1420.feature new file mode 100644 index 00000000..f7cf687d --- /dev/null +++ b/changelogs/client_server/newsfragments/1420.feature @@ -0,0 +1 @@ +Encrypt file attachments diff --git a/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml b/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml index 4d2a9964..8ff27b1e 100644 --- a/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml +++ b/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml @@ -18,8 +18,16 @@ properties: description: Size of the image in bytes. type: integer thumbnail_url: - description: The URL to a thumbnail of the image. + description: |- + The URL to a thumbnail of the image. Only present if the + thumbnail is unencrypted. type: string + thumbnail_file: + description: |- + Information on the encrypted thumbnail file, as specified in + |encrypted_files|_. Only present if the thumbnail is encrypted. + title: EncryptedFile + type: object thumbnail_info: allOf: - $ref: thumbnail_info.yaml diff --git a/event-schemas/schema/m.room.message#m.audio b/event-schemas/schema/m.room.message#m.audio index f15c71a3..c258b85f 100644 --- a/event-schemas/schema/m.room.message#m.audio +++ b/event-schemas/schema/m.room.message#m.audio @@ -27,12 +27,17 @@ properties: - m.audio type: string url: - description: The URL to the audio clip. + description: Required if the file is not encrypted. The URL to the audio clip. type: string + file: + description: |- + Required if the file is encrypted. Information on the encrypted + file, as specified in |encrypted_files|_. + title: EncryptedFile + type: object required: - msgtype - body - - url type: object type: enum: diff --git a/event-schemas/schema/m.room.message#m.file b/event-schemas/schema/m.room.message#m.file index 76e59e09..2fb4fe50 100644 --- a/event-schemas/schema/m.room.message#m.file +++ b/event-schemas/schema/m.room.message#m.file @@ -21,8 +21,16 @@ properties: description: The size of the file in bytes. type: integer thumbnail_url: - description: The URL to the thumbnail of the file. + description: |- + The URL to the thumbnail of the file. Only present if the + thumbnail is unencrypted. type: string + thumbnail_file: + description: |- + Information on the encrypted thumbnail file, as specified in + |encrypted_files|_. Only present if the thumbnail is encrypted. + title: EncryptedFile + type: object thumbnail_info: allOf: - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml @@ -34,12 +42,17 @@ properties: - m.file type: string url: - description: The URL to the file. + description: Required if the file is unencrypted. The URL to the file. type: string + file: + description: |- + Required if the file is encrypted. Information on the encrypted + file, as specified in |encrypted_files|_. + title: EncryptedFile + type: object required: - msgtype - body - - url - filename type: object type: diff --git a/event-schemas/schema/m.room.message#m.image b/event-schemas/schema/m.room.message#m.image index 1237b8f8..349f78f4 100644 --- a/event-schemas/schema/m.room.message#m.image +++ b/event-schemas/schema/m.room.message#m.image @@ -17,12 +17,17 @@ properties: - m.image type: string url: - description: The URL to the image. + description: Required if the file is unencrypted. The URL to the image. type: string + file: + description: |- + Required if the file is encrypted. Information on the encrypted + file, as specified in |encrypted_files|_. + title: EncryptedFile + type: object required: - msgtype - body - - url type: object type: enum: diff --git a/event-schemas/schema/m.room.message#m.location b/event-schemas/schema/m.room.message#m.location index e8d55769..ffc4edce 100644 --- a/event-schemas/schema/m.room.message#m.location +++ b/event-schemas/schema/m.room.message#m.location @@ -19,8 +19,16 @@ properties: type: object properties: thumbnail_url: - description: The URL to a thumbnail of the location being represented. + description: |- + The URL to the thumbnail of the file. Only present if the + thumbnail is unencrypted. type: string + thumbnail_file: + description: |- + Information on the encrypted thumbnail file, as specified in + |encrypted_files|_. Only present if the thumbnail is encrypted. + title: EncryptedFile + type: object thumbnail_info: allOf: - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml diff --git a/event-schemas/schema/m.room.message#m.video b/event-schemas/schema/m.room.message#m.video index a0240b54..8a66fdeb 100644 --- a/event-schemas/schema/m.room.message#m.video +++ b/event-schemas/schema/m.room.message#m.video @@ -27,8 +27,16 @@ properties: description: The size of the video in bytes. type: integer thumbnail_url: - description: The URL to an image thumbnail of the video clip. + description: |- + The URL to an image thumbnail of the video clip. Only present if the + thumbnail is unencrypted. type: string + thumbnail_file: + description: |- + Information on the encrypted thumbnail file, as specified in + |encrypted_files|_. Only present if the thumbnail is encrypted. + title: EncryptedFile + type: object thumbnail_info: allOf: - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml @@ -40,12 +48,17 @@ properties: - m.video type: string url: - description: The URL to the video clip. + description: Required if the file is unencrypted. The URL to the video clip. type: string + file: + description: |- + Required if the file is encrypted. Information on the encrypted + file, as specified in |encrypted_files|_. + title: EncryptedFile + type: object required: - msgtype - body - - url type: object type: enum: diff --git a/specification/modules/end_to_end_encryption.rst b/specification/modules/end_to_end_encryption.rst index 170b70f9..68434e15 100644 --- a/specification/modules/end_to_end_encryption.rst +++ b/specification/modules/end_to_end_encryption.rst @@ -224,6 +224,145 @@ process: .. |device_lists| replace:: ``device_lists`` .. _`device_lists`: `device_lists_sync`_ + +Sending encrypted attachments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When encryption is enabled in a room, files should be uploaded encrypted on +the homeserver. + +In order to achieve this, a client should generate a single-use 256-bit AES +key, and encrypt the file using AES-CTR. The counter should be 64-bit long, +starting at 0 and prefixed by a random 64-bit Initialization Vector (IV), which +together form a 128-bit unique counter block. + +.. Warning:: + An IV must never be used multiple times with the same key. This implies that + if there are multiple files to encrypt in the same message, typically an + image and its thumbnail, the files must not share both the same key and IV. + +Then, the encrypted file can be uploaded to the homeserver. +The key and the IV must be included in the room event along with the resulting +``mxc://`` in order to allow recipients to decrypt the file. As the event +containing those will be Megolm encrypted, the server will never have access to +the decrypted file. + +A hash of the ciphertext must also be included, in order to prevent the homeserver from +changing the file content. + +A client should send the data as an encrypted ``m.room.message`` event, using +either ``m.file`` as the msgtype, or the appropriate msgtype for the file +type. The key is sent using the `JSON Web Key`_ format, with a `W3C +extension`_. + +.. anchor for link from m.message api spec +.. |encrypted_files| replace:: End-to-end encryption +.. _encrypted_files: + +Extensions to ``m.message`` msgtypes +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +This module adds ``file`` and ``thumbnail_file`` properties, of type +``EncryptedFile``, to ``m.message`` msgtypes that reference files, such as +`m.file`_ and `m.image`_, replacing the ``url`` and ``thumbnail_url`` +properties. + +.. todo: generate this from a swagger definition? + +``EncryptedFile`` + +========= ================ ===================================================== +Parameter Type Description +========= ================ ===================================================== +url string **Required.** The URL to the file. +key JWK **Required.** A `JSON Web Key`_ object. +iv string **Required.** The Initialisation Vector used by + AES-CTR, encoded as unpadded base64. +hashes {string: string} **Required.** A map from an algorithm name to a hash + of the ciphertext, encoded as unpadded base64. Clients + should support the SHA-256 hash, which uses the key + ``sha256``. +v string **Required.** Version of the encrypted attachments + protocol. Must be ``v2``. +========= ================ ===================================================== + +``JWK`` + +========= ========= ============================================================ +Parameter Type Description +========= ========= ============================================================ +key string **Required.** Key type. Must be ``oct``. +key_opts [string] **Required.** Key operations. Must at least contain + ``encrypt`` and ``decrypt``. +alg string **Required.** Algorithm. Must be ``A256CTR``. +k string **Required.** The key, encoded as urlsafe unpadded base64. +ext boolean **Required.** Extractable. Must be ``true``. This is a + `W3C extension`_. +========= ========= ============================================================ + +Example: + +.. code :: json + + { + "content": { + "body": "something-important.jpg", + "file": { + "url": "mxc://domain.com/FHyPlCeYUSFFxlgbQYZmoEoe", + "mimetype": "image/jpeg", + "v": "v2", + "key": { + "alg": "A256CTR", + "ext": true, + "k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0", + "key_ops": ["encrypt","decrypt"], + "kty": "oct" + }, + "iv": "w+sE15fzSc0AAAAAAAAAAA", + "hashes": { + "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" + } + }, + "info": { + "mimetype": "image/jpeg", + "h": 1536, + "size": 422018, + "thumbnail_file": { + "hashes": { + "sha256": "/NogKqW5bz/m8xHgFiH5haFGjCNVmUIPLzfvOhHdrxY" + }, + "iv": "U+k7PfwLr6UAAAAAAAAAAA", + "key": { + "alg": "A256CTR", + "ext": true, + "k": "RMyd6zhlbifsACM1DXkCbioZ2u0SywGljTH8JmGcylg", + "key_ops": ["encrypt", "decrypt"], + "kty": "oct" + }, + "mimetype": "image/jpeg", + "url": "mxc://domain.com/pmVJxyxGlmxHposwVSlOaEOv", + "v": "v2" + }, + "thumbnail_info": { + "h": 768, + "mimetype": "image/jpeg", + "size": 211009, + "w": 432 + }, + "w": 864 + }, + "msgtype": "m.image" + }, + "event_id": "$143273582443PhrSn:domain.com", + "origin_server_ts": 1432735824653, + "room_id": "!jEsUZKDJdhlrceRyVU:domain.com", + "sender": "@example:domain.com", + "type": "m.room.message", + "unsigned": { + "age": 1234 + } + } + Claiming one-time keys ~~~~~~~~~~~~~~~~~~~~~~ @@ -583,6 +722,8 @@ Example response: .. _curve25519: https://cr.yp.to/ecdh.html .. _`Olm specification`: http://matrix.org/docs/spec/olm.html .. _`Megolm specification`: http://matrix.org/docs/spec/megolm.html +.. _`JSON Web Key`: https://tools.ietf.org/html/rfc7517#appendix-A.3 +.. _`W3C extension`: https://w3c.github.io/webcrypto/#iana-section-jwk .. _`Signing JSON`: ../appendices.html#signing-json