1use crate::envelope::decrypt_envelope;
14use crate::kdf::{derive_scoped_key, ContentKey, KeyNonce};
15use crate::{CryptoError, CryptoResult};
16
17pub struct CommitReader {
27 content_key: ContentKey,
28}
29
30impl CommitReader {
31 pub fn open(root_key: &[u8; 32], commit_blob: &[u8]) -> CryptoResult<(Vec<u8>, Self)> {
41 let (plaintext, nonce) =
42 decrypt_envelope(root_key, commit_blob, crate::aead::AAD_COMMIT)?;
43
44 let scope = format!("commit:{}", hex::encode(nonce.as_bytes()));
45 let content_key = ContentKey::new(derive_scoped_key(root_key, &scope)?);
46
47 Ok((
48 plaintext,
49 Self { content_key },
50 ))
51 }
52
53 pub fn decrypt_metadata<T>(&self, blob: &[u8]) -> CryptoResult<T>
55 where
56 T: serde::de::DeserializeOwned,
57 {
58 crate::aead::decrypt_and_parse::<T>(
59 self.content_key.as_bytes(),
60 blob,
61 crate::aead::AAD_METADATA,
62 )
63 }
64
65 pub fn decrypt_metadata_raw(&self, blob: &[u8]) -> CryptoResult<Vec<u8>> {
67 crate::aead::decrypt(
68 self.content_key.as_bytes(),
69 blob,
70 crate::aead::AAD_METADATA,
71 )
72 }
73
74 pub fn decrypt_shard(
80 &self,
81 blob: &[u8],
82 wrapped_key: Option<&crate::WrappedKey>,
83 ancestor_keys: &[ContentKey],
84 ) -> CryptoResult<Vec<u8>> {
85 use crate::aead::{decrypt, unwrap_shard_key, AAD_SHARD};
86
87 if let Some(wk) = wrapped_key {
89 if let Ok(shard_key) = unwrap_shard_key(&self.content_key, wk) {
90 if let Ok(result) = decrypt(&shard_key, blob, AAD_SHARD) {
91 return Ok(result);
92 }
93 }
94 for ak in ancestor_keys {
95 if let Ok(shard_key) = unwrap_shard_key(ak, wk) {
96 if let Ok(result) = decrypt(&shard_key, blob, AAD_SHARD) {
97 return Ok(result);
98 }
99 }
100 }
101 }
102
103 if let Ok(result) = decrypt(self.content_key.as_bytes(), blob, AAD_SHARD) {
105 return Ok(result);
106 }
107
108 for key in ancestor_keys {
110 if let Ok(result) = decrypt(key.as_bytes(), blob, AAD_SHARD) {
111 return Ok(result);
112 }
113 }
114
115 Err(CryptoError::Decryption(
116 "shard decryption failed with all known keys".into(),
117 ))
118 }
119
120 pub fn decrypt_repo_manifest(&self, blob: &[u8]) -> CryptoResult<Vec<u8>> {
122 crate::aead::decrypt(
123 self.content_key.as_bytes(),
124 blob,
125 crate::aead::AAD_REPO_MANIFEST,
126 )
127 }
128
129 pub fn content_key(&self) -> &ContentKey {
131 &self.content_key
132 }
133
134 pub fn from_content_key(content_key: ContentKey) -> Self {
139 Self { content_key }
140 }
141
142 pub fn from_share_key(key: crate::kdf::ShareKey) -> Self {
147 Self {
148 content_key: ContentKey::from_raw(*key.as_bytes()),
149 }
150 }
151
152 pub fn decrypt_envelope_body(
162 &self,
163 blob: &[u8],
164 aad: &[u8],
165 ) -> CryptoResult<(Vec<u8>, KeyNonce)> {
166 const HEADER_SIZE: usize = 4 + KeyNonce::SIZE;
167 if blob.len() <= HEADER_SIZE || !blob.starts_with(crate::envelope::MAGIC_V1) {
168 return Err(CryptoError::Decryption(
169 "invalid envelope: missing or invalid VD01 header".into(),
170 ));
171 }
172 let nonce = KeyNonce::from_bytes(&blob[4..HEADER_SIZE])
173 .ok_or_else(|| CryptoError::Decryption("invalid envelope nonce length".into()))?;
174 let plaintext = crate::aead::decrypt(self.content_key.as_bytes(), &blob[HEADER_SIZE..], aad)?;
175 Ok((plaintext, nonce))
176 }
177
178 pub fn into_parts(self) -> ContentKey {
183 self.content_key
184 }
185}
186
187pub fn decrypt_shard_data(
198 content_key: &ContentKey,
199 ancestor_keys: &[ContentKey],
200 blob: &[u8],
201 wrapped_key: Option<&crate::WrappedKey>,
202) -> CryptoResult<Vec<u8>> {
203 use crate::aead::{decrypt, unwrap_shard_key, AAD_SHARD};
204
205 if let Some(wk) = wrapped_key {
206 if let Ok(shard_key) = unwrap_shard_key(content_key, wk) {
207 if let Ok(result) = decrypt(&shard_key, blob, AAD_SHARD) {
208 return Ok(result);
209 }
210 }
211 for ak in ancestor_keys {
212 if let Ok(shard_key) = unwrap_shard_key(ak, wk) {
213 if let Ok(result) = decrypt(&shard_key, blob, AAD_SHARD) {
214 return Ok(result);
215 }
216 }
217 }
218 }
219 if let Ok(result) = decrypt(content_key.as_bytes(), blob, AAD_SHARD) {
220 return Ok(result);
221 }
222 for ak in ancestor_keys {
223 if let Ok(result) = decrypt(ak.as_bytes(), blob, AAD_SHARD) {
224 return Ok(result);
225 }
226 }
227 Err(CryptoError::Decryption(
228 "shard decryption failed with all known keys".into(),
229 ))
230}
231
232pub fn decrypt_object(key: &[u8; 32], blob: &[u8], aad: &[u8]) -> CryptoResult<Vec<u8>> {
238 decrypt_envelope(key, blob, aad).map(|(plaintext, _nonce)| plaintext)
239}
240
241pub fn decrypt_object_raw(
243 key: &[u8; 32],
244 blob: &[u8],
245 aad: &[u8],
246) -> CryptoResult<Vec<u8>> {
247 const HEADER_SIZE: usize = 4 + KeyNonce::SIZE;
248 if blob.len() > HEADER_SIZE && blob.starts_with(b"VD01") {
249 let nonce = KeyNonce::from_bytes(&blob[4..HEADER_SIZE])
250 .ok_or_else(|| CryptoError::Decryption("invalid envelope nonce length".into()))?;
251 let scope = format!("commit:{}", hex::encode(nonce.as_bytes()));
252 let derived_key = derive_scoped_key(key, &scope)?;
253 return crate::aead::decrypt(&derived_key, &blob[HEADER_SIZE..], aad);
254 }
255 Err(CryptoError::Decryption(
256 "missing VD01 envelope header".into(),
257 ))
258}
259
260pub fn decrypt_object_parse<T>(key: &[u8; 32], blob: &[u8], aad: &[u8]) -> CryptoResult<T>
262where
263 T: serde::de::DeserializeOwned,
264{
265 let plaintext = decrypt_object_raw(key, blob, aad)?;
266 ciborium::from_reader(&plaintext[..])
267 .map_err(|e| CryptoError::Serialization(format!("CBOR deserialization failed: {e}")))
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::{encrypt, encrypt_with_envelope, generate_key_nonce, wrap_shard_key, AAD_COMMIT, AAD_SHARD};
274
275 #[test]
276 fn commit_reader_open_envelope() {
277 let root_key = [0x42u8; 32];
278 let nonce = generate_key_nonce();
279 let plaintext = b"commit data";
280
281 let commit_blob =
282 encrypt_with_envelope(&root_key, &nonce, plaintext, AAD_COMMIT).unwrap();
283
284 let (decrypted, reader) = CommitReader::open(&root_key, &commit_blob).unwrap();
285 assert_eq!(decrypted, plaintext);
286 assert_ne!(reader.content_key().as_bytes(), &root_key);
287 }
288
289 #[test]
290 fn commit_reader_decrypt_shard() {
291 let root_key = [0x42u8; 32];
292 let nonce = generate_key_nonce();
293 let commit_data = b"commit";
294 let shard_data = b"shard content";
295
296 let commit_blob =
297 encrypt_with_envelope(&root_key, &nonce, commit_data, AAD_COMMIT).unwrap();
298 let (_commit, reader) = CommitReader::open(&root_key, &commit_blob).unwrap();
299
300 let shard_blob =
301 encrypt(reader.content_key().as_bytes(), shard_data, AAD_SHARD).unwrap();
302 let decrypted = reader.decrypt_shard(&shard_blob, None, &[]).unwrap();
303 assert_eq!(decrypted, shard_data);
304 }
305
306 #[test]
307 fn commit_reader_shard_with_wrapped_key() {
308 let root_key = [0x42u8; 32];
309 let nonce = generate_key_nonce();
310 let commit_data = b"commit";
311 let shard_data = b"shard with wrapped key";
312
313 let commit_blob =
314 encrypt_with_envelope(&root_key, &nonce, commit_data, AAD_COMMIT).unwrap();
315 let (_commit, reader) = CommitReader::open(&root_key, &commit_blob).unwrap();
316
317 let shard_key = crate::generate_key();
319 let wrapped = wrap_shard_key(reader.content_key(), &shard_key).unwrap();
320 let shard_blob = encrypt(&shard_key, shard_data, AAD_SHARD).unwrap();
321
322 let decrypted = reader.decrypt_shard(&shard_blob, Some(&wrapped), &[]).unwrap();
323 assert_eq!(decrypted, shard_data);
324 }
325
326 #[test]
327 fn decrypt_object_envelope() {
328 let root_key = [0x42u8; 32];
329 let nonce = generate_key_nonce();
330 let plaintext = b"envelope data";
331
332 let blob = encrypt_with_envelope(&root_key, &nonce, plaintext, AAD_COMMIT).unwrap();
333 let decrypted = decrypt_object(&root_key, &blob, AAD_COMMIT).unwrap();
334 assert_eq!(decrypted, plaintext);
335 }
336
337 #[test]
338 fn decrypt_object_raw_roundtrip() {
339 let root_key = [0x42u8; 32];
340 let nonce = generate_key_nonce();
341 let plaintext = b"raw data";
342
343 let blob = encrypt_with_envelope(&root_key, &nonce, plaintext, AAD_SHARD).unwrap();
344 let raw = decrypt_object_raw(&root_key, &blob, AAD_SHARD).unwrap();
345 assert_eq!(&raw, plaintext);
346 }
347}