Skip to main content

vitaminc_aead/
walk.rs

1//! Decrypt into any sink.
2//!
3//! Two steps, mirroring encryption:
4//!
5//! 1. [`open_all`] or [`open_all_async`]: derive each leaf's AAD from its
6//!    position, open every leaf (one `open_many` call for an async cipher),
7//!    and put the plaintexts back into the tree.
8//! 2. [`walk`]: feed the opened tree to the sink, scalar by scalar,
9//!    physical plus logical. A Go guest passes a
10//!    [`FrameWriter`](crate::frame::FrameWriter), a Node binding a builder
11//!    of JavaScript values, a typed Rust caller the sink its target names.
12//!
13//! The walk checks the rules the encrypt sink enforced: an empty marker
14//! must be empty, a container must have something sealed in it, and map
15//! keys must be distinct.
16
17use core::convert::Infallible;
18use std::collections::HashSet;
19
20use vitaminc_protected::{Controlled, Protected};
21
22use crate::leaf::{self, Item};
23use crate::plaintext::{Error, MapSink, SeqSink, Sink, SourceError};
24use crate::seal::{open_all, open_all_async, AsyncCipher, Cipher, Node, OpenError, Opened};
25use crate::{CipherText, IntoAad};
26
27/// Why decryption failed.
28///
29/// `E` is the sink's error, `C` the cipher's. [`walk`] involves no cipher,
30/// so its `C` is [`Infallible`].
31///
32/// The cipher's and the sink's errors are printed in the message rather
33/// than chained as the `source()`, so neither type need implement `Error`.
34#[derive(Debug, thiserror::Error)]
35pub enum DecryptError<E, C = Infallible> {
36    /// The cipher refused a leaf. For an AEAD that is a wrong key, a wrong
37    /// AAD or a tampered leaf; a remote provider's error type also tells an
38    /// outage apart.
39    #[error("cipher failed: {0}")]
40    Cipher(C),
41    /// An authenticated empty marker carried a payload: tampering the
42    /// cipher could not see.
43    #[error("an authenticated empty marker carried a payload")]
44    Auth,
45    /// The tree or a leaf's plaintext is not well formed, or the cipher
46    /// returned the wrong number of leaves.
47    #[error("malformed ciphertext")]
48    Malformed,
49    /// The sink refused what it was given (a typed sink got the wrong kind,
50    /// a host builder failed).
51    #[error("sink refused the value: {0}")]
52    Sink(E),
53}
54
55impl<E, C> From<E> for DecryptError<E, C> {
56    fn from(e: E) -> Self {
57        Self::Sink(e)
58    }
59}
60
61impl<E> DecryptError<E> {
62    /// The same failure, under any cipher error type: a walk's error, as
63    /// one step of a decryption.
64    pub fn with_cipher<C>(self) -> DecryptError<E, C> {
65        match self {
66            Self::Cipher(never) => match never {},
67            Self::Auth => DecryptError::Auth,
68            Self::Malformed => DecryptError::Malformed,
69            Self::Sink(e) => DecryptError::Sink(e),
70        }
71    }
72}
73
74fn opening<E, C>(e: OpenError<C>) -> DecryptError<E, C> {
75    match e {
76        OpenError::Cipher(c) => DecryptError::Cipher(c),
77        OpenError::Malformed | OpenError::Count(_) => DecryptError::Malformed,
78    }
79}
80
81/// Lets a `?` on a sink error inside `walk` also accept source errors.
82impl<E: Error, C> Error for DecryptError<E, C> {
83    fn source(error: SourceError) -> Self {
84        Self::Sink(E::source(error))
85    }
86}
87
88/// Decrypt into any sink.
89pub fn decrypt_into<'a, C, S, A>(
90    cipher: &C,
91    node: Node<C::Leaf>,
92    aad: A,
93    sink: S,
94) -> Result<S::Ok, DecryptError<S::Error, C::Error>>
95where
96    C: Cipher,
97    S: Sink,
98    A: IntoAad<'a>,
99{
100    walk(
101        open_all(cipher, node, &aad.into_aad()).map_err(opening)?,
102        sink,
103    )
104    .map_err(DecryptError::with_cipher)
105}
106
107/// Decrypt through an async cipher. Every leaf is opened before the sink
108/// sees anything, so the sink itself is never awaited.
109///
110/// The sink is held across the await, so the future is `Send` only if the
111/// sink is. A caller with a sink that is not (a builder of JavaScript
112/// values) awaits [`open_all_async`] and then calls [`walk`] itself.
113pub async fn decrypt_into_async<'a, C, S, A>(
114    cipher: &C,
115    node: Node<C::Leaf>,
116    aad: A,
117    sink: S,
118) -> Result<S::Ok, DecryptError<S::Error, C::Error>>
119where
120    C: AsyncCipher,
121    S: Sink,
122    A: IntoAad<'a>,
123{
124    let opened = open_all_async(cipher, node, &aad.into_aad())
125        .await
126        .map_err(opening)?;
127    walk(opened, sink).map_err(DecryptError::with_cipher)
128}
129
130fn sealed_any<'n, L: 'n>(mut nodes: impl Iterator<Item = &'n Node<L>>) -> bool {
131    nodes.any(|n| !matches!(n, CipherText::Passthrough(_)))
132}
133
134/// Feed an opened tree to a sink.
135pub fn walk<S: Sink>(node: Node<Opened>, sink: S) -> Result<S::Ok, DecryptError<S::Error>> {
136    let empty_marker = |plaintext: Opened| match plaintext.risky_ref().is_empty() {
137        true => Ok(()),
138        false => Err(DecryptError::Auth),
139    };
140    match node {
141        CipherText::Single(plaintext) => {
142            let item =
143                leaf::read_leaf_item(plaintext.risky_ref()).map_err(|_| DecryptError::Malformed)?;
144            Ok(match item {
145                Item::Scalar(ty, v) => sink.scalar(ty, v)?,
146                Item::TypedNull(ty) => sink.typed_null(ty)?,
147            })
148            // `plaintext` drops and wipes here.
149        }
150        CipherText::Sequence(items) => walk_seq(items, sink),
151        CipherText::EmptySequence(plaintext) => {
152            empty_marker(plaintext)?;
153            Ok(sink.seq(Some(0))?.end()?)
154        }
155        CipherText::Map(entries) => walk_map(entries, sink),
156        CipherText::EmptyMap(plaintext) => {
157            empty_marker(plaintext)?;
158            Ok(sink.map(Some(0))?.end()?)
159        }
160        CipherText::Passthrough(frame) => {
161            let frame = frame.parse().map_err(|_| DecryptError::Malformed)?;
162            Ok(sink.passthrough(frame)?)
163        }
164    }
165}
166
167fn walk_seq<S: Sink>(items: Vec<Node<Opened>>, sink: S) -> Result<S::Ok, DecryptError<S::Error>> {
168    if !sealed_any(items.iter()) {
169        return Err(DecryptError::Malformed);
170    }
171    let mut seq = sink.seq(Some(items.len()))?;
172    for item in items {
173        walk(item, seq.element()?)?;
174    }
175    Ok(seq.end()?)
176}
177
178fn walk_map<S: Sink>(
179    entries: Vec<(String, Node<Opened>)>,
180    sink: S,
181) -> Result<S::Ok, DecryptError<S::Error>> {
182    let mut seen = HashSet::with_capacity(entries.len());
183    if !entries.iter().all(|(k, _)| seen.insert(k.as_str()))
184        || !sealed_any(entries.iter().map(|(_, v)| v))
185    {
186        return Err(DecryptError::Malformed);
187    }
188    let mut map = sink.map(Some(entries.len()))?;
189    for (key, value) in entries {
190        walk(value, map.entry(&key)?)?;
191    }
192    Ok(map.end()?)
193}
194
195/// Decrypt into a frame: the Go guest's whole `decrypt`.
196///
197/// The frame is plaintext, so it is built and returned inside `Protected`
198/// and wiped on every error path.
199pub fn decrypt_to_frame<'a, C, A>(
200    cipher: &C,
201    node: Node<C::Leaf>,
202    aad: A,
203) -> Result<Protected<Vec<u8>>, DecryptError<crate::frame::FrameWriteError, C::Error>>
204where
205    C: Cipher,
206    A: IntoAad<'a>,
207{
208    let mut out = Protected::new(Vec::new());
209    decrypt_into(
210        cipher,
211        node,
212        aad,
213        crate::frame::FrameWriter::new(out.risky_inner_mut()),
214    )?;
215    Ok(out)
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::plaintext::record::{record, Event, Owned, Record};
222    use crate::seal::leaves_with_aad;
223    use crate::seal::mock::{Leaf, Mock};
224    use crate::{encrypt, Encrypt, Int};
225    use std::collections::BTreeMap;
226    use vitaminc_protected::Protected;
227
228    fn sample() -> BTreeMap<&'static str, Vec<Option<u32>>> {
229        let mut m = BTreeMap::new();
230        m.insert("a", vec![Some(1), None]);
231        m.insert("b", vec![]);
232        m
233    }
234
235    fn round_trip<P: Encrypt + Clone>(p: P) -> Vec<Event> {
236        let sealed = encrypt(p, &Mock, "ctx").expect("encrypts");
237        let mut log = Vec::new();
238        decrypt_into(&Mock, sealed, "ctx", Record(&mut log)).expect("decrypts");
239        log
240    }
241
242    #[test]
243    fn what_goes_in_comes_out() {
244        assert_eq!(round_trip(sample()), record(sample()).expect("records"));
245        assert_eq!(
246            round_trip(34u32),
247            [Event::Scalar(None, Owned::Int(Int::U32(34)))]
248        );
249        assert_eq!(
250            round_trip(Vec::<u32>::new()),
251            [Event::Seq(Some(0)), Event::EndSeq]
252        );
253        assert_eq!(
254            round_trip(BTreeMap::<&str, u32>::new()),
255            [Event::Map(Some(0)), Event::EndMap]
256        );
257    }
258
259    #[test]
260    fn passthrough_comes_back_as_a_walk() {
261        #[derive(Clone)]
262        struct Doc;
263        impl Encrypt for Doc {
264            fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
265                let mut map = sink.map(Some(2))?;
266                map.entry("id")?.passthrough(vec![7u16])?;
267                "secret".visit(map.entry("name")?)?;
268                map.end()
269            }
270        }
271        assert_eq!(
272            round_trip(Doc),
273            [
274                Event::Map(Some(2)),
275                Event::Entry("id".into()),
276                Event::Passthrough,
277                Event::Seq(Some(1)),
278                Event::Element,
279                Event::Scalar(None, Owned::Int(Int::U16(7))),
280                Event::EndSeq,
281                Event::Entry("name".into()),
282                Event::Scalar(None, Owned::Text("secret".into())),
283                Event::EndMap,
284            ]
285        );
286    }
287
288    #[test]
289    fn a_walk_error_keeps_its_kind_under_any_cipher_error() {
290        let widen = |e: DecryptError<u8>| e.with_cipher::<&str>();
291        assert!(matches!(widen(DecryptError::Auth), DecryptError::Auth));
292        assert!(matches!(
293            widen(DecryptError::Malformed),
294            DecryptError::Malformed
295        ));
296        assert!(matches!(
297            widen(DecryptError::Sink(7)),
298            DecryptError::Sink(7)
299        ));
300    }
301
302    #[test]
303    fn every_marker_that_is_not_empty_is_an_auth_failure() {
304        let tampered = || Protected::new(vec![1]);
305        let nodes: [Node<Opened>; 2] = [
306            CipherText::EmptySequence(tampered()),
307            CipherText::EmptyMap(tampered()),
308        ];
309        for node in nodes {
310            assert!(matches!(
311                walk(node, Record(&mut Vec::new())),
312                Err(DecryptError::Auth)
313            ));
314        }
315    }
316
317    #[test]
318    fn decrypt_errors_say_what_failed() {
319        type E = DecryptError<&'static str, &'static str>;
320        assert_eq!(E::Cipher("down").to_string(), "cipher failed: down");
321        assert_eq!(
322            E::Auth.to_string(),
323            "an authenticated empty marker carried a payload"
324        );
325        assert_eq!(E::Malformed.to_string(), "malformed ciphertext");
326        assert_eq!(
327            E::Sink("expected u32").to_string(),
328            "sink refused the value: expected u32"
329        );
330        fn is_error<E: core::error::Error>(_: &E) {}
331        is_error(&DecryptError::<&str>::Malformed);
332    }
333
334    #[test]
335    fn a_malformed_tree_is_refused() {
336        let leaf = |bytes: &[u8]| CipherText::Single(Protected::new(bytes.to_vec()));
337        // A leaf that is not a scalar.
338        assert!(matches!(
339            walk(leaf(&[0xFF]), Record(&mut Vec::new())),
340            Err(DecryptError::Malformed)
341        ));
342        // Duplicate keys.
343        let node: Node<Opened> = CipherText::Map(vec![
344            ("k".into(), leaf(&[0x00])),
345            ("k".into(), leaf(&[0x00])),
346        ]);
347        assert!(matches!(
348            walk(node, Record(&mut Vec::new())),
349            Err(DecryptError::Malformed)
350        ));
351        // Nothing sealed.
352        let node: Node<Opened> =
353            CipherText::Sequence(vec![CipherText::Passthrough(PassthroughFrame(vec![0x00]))]);
354        assert!(matches!(
355            walk(node, Record(&mut Vec::new())),
356            Err(DecryptError::Malformed)
357        ));
358        // A passthrough that is not a frame.
359        let node: Node<Opened> = CipherText::Passthrough(PassthroughFrame(vec![0xF0]));
360        assert!(matches!(
361            walk(node, Record(&mut Vec::new())),
362            Err(DecryptError::Malformed)
363        ));
364    }
365
366    #[test]
367    fn a_leaf_moved_to_another_position_fails_to_open() {
368        // Seal `[1]` and try to read its element as a top-level value.
369        let sealed = encrypt(vec![1u32], &Mock, "ctx").expect("encrypts");
370        let (leaf, _) = leaves_with_aad(sealed, &"ctx".into_aad()).remove(0);
371        let moved: Node<Leaf> = CipherText::Single(leaf);
372        assert!(matches!(
373            decrypt_into(&Mock, moved, "ctx", Record(&mut Vec::new())),
374            Err(DecryptError::Cipher(_))
375        ));
376    }
377
378    #[test]
379    fn decrypt_to_frame_is_the_frame_of_the_source() {
380        let sealed = encrypt(sample(), &Mock, "ctx").expect("encrypts");
381        let frame = decrypt_to_frame(&Mock, sealed, "ctx").expect("decrypts");
382        assert_eq!(
383            frame.risky_ref(),
384            crate::to_frame(sample()).expect("frames").risky_ref()
385        );
386    }
387
388    use crate::frame::PassthroughFrame;
389}