Skip to main content

aranya_crypto/
default.rs

1//! Default implementations.
2
3use derive_where::derive_where;
4pub use spideroak_crypto::default::Rng;
5use spideroak_crypto::{
6    aead::{Aead, Nonce, Tag},
7    csprng::{Csprng, Random as _},
8    ed25519,
9    generic_array::GenericArray,
10    import::Import,
11    kdf::{Kdf, Prk},
12    kem::Kem,
13    keys::{SecretKey, SecretKeyBytes},
14    mac::Mac,
15    oid::Identified as _,
16    rust,
17    signer::Signer,
18    typenum::U64,
19};
20
21use crate::{
22    ciphersuite::{CipherSuite, CipherSuiteExt as _},
23    engine::{
24        self, AlgId, Engine, RawSecret, RawSecretWrap, UnwrapError, UnwrappedKey, WrapError,
25        WrongKeyType,
26    },
27    id::{BaseId, IdError, Identified},
28};
29
30/// The default [`CipherSuite`].
31///
32/// It uses the following algorithms:
33///
34/// - AEAD: AES-256-GCM
35/// - Hash: SHA-512
36/// - KDF: HKDF-SHA-512
37/// - KEM: DH-KEM(P-256, HKDF-SHA-256)
38/// - MAC: HMAC-SHA-512
39/// - Signatures: Ed25519
40pub struct DefaultCipherSuite;
41
42impl CipherSuite for DefaultCipherSuite {
43    type Aead = rust::Aes256Gcm;
44    type Hash = rust::Sha256;
45    type Kdf = rust::HkdfSha512;
46    type Kem = DhKemP256HkdfSha256;
47    type Mac = rust::HmacSha512;
48    type Signer = ed25519::Ed25519;
49}
50
51// Keep the raw Kem newtype out of the public API surface.
52mod __private {
53    use spideroak_crypto::{oid::consts::DHKEM_P256_HKDF_SHA256, rust};
54
55    crate::kem_with_oid! {
56        /// DHKEM(P256, HKDF-SHA256).
57        #[derive(Debug)]
58        pub struct DhKemP256HkdfSha256(rust::DhKemP256HkdfSha256) => DHKEM_P256_HKDF_SHA256
59    }
60}
61pub(crate) use __private::DhKemP256HkdfSha256;
62
63/// A basic [`Engine`] implementation that wraps keys with its [`Aead`].
64pub struct DefaultEngine<R: Csprng = Rng, S: CipherSuite = DefaultCipherSuite> {
65    aead: S::Aead,
66    rng: R,
67}
68
69impl<S: CipherSuite> Clone for DefaultEngine<Rng, S>
70where
71    S::Aead: Clone,
72{
73    fn clone(&self) -> Self {
74        Self {
75            aead: self.aead.clone(),
76            rng: Rng,
77        }
78    }
79}
80
81impl<R: Csprng, S: CipherSuite> DefaultEngine<R, S> {
82    /// Creates an [`Engine`] using `key`.
83    pub fn new(key: &<S::Aead as Aead>::Key, rng: R) -> Self {
84        Self {
85            aead: S::Aead::new(key),
86            rng,
87        }
88    }
89
90    /// Creates an [`Engine`] using entropy from `rng` and
91    /// returns it and the generated key.
92    pub fn from_entropy(rng: R) -> (Self, <S::Aead as Aead>::Key) {
93        let key = <S::Aead as Aead>::Key::random(&rng);
94        let eng = Self::new(&key, rng);
95        (eng, key)
96    }
97}
98
99impl<R: Csprng, S: CipherSuite> Csprng for DefaultEngine<R, S> {
100    fn fill_bytes(&self, dst: &mut [u8]) {
101        self.rng.fill_bytes(dst);
102    }
103}
104
105impl<R: Csprng, S: CipherSuite> Engine for DefaultEngine<R, S> {
106    type CS = S;
107
108    type WrappedKey = WrappedKey<S>;
109}
110
111impl<R: Csprng, S: CipherSuite> RawSecretWrap<Self> for DefaultEngine<R, S> {
112    fn wrap_secret<T>(
113        &self,
114        id: &<T as Identified>::Id,
115        secret: RawSecret<S>,
116    ) -> Result<<Self as Engine>::WrappedKey, WrapError>
117    where
118        T: UnwrappedKey<S>,
119    {
120        let id = *id.as_ref();
121        let mut tag = Tag::<S::Aead>::default();
122        // TODO(eric): we should probably ensure that we do not
123        // repeat nonces.
124        let nonce = Nonce::<_>::random(&self.rng);
125
126        let ad = S::tuple_hash(b"DefaultEngine", [T::ID.as_bytes(), id.as_bytes()]);
127
128        let mut secret = match secret {
129            RawSecret::Aead(sk) => Ciphertext::Aead(sk.try_export_secret()?.into_bytes()),
130            RawSecret::Decap(sk) => Ciphertext::Decap(sk.try_export_secret()?.into_bytes()),
131            RawSecret::Mac(sk) => Ciphertext::Mac(sk.try_export_secret()?.into_bytes()),
132            RawSecret::Prk(sk) => Ciphertext::Prk(sk.into_bytes().into_bytes()),
133            RawSecret::Seed(sk) => Ciphertext::Seed(sk.into()),
134            RawSecret::Signing(sk) => Ciphertext::Signing(sk.try_export_secret()?.into_bytes()),
135        };
136        self.aead.seal_in_place(
137            nonce.as_ref(),
138            secret.as_bytes_mut(),
139            &mut tag,
140            ad.as_bytes(),
141        )?;
142        // `secret` is now encrypted.
143
144        Ok(WrappedKey {
145            id,
146            nonce: nonce.into_inner(),
147            ciphertext: secret,
148            tag,
149        })
150    }
151
152    fn unwrap_secret<T>(
153        &self,
154        key: &<Self as Engine>::WrappedKey,
155    ) -> Result<RawSecret<S>, UnwrapError>
156    where
157        T: UnwrappedKey<S>,
158    {
159        let mut data = key.ciphertext.clone();
160        let ad = S::tuple_hash(b"DefaultEngine", [T::ID.as_bytes(), key.id.as_bytes()]);
161
162        self.aead.open_in_place(
163            key.nonce.as_ref(),
164            data.as_bytes_mut(),
165            &key.tag,
166            ad.as_bytes(),
167        )?;
168        // `data` has now been decrypted
169
170        let secret = match (T::ID, &data) {
171            (AlgId::Aead(_), Ciphertext::Aead(data)) => {
172                RawSecret::Aead(Import::<_>::import(data.as_slice())?)
173            }
174            (AlgId::Decap(_), Ciphertext::Decap(data)) => {
175                RawSecret::Decap(Import::<_>::import(data.as_slice())?)
176            }
177            (AlgId::Mac(_), Ciphertext::Mac(data)) => {
178                RawSecret::Mac(Import::<_>::import(data.as_slice())?)
179            }
180            (AlgId::Prk(_), Ciphertext::Prk(data)) => {
181                RawSecret::Prk(Prk::new(SecretKeyBytes::new(data.clone())))
182            }
183            (AlgId::Seed(()), Ciphertext::Seed(data)) => {
184                RawSecret::Seed(Import::<_>::import(data.as_slice())?)
185            }
186            (AlgId::Signing(_), Ciphertext::Signing(data)) => {
187                RawSecret::Signing(Import::<_>::import(data.as_slice())?)
188            }
189            _ => {
190                return Err(WrongKeyType {
191                    got: data.name(),
192                    expected: T::ID.name(),
193                }
194                .into());
195            }
196        };
197        Ok(secret)
198    }
199}
200
201/// Encrypted [`RawSecret`] bytes.
202#[derive_where(Clone, Serialize, Deserialize)]
203enum Ciphertext<CS: CipherSuite> {
204    Aead(GenericArray<u8, <<CS::Aead as Aead>::Key as SecretKey>::Size>),
205    Decap(GenericArray<u8, <<CS::Kem as Kem>::DecapKey as SecretKey>::Size>),
206    Mac(GenericArray<u8, <<CS::Mac as Mac>::Key as SecretKey>::Size>),
207    Prk(GenericArray<u8, <CS::Kdf as Kdf>::PrkSize>),
208    // NB: not `[u8; 64]` because serde only supports arrays up
209    // to 32 elements without additional gymnastics.
210    Seed(GenericArray<u8, U64>),
211    Signing(GenericArray<u8, <<CS::Signer as Signer>::SigningKey as SecretKey>::Size>),
212}
213
214impl<CS: CipherSuite> Ciphertext<CS> {
215    const fn name(&self) -> &'static str {
216        self.alg_id().name()
217    }
218
219    const fn alg_id(&self) -> AlgId {
220        match self {
221            Self::Aead(_) => AlgId::Aead(CS::Aead::OID),
222            Self::Decap(_) => AlgId::Decap(CS::Kem::OID),
223            Self::Mac(_) => AlgId::Mac(CS::Mac::OID),
224            Self::Prk(_) => AlgId::Prk(CS::Kdf::OID),
225            Self::Seed(_) => AlgId::Seed(()),
226            Self::Signing(_) => AlgId::Signing(CS::Signer::OID),
227        }
228    }
229}
230
231impl<CS: CipherSuite> Ciphertext<CS> {
232    fn as_bytes_mut(&mut self) -> &mut [u8] {
233        match self {
234            Self::Aead(v) => v.as_mut_slice(),
235            Self::Decap(v) => v.as_mut_slice(),
236            Self::Mac(v) => v.as_mut_slice(),
237            Self::Prk(v) => v.as_mut_slice(),
238            Self::Seed(v) => v.as_mut_slice(),
239            Self::Signing(v) => v.as_mut_slice(),
240        }
241    }
242}
243
244/// A key wrapped by [`DefaultEngine`].
245#[derive_where(Clone, Serialize, Deserialize)]
246pub struct WrappedKey<CS: CipherSuite> {
247    id: BaseId,
248    nonce: GenericArray<u8, <CS::Aead as Aead>::NonceSize>,
249    ciphertext: Ciphertext<CS>,
250    tag: Tag<CS::Aead>,
251}
252
253impl<CS: CipherSuite> engine::WrappedKey for WrappedKey<CS> {}
254
255impl<CS: CipherSuite> Identified for WrappedKey<CS> {
256    type Id = BaseId;
257
258    fn id(&self) -> Result<Self::Id, IdError> {
259        Ok(self.id)
260    }
261}
262
263#[cfg(test)]
264#[allow(clippy::wildcard_imports)]
265mod test {
266    use super::*;
267    use crate::{Rng, test_engine, test_util::test_ciphersuite};
268
269    test_engine!(
270        default_engine,
271        || -> DefaultEngine<Rng, DefaultCipherSuite> {
272            let (eng, _) = DefaultEngine::<Rng>::from_entropy(Rng);
273            eng
274        }
275    );
276
277    test_ciphersuite!(default_ciphersuite, DefaultCipherSuite);
278}