Skip to main content

coven_format/
sealed_key.rs

1//! Anonymous key boxes embedded in the clear routing prefix of a slot (D9/D11).
2
3use crate::error::{require, Error, Rule};
4use crate::wire::{put_blob, wire_struct, Decoder, Encoder, Wire};
5use coven_crypto::{
6    CircleKey, CryptoError, MemberIdentity, MemberKeys, SealingKey, StoreKey, KEY_BOX_HEAD_LEN,
7    KEY_BOX_TAG_LEN,
8};
9use coven_foundation::id_source::{KeyId, MemberId};
10use coven_merge::Audience;
11
12/// Routing authenticated by the anonymous box and the enclosing slot signature.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct KeyCopyPrefix {
15    /// The copied key's audience.
16    pub audience: Audience,
17    /// The introduced key's identity.
18    pub key: KeyId,
19    /// The recipient's stable identity.
20    pub member: MemberId,
21    /// The exact historical sealing credential used for this copy.
22    pub sealing: SealingKey,
23}
24wire_struct!(KeyCopyPrefix, audience, key, member, sealing);
25impl KeyCopyPrefix {
26    /// Whether a held historical private credential can open the box.
27    pub fn held_by(&self, member: &MemberIdentity) -> bool {
28        self.member == member.member() && member.credential(&self.sealing).is_some()
29    }
30    /// The encoded routing of a store copy: audience tag, key, member and
31    /// sealing key.
32    pub(crate) const STORE_LEN: usize = 1 + 16 + 16 + 32;
33    /// A circle copy's routing adds the circle id to the audience.
34    pub(crate) const CIRCLE_LEN: usize = Self::STORE_LEN + 16;
35
36    pub(crate) fn material_length(&self) -> usize {
37        match self.audience {
38            Audience::Store => StoreKey::ENCODED_LEN,
39            Audience::Circle(_) => CircleKey::ENCODED_LEN,
40        }
41    }
42}
43
44/// An original anonymous box, retaining its random bytes across retries.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct CopyBox {
47    /// Visible audience, key and recipient routing.
48    pub routing: KeyCopyPrefix,
49    /// Crypto's ephemeral key, nonce, ciphertext and tag, without wire lengths.
50    pub sealed: Vec<u8>,
51}
52impl Wire for CopyBox {
53    fn put(&self, out: &mut Encoder) -> Result<(), Error> {
54        self.validate()?;
55        self.routing.put(out)?;
56        let tag = self.sealed.len() - KEY_BOX_TAG_LEN;
57        out.bytes(&self.sealed[..KEY_BOX_HEAD_LEN])?;
58        put_blob(&self.sealed[KEY_BOX_HEAD_LEN..tag], out)?;
59        out.bytes(&self.sealed[tag..])
60    }
61    fn get(input: &mut Decoder<'_>) -> Result<Self, Error> {
62        let routing = KeyCopyPrefix::get(input)?;
63        let mut sealed = input.take(KEY_BOX_HEAD_LEN)?.to_vec();
64        let length = u32::get(input)? as usize;
65        require(length == routing.material_length(), "key box ciphertext", Rule::StreamLength)?;
66        sealed.extend_from_slice(input.take(length)?);
67        sealed.extend_from_slice(input.take(KEY_BOX_TAG_LEN)?);
68        Ok(Self { routing, sealed })
69    }
70}
71impl CopyBox {
72    pub(crate) fn validate(&self) -> Result<(), Error> {
73        require(
74            self.sealed.len()
75                == KEY_BOX_HEAD_LEN + self.routing.material_length() + KEY_BOX_TAG_LEN,
76            "key box length",
77            Rule::StreamLength,
78        )
79    }
80    /// Open tentatively using the complete slot prefix through `sealing` as
81    /// context. The caller verifies the slot signature and key introduction.
82    pub fn open_store_key(
83        &self,
84        member: &MemberIdentity,
85        path: &str,
86        prefix: &[u8],
87    ) -> Result<StoreKey, CryptoError> {
88        if self.routing.audience != Audience::Store {
89            return Err(CryptoError::KeyIdentity);
90        }
91        let key = self.credential(member)?.open_store_key(path, prefix, &self.sealed)?;
92        if key.id() != self.routing.key {
93            return Err(CryptoError::KeyIdentity);
94        }
95        Ok(key)
96    }
97    /// Open a circle box with its exact historical recipient credential.
98    pub fn open_circle_key(
99        &self,
100        member: &MemberIdentity,
101        path: &str,
102        prefix: &[u8],
103    ) -> Result<CircleKey, CryptoError> {
104        let Audience::Circle(circle) = self.routing.audience else {
105            return Err(CryptoError::KeyIdentity);
106        };
107        let key = self.credential(member)?.open_circle_key(path, prefix, &self.sealed)?;
108        if key.circle() != circle || key.id() != self.routing.key {
109            return Err(CryptoError::KeyIdentity);
110        }
111        Ok(key)
112    }
113    fn credential<'m>(&self, member: &'m MemberIdentity) -> Result<&'m MemberKeys, CryptoError> {
114        if self.routing.member != member.member() {
115            return Err(CryptoError::Authentication);
116        }
117        member.credential(&self.routing.sealing).ok_or(CryptoError::Authentication)
118    }
119}
120
121#[cfg(test)]
122#[path = "sealed_key_tests.rs"]
123mod tests;