Skip to main content

spideroak_crypto/
kem.rs

1//! Key Encapsulation Mechanisms.
2
3use core::{array::TryFromSliceError, borrow::Borrow, fmt, marker::PhantomData, result::Result};
4
5use zeroize::ZeroizeOnDrop;
6
7use crate::{
8    csprng::{Csprng, Random},
9    hpke::KemId,
10    import::{Import, ImportError},
11    kdf::{Kdf, KdfError, Prk},
12    keys::{PublicKey, RawSecretBytes, SecretKey},
13    signer::PkError,
14};
15
16/// An error from a [`Kem`].
17#[derive(Debug, Eq, PartialEq)]
18pub enum KemError {
19    /// The imported secret key is invalid.
20    InvalidDecapKeyFormat,
21    /// The imported public key is invalid.
22    InvalidEncapKeyFormat,
23    /// KEM encapsulation failed.
24    Encap,
25    /// Unable to decapsulate the ephemeral symmetric key.
26    Decapsulation,
27    /// A DHKEM operation failed.
28    DhKem(DhKemError),
29    /// A public key could not be imported.
30    Import(ImportError),
31    /// A public key could not be read.
32    PublicKey(PkError),
33}
34
35impl fmt::Display for KemError {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            Self::InvalidDecapKeyFormat => write!(f, "invalid secret key data"),
39            Self::InvalidEncapKeyFormat => write!(f, "invalid public key data"),
40            Self::Encap => write!(f, "encapsulation failed"),
41            Self::Decapsulation => write!(f, "unable to decapsulate symmetric key"),
42            Self::DhKem(err) => write!(f, "{}", err),
43            Self::Import(err) => write!(f, "{}", err),
44            Self::PublicKey(err) => write!(f, "{}", err),
45        }
46    }
47}
48
49impl core::error::Error for KemError {
50    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
51        match self {
52            Self::DhKem(err) => Some(err),
53            Self::Import(err) => Some(err),
54            _ => None,
55        }
56    }
57}
58
59impl From<DhKemError> for KemError {
60    fn from(err: DhKemError) -> Self {
61        Self::DhKem(err)
62    }
63}
64
65impl From<ImportError> for KemError {
66    fn from(err: ImportError) -> Self {
67        Self::Import(err)
68    }
69}
70
71impl From<PkError> for KemError {
72    fn from(err: PkError) -> Self {
73        Self::PublicKey(err)
74    }
75}
76
77/// A Key Encapsulation Mechanism (KEM).
78///
79/// # Requirements
80///
81/// The KEM must:
82///
83/// * Have at least a 128-bit security level.
84#[allow(non_snake_case)]
85pub trait Kem {
86    /// A local secret (private) key used to decapsulate secrets.
87    type DecapKey: DecapKey<EncapKey = Self::EncapKey>;
88    /// A remote public key used to encapsulate secrets.
89    type EncapKey: EncapKey;
90
91    /// An ephemeral, fixed-length symmetric key.
92    ///
93    /// The key must be at least 128 bits.
94    type Secret: RawSecretBytes + ZeroizeOnDrop;
95
96    /// An encapsulated [`Self::Secret`].
97    type Encap: Borrow<[u8]> + for<'a> Import<&'a [u8]>;
98
99    /// A randomized algorithm that generates an ephemeral,
100    /// fixed-length symmetric key and an 'encapsulation' of that
101    /// key that can be decrypted by the holder of the private
102    /// half of the public key.
103    fn encap<R: Csprng>(
104        rng: R,
105        pkR: &Self::EncapKey,
106    ) -> Result<(Self::Secret, Self::Encap), KemError>;
107
108    /// A deterministic algorithm that generates an ephemeral,
109    /// fixed-length symmetric key and an 'encapsulation' of that
110    /// key that can be decrypted by the holder of the private
111    /// half of the public key.
112    ///
113    /// # Warning
114    ///
115    /// The security of this function relies on choosing the
116    /// correct value for `skE`. It is a catastrophic error if
117    /// you do not ensure all of the following properties:
118    ///
119    /// - it must be cryptographically secure
120    /// - it must never be reused with the same `pkR`
121    fn encap_deterministically(
122        pkR: &Self::EncapKey,
123        skE: Self::DecapKey,
124    ) -> Result<(Self::Secret, Self::Encap), KemError>;
125
126    /// A deterministic algorithm that recovers the ephemeral
127    /// symmetric key from its encapsulated representation.
128    fn decap(enc: &Self::Encap, skR: &Self::DecapKey) -> Result<Self::Secret, KemError>;
129
130    /// An authenticated, randomized algorithm that generates an
131    /// ephemeral, fixed-length symmetric key and an
132    /// 'encapsulation' of that key that can be decrypted by the
133    /// holder of the private half of the public key.
134    ///
135    /// This function is identical to [`Kem::encap`] except that
136    /// it uses `skS` to encode an assurance that the shared
137    /// secret was generated by the holder of `skS`.
138    fn auth_encap<R: Csprng>(
139        rng: R,
140        pkR: &Self::EncapKey,
141        skS: &Self::DecapKey,
142    ) -> Result<(Self::Secret, Self::Encap), KemError>;
143
144    /// An authenticated, deterministic algorithm that generates
145    /// an ephemeral, fixed-length symmetric key and an
146    /// 'encapsulation' of that key that can be decrypted by the
147    /// holder of the private half of the public key.
148    ///
149    /// This function is identical to
150    /// [`Kem::encap_deterministically`] except that it uses
151    /// `skS` to encode an assurance that the shared secret was
152    /// generated by the holder of `skS`.
153    ///
154    /// # Warning
155    ///
156    /// The security of this function relies on choosing the
157    /// correct value for `skE`. It is a catastrophic error if
158    /// you do not ensure all of the following properties:
159    ///
160    /// - it must be cryptographically secure
161    /// - it must never be reused with the same `pkR`
162    fn auth_encap_deterministically(
163        pkR: &Self::EncapKey,
164        skS: &Self::DecapKey,
165        skE: Self::DecapKey,
166    ) -> Result<(Self::Secret, Self::Encap), KemError>;
167
168    /// An authenticated, deterministic algorithm that recovers
169    /// the ephemeral symmetric key from its encapsulated
170    /// representation.
171    ///
172    /// This function is identical to [`Kem::decap`] except that
173    /// it uses `pkS` to ensure that the shared secret was
174    /// generated by the holder of the private half of `pkS`.
175    fn auth_decap(
176        enc: &Self::Encap,
177        skR: &Self::DecapKey,
178        pkS: &Self::EncapKey,
179    ) -> Result<Self::Secret, KemError>;
180}
181
182/// An asymmetric private key used to decapsulate keys.
183pub trait DecapKey: SecretKey + Random {
184    /// The corresponding public key.
185    type EncapKey: EncapKey;
186
187    /// Returns the public half of the key.
188    fn public(&self) -> Result<Self::EncapKey, PkError>;
189}
190
191/// An asymmetric public key used to encapsulate keys.
192pub trait EncapKey: PublicKey {}
193
194/// An error from an [`Ecdh`].
195#[derive(Debug, Eq, PartialEq)]
196pub enum EcdhError {
197    /// An unknown or internal error has occurred.
198    Other(&'static str),
199}
200
201impl fmt::Display for EcdhError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::Other(msg) => write!(f, "{}", msg),
205        }
206    }
207}
208
209impl core::error::Error for EcdhError {}
210
211/// Elliptic Curve Diffie Hellman key exchange.
212///
213/// # Requirements
214///
215/// The algorithm must:
216///
217/// * Have at least a 128-bit security level
218pub trait Ecdh {
219    /// The size in bytes of a scalar.
220    const SCALAR_SIZE: usize;
221
222    /// An ECDH private key.
223    type PrivateKey: DecapKey<EncapKey = Self::PublicKey>;
224    /// An ECDH public key.
225    ///
226    /// For NIST's prime order curves (P-256, P-384, and P-521),
227    /// its [`PublicKey::export`] method must return the
228    /// uncompressed format described in [SEC] section 2.3.3.
229    ///
230    /// For X25519 and X448, its [`PublicKey::export`] method
231    /// must return the point as-is.
232    ///
233    /// For all other elliptic curves the format is unspecified.
234    ///
235    /// [SEC]: https://secg.org/sec1-v2.pdf
236    type PublicKey: EncapKey;
237
238    /// The shared secret (Diffie-Hellman value) computed as the
239    /// result of the key exchange ([`Self::ecdh`]).
240    type SharedSecret: Borrow<[u8]> + ZeroizeOnDrop;
241
242    /// Performs ECDH with the local secret key and remote public
243    /// key.
244    fn ecdh(
245        local: &Self::PrivateKey,
246        remote: &Self::PublicKey,
247    ) -> Result<Self::SharedSecret, EcdhError>;
248}
249
250/// An ECDH shared secret.
251#[derive(ZeroizeOnDrop)]
252pub struct SharedSecret<const N: usize>([u8; N]);
253
254impl<const N: usize> SharedSecret<N> {
255    /// Returns a pointer to the shared secret.
256    pub fn as_mut_ptr(&mut self) -> *mut u8 {
257        self.0.as_mut_ptr()
258    }
259
260    /// Returns the length of the shared secret.
261    #[allow(clippy::len_without_is_empty)]
262    pub const fn len(&self) -> usize {
263        self.0.len()
264    }
265}
266
267impl<const N: usize> Default for SharedSecret<N> {
268    fn default() -> Self {
269        Self([0u8; N])
270    }
271}
272
273impl<const N: usize> Borrow<[u8]> for SharedSecret<N> {
274    fn borrow(&self) -> &[u8] {
275        &self.0
276    }
277}
278
279impl<const N: usize> TryFrom<&[u8]> for SharedSecret<N> {
280    type Error = TryFromSliceError;
281
282    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
283        Ok(Self(data.try_into()?))
284    }
285}
286
287impl<const N: usize> fmt::Debug for SharedSecret<N> {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        f.debug_tuple("SharedSecret").finish_non_exhaustive()
290    }
291}
292
293/// An error from a DHKEM.
294#[derive(Debug, Eq, PartialEq)]
295pub enum DhKemError {
296    /// An ECDH operation failed.
297    Ecdh(EcdhError),
298    /// A KDF operation failed.
299    Kdf(KdfError),
300    /// A DH key could not be imported.
301    Import(ImportError),
302}
303
304impl fmt::Display for DhKemError {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        match self {
307            Self::Ecdh(err) => write!(f, "{}", err),
308            Self::Kdf(err) => write!(f, "{}", err),
309            Self::Import(err) => write!(f, "{}", err),
310        }
311    }
312}
313
314impl core::error::Error for DhKemError {
315    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
316        match self {
317            Self::Ecdh(err) => Some(err),
318            Self::Kdf(err) => Some(err),
319            Self::Import(err) => Some(err),
320        }
321    }
322}
323
324/// Implements [`Kem`] for an [`Ecdh`] and [`Kdf`].
325pub struct DhKem<E, F> {
326    id: KemId,
327    _e: PhantomData<E>,
328    _f: PhantomData<F>,
329}
330
331#[allow(non_snake_case)]
332impl<E: Ecdh, F: Kdf> DhKem<E, F> {
333    /// Creates a new [`DhKem`] with the provided [`KemId`].
334    pub fn new(id: KemId) -> Self {
335        Self {
336            id,
337            _e: PhantomData,
338            _f: PhantomData,
339        }
340    }
341
342    /// See [`Kem::encap`].
343    pub fn encap<R: Csprng>(
344        &self,
345        rng: R,
346        pkR: &E::PublicKey,
347    ) -> Result<(Prk<F::PrkSize>, PubKeyData<E>), KemError> {
348        let skE = E::PrivateKey::random(rng);
349        self.encap_deterministically(pkR, skE)
350    }
351
352    /// See [`Kem::encap_deterministically`].
353    pub fn encap_deterministically(
354        &self,
355        pkR: &E::PublicKey,
356        skE: E::PrivateKey,
357    ) -> Result<(Prk<F::PrkSize>, PubKeyData<E>), KemError> {
358        // def Encap(pkR):
359        //   skE, pkE = GenerateKeyPair()
360        //   dh = DH(skE, pkR)
361        //   enc = SerializePublicKey(pkE)
362        //
363        //   pkRm = SerializePublicKey(pkR)
364        //   kem_context = concat(enc, pkRm)
365        //
366        //   shared_secret = ExtractAndExpand(dh, kem_context)
367        //   return shared_secret, enc
368        let pkE = skE.public()?;
369        let dh = (E::ecdh(&skE, pkR).map_err(DhKemError::Ecdh)?, None);
370        let enc = pkE.export();
371
372        let pkRm = pkR.export();
373
374        let shared_secret =
375            Self::extract_and_expand(&dh, &enc, &pkRm, None, self.id).map_err(DhKemError::Kdf)?;
376        Ok((shared_secret, enc))
377    }
378
379    /// See [`Kem::decap`].
380    pub fn decap(
381        &self,
382        enc: &PubKeyData<E>,
383        skR: &E::PrivateKey,
384    ) -> Result<Prk<F::PrkSize>, KemError> {
385        // def Decap(enc, skR):
386        //   pkE = DeserializePublicKey(enc)
387        //   dh = DH(skR, pkE)
388        //
389        //   pkRm = SerializePublicKey(pk(skR))
390        //   kem_context = concat(enc, pkRm)
391        //
392        //   shared_secret = ExtractAndExpand(dh, kem_context)
393        //   return shared_secret
394        let pkE = E::PublicKey::import(enc.borrow())?;
395        let dh = (E::ecdh(skR, &pkE).map_err(DhKemError::Ecdh)?, None);
396
397        let pkRm = skR.public()?.export();
398
399        let shared_secret =
400            Self::extract_and_expand(&dh, enc, &pkRm, None, self.id).map_err(DhKemError::Kdf)?;
401        Ok(shared_secret)
402    }
403
404    /// See [`Kem::auth_encap`].
405    pub fn auth_encap<R: Csprng>(
406        &self,
407        rng: R,
408        pkR: &E::PublicKey,
409        skS: &E::PrivateKey,
410    ) -> Result<(Prk<F::PrkSize>, PubKeyData<E>), KemError> {
411        let skE = E::PrivateKey::random(rng);
412        self.auth_encap_deterministically(pkR, skS, skE)
413    }
414
415    /// See [`Kem::auth_encap_deterministically`].
416    pub fn auth_encap_deterministically(
417        &self,
418        pkR: &E::PublicKey,
419        skS: &E::PrivateKey,
420        skE: E::PrivateKey,
421    ) -> Result<(Prk<F::PrkSize>, PubKeyData<E>), KemError> {
422        // def AuthEncap(pkR, skS):
423        //   skE, pkE = GenerateKeyPair()
424        //   dh = concat(DH(skE, pkR), DH(skS, pkR))
425        //   enc = SerializePublicKey(pkE)
426        //
427        //   pkRm = SerializePublicKey(pkR)
428        //   pkSm = SerializePublicKey(pk(skS))
429        //   kem_context = concat(enc, pkRm, pkSm)
430        //
431        //   shared_secret = ExtractAndExpand(dh, kem_context)
432        //   return shared_secret, enc
433        let pkE = skE.public()?;
434        let dh = (
435            E::ecdh(&skE, pkR).map_err(DhKemError::Ecdh)?,
436            Some(E::ecdh(skS, pkR).map_err(DhKemError::Ecdh)?),
437        );
438        let enc = pkE.export();
439
440        let pkRm = pkR.export();
441        let pkSm = skS.public()?.export();
442
443        let shared_secret = Self::extract_and_expand(&dh, &enc, &pkRm, Some(&pkSm), self.id)
444            .map_err(DhKemError::Kdf)?;
445        Ok((shared_secret, enc))
446    }
447
448    /// See [`Kem::auth_decap`].
449    pub fn auth_decap(
450        &self,
451        enc: &PubKeyData<E>,
452        skR: &E::PrivateKey,
453        pkS: &E::PublicKey,
454    ) -> Result<Prk<F::PrkSize>, KemError> {
455        // def AuthDecap(enc, skR, pkS):
456        //   pkE = DeserializePublicKey(enc)
457        //   dh = concat(DH(skR, pkE), DH(skR, pkS))
458        //
459        //   pkRm = SerializePublicKey(pk(skR))
460        //   pkSm = SerializePublicKey(pkS)
461        //   kem_context = concat(enc, pkRm, pkSm)
462        //
463        //   shared_secret = ExtractAndExpand(dh, kem_context)
464        //   return shared_secret
465        let pkE = E::PublicKey::import(enc.borrow())?;
466        let dh = (
467            E::ecdh(skR, &pkE).map_err(DhKemError::Ecdh)?,
468            Some(E::ecdh(skR, pkS).map_err(DhKemError::Ecdh)?),
469        );
470
471        let pkRm = skR.public()?.export();
472        let pkSm = pkS.export();
473
474        let shared_secret = Self::extract_and_expand(&dh, enc, &pkRm, Some(&pkSm), self.id)
475            .map_err(DhKemError::Kdf)?;
476        Ok(shared_secret)
477    }
478
479    /// Performs `ExtractAndExpand`.
480    fn extract_and_expand(
481        dh: &(E::SharedSecret, Option<E::SharedSecret>),
482        enc: &PubKeyData<E>,
483        pkRm: &PubKeyData<E>,
484        pkSm: Option<&PubKeyData<E>>,
485        id: KemId,
486    ) -> Result<Prk<F::PrkSize>, KdfError> {
487        // def LabeledExtract(salt, label, ikm):
488        //   ...
489        // def LabeledExpand(prk, label, info, L):
490        //   ...
491        //
492        // def ExtractAndExpand(dh, kem_context):
493        //   eae_prk = LabeledExtract("", "eae_prk", dh)
494        //   shared_secret = LabeledExpand(eae_prk, "shared_secret",
495        //                                 kem_context, Nsecret)
496        //   return shared_secret
497        let mut out = Prk::<F::PrkSize>::default();
498
499        // NB: `labeled_ikm` and `labeled_info` are slices
500        // instead of arrays to cut down on stack usage.
501        // Additionally, `labeled_info` needs to be `Clone`, and
502        // it's significantly cheaper to clone `slice::Iter` that
503        // it is to clone `array::IntoIter`.
504
505        //  labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
506        let labeled_ikm: &[&[u8]] = &[
507            b"HPKE-v1",
508            // suite_id = concat("KEM", I2OSP(kem_id, 2))
509            b"KEM",
510            &id.to_be_bytes(),
511            // label
512            b"eae_prk",
513            // ikm
514            dh.0.borrow(),
515            dh.1.as_ref().map_or(&[], |v| v.borrow()),
516        ];
517
518        //  labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
519        //                 label, info)
520        let labeled_info: &[&[u8]] = &[
521            &(F::PRK_SIZE as u16).to_be_bytes()[..],
522            b"HPKE-v1",
523            // suite_id = concat("KEM", I2OSP(kem_id, 2))
524            b"KEM",
525            &id.to_be_bytes(),
526            // label
527            b"shared_secret",
528            // kem_context
529            enc.borrow(),
530            pkRm.borrow(),
531            pkSm.map_or(&[], |v| v.borrow()),
532        ];
533
534        F::extract_and_expand_multi(
535            out.as_bytes_mut(),
536            labeled_ikm.iter().copied(),
537            &[],
538            labeled_info.iter().copied(),
539        )?;
540        Ok(out)
541    }
542}
543
544impl<E, F> fmt::Debug for DhKem<E, F> {
545    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546        f.debug_struct("DhKem").field("id", &self.id).finish()
547    }
548}
549
550type PubKeyData<T> = <<T as Ecdh>::PublicKey as PublicKey>::Data;
551
552/// Implement [`Kem`] for an [`Ecdh`].
553///
554/// - `name`: The name of the resulting [`Kem`] impl.
555/// - `doc`: A string to use for documentation.
556/// - `ecdh`: The [`Ecdh`].
557/// - `kdf`: The [`Kdf`][crate::kdf::Kdf] to use.
558/// - `sk`: The [`DecapKey`] to use.
559/// - `pk`: The [`EncapKey`] to use.
560///
561/// # Example
562///
563/// ```rust,ignore
564/// use spideroak_crypto::dhkem_impl;
565/// dhkem_impl! {
566///     DhKemP256HkdfSha256,
567///     "DHKEM(P256, HKDF-SHA256)",
568///     P256,
569///     HkdfSha256,
570///     P256PrivateKey,
571///     P256PublicKey,
572/// }
573/// ```
574#[macro_export]
575macro_rules! dhkem_impl {
576    (
577        $name:ident,
578        $doc:expr,
579        $kem_id:expr,
580        $ecdh:ty,
581        $kdf:ty,
582        $sk:ident,
583        $pk:ident
584        $(, oid = $oid:ident)?
585        $(,)?
586    ) => {
587        #[doc = concat!($doc, ".")]
588        #[derive(Debug)]
589        pub struct $name;
590
591        impl $name {
592            const KEM_ID: $crate::hpke::KemId = $kem_id;
593        }
594
595        #[allow(non_snake_case)]
596        impl $crate::kem::Kem for $name {
597            type DecapKey = $sk;
598            type EncapKey = $pk;
599            type Secret = $crate::kdf::Prk<<$kdf as $crate::kdf::Kdf>::PrkSize>;
600            type Encap = <$pk as $crate::keys::PublicKey>::Data;
601
602            fn encap<R: $crate::csprng::Csprng>(
603                rng: R,
604                pkR: &Self::EncapKey,
605            ) -> ::core::result::Result<(Self::Secret, Self::Encap), $crate::kem::KemError> {
606                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID).encap(rng, pkR)
607            }
608
609            fn encap_deterministically(
610                pkR: &Self::EncapKey,
611                skE: Self::DecapKey,
612            ) -> ::core::result::Result<(Self::Secret, Self::Encap), $crate::kem::KemError> {
613                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID)
614                    .encap_deterministically(pkR, skE)
615            }
616
617            fn decap(
618                enc: &Self::Encap,
619                skR: &Self::DecapKey,
620            ) -> ::core::result::Result<Self::Secret, $crate::kem::KemError> {
621                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID).decap(enc, skR)
622            }
623
624            fn auth_encap<R: $crate::csprng::Csprng>(
625                rng: R,
626                pkR: &Self::EncapKey,
627                skS: &Self::DecapKey,
628            ) -> ::core::result::Result<(Self::Secret, Self::Encap), $crate::kem::KemError> {
629                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID).auth_encap(rng, pkR, skS)
630            }
631
632            fn auth_encap_deterministically(
633                pkR: &Self::EncapKey,
634                skS: &Self::DecapKey,
635                skE: Self::DecapKey,
636            ) -> ::core::result::Result<(Self::Secret, Self::Encap), $crate::kem::KemError> {
637                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID)
638                    .auth_encap_deterministically(pkR, skS, skE)
639            }
640
641            fn auth_decap(
642                enc: &Self::Encap,
643                skR: &Self::DecapKey,
644                pkS: &Self::EncapKey,
645            ) -> ::core::result::Result<Self::Secret, $crate::kem::KemError> {
646                $crate::kem::DhKem::<$ecdh, $kdf>::new(Self::KEM_ID).auth_decap(enc, skR, pkS)
647            }
648        }
649
650        impl $crate::hpke::HpkeKem for $name {
651            const ID: $crate::hpke::KemId = Self::KEM_ID;
652        }
653
654        $(impl $crate::oid::Identified for $name {
655            const OID: &'static $crate::oid::Oid = $oid;
656        })?
657    };
658}
659pub(crate) use dhkem_impl;