17 KiB
Bi-directional Key verification using QR codes
Problem/Background
Key verification is essential in ensuring that end-to-end encrypted messages cannot be read by unauthorized parties. Traditionally, key verification is done by comparing long strings. To save users from the tedium of reading out long strings, some systems allow one party to verify the other party by scanning a QR code; by doing this twice, both parties can verify each other. In this proposal, we present a method for both parties to verify each other by only scanning one QR code.
Proposal
When Alice and Bob meet in person to verify keys, Alice will scan a QR code generated by Bob's device. The QR code will encode both Bob's key as well as what Bob thinks Alice's key is. When Alice scans the QR code, she will ensure that the keys match what is expected, in which case, she relays this information to Bob, who can then tell his device that the keys match.
Example flow
-
Alice and Bob meet in person, and want to verify each other's keys.
-
Alice requests a key verification through her device by sending an
m.key.verification.request
message (see MSC2241), withm.qr_code.show.v1
,m.qr_code.scan.v1
, andm.reciprocate.v1
listed inmethods
, and Bob responds with am.key.verification.ready
message. -
Alice's client displays a QR code that Bob is able to scan, and an option to scan Bob's QR code.
-
Bob's client prompts Bob to verify Alice's key. The prompt includes a QR code that Alice can scan (if the
m.key.verification.request
message listedm.qr_code.scan.v1
), and an option to scan Alice's QR code (if them.key.verification.request
message listedm.qr_code.show.v1
). The QR code encodes:- Bob's master cross-signing public key,
- what Bob thinks Alice's master cross-signing public key is,
- a random shared secret.
-
Alice scans Bob's QR code.
-
Alice's device ensures that:
- Bob's key encoded in the QR code match the key that she already has for Bob, and
- Alice's cross-signing key matches the cross-signing key encoded in the QR code.
If any of these checks fail, Alice's device displays an error message indicating that the code is incorrect, and sends a
m.key.verification.cancel
message to Bob's device.Otherwise, at this point:
- Alice's device has now verified Bob's key, and
- Alice's device knows that Bob has the correct key for her.
Thus for Bob to verify Alice's key, Alice needs to tell Bob that he has the right key.
-
Alice's device displays a message saying that all is well. This message tells Alice that she has the right key for Bob, and tells Bob that he has the right key for Alice.
-
Alice's device sends a
m.key.verification.start
message withmethod
set tom.reciprocate.v1
to Bob (see below). The message includes the shared secret from the QR code. This signals to Bob's device that Alice has scanned Bob's QR code.This message is merely a signal for Bob's device to proceed to the next step, and is not used for verification purposes.
-
Upon receipt of the
m.key.verification.start
message, Bob's device ensures that the shared secret matches.If the shared secret does not match, it should display an error message indicating that an attack was attempted. (This does not affect Alice's verification of Bob's keys.)
If the shared secret does match, it asks Bob to confirm that Alice has scanned the QR code.
-
Bob sees Alice's device confirm that the key matches, and presses the button on his device to indicate that Alice's key is verified.
Bob's verification of Alice's key hinges on Alice telling Bob the result of her scan. Since the QR code includes what Bob thinks Alice's key is, Alice's device can check whether Bob has the right key for her. Alice has no motivation to lie about the result, as getting Bob to trust an incorrect key would only affect communications between herself and Bob. Thus Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key, under the assumption that this communication is done over a trusted medium (such as in-person).
-
Both devices send an
m.key.verification.done
message.
This flow allows Alice to verify Bob's key, and Bob to verify Alice's key. Alice verifies Bob's key because she can trust the QR code that Bob displays for her, as this is done over a trusted medium. Bob verifies Alice's key because Alice can trust the QR code that Bob displays, and Bob can trust Alice to tell him the result of the verification.
Self-verification
QR codes can also be used by a user to verify their own devices. These examples shows Alice verifying two devices, one of them (Osborne2) having cross-signing already set up, and the other one (Dynabook) having just logged in.
In the first example, Osborne2 scans Dynabook:
-
Alice logs into her new Dynabook and wants other users to be able to trust it via cross-signing, and to trust other devices via cross-signing.
-
Dynabook retrieves Alice's public cross-signing key from the server, and displays a QR code that encodes:
- Dynabook's device key,
- what it thinks Alice's master key is, and
- a random shared secret.
Note that in this case, the QR code does not include Alice's master key in a
key_<key_id>
parameter, since Dynabook does not know whether it is trusted or not. -
Osborne2 scans the QR code displayed by Dynabook. At this point, Osborne2 knows Dynabook's device key and can sign it with the self-signing key and upload the signature, and can trust Dynabook for sending secrets via SSSS. It also knows that Dynabook has the correct cross-signing key.
-
Osborne2 tells Alice that the scan was successful, and sends the
reciprocate
message containing the shared secret. -
Upon receipt of the
reciprocate
message, Dynabook (after checking the shared secret) confirms with Alice that she successfully scanned the QR code. -
Alice confirms.
-
Dynabook now knows that it can trust Alice's cross-signing keys that it fetched from the server.
In the second example, Dynabook scans Osborne2:
- Alice logs into her new Dynabook and wants other users to be able to trust it via cross-signing, and to trust other devices via cross-signing.
- Osborne2 notices that Dynabook is a new device. Osborne2 fetches Dynabook's
identity key and displays a QR code that encodes:
- what it thinks Dynabook's key is,
- Alice's master key, and
- a random shared secret.
- Dynabook scans the QR code shown by Osborne2. At this point, Dynabook knows Alice's cross-signing key, and so it can trust it to sign other devices. It also knows that Osborne2 as the correct key for it.
- Dynabook tells Alice that the scan is successful, and sends the
reciprocate
message containing the shared secret. - Upon receipt of the
reciprocate
message, Osborne2 (after checking the shared secret) confirms with Alice that she successfully scanned the QR code. - Alice confirms.
- Osborne2 now knows that it has the correct device key for Dynabook, and can sign it with the self-signing key and upload the signature. Osborne2 can also trust Dynabook for sending secrets via SSSS.
Verification methods
This proposal defines three verification methods that can be used in
m.key.verification.request
messages (see
MSC2241).
m.qr_code.show.v2
: means that the sender of them.key.verification.request
message can show a QR code that the recipient can scan. If the recipient can scan the QR code, it should allow the user to do so. This method is never sent as part of am.key.verification.start
message.m.qr_code.scan.v2
: means that the sender of them.key.verification.request
message can scan a QR code displayed by the recipient. If the recipient can display a QR code, it should allow the user to display it so that the sender can scan it. This method is never sent as part of am.key.verification.start
message.m.reciprocate.v1
: means that the sender can participate in a reciprocal verification, either as initiator or responder, as described in the Message types section below.
QR code format
Note: only one of the following will be supported. They are all being documented here while we determine how well different formats are supported.
Binary format
The QR codes to be displayed and scanned using this format will encode binary strings in the general form:
- the ASCII string "MATRIX"
- one byte indicating the QR code version (must be
0x02
) - one byte indicating the QR code verification mode. May be one of the
following values:
0x00
verifying another user with cross-signing0x01
self-verifying in which the current device does trust the master key0x02
self-verifying in which the current device does not yet trust the master key
- the event ID or
transaction_id
of the associated verification request event, encoded as:- two bytes in network byte order (big-endian) indicating the length in bytes of the ID as a UTF-8 string
- the ID as a UTF-8 string
- the first key, as 32 bytes. The key to use depends on the mode field:
- if
0x00
or0x01
, then the user's own master cross-signing public key - if
0x02
, then the current device's device key
- if
- the second key, as 32 bytes. The key to use depends on the mode field:
- if
0x00
, then what the device thinks the other user's master cross-signing key is - if
0x01
, then what the device thinks the other device's device key is - if
0x02
, then what the device thinks the user's master cross-signing key is
- if
- a random shared secret, as a byte string. It is suggested to use a secret that is about 8 bytes long. Note: as we do not share the length of the secret, and it is not a fixed size, clients will just use the remainder of binary string as the shared secret.
For example, if Alice displays a QR code encoding the following binary string:
"MATRIX" |ver|mode| len | event ID
4D 41 54 52 49 58 02 00 00 2D 21 41 42 43 44 ...
| user's cross-signing key | other user's cross-signing key | shared secret
00 01 02 03 04 05 06 07 ... 10 11 12 13 14 15 16 17 ... 20 21 22 23 24 25 26 27
this indicates that Alice is verifying another user (say Bob), in response to
the request from event "!ABCD...", her cross-signing key is
0001020304050607...
(which is "AAECAwQFBg..." in base64), she thinks that
Bob's cross-signing key is 1011121314151617...
(which is "EBESExQVFh..." in
base64), and the shared secret is 2021222324252627
(which is "ICEiIyQlJic" in
base64).
Base32 format
The QR codes to be displayed and scanned using this format will encode alphanumeric strings generated as follows:
- generate a binary string by concatenating:
- the event ID or
transaction_id
of the associated verification request event, encoded as:- two bytes in network byte order (big-endian) indicating the length in bytes of the ID as a UTF-8 string
- the ID as a UTF-8 string
- the first key, as 32 bytes. The key to use depends on the mode field as
described in step 3:
- if "0" or "1", then the user's own master cross-signing public key
- if "2", then the current device's device key
- the second key, as 32 bytes. The key to use depends on the mode field:
- if "0", then what the device thinks the other user's master cross-signing key is
- if "1", then what the device thinks the other device's device key is
- if "2", then what the device thinks the user's master cross-signing key is
- a random shared secret, as a byte string. It is suggested to use a secret that is about 8 bytes long. Note: as we do not share the length of the secret, and it is not a fixed size, clients will just use the remainder of binary string as the shared secret.
- encode the above string using unpadded base32 as defined in RFC4648
- prepend the resulting string with
- the string "MATRIX"
- one character indicating the version (must by "2")
- one character indicating the QR code verification mode. May be one of the
following values:
- "0" verifying another user with cross-signing
- "1" self-verifying in which the current device does trust the master key
- "2" self-verifying in which the current device does not yet trust the master key
The QR code is then generated using alphanumeric encoding mode.
Base64 format
The QR codes to be displayed and scanned using this format will be a string of the following form:
- the string "MATRIX"
- one character indicating the version (must by "2")
- one character indicating the QR code verification mode. May be one of the
following values:
- "0" verifying another user with cross-signing
- "1" self-verifying in which the current device does trust the master key
- "2" self-verifying in which the current device does not yet trust the master key
- the event ID or
transaction_id
of the associated verification request event, encoded as:- two bytes in network byte order (big-endian), encoded in unpadded base64 (3 characters), indicating the length in bytes of the ID as a UTF-8 string
- the ID as a UTF-8 string
- the first key as unpadded base64 (43 characters). The key to use depends on
the mode field:
- if "0" or "1", then the user's own master cross-signing public key
- if "2", then the current device's device key
- the second key as unpadded base64 (43 characters). The key to use depends on
the mode field:
- if "0", then what the device thinks the other user's master cross-signing key is
- if "1", then what the device thinks the other device's device key is
- if "2", then what the device thinks the user's master cross-signing key is
- a random shared secret, as an ASCII string. It is suggested to use a secret that is about 11 bytes long. (This is approximately the length of 8 bytes as a base64 string.) Note: as we do not share the length of the secret, and it is not a fixed size, clients will just use the remainder of binary string as the shared secret.
Message types
m.key.verification.start
Alice's device tells Bob's device that the QR code has been scanned.
message contents:
method
:m.reciprocate.v1
m.relates_to
: as per key verification frameworksecret
: the shared secret from the QR code, encoded using unpadded base64
Example:
{
"method": "m.reciprocate.v1",
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "!event_id_of_verification_request"
},
"secret": "shared+secret"
}
Note that this message could be sent by either the sender or the recipient of
the m.key.verification.request
message, depending on which user scanned the
QR code.
Cancellation
In addition to the cancellation codes specified in the spec for
m.key.verification.cancel
,
the following cancellation codes may be used:
m.qr_code.invalid
: The QR code is invalid (e.g. it is not a URL of the required form)
The verification can also be cancelled with the error codes:
m.key_mismatch
: if the QR code has keys that do not match the expected valuem.user_mismatch
: if the QR code is for a different user from what was expected
Tradeoffs/Alternatives
Other methods of verifying keys, which do not require scanning QR codes, are needed for devices that are unable to scan QR codes. One such method is MSC1267. Since the key verification framework allows for multiple methods to be supported, clients can allow users to use different methods depending on their capability.
Rather than embedding the keys in the QR codes directly, the two clients could perform an exchange similar to MSC1267, and encoding the Short Authentication String code in the QR code. However, this means that the clients must exchange several messages before they can verify each other, which would delay showing the QR codes. This proposal is also simpler to implement.
This proposal does not support the case of asynchronous verification, such as printing a QR code on a business card for others to scan. That may be address in a separate MSC.
Security Considerations
The security of verifying Alice's key depends on Bob not hitting the "Verified"
button (step 10 in the example flow) until after Alice's device indicates
success or failure. Users have a tendency to click on buttons without reading
what the screen says, but this is partially mitigated by the fact that it is
unlikely that Bob will be interacting with the device while Alice is scanning
and Alice's device will display the verification results immediately upon
scanning. Also, Bob's device will not display the button until it receives the
m.key.verification.start
message that contains the shared secret from the QR
code, which means that an attacker would need to be physically present while
Alice and Bob verify. This issue can also be addressed by allowing Bob to
easily undo the verification if Alice's device displays an error.