1use std::collections::BTreeMap;
4use std::fmt;
5
6use coven_foundation::id_source::KeyId;
7use sha2::{Digest, Sha256};
8use subtle::ConstantTimeEq;
9use uuid::Uuid;
10use zeroize::Zeroizing;
11
12use crate::{derivation, randomness, wire};
13use crate::{CircleId, CryptoError, DerivedKeys, EncryptionKey, MaterialError, SecretBytes};
14
15#[derive(Clone)]
18pub struct StoreKey {
19 id: KeyId,
20 bytes: Zeroizing<[u8; 32]>,
21}
22
23impl StoreKey {
24 pub const ENCODED_LEN: usize = 16 + 32;
26
27 pub fn generate(id: KeyId) -> Result<Self, CryptoError> {
29 Ok(Self { id, bytes: randomness::secret_bytes()? })
30 }
31
32 pub fn from_bytes(id: KeyId, bytes: [u8; 32]) -> Self {
34 let bytes = Zeroizing::new(bytes);
35 Self { id, bytes }
36 }
37
38 pub fn id(&self) -> KeyId {
40 self.id
41 }
42
43 pub fn hash(&self) -> [u8; 32] {
45 Sha256::digest(self.bytes.as_ref()).into()
46 }
47
48 pub fn check_hash(&self, expected: &[u8; 32]) -> Result<(), MaterialError> {
50 if !bool::from(self.hash().ct_eq(expected)) {
51 return Err(MaterialError::StoreKeyConflict(self.id));
52 }
53 Ok(())
54 }
55
56 pub fn derive(&self) -> DerivedKeys {
58 DerivedKeys::new(&self.bytes)
59 }
60
61 pub(crate) fn encode_into(&self, bytes: &mut Vec<u8>) {
62 bytes.extend_from_slice(self.id().0.as_bytes());
63 bytes.extend_from_slice(self.bytes.as_ref());
64 }
65
66 pub(crate) fn decode(bytes: &mut &[u8]) -> Result<Self, MaterialError> {
67 Ok(Self::from_encoded(&Zeroizing::new(wire::array::<{ Self::ENCODED_LEN }>(bytes)?)))
68 }
69
70 pub(crate) fn from_encoded(encoded: &[u8; Self::ENCODED_LEN]) -> Self {
72 let id = KeyId(Uuid::from_bytes(std::array::from_fn(|i| encoded[i])));
73 Self::from_bytes(id, std::array::from_fn(|i| encoded[16 + i]))
74 }
75}
76
77impl fmt::Debug for StoreKey {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.debug_struct("StoreKey").field("id", &self.id).finish_non_exhaustive()
80 }
81}
82
83#[derive(Clone, Debug)]
86pub struct CircleKey {
87 circle: CircleId,
88 key: StoreKey,
89}
90
91impl CircleKey {
92 pub const ENCODED_LEN: usize = 16 + StoreKey::ENCODED_LEN;
95
96 pub fn generate(circle: CircleId, id: KeyId) -> Result<Self, CryptoError> {
98 Ok(Self { circle, key: StoreKey::generate(id)? })
99 }
100
101 pub fn from_bytes(circle: CircleId, id: KeyId, bytes: [u8; 32]) -> Self {
103 Self { circle, key: StoreKey::from_bytes(id, bytes) }
104 }
105
106 pub fn circle(&self) -> CircleId {
108 self.circle
109 }
110
111 pub fn id(&self) -> KeyId {
113 self.key.id()
114 }
115
116 pub fn hash(&self) -> [u8; 32] {
118 self.key.hash()
119 }
120
121 pub fn check_hash(&self, expected: &[u8; 32]) -> Result<(), MaterialError> {
123 if !bool::from(self.key.hash().ct_eq(expected)) {
124 return Err(MaterialError::CircleKeyConflict { circle: self.circle, key: self.id() });
125 }
126 Ok(())
127 }
128
129 pub fn derive(&self) -> DerivedKeys {
131 self.key.derive()
132 }
133
134 pub(crate) fn encode_into(&self, bytes: &mut Vec<u8>) {
135 bytes.extend_from_slice(self.circle.0.as_bytes());
136 self.key.encode_into(bytes);
137 }
138
139 pub(crate) fn decode(bytes: &mut &[u8]) -> Result<Self, MaterialError> {
140 Ok(Self::from_encoded(&Zeroizing::new(wire::array::<{ Self::ENCODED_LEN }>(bytes)?)))
141 }
142
143 pub(crate) fn from_encoded(encoded: &[u8; Self::ENCODED_LEN]) -> Self {
145 Self {
146 circle: CircleId(Uuid::from_bytes(std::array::from_fn(|i| encoded[i]))),
147 key: StoreKey::from_encoded(&Zeroizing::new(std::array::from_fn(|i| encoded[16 + i]))),
148 }
149 }
150}
151
152const KEYRING_MAGIC: &[u8] = b"CVKR\x01";
154
155#[derive(Clone, Debug)]
158pub struct StoreKeyring {
159 stores: BTreeMap<KeyId, StoreKey>,
160 circles: BTreeMap<(CircleId, KeyId), CircleKey>,
161}
162
163impl StoreKeyring {
164 pub fn new(key: StoreKey) -> Self {
166 Self { stores: BTreeMap::from([(key.id(), key)]), circles: BTreeMap::new() }
167 }
168
169 pub fn insert_store_key(&mut self, key: StoreKey) -> Result<(), MaterialError> {
171 if let Some(existing) = self.stores.get(&key.id()) {
172 if !bool::from(existing.bytes.as_ref().ct_eq(key.bytes.as_ref())) {
173 return Err(MaterialError::StoreKeyConflict(key.id()));
174 }
175 } else {
176 self.stores.insert(key.id(), key);
177 }
178 Ok(())
179 }
180
181 pub fn insert_circle_key(&mut self, key: CircleKey) -> Result<(), MaterialError> {
183 let id = (key.circle(), key.id());
184 if let Some(existing) = self.circles.get(&id) {
185 if !bool::from(existing.key.bytes.as_ref().ct_eq(key.key.bytes.as_ref())) {
186 return Err(MaterialError::CircleKeyConflict { circle: id.0, key: id.1 });
187 }
188 } else {
189 self.circles.insert(id, key);
190 }
191 Ok(())
192 }
193
194 pub fn store_key(&self, key: KeyId) -> Result<&StoreKey, MaterialError> {
196 self.stores.get(&key).ok_or(MaterialError::UnknownStoreKey(key))
197 }
198
199 pub fn store_key_ids(&self) -> impl Iterator<Item = KeyId> + '_ {
201 self.stores.keys().copied()
202 }
203
204 pub fn circle_key_ids(&self, circle: CircleId) -> impl Iterator<Item = KeyId> + '_ {
206 self.circles.keys().filter_map(move |(owner, key)| (*owner == circle).then_some(*key))
207 }
208
209 pub fn circle_key(&self, circle: CircleId, key: KeyId) -> Result<&CircleKey, MaterialError> {
211 self.circles.get(&(circle, key)).ok_or(MaterialError::UnknownCircleKey { circle, key })
212 }
213
214 pub fn to_secret_bytes(&self) -> SecretBytes {
216 let mut bytes = Zeroizing::new(Vec::with_capacity(
218 KEYRING_MAGIC.len()
219 + 8
220 + self.stores.len() * StoreKey::ENCODED_LEN
221 + 8
222 + self.circles.len() * CircleKey::ENCODED_LEN,
223 ));
224 bytes.extend_from_slice(KEYRING_MAGIC);
225 bytes.extend_from_slice(&(self.stores.len() as u64).to_le_bytes());
226 for key in self.stores.values() {
227 key.encode_into(&mut bytes);
228 }
229 bytes.extend_from_slice(&(self.circles.len() as u64).to_le_bytes());
230 for key in self.circles.values() {
231 key.encode_into(&mut bytes);
232 }
233 SecretBytes(bytes)
234 }
235
236 pub fn from_secret_bytes(mut bytes: &[u8]) -> Result<Self, MaterialError> {
238 wire::prefix(&mut bytes, KEYRING_MAGIC)?;
239 let count = wire::number(&mut bytes)?;
240 if count == 0 || count > (bytes.len() / StoreKey::ENCODED_LEN) as u64 {
241 return Err(MaterialError::Encoding);
242 }
243 let mut stores = BTreeMap::new();
244 for _ in 0..count {
245 let key = StoreKey::decode(&mut bytes)?;
246 if stores.insert(key.id(), key).is_some() {
247 return Err(MaterialError::Encoding);
248 }
249 }
250 let count = wire::number(&mut bytes)?;
251 if count > (bytes.len() / CircleKey::ENCODED_LEN) as u64 {
252 return Err(MaterialError::Encoding);
253 }
254 let mut circles = BTreeMap::new();
255 for _ in 0..count {
256 let key = CircleKey::decode(&mut bytes)?;
257 if circles.insert((key.circle(), key.id()), key).is_some() {
258 return Err(MaterialError::Encoding);
259 }
260 }
261 wire::end(bytes)?;
262 Ok(Self { stores, circles })
263 }
264}
265
266pub struct InviteSecret(Zeroizing<[u8; 32]>);
268
269impl InviteSecret {
270 pub fn generate() -> Result<Self, CryptoError> {
272 Ok(Self(randomness::secret_bytes()?))
273 }
274
275 pub fn from_bytes(bytes: [u8; 32]) -> Self {
277 Self(Zeroizing::new(bytes))
278 }
279
280 pub fn to_secret_bytes(&self) -> SecretBytes {
282 SecretBytes::new(self.0.to_vec())
283 }
284
285 pub fn join_request_key(&self) -> EncryptionKey {
287 EncryptionKey(derivation::derive_label(&self.0, derivation::JOIN_REQUEST))
288 }
289}
290
291impl fmt::Debug for InviteSecret {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 f.write_str("InviteSecret([REDACTED])")
294 }
295}
296
297#[cfg(test)]
298#[path = "keys_tests.rs"]
299mod tests;