Merge pull request #2072 from matrix-org/travis/1.0/msc1717-msc1267-sas-verification

Spec SAS verification and the common key verification framework
pull/977/head
Travis Ralston 6 years ago committed by GitHub
commit 4b6e2cc956
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1 @@
Add interactive device verification, including a common framework for device verification.

@ -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"}
]

@ -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"
}
}

@ -0,0 +1,8 @@
{
"type": "m.key.verification.cancel",
"content": {
"transaction_id": "S0meUniqueAndOpaqueString",
"code": "m.user",
"reason": "User rejected the key verification request"
}
}

@ -0,0 +1,7 @@
{
"type": "m.key.verification.key",
"content": {
"transaction_id": "S0meUniqueAndOpaqueString",
"key": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
}
}

@ -0,0 +1,10 @@
{
"type": "m.key.verification.mac",
"content": {
"transaction_id": "S0meUniqueAndOpaqueString",
"keys": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA",
"mac": {
"ed25519:ABCDEF": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
}
}
}

@ -0,0 +1,11 @@
{
"type": "m.key.verification.request",
"content": {
"from_device": "AliceDevice2",
"transaction_id": "S0meUniqueAndOpaqueString",
"methods": [
"m.sas.v1"
],
"timestamp": 1559598944869
}
}

@ -0,0 +1,8 @@
{
"type": "m.key.verification.start",
"content": {
"from_device": "BobDevice1",
"transaction_id": "S0meUniqueAndOpaqueString",
"method": "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"]
}
}

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

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

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

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

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

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

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

@ -52,7 +52,7 @@ func main() {
walker := makeWalker(dir, w) walker := makeWalker(dir, w)
paths := []string{"api", "changelogs", "event-schemas", "scripts", paths := []string{"api", "changelogs", "event-schemas", "scripts",
"specification"} "specification", "schemas", "data-definitions"}
for _, p := range paths { for _, p := range paths {
filepath.Walk(path.Join(dir, p), walker) filepath.Walk(path.Join(dir, p), walker)

@ -18,6 +18,7 @@ import inspect
import json import json
import os import os
import logging import logging
import re
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -225,3 +226,19 @@ class MatrixSections(Sections):
examples=swagger_def['examples'], examples=swagger_def['examples'],
title_kind=subtitle_title_char) title_kind=subtitle_title_char)
return rendered 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

@ -59,6 +59,8 @@ TARGETS = os.path.join(matrix_doc_dir, "specification/targets.yaml")
ROOM_EVENT = "core-event-schema/room_event.yaml" ROOM_EVENT = "core-event-schema/room_event.yaml"
STATE_EVENT = "core-event-schema/state_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__) logger = logging.getLogger(__name__)
# a yaml Loader which loads mappings into OrderedDicts instead of regular # a yaml Loader which loads mappings into OrderedDicts instead of regular
@ -1088,3 +1090,21 @@ class MatrixUnits(Units):
"string": git_version, "string": git_version,
"revision": git_commit "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

@ -384,20 +384,10 @@ man-in-the-middle. This verification process requires an out-of-band channel:
there is no way to do it within Matrix without trusting the administrators of there is no way to do it within Matrix without trusting the administrators of
the homeservers. the homeservers.
In Matrix, the basic process for device verification is for Alice to verify In Matrix, verification works by Alice meeting Bob in person, or contacting him
that the public Ed25519 signing key she received via ``/keys/query`` for Bob's via some other trusted medium, and use `SAS Verification`_ to interactively
device corresponds to the private key in use by Bob's device. For now, it is verify Bob's devices. Alice and Bob may also read aloud their unpadded base64
recommended that clients provide mechanisms by which the user can see: encoded Ed25519 public key, as returned by ``/keys/query``.
1. The public part of their device's Ed25519 signing key, encoded using
`unpadded Base64`_.
2. The list of devices in use for each user in a room, along with the public
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.
Device verification may reach one of several conclusions. For example: Device verification may reach one of several conclusions. For example:
@ -423,6 +413,328 @@ Device verification may reach one of several conclusions. For example:
decrypted by such a device. For the Olm protocol, this is documented at decrypted by such a device. For the Olm protocol, this is documented at
https://matrix.org/git/olm/about/docs/signing.rst. 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 <https://tools.ietf.org/html/rfc6189#section-4.4.1>`_).
#. 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}`
.. |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 out-of-band connection, such as meeting
in-person or a video call. "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 keys
and a comma-separated sorted list of of the key IDs that they wish the other user
to verify, using SHA-256 as the hash function. HMAC is defined in [RFC 2104](https://tools.ietf.org/html/rfc2104).
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 cancellation.
* The verification can time out. Clients should time out a verification that does not
complete within 10 minutes. Additionally, clients should expire a ``transaction_id``
which goes unused for 10 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, the receipient
should 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](https://tools.ietf.org/html/rfc5869)
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.
.. admonition:: Rationale
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) \ll 10 | 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 .. section name changed, so make sure that old links keep working
.. _key-sharing: .. _key-sharing:

Loading…
Cancel
Save