Skip to main content

hpke_rs_libcrux/
lib.rs

1#![doc = include_str!("../Readme.md")]
2#![cfg_attr(not(test), no_std)]
3extern crate alloc;
4
5use alloc::{format, string::String, vec::Vec};
6use core::fmt::Display;
7
8use hpke_rs_crypto::{
9    error::Error,
10    types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm},
11    CryptoRng, HpkeCrypto, HpkeTestRng,
12};
13
14use rand_core::SeedableRng;
15
16/// The Libcrux HPKE Provider
17#[derive(Debug)]
18pub struct HpkeLibcrux {}
19
20/// The PRNG for the Libcrux Provider.
21pub struct HpkeLibcruxPrng {
22    #[cfg(feature = "deterministic-prng")]
23    fake_rng: Vec<u8>,
24    rng: rand_chacha::ChaCha20Rng,
25}
26
27impl HpkeCrypto for HpkeLibcrux {
28    fn name() -> String {
29        "Libcrux".into()
30    }
31
32    fn kdf_extract(alg: KdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result<Vec<u8>, Error> {
33        let alg = kdf_algorithm_to_libcrux_hkdf_algorithm(alg);
34        let mut prk = alloc::vec![0u8; alg.hash_len()];
35        libcrux_hkdf::extract(alg, &mut prk, salt, ikm)
36            .map_err(|e| Error::CryptoLibraryError(format!("KDF extract error: {:?}", e)))?;
37        Ok(prk)
38    }
39
40    fn kdf_expand(
41        alg: KdfAlgorithm,
42        prk: &[u8],
43        info: &[u8],
44        output_size: usize,
45    ) -> Result<Vec<u8>, Error> {
46        let alg = kdf_algorithm_to_libcrux_hkdf_algorithm(alg);
47        let mut okm = alloc::vec![0u8; output_size];
48        libcrux_hkdf::expand(alg, &mut okm, prk, info)
49            .map_err(|e| Error::CryptoLibraryError(format!("KDF expand error: {:?}", e)))?;
50        Ok(okm)
51    }
52
53    fn dh(alg: KemAlgorithm, pk: &[u8], sk: &[u8]) -> Result<Vec<u8>, Error> {
54        let alg = kem_key_type_to_ecdh_alg(alg)?;
55
56        libcrux_ecdh::derive(alg, pk, sk)
57            .map_err(|e| Error::CryptoLibraryError(format!("ECDH derive error: {:?}", e)))
58            .map(|mut p| {
59                if alg == libcrux_ecdh::Algorithm::P256 {
60                    p.truncate(32);
61                    p
62                } else {
63                    p
64                }
65            })
66    }
67
68    fn secret_to_public(alg: KemAlgorithm, sk: &[u8]) -> Result<Vec<u8>, Error> {
69        let alg = kem_key_type_to_ecdh_alg(alg)?;
70
71        kem_ecdh_secret_to_public(alg, sk)
72    }
73
74    fn kem_key_gen(
75        alg: KemAlgorithm,
76        prng: &mut Self::HpkePrng,
77    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
78        match alg {
79            KemAlgorithm::XWingDraft06 => {
80                libcrux_kem::key_gen(libcrux_kem::Algorithm::XWingKemDraft06, prng)
81                    .map(|(sk, pk)| (pk.encode(), sk.encode()))
82                    .map_err(|e| Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e)))
83            }
84            other_alg => {
85                // ECDH only
86                let ecdh_alg = kem_key_type_to_ecdh_alg(other_alg)?;
87                let sk = libcrux_ecdh::generate_secret(ecdh_alg, prng).map_err(|e| {
88                    Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e))
89                })?;
90
91                let pk = kem_ecdh_secret_to_public(ecdh_alg, &sk)?;
92
93                Ok((pk, sk))
94            }
95        }
96    }
97
98    fn kem_key_gen_derand(alg: KemAlgorithm, seed: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
99        let alg = kem_key_type_to_libcrux_alg(alg)?;
100
101        libcrux_kem::key_gen_derand(alg, seed)
102            .map_err(|e| Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e)))
103            .map(|(sk, pk)| (pk.encode(), sk.encode()))
104    }
105
106    fn kem_encaps(
107        alg: KemAlgorithm,
108        pk_r: &[u8],
109        prng: &mut Self::HpkePrng,
110    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
111        let alg = kem_key_type_to_libcrux_alg(alg)?;
112
113        let pk =
114            libcrux_kem::PublicKey::decode(alg, pk_r).map_err(|_| Error::KemInvalidPublicKey)?;
115        pk.encapsulate(prng)
116            .map_err(|e| Error::CryptoLibraryError(format!("Encaps error {:?}", e)))
117            .map(|(ss, ct)| (ss.encode(), ct.encode()))
118    }
119
120    fn kem_decaps(alg: KemAlgorithm, ct: &[u8], sk_r: &[u8]) -> Result<Vec<u8>, Error> {
121        let alg = kem_key_type_to_libcrux_alg(alg)?;
122
123        let ct = libcrux_kem::Ct::decode(alg, ct).map_err(|_| Error::AeadInvalidCiphertext)?;
124        let sk =
125            libcrux_kem::PrivateKey::decode(alg, sk_r).map_err(|_| Error::KemInvalidSecretKey)?;
126        ct.decapsulate(&sk)
127            .map_err(|e| Error::CryptoLibraryError(format!("Decaps error {:?}", e)))
128            .map(|ss| ss.encode())
129    }
130
131    fn dh_validate_sk(alg: KemAlgorithm, sk: &[u8]) -> Result<Vec<u8>, Error> {
132        match alg {
133            KemAlgorithm::DhKemP256 => libcrux_ecdh::p256::validate_scalar_slice(&sk)
134                .map_err(|e| Error::CryptoLibraryError(format!("ECDH invalid sk error: {:?}", e)))
135                .map(|sk| sk.0.to_vec()),
136            _ => Err(Error::UnknownKemAlgorithm),
137        }
138    }
139
140    fn aead_seal(
141        alg: AeadAlgorithm,
142        key: &[u8],
143        nonce: &[u8],
144        aad: &[u8],
145        msg: &[u8],
146    ) -> Result<Vec<u8>, Error> {
147        let alg = aead_alg(alg)?;
148
149        use libcrux_traits::aead::typed_refs::Aead as _;
150
151        // set up buffer for ctxt and tag
152        let mut msg_ctx: Vec<u8> = alloc::vec![0; msg.len() + alg.tag_len()];
153        let (ctxt, tag) = msg_ctx.split_at_mut(msg.len());
154
155        // set up nonce
156        let nonce = alg.new_nonce(nonce).map_err(|_| Error::AeadInvalidNonce)?;
157
158        // set up key
159        let key = alg
160            .new_key(key)
161            .map_err(|_| Error::CryptoLibraryError("AEAD invalid key length".into()))?;
162
163        // set up tag
164        let tag = alg
165            .new_tag_mut(tag)
166            .map_err(|_| Error::CryptoLibraryError("Invalid tag length".into()))?;
167
168        key.encrypt(ctxt, tag, nonce, aad, msg)
169            .map_err(|_| Error::CryptoLibraryError("Invalid configuration".into()))?;
170
171        Ok(msg_ctx)
172    }
173
174    fn aead_open(
175        alg: AeadAlgorithm,
176        key: &[u8],
177        nonce: &[u8],
178        aad: &[u8],
179        cipher_txt: &[u8],
180    ) -> Result<Vec<u8>, Error> {
181        let alg = aead_alg(alg)?;
182
183        use libcrux_traits::aead::typed_refs::{Aead as _, DecryptError};
184
185        if cipher_txt.len() < alg.tag_len() {
186            return Err(Error::AeadInvalidCiphertext);
187        }
188
189        let boundary = cipher_txt.len() - alg.tag_len();
190
191        // set up buffers for ptext, ctext, and tag
192        let mut ptext = alloc::vec![0; boundary];
193        let (ctext, tag) = cipher_txt.split_at(boundary);
194
195        // set up nonce
196        let nonce = alg.new_nonce(nonce).map_err(|_| Error::AeadInvalidNonce)?;
197
198        // set up key
199        let key = alg
200            .new_key(key)
201            .map_err(|_| Error::CryptoLibraryError("AEAD invalid key length".into()))?;
202
203        // set up tag
204        let tag = alg
205            .new_tag(tag)
206            .map_err(|_| Error::CryptoLibraryError("Invalid tag length".into()))?;
207
208        key.decrypt(&mut ptext, nonce, aad, ctext, tag)
209            .map_err(|e| match e {
210                DecryptError::InvalidTag => {
211                    Error::CryptoLibraryError(format!("AEAD decryption error: {:?}", e))
212                }
213                _ => Error::CryptoLibraryError("Invalid configuration".into()),
214            })?;
215
216        Ok(ptext)
217    }
218
219    type HpkePrng = HpkeLibcruxPrng;
220
221    fn prng() -> Self::HpkePrng {
222        #[cfg(feature = "deterministic-prng")]
223        {
224            use rand::TryRngCore;
225            let mut fake_rng = alloc::vec![0u8; 256];
226            rand_chacha::ChaCha20Rng::from_os_rng()
227                .try_fill_bytes(&mut fake_rng)
228                .unwrap();
229            HpkeLibcruxPrng {
230                fake_rng,
231                rng: rand_chacha::ChaCha20Rng::from_os_rng(),
232            }
233        }
234        #[cfg(not(feature = "deterministic-prng"))]
235        HpkeLibcruxPrng {
236            rng: rand_chacha::ChaCha20Rng::from_os_rng(),
237        }
238    }
239
240    /// Returns an error if the KDF algorithm is not supported by this crypto provider.
241    fn supports_kdf(_: KdfAlgorithm) -> Result<(), Error> {
242        Ok(())
243    }
244
245    /// Returns an error if the KEM algorithm is not supported by this crypto provider.
246    fn supports_kem(alg: KemAlgorithm) -> Result<(), Error> {
247        match alg {
248            KemAlgorithm::DhKem25519 | KemAlgorithm::DhKemP256 | KemAlgorithm::XWingDraft06 => {
249                Ok(())
250            }
251            _ => Err(Error::UnknownKemAlgorithm),
252        }
253    }
254
255    /// Returns an error if the AEAD algorithm is not supported by this crypto provider.
256    fn supports_aead(alg: AeadAlgorithm) -> Result<(), Error> {
257        match alg {
258            AeadAlgorithm::Aes128Gcm | AeadAlgorithm::Aes256Gcm => Ok(()),
259            AeadAlgorithm::ChaCha20Poly1305 => Ok(()),
260            AeadAlgorithm::HpkeExport => Ok(()),
261        }
262    }
263}
264
265#[inline(always)]
266fn kem_ecdh_secret_to_public(alg: libcrux_ecdh::Algorithm, sk: &[u8]) -> Result<Vec<u8>, Error> {
267    libcrux_ecdh::secret_to_public(alg, sk)
268        .map_err(|e| Error::CryptoLibraryError(format!("ECDH derive base error: {:?}", e)))
269        .map(|p| {
270            if alg == libcrux_ecdh::Algorithm::P256 {
271                nist_format_uncompressed(p)
272            } else {
273                p
274            }
275        })
276}
277
278/// Prepend 0x04 for uncompressed NIST curve points.
279#[inline(always)]
280fn nist_format_uncompressed(mut pk: Vec<u8>) -> Vec<u8> {
281    let mut tmp = Vec::with_capacity(pk.len() + 1);
282    tmp.push(0x04);
283    tmp.append(&mut pk);
284    tmp
285}
286
287#[inline(always)]
288fn kdf_algorithm_to_libcrux_hkdf_algorithm(alg: KdfAlgorithm) -> libcrux_hkdf::Algorithm {
289    match alg {
290        KdfAlgorithm::HkdfSha256 => libcrux_hkdf::Algorithm::Sha256,
291        KdfAlgorithm::HkdfSha384 => libcrux_hkdf::Algorithm::Sha384,
292        KdfAlgorithm::HkdfSha512 => libcrux_hkdf::Algorithm::Sha512,
293    }
294}
295
296#[inline(always)]
297fn kem_key_type_to_libcrux_alg(alg: KemAlgorithm) -> Result<libcrux_kem::Algorithm, Error> {
298    match alg {
299        KemAlgorithm::DhKem25519 => Ok(libcrux_kem::Algorithm::X25519),
300        KemAlgorithm::DhKemP256 => Ok(libcrux_kem::Algorithm::Secp256r1),
301        KemAlgorithm::XWingDraft06 => Ok(libcrux_kem::Algorithm::XWingKemDraft06),
302        _ => Err(Error::UnknownKemAlgorithm),
303    }
304}
305
306#[inline(always)]
307fn kem_key_type_to_ecdh_alg(alg: KemAlgorithm) -> Result<libcrux_ecdh::Algorithm, Error> {
308    match alg {
309        KemAlgorithm::DhKem25519 => Ok(libcrux_ecdh::Algorithm::X25519),
310        KemAlgorithm::DhKemP256 => Ok(libcrux_ecdh::Algorithm::P256),
311        _ => Err(Error::UnknownKemAlgorithm),
312    }
313}
314
315#[inline(always)]
316fn aead_alg(alg_type: AeadAlgorithm) -> Result<libcrux_aead::Aead, Error> {
317    match alg_type {
318        AeadAlgorithm::ChaCha20Poly1305 => Ok(libcrux_aead::Aead::ChaCha20Poly1305),
319        AeadAlgorithm::Aes128Gcm => Ok(libcrux_aead::Aead::AesGcm128),
320        AeadAlgorithm::Aes256Gcm => Ok(libcrux_aead::Aead::AesGcm256),
321        _ => Err(Error::UnknownAeadAlgorithm),
322    }
323}
324
325impl hpke_rs_crypto::RngCore for HpkeLibcruxPrng {
326    fn next_u32(&mut self) -> u32 {
327        self.rng.next_u32()
328    }
329
330    fn next_u64(&mut self) -> u64 {
331        self.rng.next_u64()
332    }
333
334    fn fill_bytes(&mut self, dest: &mut [u8]) {
335        self.rng.fill_bytes(dest)
336    }
337}
338impl CryptoRng for HpkeLibcruxPrng {}
339
340impl HpkeTestRng for HpkeLibcruxPrng {
341    type Error = Error;
342
343    #[cfg(feature = "deterministic-prng")]
344    fn try_fill_test_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
345        // Here we fake our randomness for testing.
346        if dest.len() > self.fake_rng.len() {
347            return Err(Error::InsufficientRandomness);
348        }
349        dest.clone_from_slice(&self.fake_rng.split_off(self.fake_rng.len() - dest.len()));
350        Ok(())
351    }
352    #[cfg(not(feature = "deterministic-prng"))]
353    fn try_fill_test_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
354        use rand_core::TryRngCore;
355        self.try_fill_bytes(dest)
356            .map_err(|_| Error::InsufficientRandomness)
357    }
358
359    #[cfg(feature = "deterministic-prng")]
360    fn seed(&mut self, seed: &[u8]) {
361        self.fake_rng = seed.to_vec();
362    }
363    #[cfg(not(feature = "deterministic-prng"))]
364    fn seed(&mut self, _: &[u8]) {}
365}
366
367impl Display for HpkeLibcrux {
368    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
369        write!(f, "{}", Self::name())
370    }
371}