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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Cross-device confidentiality envelope.
//!
//! When `A` and `T` do not share a live TLS session — typical when the Authorizer
//! holds the passkey on a phone and the custodian runs on hosted
//! infrastructure — Phase II's `A → T` channel must be realised by an
//! alternative transport that supplies confidentiality (for `W*`) and
//! channel-binding (against substitution / replay).
//!
//! This module ships the **cryptographic core** of that realisation:
//!
//! 1. [`derive_session_key`] — the KDF stitching
//! `k_xd = KDF(ss; r, DS_xd_enc ‖ pk_A ‖ pk_T)`.
//! 2. [`seal_grant`] / [`open_grant`] — the AEAD envelope over a [`Grant`]
//! with `AD = H(pk_A ‖ pk_T ‖ r)`, channel-binding both ends and the
//! freshness token.
//!
//! ## What is intentionally outside scope
//!
//! - The ECDH (or other) key-agreement step itself. The caller computes `ss`
//! with whatever primitive is appropriate (`p256::ecdh`, `x25519-dalek`,
//! an HSM, a PAKE) and passes the raw shared secret in. SUDP does not
//! define a `KeyExchange` trait — lists ECDH only in the
//! profile, not the abstract primitive set.
//! - **`pk_T` trust establishment** — ("Authenticated key
//! agreement is required for confidentiality") lists four profile options
//! (signature under `T`'s long-term key, binding to an existing
//! authenticated orchestration session, an OOB channel like a QR code, or
//! a mutually-authenticated PAKE). All of these are deployment choices;
//! without `pk_T` authenticity the confidentiality argument does not hold
//! regardless of what this module does.
//! - Multi-device passkey management, transport (HTTP / WebSocket /
//! QR-polling), and any UI concern.
//!
//! These are deployment glue, not protocol crypto.
use crateGrant;
use crate;
use crateResult;
/// Derive the cross-device session key:
/// `k_xd = KDF(ss; r, DS_xd_enc ‖ pk_A ‖ pk_T)`.
///
/// - `ss` — raw shared secret from the key-agreement step (e.g.
/// `p256::ecdh::SharedSecret::raw_secret_bytes()`).
/// - `r` — the freshness token also bound into the Phase II.3 β.
/// - `pk_a_bytes` / `pk_t_bytes` — wire-format public keys (e.g. SEC1
/// uncompressed, X25519 raw, etc.). The encoding is profile-defined; both
/// sides MUST agree.
///
/// Output length is the AEAD key length of the chosen primitive suite.
/// The cross-device AEAD associated data: `H(pk_A ‖ pk_T ‖ r)`.
///
/// Channel-binds both ephemeral public keys and the freshness token so that
/// any in-flight substitution fails AEAD authentication.
/// Seal a grant for cross-device transport.
///
/// Output is the wire ciphertext `ct_G = Enc_{k_xd}(canonical(G); AD)`.
/// Caller is responsible for transporting `(pk_a_bytes, ct_G)` to `T`; `T`
/// already knows `r` from its freshness pool.
/// Open a cross-device sealed grant on `T`'s side.
///
/// `T` derives the same `k_xd` from its own end of the key agreement, then
/// runs the standard Phase II.3 redemption pipeline on the recovered Grant.