1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Short-code pairing (`rk-pair/1`), the shared half: the commitment, the code, and the device
//! side's request bodies. No transport, no state. The host side is `Daemon::open_pairing` and
//! friends (feature `remote`).
//!
//! The protocol is a short authenticated string (SAS) exchange with a commit-reveal step:
//!
//! 1. The joining device D commits: `commit = SHA-256(frame("rk-pair/1 commit", key_D, nonce_D,
//! label))`, and sends `{publicKey, commit, label}` to the host H.
//! 2. H answers with a fresh random `nonce_H`. D has committed to its key and nonce before it
//! sees `nonce_H`, and H picked `nonce_H` before it saw `nonce_D`.
//! 3. D reveals `nonce_D`; H checks it against the commitment.
//! 4. Both compute the same six-digit code from the whole transcript:
//! `SHA-256(frame("rk-pair/1 sas", key_D, nonce_D, nonce_H, label, host_binding))`, first four
//! bytes as a big-endian number, modulo 10^6. H shows it only to its owner (in-process);
//! D shows its own. The owner reads D's code off D's screen and types it into H.
//! 5. H enrols `key_D` only if the typed code equals the code H computed.
//!
//! Why this resists a man in the middle: a party that sits between D and H and swaps in its own
//! key must commit to `(key_M, nonce_M)` toward H before it learns `nonce_H`, and must pick the
//! `nonce_H'` it shows D before it learns `nonce_D` (D's commitment hides it). Neither code can
//! therefore be steered, and the two codes agree only by chance, 1 in 10^6 per attempt. The host
//! caps attempts (see `PairingOptions`), so the odds stay about that for the whole window.
//! `host_binding` (for example a hash of the TLS certificate both sides see) additionally makes
//! the code differ when the TLS channel was terminated by someone else.
//!
//! The code is 20 bits. It is safe only because it is checked online, by a person, under a
//! small attempt budget; it is not a secret to offline-guess against and must never be logged,
//! put in an event or sent over the channel.
use Engine;
use ;
use crate;
// The commitment, the code and their constants are `rightkit-identity`'s (it owns the wire
// bytes); re-exported so these paths stay the daemon's public ones.
use URL_SAFE_NO_PAD;
pub use ;
/// The device side of a pairing, with no transport: build the three request bodies and compute
/// the code to display. The caller sends them (`POST /v1/pair`, `POST /v1/pair/{id}/reveal`,
/// `GET /v1/pair/{id}`) over the pinned TLS channel it already uses for the host.