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#[derive(Debug)]
18pub struct HpkeLibcrux {}
19
20pub 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 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 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 let nonce = alg.new_nonce(nonce).map_err(|_| Error::AeadInvalidNonce)?;
157
158 let key = alg
160 .new_key(key)
161 .map_err(|_| Error::CryptoLibraryError("AEAD invalid key length".into()))?;
162
163 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 let mut ptext = alloc::vec![0; boundary];
193 let (ctext, tag) = cipher_txt.split_at(boundary);
194
195 let nonce = alg.new_nonce(nonce).map_err(|_| Error::AeadInvalidNonce)?;
197
198 let key = alg
200 .new_key(key)
201 .map_err(|_| Error::CryptoLibraryError("AEAD invalid key length".into()))?;
202
203 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 fn supports_kdf(_: KdfAlgorithm) -> Result<(), Error> {
242 Ok(())
243 }
244
245 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 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#[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 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}