Skip to main content

aven_protocol/claim/
peer.rs

1//! Shared PSK invitation, request PoP and independent installation key primitives.
2pub use crate::wire::enrollment::{Authentication, RegistrationStatus};
3
4use super::*;
5const DOMAIN_VERSION: u32 = 1;
6use ed25519_dalek::{Signature, VerifyingKey};
7use hpke::PskBundle;
8use sha2::Sha256;
9
10pub const REQUEST_BYTES: usize = 314;
11pub const PEER_STORAGE_BYTES: usize = 538;
12pub const CONTROL_LIMIT: usize = 16384;
13
14fn check(ok: bool) -> Result<()> {
15    ensure!(ok, "error enrollment-invalid");
16    Ok(())
17}
18
19pub(super) fn verify(pk: &[u8; 32], label: &str, fields: &[&[u8]], signature: &[u8]) -> Result<()> {
20    VerifyingKey::from_bytes(pk)
21        .and_then(|key| key.verify_strict(&cce(label, fields), &Signature::from_slice(signature)?))
22        .map_err(|_| anyhow::anyhow!("error enrollment-signature"))
23}
24
25/// Secret-bearing out-of-band handoff. Locator ownership remains with the host.
26pub struct Invitation {
27    pub(super) vault: [u8; 32],
28    pub(super) inviter: [u8; 32],
29    pub(super) psk: Secret,
30}
31
32impl fmt::Debug for Invitation {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str("Invitation([REDACTED])")
35    }
36}
37
38impl Invitation {
39    pub fn vault(&self) -> [u8; 32] {
40        self.vault
41    }
42    pub fn handle(&self) -> [u8; 32] {
43        let kdf = hkdf::Hkdf::<Sha256>::new(Some(b"aven-e2ee/v1/invitation"), self.psk.expose());
44        let mut out = [0; 32];
45        kdf.expand(
46            &cce("aven-e2ee/v1/invitation/handle", &[&self.vault]),
47            &mut out,
48        )
49        .expect("fixed HKDF length");
50        out
51    }
52    pub fn protected_storage_bytes(&self) -> Zeroizing<Vec<u8>> {
53        let mut out = Zeroizing::new(Vec::with_capacity(96));
54        out.extend(self.vault);
55        out.extend(self.inviter);
56        out.extend(self.psk.expose());
57        out
58    }
59    pub fn from_protected_storage(bytes: &[u8]) -> Result<Self> {
60        check(bytes.len() == 96)?;
61        let mut r = Reader(bytes);
62        Ok(Self {
63            vault: r.array()?,
64            inviter: r.array()?,
65            psk: Secret::new(r.array()?),
66        })
67    }
68}
69
70#[derive(Clone, PartialEq, Eq)]
71pub(super) struct Recipient {
72    pub(super) device: [u8; 32],
73    pub(super) sign: [u8; 32],
74    pub(super) hpke: [u8; 32],
75    pub(super) verifier: [u8; 32],
76    pub(super) pop: [u8; 64],
77}
78impl Recipient {
79    pub(super) fn pop_message(
80        &self,
81        vault: &[u8; 32],
82        handle: &[u8; 32],
83        inviter: &[u8; 32],
84    ) -> Vec<u8> {
85        cce(
86            "aven-e2ee/v1/pairing/request-pop",
87            &[
88                vault,
89                handle,
90                inviter,
91                &self.device,
92                &self.sign,
93                &self.hpke,
94                &self.verifier,
95                &1_u32.to_be_bytes(),
96                &DOMAIN_VERSION.to_be_bytes(),
97            ],
98        )
99    }
100    pub(super) fn verify(
101        &self,
102        vault: &[u8; 32],
103        handle: &[u8; 32],
104        inviter: &[u8; 32],
105    ) -> Result<()> {
106        VerifyingKey::from_bytes(&self.sign)
107            .and_then(|key| {
108                key.verify_strict(
109                    &self.pop_message(vault, handle, inviter),
110                    &Signature::from_bytes(&self.pop),
111                )
112            })
113            .map_err(|_| anyhow::anyhow!("error enrollment-pop"))
114    }
115    pub(super) fn plaintext(&self) -> Vec<u8> {
116        let mut out = vec![1];
117        for v in [self.device, self.sign, self.hpke, self.verifier] {
118            bytes(&mut out, &v);
119        }
120        out.extend(1_u32.to_be_bytes());
121        out.extend(DOMAIN_VERSION.to_be_bytes());
122        bytes(&mut out, &self.pop);
123        out
124    }
125    pub(super) fn parse(raw: &[u8]) -> Result<Self> {
126        check(raw.len() == 221)?;
127        let mut r = Reader(raw);
128        check(r.take(1)? == [1])?;
129        let device = r.blob(32)?.try_into()?;
130        let sign = r.blob(32)?.try_into()?;
131        let hpke = r.blob(32)?.try_into()?;
132        let verifier = r.blob(32)?.try_into()?;
133        check(
134            u32::from_be_bytes(r.array()?) == 1 && u32::from_be_bytes(r.array()?) == DOMAIN_VERSION,
135        )?;
136        let pop = r.blob(64)?.try_into()?;
137        r.end()?;
138        Ok(Self {
139            device,
140            sign,
141            hpke,
142            verifier,
143            pop,
144        })
145    }
146}
147
148pub(super) fn seal(
149    inv: &Invitation,
150    public: &[u8; 32],
151    info: &[u8],
152    aad: &[u8],
153    plaintext: &[u8],
154) -> Result<(Vec<u8>, Vec<u8>)> {
155    let entropy = Secret::generate()?;
156    let mut rng = ChaCha20Rng::from_seed(*entropy.expose());
157    let pk = <Kem as hpke::Kem>::PublicKey::from_bytes(public)
158        .map_err(|_| anyhow::anyhow!("error enrollment-recipient"))?;
159    let handle = inv.handle();
160    let mode = OpModeS::Psk(
161        PskBundle::new(inv.psk.expose(), &handle)
162            .map_err(|_| anyhow::anyhow!("error enrollment-psk"))?,
163    );
164    let (enc, cipher) = hpke::single_shot_seal_with_rng::<Aead, Kdf, Kem>(
165        &mode, &pk, info, plaintext, aad, &mut rng,
166    )
167    .map_err(|_| anyhow::anyhow!("error enrollment-seal"))?;
168    Ok((enc.to_bytes().to_vec(), cipher))
169}
170pub(super) fn open(
171    inv: &Invitation,
172    private: &Secret,
173    info: &[u8],
174    aad: &[u8],
175    enc: &[u8],
176    cipher: &[u8],
177) -> Result<Zeroizing<Vec<u8>>> {
178    let private = HpkePrivate::from_bytes(private.expose())
179        .map_err(|_| anyhow::anyhow!("error enrollment-recipient"))?;
180    let enc =
181        Enc::from_bytes(enc).map_err(|_| anyhow::anyhow!("error enrollment-encapsulation"))?;
182    let handle = inv.handle();
183    let mode = OpModeR::Psk(
184        PskBundle::new(inv.psk.expose(), &handle)
185            .map_err(|_| anyhow::anyhow!("error enrollment-psk"))?,
186    );
187    hpke::single_shot_open::<Aead, Kdf, Kem>(&mode, &private, &enc, info, cipher, aad)
188        .map(Zeroizing::new)
189        .map_err(|_| anyhow::anyhow!("error enrollment-open"))
190}
191pub fn request_parts<'a>(raw: &'a [u8], handle: &[u8; 32]) -> Result<(&'a [u8], &'a [u8])> {
192    check(raw.len() == REQUEST_BYTES)?;
193    let mut r = Reader(raw);
194    check(r.take(1)? == [1])?;
195    check(r.blob(32)? == handle)?;
196    let enc = r.blob(32)?;
197    let cipher = r.blob(237)?;
198    r.end()?;
199    Ok((enc, cipher))
200}
201
202/// Independent peer authority. Exact storage includes its one frozen request.
203pub struct PeerAuthority {
204    pub(super) device: [u8; 32],
205    pub(super) signing: Secret,
206    pub(super) recipient: Secret,
207    pub(super) bearer: Secret,
208    pub(super) invitation: Invitation,
209    pub(super) request: Vec<u8>,
210}
211impl fmt::Debug for PeerAuthority {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        f.write_str("PeerAuthority([REDACTED])")
214    }
215}
216impl PeerAuthority {
217    pub fn generate(invitation: Invitation) -> Result<Self> {
218        let ikm = Secret::generate()?;
219        let (recipient, _) = <Kem as hpke::Kem>::derive_keypair(ikm.expose());
220        Self {
221            device: *Secret::generate()?.expose(),
222            signing: Secret::generate()?,
223            recipient: Secret::new(recipient.to_bytes().into()),
224            bearer: Secret::generate()?,
225            invitation,
226            request: Vec::new(),
227        }
228        .sealed()
229    }
230    /// The same installation keys with a new request sealed to `invitation`.
231    pub fn retry(&self, invitation: Invitation) -> Result<Self> {
232        Self {
233            device: self.device,
234            signing: Secret::new(*self.signing.expose()),
235            recipient: Secret::new(*self.recipient.expose()),
236            bearer: Secret::new(*self.bearer.expose()),
237            invitation,
238            request: Vec::new(),
239        }
240        .sealed()
241    }
242    fn sealed(mut self) -> Result<Self> {
243        let row = self.recipient()?;
244        let handle = self.invitation.handle();
245        let info = cce(
246            "aven-e2ee/v1/pairing/request",
247            &[&self.invitation.vault, &handle],
248        );
249        let aad = cce(
250            "aven-e2ee/v1/pairing/request-aad",
251            &[&self.invitation.vault, &handle, &self.invitation.inviter],
252        );
253        let (enc, cipher) = seal(
254            &self.invitation,
255            &self.invitation.inviter,
256            &info,
257            &aad,
258            &row.plaintext(),
259        )?;
260        let mut request = vec![1];
261        bytes(&mut request, &handle);
262        bytes(&mut request, &enc);
263        bytes(&mut request, &cipher);
264        self.request = request;
265        Ok(self)
266    }
267    pub(super) fn recipient(&self) -> Result<Recipient> {
268        let private = HpkePrivate::from_bytes(self.recipient.expose())
269            .map_err(|_| anyhow::anyhow!("error enrollment-recipient"))?;
270        let signer = SigningKey::from_bytes(self.signing.expose());
271        let mut row = Recipient {
272            device: self.device,
273            sign: signer.verifying_key().to_bytes(),
274            hpke: <Kem as hpke::Kem>::sk_to_pk(&private).to_bytes().into(),
275            verifier: credential_verifier(self.invitation.vault, self.device, &self.bearer),
276            pop: [0; 64],
277        };
278        row.pop = signer
279            .sign(&row.pop_message(
280                &self.invitation.vault,
281                &self.invitation.handle(),
282                &self.invitation.inviter,
283            ))
284            .to_bytes();
285        Ok(row)
286    }
287    pub fn matches_invitation(&self, invitation: &Invitation) -> bool {
288        bool::from(
289            self.invitation
290                .protected_storage_bytes()
291                .as_slice()
292                .ct_eq(invitation.protected_storage_bytes().as_slice()),
293        )
294    }
295    pub fn device(&self) -> [u8; 32] {
296        self.device
297    }
298    pub fn vault(&self) -> [u8; 32] {
299        self.invitation.vault
300    }
301    pub fn handle(&self) -> [u8; 32] {
302        self.invitation.handle()
303    }
304    pub fn bearer(&self) -> &Secret {
305        &self.bearer
306    }
307    pub fn request(&self) -> &[u8] {
308        &self.request
309    }
310    pub fn protected_storage_bytes(&self) -> Zeroizing<Vec<u8>> {
311        let mut out = Zeroizing::new(Vec::new());
312        out.extend(self.device);
313        for secret in [&self.signing, &self.recipient, &self.bearer] {
314            out.extend(secret.expose());
315        }
316        out.extend(self.invitation.protected_storage_bytes().iter());
317        out.extend(&self.request);
318        out
319    }
320    pub fn from_protected_storage(raw: &[u8]) -> Result<Self> {
321        check(raw.len() == PEER_STORAGE_BYTES)?;
322        let mut r = Reader(raw);
323        let peer = Self {
324            device: r.array()?,
325            signing: Secret::new(r.array()?),
326            recipient: Secret::new(r.array()?),
327            bearer: Secret::new(r.array()?),
328            invitation: Invitation::from_protected_storage(r.take(96)?)?,
329            request: r.take(REQUEST_BYTES)?.to_vec(),
330        };
331        request_parts(&peer.request, &peer.handle())?;
332        peer.recipient()?;
333        Ok(peer)
334    }
335}