From a3364ff35712be278fc4f5914a89dbb27f41d08a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 4 Jun 2019 12:41:30 -0600 Subject: [PATCH] Spec SAS verification and the common key verification framework Reference implementations: * https://gitlab.matrix.org/matrix-org/olm/commit/94f664e7256215f33639dbbad6aaf87ada082a9f * https://github.com/matrix-org/matrix-react-sdk/pull/2461 * https://github.com/matrix-org/matrix-js-sdk/pull/818 * https://github.com/matrix-org/matrix-react-sdk/pull/2596 * https://github.com/matrix-org/matrix-js-sdk/pull/837 Proposals: * [MSC1717](https://github.com/matrix-org/matrix-doc/pull/1717) * [MSC1267](https://github.com/matrix-org/matrix-doc/issues/1267) No alterations to either proposal have been made intentionally here. --- .../client_server/newsfragments/2072.feature | 1 + data-definitions/sas-emoji.json | 66 ++++ .../examples/m.key.verification.accept | 12 + .../examples/m.key.verification.cancel | 8 + event-schemas/examples/m.key.verification.key | 7 + event-schemas/examples/m.key.verification.mac | 10 + .../examples/m.key.verification.request | 11 + .../examples/m.key.verification.start | 8 + .../m.key.verification.start$m.sas.v1 | 12 + .../schema/m.key.verification.accept | 64 ++++ .../schema/m.key.verification.cancel | 70 ++++ event-schemas/schema/m.key.verification.key | 28 ++ event-schemas/schema/m.key.verification.mac | 38 ++ .../schema/m.key.verification.request | 43 +++ event-schemas/schema/m.key.verification.start | 39 +++ .../schema/m.key.verification.start$m.sas.v1 | 69 ++++ scripts/continuserv/main.go | 2 +- .../templating/matrix_templates/sections.py | 17 + scripts/templating/matrix_templates/units.py | 20 ++ .../modules/end_to_end_encryption.rst | 325 +++++++++++++++++- 20 files changed, 847 insertions(+), 3 deletions(-) create mode 100644 changelogs/client_server/newsfragments/2072.feature create mode 100644 data-definitions/sas-emoji.json create mode 100644 event-schemas/examples/m.key.verification.accept create mode 100644 event-schemas/examples/m.key.verification.cancel create mode 100644 event-schemas/examples/m.key.verification.key create mode 100644 event-schemas/examples/m.key.verification.mac create mode 100644 event-schemas/examples/m.key.verification.request create mode 100644 event-schemas/examples/m.key.verification.start create mode 100644 event-schemas/examples/m.key.verification.start$m.sas.v1 create mode 100644 event-schemas/schema/m.key.verification.accept create mode 100644 event-schemas/schema/m.key.verification.cancel create mode 100644 event-schemas/schema/m.key.verification.key create mode 100644 event-schemas/schema/m.key.verification.mac create mode 100644 event-schemas/schema/m.key.verification.request create mode 100644 event-schemas/schema/m.key.verification.start create mode 100644 event-schemas/schema/m.key.verification.start$m.sas.v1 diff --git a/changelogs/client_server/newsfragments/2072.feature b/changelogs/client_server/newsfragments/2072.feature new file mode 100644 index 00000000..c7d8bd76 --- /dev/null +++ b/changelogs/client_server/newsfragments/2072.feature @@ -0,0 +1 @@ +Add interactive device verification, including a common framework for device verification. diff --git a/data-definitions/sas-emoji.json b/data-definitions/sas-emoji.json new file mode 100644 index 00000000..060fbd49 --- /dev/null +++ b/data-definitions/sas-emoji.json @@ -0,0 +1,66 @@ +[ + {"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"}, + {"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, + {"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, + {"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, + {"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, + {"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, + {"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, + {"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, + {"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, + {"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, + {"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, + {"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, + {"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, + {"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, + {"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, + {"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, + {"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, + {"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, + {"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, + {"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, + {"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, + {"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"}, + {"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, + {"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, + {"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, + {"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, + {"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, + {"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, + {"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, + {"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"}, + {"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, + {"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, + {"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, + {"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, + {"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, + {"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, + {"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, + {"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, + {"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, + {"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, + {"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, + {"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, + {"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, + {"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"}, + {"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, + {"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"}, + {"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, + {"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, + {"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, + {"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"}, + {"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, + {"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, + {"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, + {"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, + {"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, + {"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, + {"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, + {"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, + {"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, + {"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, + {"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, + {"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, + {"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, + {"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} +] diff --git a/event-schemas/examples/m.key.verification.accept b/event-schemas/examples/m.key.verification.accept new file mode 100644 index 00000000..98e89c06 --- /dev/null +++ b/event-schemas/examples/m.key.verification.accept @@ -0,0 +1,12 @@ +{ + "type": "m.key.verification.accept", + "content": { + "transaction_id": "S0meUniqueAndOpaqueString", + "method": "m.sas.v1", + "key_agreement_protocol": "curve25519", + "hash": "sha256", + "message_authentication_code": "hkdf-hmac-sha256", + "short_authentication_string": ["decimal", "emoji"], + "commitment": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" + } +} diff --git a/event-schemas/examples/m.key.verification.cancel b/event-schemas/examples/m.key.verification.cancel new file mode 100644 index 00000000..9d78f67c --- /dev/null +++ b/event-schemas/examples/m.key.verification.cancel @@ -0,0 +1,8 @@ +{ + "type": "m.key.verification.cancel", + "content": { + "transaction_id": "S0meUniqueAndOpaqueString", + "code": "m.user", + "reason": "User rejected the key verification request" + } +} diff --git a/event-schemas/examples/m.key.verification.key b/event-schemas/examples/m.key.verification.key new file mode 100644 index 00000000..608a2ebd --- /dev/null +++ b/event-schemas/examples/m.key.verification.key @@ -0,0 +1,7 @@ +{ + "type": "m.key.verification.key", + "content": { + "transaction_id": "S0meUniqueAndOpaqueString", + "key": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" + } +} diff --git a/event-schemas/examples/m.key.verification.mac b/event-schemas/examples/m.key.verification.mac new file mode 100644 index 00000000..c77c3a8d --- /dev/null +++ b/event-schemas/examples/m.key.verification.mac @@ -0,0 +1,10 @@ +{ + "type": "m.key.verification.mac", + "content": { + "transaction_id": "S0meUniqueAndOpaqueString", + "keys": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA", + "mac": { + "ed25519:ABCDEF": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" + } + } +} diff --git a/event-schemas/examples/m.key.verification.request b/event-schemas/examples/m.key.verification.request new file mode 100644 index 00000000..258471d2 --- /dev/null +++ b/event-schemas/examples/m.key.verification.request @@ -0,0 +1,11 @@ +{ + "type": "m.key.verification.request", + "content": { + "from_device": "AliceDevice2", + "transaction_id": "S0meUniqueAndOpaqueString", + "methods": [ + "m.sas.v1" + ], + "timestamp": 1559598944869 + } +} diff --git a/event-schemas/examples/m.key.verification.start b/event-schemas/examples/m.key.verification.start new file mode 100644 index 00000000..52f16150 --- /dev/null +++ b/event-schemas/examples/m.key.verification.start @@ -0,0 +1,8 @@ +{ + "type": "m.key.verification.start", + "content": { + "from_device": "BobDevice1", + "transaction_id": "S0meUniqueAndOpaqueString", + "method": "m.sas.v1" + } +} diff --git a/event-schemas/examples/m.key.verification.start$m.sas.v1 b/event-schemas/examples/m.key.verification.start$m.sas.v1 new file mode 100644 index 00000000..dae1d405 --- /dev/null +++ b/event-schemas/examples/m.key.verification.start$m.sas.v1 @@ -0,0 +1,12 @@ +{ + "type": "m.key.verification.start", + "content": { + "from_device": "BobDevice1", + "transaction_id": "S0meUniqueAndOpaqueString", + "method": "m.sas.v1", + "key_agreement_protocols": ["curve25519"], + "hashes": ["sha256"], + "message_authentication_codes": ["hkdf-hmac-sha256"], + "short_authentication_string": ["decimal", "emoji"] + } +} diff --git a/event-schemas/schema/m.key.verification.accept b/event-schemas/schema/m.key.verification.accept new file mode 100644 index 00000000..e52df39e --- /dev/null +++ b/event-schemas/schema/m.key.verification.accept @@ -0,0 +1,64 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Accepts a previously sent ``m.key.verification.start`` messge. Typically sent as a + `to-device`_ event. +properties: + content: + properties: + transaction_id: + type: string + description: |- + An opaque identifier for the verification process. Must be the same as + the one used for the ``m.key.verification.start`` message. + method: + type: string + enum: ["m.sas.v1"] + description: |- + The verification method to use. Must be ``m.sas.v1``. + key_agreement_protocol: + type: string + description: |- + The key agreement protocol the device is choosing to use, out of the + options in the ``m.key.verification.start`` message. + hash: + type: string + description: |- + The hash method the device is choosing to use, out of the options in + the ``m.key.verification.start`` message. + message_authentication_code: + type: string + description: |- + The message authentication code the device is choosing to use, out of + the options in the ``m.key.verification.start`` message. + short_authentication_string: + type: array + description: |- + The SAS methods both devices involved in the verification process + understand. Must be a subset of the options in the ``m.key.verification.start`` + message. + items: + type: string + enum: ["decimal", "emoji"] + commitment: + type: string + description: |- + The hash (encoded as unpadded base64) of the concatenation of the device's + ephemeral public key (encoded as unpadded base64) and the canonical JSON + representation of the ``m.key.verification.start`` message. + required: + - transaction_id + - method + - key_agreement_protocol + - hash + - message_authentication_code + - short_authentication_string + - commitment + type: object + type: + enum: + - m.key.verification.accept + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.cancel b/event-schemas/schema/m.key.verification.cancel new file mode 100644 index 00000000..36ffc9ea --- /dev/null +++ b/event-schemas/schema/m.key.verification.cancel @@ -0,0 +1,70 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Cancels a key verification process/request. Typically sent as a `to-device`_ event. +properties: + content: + properties: + transaction_id: + type: string + description: |- + The opaque identifier for the verification process/request. + reason: + type: string + description: |- + A human readable description of the ``code``. The client should only rely on this + string if it does not understand the ``code``. + code: + type: string + # Note: this is not an enum because we go into detail about the different + # error codes. If we made this an enum, we'd be repeating information. + # Also, we can't put a real bulleted list in here because the HTML2RST parser + # cuts the text at weird points, breaking the list completely. + description: |- + The error code for why the process/request was cancelled by the user. Error + codes should use the Java package naming convention if not in the following + list: + + ``m.user``: The user cancelled the verification. + + ``m.timeout``: The verification process timed out. Verification processes + can define their own timeout parameters. + + ``m.unknown_transaction``: The device does not know about the given transaction + ID. + + ``m.unknown_method``: The device does not know how to handle the requested + method. This should be sent for ``m.key.verification.start`` messages and + messages defined by individual verification processes. + + ``m.unexpected_message``: The device received an unexpected message. Typically + raised when one of the parties is handling the verification out of order. + + ``m.key_mismatch``: The key was not verified. + + ``m.user_mismatch``: The expected user did not match the user verified. + + ``m.invalid_message``: The message received was invalid. + + ``m.accepted``: A ``m.key.verification.request`` was accepted by a different + device. The device receiving this error can ignore the verification request. + + Clients should be careful to avoid error loops. For example, if a device sends + an incorrect message and the client returns ``m.invalid_message`` to which it + gets an unexpected response with ``m.unexpected_message``, the client should not + respond again with ``m.unexpected_message`` to avoid the other device potentially + sending another error response. + + .. The above blank line is important for RST. + required: + - transaction_id + - code + - reason + type: object + type: + enum: + - m.key.verification.cancel + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.key b/event-schemas/schema/m.key.verification.key new file mode 100644 index 00000000..6dc4954b --- /dev/null +++ b/event-schemas/schema/m.key.verification.key @@ -0,0 +1,28 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Sends the ephemeral public key for a device to the partner device. Typically sent as a + `to-device`_ event. +properties: + content: + properties: + transaction_id: + type: string + description: |- + An opaque identifier for the verification process. Must be the same as + the one used for the ``m.key.verification.start`` message. + key: + type: string + description: |- + The device's ephemeral public key, encoded as unpadded base64. + required: + - transaction_id + - key + type: object + type: + enum: + - m.key.verification.key + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.mac b/event-schemas/schema/m.key.verification.mac new file mode 100644 index 00000000..769ebe15 --- /dev/null +++ b/event-schemas/schema/m.key.verification.mac @@ -0,0 +1,38 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Sends the MAC of a device's key to the partner device. Typically sent as a + `to-device`_ event. +properties: + content: + properties: + transaction_id: + type: string + description: |- + An opaque identifier for the verification process. Must be the same as + the one used for the ``m.key.verification.start`` message. + mac: + type: object + description: |- + A map of the key ID to the MAC of the key, using the algorithm in the + verification process. The MAC is encoded as unpadded base64. + additionalProperties: + type: string + description: The key's MAC, encoded as unpadded base64. + keys: + type: string + description: |- + The MAC of the comma-separated, sorted, list of key IDs given in the ``mac`` + property, encoded as unpadded base64. + required: + - transaction_id + - mac + - keys + type: object + type: + enum: + - m.key.verification.mac + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.request b/event-schemas/schema/m.key.verification.request new file mode 100644 index 00000000..c9efa14e --- /dev/null +++ b/event-schemas/schema/m.key.verification.request @@ -0,0 +1,43 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Requests a key verification with another user's devices. Typically sent as a + `to-device`_ event. +properties: + content: + properties: + from_device: + type: string + description: |- + The device ID which is initiating the request. + transaction_id: + type: string + description: |- + An opaque identifier for the verification request. Must be unique + with respect to the devices involved. + methods: + type: array + description: |- + The verification methods supported by the sender. + items: + type: string + timestamp: + type: integer + format: int64 + description: |- + The POSIX timestamp in milliseconds for when the request was made. If + the request is in the future by more than 5 minutes or more than 10 + minutes in the past, the message should be ignored by the receiver. + required: + - from_device + - transaction_id + - methods + - timestamp + type: object + type: + enum: + - m.key.verification.request + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.start b/event-schemas/schema/m.key.verification.start new file mode 100644 index 00000000..ad59d6c7 --- /dev/null +++ b/event-schemas/schema/m.key.verification.start @@ -0,0 +1,39 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Begins a key verification process. Typically sent as a `to-device`_ event. +properties: + content: + properties: + from_device: + type: string + description: |- + The device ID which is initiating the process. + transaction_id: + type: string + description: |- + An opaque identifier for the verification process. Must be unique + with respect to the devices involved. Must be the same as the + ``transaction_id`` given in the ``m.key.verification.request`` + if this process is originating from a request. + method: + type: string + description: |- + The verification method to use. + next_method: + type: string + description: |- + Optional method to use to verify the other user's key with. Applicable + when the ``method`` chosen only verifies one user's key. + required: + - from_device + - transaction_id + - method + type: object + type: + enum: + - m.key.verification.start + type: string +type: object diff --git a/event-schemas/schema/m.key.verification.start$m.sas.v1 b/event-schemas/schema/m.key.verification.start$m.sas.v1 new file mode 100644 index 00000000..867ca820 --- /dev/null +++ b/event-schemas/schema/m.key.verification.start$m.sas.v1 @@ -0,0 +1,69 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml + +description: |- + Begins a SAS key verification process. Typically sent as a `to-device`_ event. +properties: + content: + properties: + from_device: + type: string + description: |- + The device ID which is initiating the process. + transaction_id: + type: string + description: |- + An opaque identifier for the verification process. Must be unique + with respect to the devices involved. Must be the same as the + ``transaction_id`` given in the ``m.key.verification.request`` + if this process is originating from a request. + method: + type: string + enum: ["m.sas.v1"] + description: |- + The verification method to use. Must be ``m.sas.v1``. + key_agreement_protocols: + type: array + description: |- + The key agreement protocols the sending device understands. Must + include at least ``curve25519``. + items: + type: string + hashes: + type: array + description: |- + The hash methods the sending device understands. Must include at least + ``sha256``. + items: + type: string + message_authentication_codes: + type: array + description: |- + The message authentication codes that the sending device understands. + Must include at least ``hkdf-hmac-sha256``. + items: + type: string + short_authentication_string: + type: array + description: |- + The SAS methods the sending device (and the sending device's user) + understands. Must include at least ``decimal``. Optionally can include + ``emoji``. + items: + type: string + enum: ["decimal", "emoji"] + required: + - from_device + - transaction_id + - method + - key_agreement_protocols + - hashes + - message_authentication_codes + - short_authentication_string + type: object + type: + enum: + - m.key.verification.start + type: string +type: object diff --git a/scripts/continuserv/main.go b/scripts/continuserv/main.go index 2ef6fed9..1bd07e6e 100644 --- a/scripts/continuserv/main.go +++ b/scripts/continuserv/main.go @@ -52,7 +52,7 @@ func main() { walker := makeWalker(dir, w) paths := []string{"api", "changelogs", "event-schemas", "scripts", - "specification"} + "specification", "schemas", "data-definitions"} for _, p := range paths { filepath.Walk(path.Join(dir, p), walker) diff --git a/scripts/templating/matrix_templates/sections.py b/scripts/templating/matrix_templates/sections.py index c88959ed..7000916b 100644 --- a/scripts/templating/matrix_templates/sections.py +++ b/scripts/templating/matrix_templates/sections.py @@ -18,6 +18,7 @@ import inspect import json import os import logging +import re logger = logging.getLogger(__name__) @@ -225,3 +226,19 @@ class MatrixSections(Sections): examples=swagger_def['examples'], title_kind=subtitle_title_char) return rendered + + def render_sas_emoji_table(self): + emoji = self.units.get("sas_emoji") + rendered = ".. csv-table::\n" + rendered += " :header: \"Number\", \"Emoji\", \"Unicode\", \"Description\"\n" + rendered += " :widths: 10, 10, 15, 20\n" + rendered += "\n" + for row in emoji: + rendered += " %d, \"%s\", \"``%s``\", \"%s\"\n" % ( + row['number'], + row['emoji'], + row['unicode'], + row['description'], + ) + rendered += "\n" + return rendered diff --git a/scripts/templating/matrix_templates/units.py b/scripts/templating/matrix_templates/units.py index ddb65efe..fe3ba5d2 100644 --- a/scripts/templating/matrix_templates/units.py +++ b/scripts/templating/matrix_templates/units.py @@ -59,6 +59,8 @@ TARGETS = os.path.join(matrix_doc_dir, "specification/targets.yaml") ROOM_EVENT = "core-event-schema/room_event.yaml" STATE_EVENT = "core-event-schema/state_event.yaml" +SAS_EMOJI_JSON = os.path.join(matrix_doc_dir, "data-definitions/sas-emoji.json") + logger = logging.getLogger(__name__) # a yaml Loader which loads mappings into OrderedDicts instead of regular @@ -1088,3 +1090,21 @@ class MatrixUnits(Units): "string": git_version, "revision": git_commit } + + def load_sas_emoji(self): + with open(SAS_EMOJI_JSON, 'r', encoding='utf-8') as sas_json: + emoji = json.load(sas_json) + + # Verify the emoji matches the unicode + for c in emoji: + e = c['emoji'] + logger.info("Checking emoji %s (%s)", e, c['description']) + u = re.sub(r'U\+([0-9a-fA-F]+)', lambda m: chr(int(m.group(1), 16)), c['unicode']) + if e != u: + raise Exception("Emoji %s should be %s not %s" % ( + c['description'], + repr(e), + c['unicode'], + )) + + return emoji diff --git a/specification/modules/end_to_end_encryption.rst b/specification/modules/end_to_end_encryption.rst index 1fd28e93..4bd12b71 100644 --- a/specification/modules/end_to_end_encryption.rst +++ b/specification/modules/end_to_end_encryption.rst @@ -396,8 +396,8 @@ recommended that clients provide mechanisms by which the user can see: Ed25519 signing key for each device, again encoded using unpadded Base64. Alice can then meet Bob in person, or contact him via some other trusted -medium, and ask him to read out the Ed25519 key shown on his device. She -compares this with the value shown for his device on her client. +medium, and use `SAS Verification`_ or ask him to read out the Ed25519 key +shown on his device, comparing it to the one shown on Alice's device. Device verification may reach one of several conclusions. For example: @@ -423,6 +423,327 @@ Device verification may reach one of several conclusions. For example: decrypted by such a device. For the Olm protocol, this is documented at https://matrix.org/git/olm/about/docs/signing.rst. + +Key verification framework +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Verifying keys manually by reading out the Ed25519 key is not very user friendly, +and can lead to errors. In order to help mitigate errors, and to make the process +eaiser for users, some verification methods are supported by the specification. +The methods all use a common framework for negotiating the key verification. + +To use this framework, Alice's client would send ``m.key.verification.request`` +events to Bob's devices. All of the ``to_device`` messages sent to Bob MUST have +the same ``transaction_id`` to indicate they are part of the same request. This +allows Bob to reject the request on one device, and have it apply to all of his +devices. Similarly, it allows Bob to process the verification on one device without +having to involve all of his devices. + +When Bob's device receives a ``m.key.verification.request``, it should prompt Bob +to verify keys with Alice using one of the supported methods in the request. If +Bob's device does not understand any of the methods, it should not cancel the request +as one of his other devices may support the request. Instead, Bob's device should +tell Bob that an unsupported method was used for starting key verification. The +prompt for Bob to accept/reject Alice's request (or the unsupported method prompt) +should be automatically dismissed 10 minutes after the ``timestamp`` field or 2 +minutes after Bob's client receives the message, whichever comes first, if Bob +does not interact with the prompt. The prompt should additionally be hidden if +an appropriate ``m.key.verification.cancel`` message is received. + +If Bob rejects the request, Bob's client must send a ``m.key.verification.cancel`` +message to Alice's device. Upon receipt, Alice's device should tell her that Bob +does not want to verify her device and send ``m.key.verification.cancel`` messages +to all of Bob's devices to notify them that the request was rejected. + +If Bob accepts the request, Bob's device starts the key verification process by +sending a ``m.key.verification.start`` message to Alice's device. Upon receipt +of this message, Alice's device should send a ``m.key.verification.cancel`` message +to all of Bob's other devices to indicate the process has been started. The start +message must use the same ``transaction_id`` from the original key verification +request if it is in response to the request. The start message can be sent indepdently +of any request. + +Individual verification methods may add additional steps, events, and properties to +the verification messages. Event types for methods defined in this specification must +be under the ``m.key.verification`` namespace and any other event types must be namespaced +according to the Java package naming convention. + +Any of Alice's or Bob's devices can cancel the key verification request or process +at any time with a ``m.key.verification.cancel`` message to all applicable devices. + +This framework yields the following handshake, assuming both Alice and Bob each have +2 devices, Bob's first device accepts the key verification request, and Alice's second +device initiates the request. Note how Alice's first device is not involved in the +request or verification process. + +:: + + +---------------+ +---------------+ +-------------+ +-------------+ + | AliceDevice1 | | AliceDevice2 | | BobDevice1 | | BobDevice2 | + +---------------+ +---------------+ +-------------+ +-------------+ + | | | | + | | m.key.verification.request | | + | |---------------------------------->| | + | | | | + | | m.key.verification.request | | + | |-------------------------------------------------->| + | | | | + | | m.key.verification.start | | + | |<----------------------------------| | + | | | | + | | m.key.verification.cancel | | + | |-------------------------------------------------->| + | | | | + + +After the handshake, the verification process begins. + +{{m_key_verification_request_event}} + +{{m_key_verification_start_event}} + +{{m_key_verification_cancel_event}} + + +.. _`SAS Verification`: + +Short Authentication String (SAS) verification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SAS verification is a user-friendly key verification process built off the common +framework outlined above. SAS verification is intended to be a highly interactive +process for users, and as such exposes verfiication methods which are easier for +users to use. + +The verification process is heavily inspired by Phil Zimmerman's ZRTP key agreement +handshake. A key part of key agreement in ZRTP is the hash commitment: the party that +begins the Diffie-Hellman key sharing sends a hash of their part of the Diffie-Hellman +exchange, and does not send their part of the Diffie-Hellman exchange until they have +received the other party's part. Thus an attacker essentially only has one attempt to +attack the Diffie-Hellman exchange, and hence we can verify fewer bits while still +achieving a high degree of security: if we verify n bits, then an attacker has a 1 in +2\ :sup:`n` chance of success. For example, if we verify 40 bits, then an attacker has +a 1 in 1,099,511,627,776 chance (or less than 1 in 1012 chance) of success. A failed +attack would result in a mismatched Short Authentication String, alerting users to the +attack. + +The verification process takes place over `to-device`_ messages in two phases: + +1. Key agreement phase (based on `ZRTP key agreement `_). +#. Key verification phase (based on HMAC). + +The process between Alice and Bob verifying each other would be: + +.. |AlicePublicKey| replace:: :math:`K_{A}^{public}` +.. |AlicePrivateKey| replace:: :math:`K_{A}^{private}` +.. |AliceCurve25519| replace:: :math:`K_{A}^{private}K_{A}^{public}` +.. |BobPublicKey| replace:: :math:`K_{B}^{public}` +.. |BobPrivateKey| replace:: :math:`K_{B}^{private}` +.. |BobCurve25519| replace:: :math:`K_{B}^{private}K_{B}^{public}` +.. |AliceBobCurve25519| replace:: :math:`K_{A}^{private}K_{B}^{public}` +.. |BobAliceCurve25519| replace:: :math:`K_{B}^{private}K_{A}^{public}` +.. |AliceBobECDH| replace:: :math:`ECDH(K_{A}^{private},K_{B}^{public})` + +1. Alice and Bob establish a secure connection, likely meeting in-person. "Secure" + here means that either party cannot be impersonated, not explicit secrecy. +#. Alice and Bob communicate which devices they'd like to verify with each other. +#. Alice selects Bob's device from the device list and begins verification. +#. Alice's client ensures it has a copy of Bob's device key. +#. Alice's device sends Bob's device a ``m.key.verification.start`` message. +#. Bob's device receives the message and selects a key agreement protocol, hash + algorithm, message authentication code, and SAS method supported by Alice's + device. +#. Bob's device ensures it has a copy of Alice's device key. +#. Bob's device creates an ephemeral Curve25519 key pair (|BobCurve25519|), and + calculates the hash (using the chosen algorithm) of the public key |BobPublicKey|. +#. Bob's device replies to Alice's device with a ``m.key.verification.accept`` message. +#. Alice's device receives Bob's message and stores the commitment hash for later use. +#. Alice's device creates an ephemeral Curve25519 key pair (|AliceCurve25519|) and + replies to Bob's device with a ``m.key.verification.key``, sending only the public + key |AlicePublicKey|. +#. Bob's device receives Alice's message and replies with its own ``m.key.verification.key`` + message containing its public key |BobPublicKey|. +#. Alice's device receives Bob's message and verifies the commitment hash from earlier + matches the hash of the key Bob's device just sent and the content of Alice's + ``m.key.verification.start`` message. +#. Both Alice and Bob's devices perform an Elliptic-curve Diffie-Hellman (|AliceBobECDH|), + using the result as the shared secret. +#. Both Alice and Bob's devices display a SAS to their users, which is derived + from the shared key using one of the methods in this section. If multiple SAS + methods are available, clients should allow the users to select a method. +#. Alice and Bob compare the strings shown by their devices, and tell their devices if + they match or not. +#. Assuming they match, Alice and Bob's devices calculate the HMAC of their own device + and a comma-separated sorted list of of the key IDs that they wish the other user + to verify. HMAC is defined in RFC 2104, and SHA-256 as the hash function. The key for + the HMAC is different for each item and is calculated by generating 32 bytes (256 bits) + using `the key verification HKDF <#SAS-HKDF>`_. +#. Alice's device sends Bob's device a ``m.key.verification.mac`` message containing the + MAC of Alice's device keys and the MAC of her key IDs to be verified. Bob's device does + the same for Bob's device keys and key IDs concurrently with Alice. +#. When the other device receives the ``m.key.verification.mac`` message, the device + calculates the HMAC of its copies of the other device's keys given in the message, + as well as the HMAC of the comma-seperated, sorted, list of key IDs in the message. + The device compares these with the HMAC values given in the message, and if everything + matches then the device keys are verified. + +The wire protocol looks like the following between Alice and Bob's devices:: + + +-------------+ +-----------+ + | AliceDevice | | BobDevice | + +-------------+ +-----------+ + | | + | m.key.verification.start | + |-------------------------------->| + | | + | m.key.verification.accept | + |<--------------------------------| + | | + | m.key.verification.key | + |-------------------------------->| + | | + | m.key.verification.key | + |<--------------------------------| + | | + | m.key.verification.mac | + |-------------------------------->| + | | + | m.key.verification.mac | + |<--------------------------------| + | | + +Error and exception handling +<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +At any point the interactive verfication can go wrong. The following describes what +to do when an error happens: + +* Alice or Bob can cancel the verification at any time. A ``m.key.verification.cancel`` + message must be sent to signify the cancelation. +* The verification can time out. Clients should time out a verification that does not + complete within 5 minutes. Additionally, clients should expire a ``transaction_id`` + which goes unused for 5 minutes after having last sent/received it. The client should + inform the user that the verification timed out, and send an appropriate ``m.key.verification.cancel`` + message to the other device. +* When the same device attempts to intiate multiple verification attempts, cancel all + attempts with that device. +* When a device receives an unknown ``transaction_id``, it should send an appropriate + ``m.key.verfication.cancel`` message to the other device indicating as such. This + does not apply for inbound ``m.key.verification.start`` or ``m.key.verification.cancel`` + messages. +* If the two devices do not share a common key share, hash, HMAC, or SAS method then + the device should notify the other device with an appropriate ``m.key.verification.cancel`` + message. +* If the user claims the Short Authentication Strings do not match, the device should + send an appropriate ``m.key.verification.cancel`` message to the other device. +* If the device receives a message out of sequence or that it was not expecting, it should + notify the other device with an appropriate ``m.key.verification.cancel`` message. + + +Verification messages specific to SAS +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +Building off the common framework, the following events are involved in SAS verification. + +The ``m.key.verification.cancel`` event is unchanged, however the following error codes +are used in addition to those already specified: + +* ``m.unknown_method``: The devices are unable to agree on the key agreement, hash, MAC, + or SAS method. +* ``m.mismatched_commitment``: The hash commitment did not match. +* ``m.mismatched_sas``: The SAS did not match. + + +{{m_key_verification_start_m_sas_v1_event}} + +{{m_key_verification_accept_event}} + +{{m_key_verification_key_event}} + +{{m_key_verification_mac_event}} + + +.. _`SAS-HKDF`: + +HKDF calculation +<<<<<<<<<<<<<<<< + +In all of the SAS methods, HKDF is as defined in RFC 5869 and uses the previously +agreed upon hash function for the hash function. The shared secret is supplied +as the input keying material. No salt is used, and the input parameter is the +concatenation of: + + * The string ``MATRIX_KEY_VERIFICATION_SAS``. + * The Matrix ID of the user who sent the ``m.key.verification.start`` message. + * The Device ID of the device which sent the ``m.key.verification.start`` message. + * The Matrix ID of the user who sent the ``m.key.verification.accept`` message. + * The Device ID of the device which sent the ``m.key.verification.accept`` message. + * The ``transaction_id`` being used. + +HKDF is used over the plain shared secret as it results in a harder attack +as well as more uniform data to work with. + +For verification of each party's device keys, HKDF is as defined in RFC 5869 and +uses SHA-256 as the hash function. The shared secret is supplied as the input keying +material. No salt is used, and in the input parameter is the concatenation of: + + * The string ``MATRIX_KEY_VERIFICATION_MAC``. + * The Matrix ID of the user whose key is being MAC-ed. + * The Device ID of the device sending the MAC. + * The Matrix ID of the other user. + * The Device ID of the device receiving the MAC. + * The ``transaction_id`` being used. + * The Key ID of the key being MAC-ed, or the string ``KEY_IDS`` if the item + being MAC-ed is the list of key IDs. + +SAS method: ``decimal`` +<<<<<<<<<<<<<<<<<<<<<<< + +Generate 5 bytes using `HKDF <#SAS-HKDF>`_ then take sequences of 13 bits to +convert to decimal numbers (resulting in 3 numbers between 0 and 8191 inclusive +each). Add 1000 to each calculated number. + +The bitwise operations to get the numbers given the 5 bytes +:math:`B_{0}, B_{1}, B_{2}, B_{3}, B_{4}` would be: + +* First: :math:`(B_{0} \ll 5 | B_{1} \gg 3) + 1000` +* Second: :math:`(B_{1} \& 0x7 | B_{2} \ll 2 | B_{3} \gg 6) + 1000` +* Third: :math:`((B_{3} \& 0x3F) \ll 7 | B_{4} \gg 1) + 1000` + +The digits are displayed to the user either with an appropriate separator, +such as dashes, or with the numbers on individual lines. + +SAS method: ``emoji`` +<<<<<<<<<<<<<<<<<<<<< + +Generate 6 bytes using `HKDF <#SAS-HKDF>`_ then split the first 42 bits into +7 groups of 6 bits, similar to how one would base64 encode something. Convert +each group of 6 bits to a number and use the following table to get the corresponding +emoji: + +{{sas_emoji_table}} + +.. Note:: + This table is available as JSON at + https://github.com/matrix-org/matrix-doc/blob/master/data-definitions/sas-emoji.json + +.. admonition:: Rationale + + The emoji above were chosen to: + + * Be recognisable without colour. + * Be recognisable at a small size. + * Be recognisable by most cultures. + * Be distinguishable from each other. + * Easily described by a few words. + * Avoid symbols with negative connotations. + * Be likely similar across multiple platforms. + +Clients SHOULD show the emoji with the descriptions from the table, or appropriate +translation of those descriptions. Client authors SHOULD collaborate to create a +common set of translations for all languages. + + .. section name changed, so make sure that old links keep working .. _key-sharing: