aranya_crypto/
engine.rs

1//! The cryptography engine.
2//!
3//! # Warning
4//!
5//! This is a low-level module. You should not be using it
6//! directly unless you are implementing an engine.
7
8#![forbid(unsafe_code)]
9
10use core::{convert::Infallible, fmt::Debug, hash::Hash, result::Result};
11
12use buggy::Bug;
13use serde::{Serialize, de::DeserializeOwned};
14use spideroak_crypto::{
15    aead::{Aead, OpenError, SealError},
16    csprng::Csprng,
17    import::{ExportError, ImportError},
18    kdf::{Kdf, Prk},
19    kem::Kem,
20    mac::Mac,
21    oid::{self, Identified as _, Oid},
22    signer::Signer,
23};
24
25use crate::{
26    ciphersuite::CipherSuite,
27    id::{IdError, Identified},
28};
29
30/// The core trait used by the cryptography engine APIs.
31pub trait Engine: Csprng + RawSecretWrap<Self> + Sized {
32    /// The engine's [`CipherSuite`].
33    type CS: CipherSuite;
34
35    /// An encrypted, authenticated key that can only be
36    /// decrypted with [`Engine::unwrap`].
37    type WrappedKey: WrappedKey;
38
39    /// Encrypts and authenticates an unwrapped key.
40    fn wrap<T>(&mut self, key: T) -> Result<Self::WrappedKey, WrapError>
41    where
42        T: UnwrappedKey<Self::CS>,
43    {
44        let id = key.id()?;
45        let secret = key.into_secret();
46        self.wrap_secret::<T>(&id, secret.0)
47    }
48
49    /// Decrypts and authenticates the wrapped key.
50    fn unwrap<T>(&self, key: &Self::WrappedKey) -> Result<T, UnwrapError>
51    where
52        T: UnwrappedKey<Self::CS>,
53    {
54        let secret = self.unwrap_secret::<T>(key)?;
55        Ok(T::try_from_secret(UnwrappedSecret(secret))?)
56    }
57
58    /// Makes a best-effort attempt to render irrecoverable all
59    /// key material protected by the [`Engine`].
60    ///
61    /// This is usually implemented by destroying the key
62    /// wrapping keys.
63    fn destroy(self) {}
64}
65
66/// An encrypted, authenticated key created by [`Engine::wrap`]
67/// that can only be decrypted by [`Engine::unwrap`].
68///
69/// It need not directly contain the ciphertext. For example,
70/// it might only contain an identifier used to look up the
71/// key in an HSM.
72pub trait WrappedKey: Identified + Serialize + DeserializeOwned + Sized {}
73
74/// A key that an [`Engine`] can wrap.
75pub trait UnwrappedKey<CS: CipherSuite>: Sized + Identified {
76    /// The key's algorithm identifier.
77    const ID: AlgId;
78
79    /// Converts itself into the underlying [`Secret`].
80    fn into_secret(self) -> Secret<CS>;
81
82    /// Converts itself from a [`UnwrappedSecret`].
83    fn try_from_secret(key: UnwrappedSecret<CS>) -> Result<Self, WrongKeyType>;
84}
85
86/// A cryptographic secret underlying an [`UnwrappedKey`].
87///
88/// It is intentionally opaque; only [`Engine::wrap`] can access
89/// the internal [`RawSecret`].
90pub struct Secret<CS: CipherSuite>(RawSecret<CS>);
91
92impl<CS: CipherSuite> Secret<CS> {
93    /// Creates a new [`Secret`].
94    pub const fn new(secret: RawSecret<CS>) -> Self {
95        Self(secret)
96    }
97}
98
99/// A cryptographic secret as unwrapped by an [`Engine`].
100///
101/// It is intentionally opaque; only [`Engine::unwrap`] can
102/// construct this type.
103pub struct UnwrappedSecret<CS: CipherSuite>(RawSecret<CS>);
104
105impl<CS: CipherSuite> UnwrappedSecret<CS> {
106    /// Returns the underlying [`RawSecret`].
107    pub fn into_raw(self) -> RawSecret<CS> {
108        self.0
109    }
110}
111
112/// Encrypts and authenticates [`RawSecret`]s from
113/// [`UnwrappedKey`]s.
114pub trait RawSecretWrap<E: Engine> {
115    /// Encrypts and authenticates an unwrapped key.
116    ///
117    /// # Warning
118    ///
119    /// This method is used by [`Engine::wrap`] and should not be
120    /// called manually.
121    fn wrap_secret<T>(
122        &mut self,
123        id: &<T as Identified>::Id,
124        secret: RawSecret<E::CS>,
125    ) -> Result<E::WrappedKey, WrapError>
126    where
127        T: UnwrappedKey<E::CS>;
128
129    /// Decrypts and authenticates the wrapped key.
130    ///
131    /// # Warning
132    ///
133    /// This method is used by [`Engine::unwrap`] and should not
134    /// be called manually.
135    fn unwrap_secret<T>(&self, key: &E::WrappedKey) -> Result<RawSecret<E::CS>, UnwrapError>
136    where
137        T: UnwrappedKey<E::CS>;
138}
139
140/// A raw, unwrapped secret.
141pub enum RawSecret<CS: CipherSuite> {
142    /// A symmetric AEAD key.
143    Aead(<CS::Aead as Aead>::Key),
144    /// An asymmetric decapsulation key.
145    Decap(<CS::Kem as Kem>::DecapKey),
146    /// A MAC key.
147    Mac(<CS::Mac as Mac>::Key),
148    /// A PRK.
149    Prk(Prk<<CS::Kdf as Kdf>::PrkSize>),
150    /// Cryptographic seeds.
151    Seed([u8; 64]),
152    /// An asymmetric signing key.
153    Signing(<CS::Signer as Signer>::SigningKey),
154}
155
156impl<CS: CipherSuite> RawSecret<CS> {
157    /// Returns the string name of the key.
158    pub const fn name(&self) -> &'static str {
159        self.alg_id().name()
160    }
161
162    /// Returns the secret's algorithm identifier.
163    pub const fn alg_id(&self) -> AlgId {
164        match self {
165            Self::Aead(_) => AlgId::Aead(CS::Aead::OID),
166            Self::Decap(_) => AlgId::Decap(CS::Kem::OID),
167            Self::Mac(_) => AlgId::Mac(CS::Mac::OID),
168            Self::Prk(_) => AlgId::Prk(CS::Kdf::OID),
169            Self::Seed(_) => AlgId::Seed(()),
170            Self::Signing(_) => AlgId::Signing(CS::Signer::OID),
171        }
172    }
173}
174
175/// An algorithm identifier for [`UnwrappedKey`].
176#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
177pub enum AlgId {
178    /// See [`RawSecret::Aead`].
179    Aead(&'static Oid),
180    /// See [`RawSecret::Decap`].
181    Decap(&'static Oid),
182    /// See [`RawSecret::Mac`].
183    Mac(&'static Oid),
184    /// See [`RawSecret::Prk`].
185    Prk(&'static Oid),
186    /// See [`RawSecret::Seed`].
187    Seed(()),
188    /// See [`RawSecret::Signing`].
189    Signing(&'static Oid),
190}
191
192impl AlgId {
193    /// Returns the string name of the key.
194    #[inline]
195    pub const fn name(&self) -> &'static str {
196        match self {
197            Self::Aead(_) => "Aead",
198            Self::Decap(_) => "Decap",
199            Self::Mac(_) => "Mac",
200            Self::Prk(_) => "Prk",
201            Self::Seed(()) => "Seed",
202            Self::Signing(_) => "Signing",
203        }
204    }
205
206    pub(crate) const fn as_bytes(&self) -> &[u8] {
207        match self {
208            Self::Aead(id)
209            | Self::Decap(id)
210            | Self::Mac(id)
211            | Self::Prk(id)
212            | Self::Signing(id) => id.as_bytes(),
213            Self::Seed(()) => b"64 byte Seed",
214        }
215    }
216}
217
218macro_rules! alg_id_from_impl {
219    ($($name:ident => $ty:ident),* $(,)?) => {
220        $(impl AlgId {
221            #[doc(hidden)]
222            // Not part of the public API. Do not use.
223            pub const fn $name<CS: CipherSuite>() -> &'static Oid {
224                <CS::$ty as oid::Identified>::OID
225            }
226        })*
227    }
228}
229alg_id_from_impl! {
230    _from_aead => Aead,
231    _from_kem => Kem,
232    _from_mac => Mac,
233    _from_kdf => Kdf,
234    _from_signer => Signer,
235}
236
237/// Implements [`UnwrappedKey`] for `$name`.
238///
239/// - `$type` identifies which variant should be used.
240/// - `$into` is a function that takes `Self` and returns the
241///   inner value for the `$type` variant.
242/// - `$from` is a function that takes the inner value for the
243///   `$type` variant and returns `Self`.
244#[macro_export]
245macro_rules! unwrapped {
246    { name: $name:ident; type: Aead; into: $into:expr; from: $from:expr $(;)? } => {
247        $crate::__unwrapped_inner!(Aead, $crate::engine::AlgId::_from_aead::<CS>(), $name, $into, $from);
248    };
249    { name: $name:ident; type: Decap; into: $into:expr; from: $from:expr $(;)? } => {
250        $crate::__unwrapped_inner!(Decap, $crate::engine::AlgId::_from_kem::<CS>(), $name, $into, $from);
251    };
252    { name: $name:ident; type: Mac; into: $into:expr; from: $from:expr $(;)? } => {
253        $crate::__unwrapped_inner!(Mac, $crate::engine::AlgId::_from_mac::<CS>(), $name, $into, $from);
254    };
255    { name: $name:ident; type: Prk; into: $into:expr; from: $from:expr $(;)? } => {
256        $crate::__unwrapped_inner!(Prk, $crate::engine::AlgId::_from_kdf::<CS>(), $name, $into, $from);
257    };
258    { name: $name:ident; type: Seed; into: $into:expr; from: $from:expr $(;)? } => {
259        $crate::__unwrapped_inner!(Seed, (), $name, $into, $from);
260    };
261    { name: $name:ident; type: Signing; into: $into:expr; from: $from:expr $(;)? } => {
262        $crate::__unwrapped_inner!(Signing, $crate::engine::AlgId::_from_signer::<CS>(), $name, $into, $from);
263    };
264    ($($fallthrough:tt)*) => {
265        ::core::compile_error!("unknown variant");
266    };
267}
268pub(crate) use unwrapped;
269
270#[doc(hidden)]
271#[macro_export]
272macro_rules! __unwrapped_inner {
273    ($enum:ident, $id:expr, $name:ident, $into:expr, $from:expr) => {
274        impl<CS: $crate::CipherSuite> $crate::engine::UnwrappedKey<CS> for $name<CS> {
275            const ID: $crate::engine::AlgId = $crate::engine::AlgId::$enum($id);
276
277            #[inline]
278            fn into_secret(self) -> $crate::engine::Secret<CS> {
279                $crate::engine::Secret::new($crate::engine::RawSecret::$enum(
280                    #[allow(clippy::redundant_closure_call)]
281                    $into(self),
282                ))
283            }
284
285            #[inline]
286            fn try_from_secret(
287                key: $crate::engine::UnwrappedSecret<CS>,
288            ) -> ::core::result::Result<Self, $crate::engine::WrongKeyType> {
289                match key.into_raw() {
290                    $crate::engine::RawSecret::$enum(key) => ::core::result::Result::Ok(
291                        #[allow(clippy::redundant_closure_call)]
292                        $from(key),
293                    ),
294                    got => ::core::result::Result::Err($crate::engine::WrongKeyType {
295                        got: got.name(),
296                        expected: ::core::stringify!($name),
297                    }),
298                }
299            }
300        }
301    };
302}
303
304/// Returned when converting [`UnwrappedKey`]s to concrete key
305/// types.
306#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
307#[error("wrong key type: got {got}, expected {expected}")]
308pub struct WrongKeyType {
309    /// The type of key received.
310    pub got: &'static str,
311    /// The expected key type.
312    pub expected: &'static str,
313}
314
315/// An error from [`Engine::wrap`].
316#[derive(Debug, Eq, PartialEq, thiserror::Error)]
317pub enum WrapError {
318    /// An unknown or internal error has occurred.
319    #[error("unable to wrap key: {0}")]
320    Other(&'static str),
321    /// The secret key data cannot be exported.
322    #[error("unable to wrap key: {0}")]
323    Export(#[from] ExportError),
324    /// The encoded secret key cannot be encrypted.
325    #[error("unable to wrap key: {0}")]
326    Seal(#[from] SealError),
327    /// A bug was discovered.
328    #[error("unable to wrap key: {0}")]
329    Bug(#[from] Bug),
330    /// An error occurred accessing the unique ID.
331    #[error("unable to wrap key: {0}")]
332    Id(#[from] IdError),
333}
334
335impl From<Infallible> for WrapError {
336    fn from(err: Infallible) -> Self {
337        match err {}
338    }
339}
340
341/// An error from [`Engine::unwrap`].
342#[derive(Debug, Eq, PartialEq, thiserror::Error)]
343pub enum UnwrapError {
344    /// An unknown or internal error has occurred.
345    #[error("unable to unwrap key: {0}")]
346    Other(&'static str),
347    /// The wrapped key could not be decrypted.
348    #[error("unable to unwrap key: {0}")]
349    Open(#[from] OpenError),
350    /// The unwrapped secret key data cannot be imported.
351    #[error("unable to unwrap key: {0}")]
352    Import(#[from] ImportError),
353    /// Could not convert the [`UnwrappedKey`] to `T`.
354    #[error("unable to unwrap key: {0}")]
355    WrongKeyType(#[from] WrongKeyType),
356    /// A bug was discovered.
357    #[error("unable to unwrap key: {0}")]
358    Bug(#[from] Bug),
359}
360
361impl From<Infallible> for UnwrapError {
362    fn from(err: Infallible) -> Self {
363        match err {}
364    }
365}