There are two possible scenarios when pairing two devices. In the simplest case, one of the devices is already connected to Sync. This is the only scenario considered in v1/v2. In other other scenario, none of the devices are connected to Sync and the user must connect to or create a Sync account on one of them after the pairing. This scenario is new in v3.
For the sake of this document, “Mobile” refers to a mobile device or a desktop computer, and “Desktop” refers to a desktop computer.
Mobile Server Desktop
===================================================================
|
retrieve channel <---------------|
generate random secret |
show PIN = secret + channel | ask user for PIN
upload Mobile's message 1 ------>|
|----> retrieve Mobile's message 1
|<----- upload Desktop's message 1
retrieve Desktop's message 1 <---|
upload Mobile's message 2 ------>|
|----> retrieve Mobile's message 2
| compute key
|<----- upload Desktop's message 2
retrieve Desktop's message 2 <---|
compute key |
encrypt known value ------------>|
|-------> retrieve encrypted value
| verify against local known value
At this point Desktop knows whether the PIN was entered correctly.
If it wasn't, Desktop deletes the session. If it was, the account
setup can proceed. If Desktop doesn't have an account set up yet,
it will keep the channel open and let the user connect to or
create an account.
| encrypt credentials
|<------------- upload credentials
retrieve credentials <-----------|
verify HMAC |
decrypt credentials |
delete session ----------------->|
start syncing |
Mobile asks server for new channel ID (4 characters a-z0-9)
C: GET /new_channel HTTP/1.1
S: "a7id"
Mobile generates PIN from random weak secret (4 characters a-z0-9) and the channel ID, computes and uploads J-PAKE msg 1.
New in v2: To prevent double uploads in case of retries, the
If-None-Match: *
header may be specified. This ensures that the message
is only uploaded if the channel is empty. If it is not then the request
will fail with a 412 Precondition Failed which should be considered the
same as 200 OK. The 412 will also contain the Etag of the data was the
client just uploaded.
C: PUT /a7id HTTP/1.1
C: If-None-Match: *
C:
C: {
C: 'type': 'receiver1',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'gx1': '45...9b',
C: 'zkp_x1': {
C: 'b': '09e22607ead737150b1a6e528d0c589cb6faa54a',
C: 'gr': '58...7a'
C: 'id': 'receiver',
C: }
C: 'gx2': 'be...93',
C: 'zkp_x2': {
C: 'b': '222069aabbc777dc988abcc56547cd944f056b4c',
C: 'gr': '5c...23'
C: 'id': 'receiver',
C: }
C: }
C: }
Success response:
S: HTTP/1.1 200 OK
S: ETag: "etag-of-receiver1-message"
New in v2: Response that will be returned on retries if the Desktop already replaced the message:
S: HTTP/1.1 412 Precondition Failed
S: ETag: "etag-of-receiver1-message"
Desktop asks user for the PIN, extracts channel ID and weak secret, fetches Mobile’s msg 1:
C: GET /a7id HTTP/1.1
Success response:
S: HTTP/1.1 200 OK
S: ETag: "etag-of-receiver1-message"
New in v3: Prior to v3, clients would only allow a 10 second timeout for messages after the first. This means that if Desktop does not yet have credentials, a Mobile client that implements v2 or lower will not wait for the account setup to finish. Desktop should therefore detect Mobile’s API version at this point and abort the pairing right away if there are no credentials present on Desktop.
Desktop computes and uploads msg 1.
New in v2: The If-Match
header may be set so that we only upload this
message if the other side’s previous message is still in the channel. This
is to prevent double PUTs during retries. If a 412 is received then it
means that our first PUT was actually correctly received by the server and
that the other side has already uploaded its next message.
So just consider the 412 to be a 200.
C: PUT /a7id HTTP/1.1
C: If-Match: "etag-of-receiver1-message"
C:
C: {
C: 'type': 'sender1',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'gx1': '45...9b',
C: 'zkp_x1': {
C: 'b': '09e22607ead737150b1a6e528d0c589cb6faa54a',
C: 'gr': '58...7a'
C: 'id': 'sender',
C: }
C: 'gx2': 'be...93',
C: 'zkp_x2': {
C: 'b': '222069aabbc777dc988abcc56547cd944f056b4c',
C: 'gr': '5c...23'
C: 'id': 'sender',
C: }
C: }
C: }
Success response:
S: HTTP/1.1 200 OK
S: Etag: "etag-of-sender1-message"
New in v2: Response that will be returned on retries if Mobile already replaced the message:
S: HTTP/1.1 412 Precondition Failed
S: Etag: "etag-of-sender1-message"
Mobile polls for Desktop’s msg 1 once per second for at least 300 seconds:
C: GET /a7id HTTP/1.1
C: If-None-Match: "etag-of-receiver1-message"
S: HTTP/1.1 304 Not Modified
Mobile tries again after 1 second:
C: GET /a7id HTTP/1.1
S: HTTP/1.1 200 OK
S: Etag: "etag-of-sender1-message"
Mobile computes and uploads msg 2.
New in v2: The If-Match
header may be set so that we only upload this
message if the other side’s previous message is still in the channel. This
is to prevent double PUTs during retries. If a 412 is received then it means
that our first PUT was actually correctly received by the server and that
the other side has already uploaded its next message. In this instance, the
client can effectively consider the 412 to be a 200.:
C: PUT /a7id HTTP/1.1
C: If-Match: "etag-of-sender1-message"
C:
C: {
C: 'type': 'receiver2',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'A': '87...82',
C: 'zkp_A': {
C: 'b': '6f...08',
C: 'id': 'receiver',
C: 'gr': 'f8...49'
C: }
C: }
C: }
S: HTTP/1.1 200 OK
S: ETag: "etag-of-receiver2-message"
New in v2: Response that will be returned on retries if Desktop already replaced the message:
S: HTTP/1.1 412 Precondition Failed
S: ETag: "etag-of-receiver2-message"
Desktop polls for Mobile’s msg 2 once per second for at least 10 seconds:
C: GET /a7id HTTP/1.1
C: If-None-Match: "etag-of-sender1-message"
S: HTTP/1.1 304 Not Modified
and eventually retrieves it:
S: HTTP/1.1 200 OK
S: Etag: "etag-of-receiver2-message"
Desktop computes key, computes and uploads msg 2.
New in v2: The If-Match
header may be set so that we only upload this
message if the other side’s previous message is still in the channel. This
is to prevent double PUTs during retries. If a 412 is received then it
means that our first PUT was actually correctly received by the server and
that the other side has already uploaded its next message. In this
instance, the client can effectively consider the 412 to be a 200.
C: PUT /a7id HTTP/1.1
C: If-Match: "etag-of-receiver2-message"
C:
C: {
C: 'type': 'sender2',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'A': '87...82',
C: 'zkp_A': {
C: 'b': '6f...08',
C: 'id': 'sender',
C: 'gr': 'f8...49'
C: }
C: }
C: }
S: HTTP/1.1 200 OK
S: ETag: "etag-of-sender2-message"
New in v2: Response that will be returned on retries if Mobile already replaced the message:
S: HTTP/1.1 412 Precondition Failed
S: ETag: "etag-of-sender2-message"
Mobile polls for Desktop’s msg 2 once per second for at least 10 seconds and eventually retrieves it:
C: GET /a7id HTTP/1.1
C: If-No-Match: "etag-of-receiver2-message"
S: HTTP/1.1 200 OK
S: Etag: "etag-of-sender2-message"
{ 'type': 'sender2', ... }
S: HTTP/1.1 304 Not Modified
Mobile computes key, uploads encrypted known message “0123456789ABCDEF” to prove its knowledge (msg 3).
New in v2: The If-Match
header may be set so that we only upload
this message if the other side’s previous message is still in the channel.
This is to prevent double PUTs during retries. If a 412 is received then it
means that our first PUT was actually correctly received by the server and
that the other side has already uploaded its next message.
In this instance, the client can effectively consider the 412 to be a 200.
C: PUT /a7id HTTP/1.1
C: If-Match: "etag-of-sender2-message"
C:
C: {
C: 'type': 'receiver3',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'ciphertext': "base64encoded=",
C: 'IV': "base64encoded=",
C: }
C: }
S: HTTP/1.1 200 OK
S: Etag: "etag-of-receiver3-message"
New in v2: Response that will be returned on retries if Desktop already replaced the message:
S: HTTP/1.1 412 Precondition failed
S: Etag: "etag-of-receiver3-message"
Desktop retrieves Mobile’s msg 3 to confirm the key. It polls once per second for at least 10 seconds:
C: GET /a7id HTTP/1.1
C: If-No-Match: "etag-of-sender2-message"
S: HTTP/1.1 200 OK
C: ETag: "etag-of-receiver3-message"
...
Desktop verifies it against its own version. If the values don’t match, the pairing is aborted and the session should be deleted.
Once credentials are available, and if the channel is still available, Desktop encrypts the credentials and uploads them.
New in v2: The If-Match
header may be set so that we only upload
this message if the other side’s previous message is still in the channel.
This is to prevent double PUTs during retries. If a 412 is received then
it means that our first PUT was actually correctly received by the server
and that the other side has already uploaded its next message.
In this instance, the client can effectively consider the 412 to be a 200.
New in v3: Desktop must include the If-Match header to ensure the session hasn’t been deleted yet (e.g., due to a timeout) or tampered with in the meantime.
C: PUT /a7id HTTP/1.1
C: If-Match: "etag-of-receiver3-message"
C:
C: {
C: 'type': 'sender3',
C: 'version': 3, // new in v3
C: 'payload': {
C: 'ciphertext': "base64encoded=",
C: 'IV': "base64encoded=",
C: 'hmac': "base64encoded=",
C: }
C: }
S: HTTP/1.1 200 OK
S: Etag: "etag-of-sender3-message"
New in v2: Response that will be returned on retries if Mobile already replaced the message:
S: HTTP/1.1 412 Precondition failed
S: Etag: "etag-of-sender3-message"
If the hash does not match, the Desktop deletes the session:
C: DELETE /a7id HTTP/1.1
S: HTTP/1.1 200 OK
...
This means that Mobile will receive a 404 when it tries to retrieve the encrypted credentials.
Mobile polls for the encrypted credentials once per second for at least 300 seconds to allow for the account process (the increased timeout is new in v3):
C: GET /a7id HTTP/1.1
C: If-None-Match: "etag-of-receiver3-message"
S: HTTP/1.1 200 OK
...
Decrypts Sync credentials and verifies HMAC.
Mobile deletes the session [OPTIONAL]
C: DELETE /a7id HTTP/1.1
S: HTTP/1.1 200 OK
...
Mobile starts syncing.