Skip to main content

aranya_crypto/afc/
keys.rs

1use core::{cmp::Ordering, fmt};
2
3use buggy::Bug;
4use byteorder::{ByteOrder as _, LittleEndian};
5pub use spideroak_crypto::hpke::MessageLimitReached;
6use spideroak_crypto::{
7    aead,
8    hpke::{self, HpkeError, OpenCtx, SealCtx},
9    import::ImportError,
10};
11
12use crate::{
13    afc::shared::{RawOpenKey, RawSealKey},
14    ciphersuite::CipherSuite,
15    policy::LabelId,
16};
17
18/// Identifies the position of a ciphertext in a channel.
19#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
20pub struct Seq(hpke::Seq);
21
22impl Seq {
23    /// The zero value of a `Seq`.
24    pub const ZERO: Self = Self(hpke::Seq::ZERO);
25
26    /// Creates a sequence number.
27    pub const fn new(seq: u64) -> Self {
28        Self(hpke::Seq::new(seq))
29    }
30
31    /// Converts itself to a `u64`.
32    pub const fn to_u64(&self) -> u64 {
33        self.0.to_u64()
34    }
35
36    /// Returns the maximum allowed sequence number.
37    ///
38    /// For testing only.
39    #[cfg(any(test, feature = "test_util"))]
40    pub(crate) fn max<N: crate::generic_array::ArrayLength>() -> u64 {
41        hpke::Seq::max::<N>()
42    }
43}
44
45impl From<Seq> for u64 {
46    fn from(seq: Seq) -> Self {
47        seq.to_u64()
48    }
49}
50
51impl From<u64> for Seq {
52    fn from(seq: u64) -> Self {
53        Self::new(seq)
54    }
55}
56
57impl PartialEq<u64> for Seq {
58    fn eq(&self, other: &u64) -> bool {
59        PartialEq::eq(&self.to_u64(), other)
60    }
61}
62
63impl PartialOrd<u64> for Seq {
64    fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
65        PartialOrd::partial_cmp(&self.to_u64(), other)
66    }
67}
68
69impl fmt::Display for Seq {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        self.0.fmt(f)
72    }
73}
74
75macro_rules! packed {
76    (
77        $(#[$meta:meta])*
78        $vis:vis struct $name:ident $($tokens:tt)*
79    ) => {
80        $(#[$meta])*
81        $vis struct $name $($tokens)*
82        impl $name {
83            /// The size in bytes of the packed struct.
84            $vis const PACKED_SIZE: usize = {
85                #[repr(C, packed)]
86                #[allow(dead_code)]
87                $vis struct $name $($tokens)*
88                ::core::mem::size_of::<$name>()
89            };
90        }
91    };
92}
93
94packed! {
95    /// The authenticated data for each encryotion.
96    ///
97    /// Note that the sequence number is not part of the AD because
98    /// it is included in the nonce.
99    pub struct AuthData {
100        /// The AFC version number.
101        pub version: u32,
102        /// The label ID associated with the channel.
103        pub label_id: LabelId,
104    }
105}
106
107impl AuthData {
108    fn to_bytes(&self) -> [u8; Self::PACKED_SIZE] {
109        let mut b = [0u8; Self::PACKED_SIZE];
110        LittleEndian::write_u32(&mut b[0..4], self.version);
111        b[4..].copy_from_slice(self.label_id.as_bytes());
112        b
113    }
114}
115
116/// An encryption key.
117pub struct SealKey<CS: CipherSuite> {
118    ctx: SealCtx<CS::Aead>,
119}
120
121impl<CS: CipherSuite> SealKey<CS> {
122    /// The size in bytes of the overhead added to the plaintext.
123    pub const OVERHEAD: usize = SealCtx::<CS::Aead>::OVERHEAD;
124
125    /// Creates an encryption key from its raw parts.
126    pub fn from_raw(key: &RawSealKey<CS>, seq: Seq) -> Result<Self, ImportError> {
127        let RawSealKey { key, base_nonce } = key;
128        let ctx = SealCtx::new(key, base_nonce, seq.0)?;
129        Ok(Self { ctx })
130    }
131
132    /// Encrypts and authenticates `plaintext`, returning the
133    /// resulting sequence number.
134    ///
135    /// The resulting ciphertext is written to `dst`, which must
136    /// be at least `plaintext.len()` + [`OVERHEAD`][Self::OVERHEAD]
137    /// bytes long.
138    pub fn seal(
139        &mut self,
140        dst: &mut [u8],
141        plaintext: &[u8],
142        ad: &AuthData,
143    ) -> Result<Seq, SealError> {
144        let seq = self.ctx.seal(dst, plaintext, &ad.to_bytes())?;
145        Ok(Seq(seq))
146    }
147
148    /// Encrypts and authenticates `plaintext` in place,
149    /// returning the resulting sequence number.
150    pub fn seal_in_place(
151        &mut self,
152        data: impl AsMut<[u8]>,
153        tag: &mut [u8],
154        ad: &AuthData,
155    ) -> Result<Seq, SealError> {
156        let seq = self.ctx.seal_in_place(data, tag, &ad.to_bytes())?;
157        Ok(Seq(seq))
158    }
159
160    /// Returns the current sequence number.
161    #[inline]
162    pub fn seq(&self) -> Seq {
163        Seq(self.ctx.seq())
164    }
165}
166
167/// An error from [`SealKey`].
168#[derive(Debug, Eq, PartialEq, thiserror::Error)]
169pub enum SealError {
170    /// The maximum nuumber of messages have been encrypted with
171    /// this particular key.
172    #[error("message limit reached")]
173    MessageLimitReached,
174    /// Some other error occurred.
175    #[error(transparent)]
176    Other(HpkeError),
177    /// An internal bug was discovered.
178    #[error(transparent)]
179    Bug(#[from] Bug),
180}
181
182impl From<HpkeError> for SealError {
183    fn from(err: HpkeError) -> Self {
184        match err {
185            HpkeError::MessageLimitReached => Self::MessageLimitReached,
186            err => Self::Other(err),
187        }
188    }
189}
190
191/// A decryption key.
192pub struct OpenKey<CS: CipherSuite> {
193    ctx: OpenCtx<CS::Aead>,
194}
195
196impl<CS: CipherSuite> OpenKey<CS> {
197    /// The size in bytes of the overhead added to the plaintext.
198    pub const OVERHEAD: usize = OpenCtx::<CS::Aead>::OVERHEAD;
199
200    /// Creates decryption key from a raw key.
201    pub fn from_raw(key: &RawOpenKey<CS>) -> Result<Self, ImportError> {
202        let RawOpenKey { key, base_nonce } = key;
203        // We unconditionally set the sequence number to zero
204        // because `OpenKey` only supports decrypting with an
205        // explicit sequence number.
206        let ctx = OpenCtx::new(key, base_nonce, Seq::ZERO.0)?;
207        Ok(Self { ctx })
208    }
209
210    /// Decrypts and authenticates `ciphertext` at a particular
211    /// sequence number.
212    ///
213    /// The resulting plaintext is written to `dst`, which must
214    /// must be at least `ciphertext.len()` - [`OVERHEAD`][Self::OVERHEAD]
215    /// bytes long.
216    pub fn open(
217        &self,
218        dst: &mut [u8],
219        ciphertext: &[u8],
220        ad: &AuthData,
221        seq: Seq,
222    ) -> Result<(), OpenError> {
223        self.ctx.open_at(dst, ciphertext, &ad.to_bytes(), seq.0)?;
224        Ok(())
225    }
226
227    /// Decrypts and authenticates `ciphertext` at a particular
228    /// sequence number.
229    ///
230    /// The resulting plaintext is written to `dst`, which must
231    /// must be at least `ciphertext.len()` - [`OVERHEAD`][Self::OVERHEAD]
232    /// bytes long.
233    pub fn open_in_place(
234        &self,
235        data: impl AsMut<[u8]>,
236        tag: &[u8],
237        ad: &AuthData,
238        seq: Seq,
239    ) -> Result<(), OpenError> {
240        self.ctx
241            .open_in_place_at(data, tag, &ad.to_bytes(), seq.0)?;
242        Ok(())
243    }
244}
245
246/// An error from [`OpenKey`].
247#[derive(Debug, Eq, PartialEq, thiserror::Error)]
248pub enum OpenError {
249    /// The ciphertext could not be authenticated.
250    #[error("authentication error")]
251    Authentication,
252    /// The sequence number is out of range.
253    ///
254    /// Note that [`SealKey`] will never produce sequence numbers
255    /// that are out of range. See
256    /// [`SealError::MessageLimitReached`] for more information.
257    #[error("message limit reached")]
258    MessageLimitReached,
259    /// Some other error occurred.
260    #[error(transparent)]
261    Other(HpkeError),
262    /// An internal bug was discovered.
263    #[error(transparent)]
264    Bug(#[from] Bug),
265}
266
267impl From<HpkeError> for OpenError {
268    fn from(err: HpkeError) -> Self {
269        match err {
270            HpkeError::Open(aead::OpenError::Authentication) => Self::Authentication,
271            HpkeError::MessageLimitReached => Self::MessageLimitReached,
272            err => Self::Other(err),
273        }
274    }
275}