1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3
4pub mod public;
9
10use crate::drbg::rand_adapter::DrbgRng;
11use crate::drbg::{Error as InnerError, HmacDrbg};
12use core::{convert::TryFrom, fmt, mem, str::FromStr};
13use ml_dsa::{
14 EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, KeyGen, MlDsa87,
15 Signature as MlSignature, SigningKey, VerifyingKey,
16};
17use pkcs8::EncodePublicKey;
18pub use public::{
19 kid_from_spki_der, spki_der_canonical, spki_mldsa_paramset, spki_subject_key_bytes,
20};
21use sha2::{Digest, Sha256};
22use std::{format, string::String, vec, vec::Vec};
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25pub mod mldsa87 {
27 pub const PUBLIC_KEY_LEN: usize = 2592;
29 pub const SECRET_KEY_LEN: usize = 4896;
31 pub const SIGNATURE_LEN: usize = 4627;
33}
34
35pub const MLDSA87_SECRET_KEY_LEN: usize = mem::size_of::<EncodedSigningKey<MlDsa87>>();
37pub const MLDSA87_PUBLIC_KEY_LEN: usize = mem::size_of::<EncodedVerifyingKey<MlDsa87>>();
39pub const MLDSA87_SIGNATURE_LEN: usize = mem::size_of::<EncodedSignature<MlDsa87>>();
41
42const SIGNING_CONTEXT: &[u8] = b"quantum-sign.v1";
43const TRANSCRIPT_DOMAIN: &[u8] = b"quantum-sign:v1";
45pub const TRANSCRIPT_DIGEST_LEN: usize = 32;
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum DigestAlg {
51 Sha256,
53 Sha512,
55 Shake256_64,
57}
58
59impl DigestAlg {
60 pub fn as_str(self) -> &'static str {
62 match self {
63 DigestAlg::Sha256 => "sha256",
64 DigestAlg::Sha512 => "sha512",
65 DigestAlg::Shake256_64 => "shake256-64",
66 }
67 }
68
69 pub fn output_len(self) -> usize {
71 match self {
72 DigestAlg::Sha256 => 32,
73 DigestAlg::Sha512 | DigestAlg::Shake256_64 => 64,
74 }
75 }
76}
77
78impl FromStr for DigestAlg {
79 type Err = &'static str;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 match s.to_ascii_lowercase().as_str() {
83 "sha256" => Ok(DigestAlg::Sha256),
84 "sha512" => Ok(DigestAlg::Sha512),
85 "shake256-64" | "shake256" => Ok(DigestAlg::Shake256_64),
86 _ => Err("unsupported digest algorithm"),
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum DrbgError {
94 RequestTooLarge,
96 ReseedRequired,
98 EntropyUnavailable,
100 EntropyHealthFailed,
102}
103
104impl fmt::Display for DrbgError {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 DrbgError::RequestTooLarge => write!(f, "DRBG request exceeds per-call limit"),
108 DrbgError::ReseedRequired => write!(f, "DRBG reseed required"),
109 DrbgError::EntropyUnavailable => write!(f, "OS entropy unavailable"),
110 DrbgError::EntropyHealthFailed => write!(f, "entropy health check failed"),
111 }
112 }
113}
114
115impl std::error::Error for DrbgError {}
116
117impl From<InnerError> for DrbgError {
118 fn from(value: InnerError) -> Self {
119 match value {
120 InnerError::RequestTooLarge => DrbgError::RequestTooLarge,
121 InnerError::ReseedRequired => DrbgError::ReseedRequired,
122 InnerError::EntropyUnavailable => DrbgError::EntropyUnavailable,
123 InnerError::EntropyHealthFailed => DrbgError::EntropyHealthFailed,
124 }
125 }
126}
127
128#[derive(Debug)]
130pub enum CryptoError {
131 Drbg(DrbgError),
133 InvalidKey,
135 InvalidSignature,
137 SigningFailed,
139 BadDigestLen {
141 expected: usize,
143 got: usize,
145 },
146 PublicKey(String),
148}
149
150impl fmt::Display for CryptoError {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 match self {
153 CryptoError::Drbg(err) => write!(f, "{err}"),
154 CryptoError::InvalidKey => write!(f, "invalid key material"),
155 CryptoError::InvalidSignature => write!(f, "invalid signature"),
156 CryptoError::SigningFailed => write!(f, "signature generation failed"),
157 CryptoError::BadDigestLen { expected, got } => {
158 write!(f, "bad digest length: expected {expected}, got {got}")
159 }
160 CryptoError::PublicKey(msg) => write!(f, "public key error: {msg}"),
161 }
162 }
163}
164
165impl From<DrbgError> for CryptoError {
166 fn from(err: DrbgError) -> Self {
167 CryptoError::Drbg(err)
168 }
169}
170
171impl std::error::Error for CryptoError {}
172
173pub fn transcript_digest(
175 sign_alg: &str,
176 digest_alg: &str,
177 message_digest: &[u8],
178 policy_hash: Option<&[u8]>,
179) -> [u8; TRANSCRIPT_DIGEST_LEN] {
180 let mut hasher = Sha256::new();
181 hasher.update(TRANSCRIPT_DOMAIN);
182 hasher.update(b"|alg:");
183 hasher.update(sign_alg.as_bytes());
184 hasher.update(b"|hash:");
185 hasher.update(digest_alg.as_bytes());
186 if let Some(ph) = policy_hash {
187 hasher.update(b"|policy:");
188 hasher.update(ph);
189 }
190 hasher.update(b"|msg:");
191 hasher.update(message_digest);
192 let digest = hasher.finalize();
193 let mut out = [0u8; TRANSCRIPT_DIGEST_LEN];
194 out.copy_from_slice(&digest);
195 out
196}
197
198pub trait DeterministicRng {
200 fn fill_bytes(&mut self, out: &mut [u8]) -> Result<(), DrbgError>;
202
203 fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), DrbgError>;
205
206 fn set_reseed_interval(&mut self, interval: u64);
208
209 fn set_max_bytes_between_reseed(&mut self, bytes: u128);
211}
212
213#[derive(Zeroize, ZeroizeOnDrop)]
215pub struct HmacSha512Drbg {
216 inner: HmacDrbg,
217}
218
219impl HmacSha512Drbg {
220 pub fn new(
222 entropy: &[u8],
223 nonce: &[u8],
224 personalization: Option<&[u8]>,
225 ) -> Result<Self, DrbgError> {
226 let inner = HmacDrbg::new(entropy, nonce, personalization).map_err(DrbgError::from)?;
227 Ok(Self { inner })
228 }
229
230 pub fn from_os(personalization: Option<&[u8]>) -> Result<Self, DrbgError> {
232 let inner = HmacDrbg::from_os(personalization).map_err(DrbgError::from)?;
233 Ok(Self { inner })
234 }
235
236 pub fn inner_mut(&mut self) -> &mut HmacDrbg {
238 &mut self.inner
239 }
240
241 #[cfg(test)]
243 pub fn inner(&self) -> &HmacDrbg {
244 &self.inner
245 }
246}
247
248impl fmt::Debug for HmacSha512Drbg {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 f.debug_struct("HmacSha512Drbg").finish_non_exhaustive()
251 }
252}
253
254impl DeterministicRng for HmacSha512Drbg {
255 fn fill_bytes(&mut self, out: &mut [u8]) -> Result<(), DrbgError> {
256 self.inner.generate(out, None).map_err(DrbgError::from)
257 }
258
259 fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), DrbgError> {
260 self.inner
261 .reseed(entropy, additional_input)
262 .map_err(DrbgError::from)
263 }
264
265 fn set_reseed_interval(&mut self, interval: u64) {
266 self.inner.set_reseed_interval(interval);
267 }
268
269 fn set_max_bytes_between_reseed(&mut self, bytes: u128) {
270 self.inner.set_max_bytes_between_reseed(bytes);
271 }
272}
273
274pub fn random_bytes(len: usize) -> Result<Vec<u8>, DrbgError> {
276 let mut drbg = HmacSha512Drbg::from_os(None)?;
277 let mut buf = vec![0u8; len];
278 drbg.fill_bytes(&mut buf)?;
279 Ok(buf)
280}
281
282#[derive(Debug, Clone)]
284pub struct Keypair {
285 pub secret: Vec<u8>,
287 pub public: Vec<u8>,
289}
290
291impl Drop for Keypair {
292 fn drop(&mut self) {
293 self.zeroize();
294 }
295}
296
297impl Zeroize for Keypair {
298 fn zeroize(&mut self) {
299 self.secret.zeroize();
300 self.public.zeroize();
301 }
302}
303
304impl ZeroizeOnDrop for Keypair {}
305
306pub fn keypair_mldsa87(drbg: &mut HmacSha512Drbg) -> Result<Keypair, CryptoError> {
308 let mut rng = DrbgRng::new(drbg.inner_mut());
309 let kp = MlDsa87::key_gen(&mut rng);
310 let sk = kp.signing_key().encode().to_vec();
311 let pk = kp.verifying_key().encode().to_vec();
312 Ok(Keypair {
313 secret: sk,
314 public: pk,
315 })
316}
317
318pub fn sign_mldsa87(
320 drbg: &mut HmacSha512Drbg,
321 secret_key: &[u8],
322 message_digest: &[u8],
323 digest_alg: DigestAlg,
324 policy_hash: Option<&[u8]>,
325) -> Result<Vec<u8>, CryptoError> {
326 if message_digest.len() != digest_alg.output_len() {
327 return Err(CryptoError::BadDigestLen {
328 expected: digest_alg.output_len(),
329 got: message_digest.len(),
330 });
331 }
332 let enc =
333 EncodedSigningKey::<MlDsa87>::try_from(secret_key).map_err(|_| CryptoError::InvalidKey)?;
334 let sk = SigningKey::<MlDsa87>::decode(&enc);
335 let transcript =
336 transcript_digest("mldsa-87", digest_alg.as_str(), message_digest, policy_hash);
337 drbg.inner_mut()
338 .generate(&mut [], Some(&transcript))
339 .map_err(DrbgError::from)?;
340 let mut rng = DrbgRng::new(drbg.inner_mut());
341 let sig = sk
342 .sign_randomized(transcript.as_slice(), SIGNING_CONTEXT, &mut rng)
343 .map_err(|_| CryptoError::SigningFailed)?;
344 Ok(sig.encode().to_vec())
345}
346
347pub fn verify_mldsa87(
349 public_key: &[u8],
350 message_digest: &[u8],
351 digest_alg: DigestAlg,
352 signature: &[u8],
353 policy_hash: Option<&[u8]>,
354) -> Result<(), CryptoError> {
355 if message_digest.len() != digest_alg.output_len() {
356 return Err(CryptoError::BadDigestLen {
357 expected: digest_alg.output_len(),
358 got: message_digest.len(),
359 });
360 }
361 if public_key.len() != mldsa87::PUBLIC_KEY_LEN {
362 return Err(CryptoError::InvalidSignature);
363 }
364 if signature.len() != mldsa87::SIGNATURE_LEN {
365 return Err(CryptoError::InvalidSignature);
366 }
367 let enc_vk = EncodedVerifyingKey::<MlDsa87>::try_from(public_key)
368 .map_err(|_| CryptoError::InvalidKey)?;
369 let vk = VerifyingKey::<MlDsa87>::decode(&enc_vk);
370 let enc_sig = EncodedSignature::<MlDsa87>::try_from(signature)
371 .map_err(|_| CryptoError::InvalidSignature)?;
372 let sig = MlSignature::<MlDsa87>::decode(&enc_sig).ok_or(CryptoError::InvalidSignature)?;
373 let transcript =
374 transcript_digest("mldsa-87", digest_alg.as_str(), message_digest, policy_hash);
375 if vk.verify_with_context(transcript.as_slice(), SIGNING_CONTEXT, &sig) {
376 Ok(())
377 } else {
378 Err(CryptoError::InvalidSignature)
379 }
380}
381
382pub fn kid_from_public_key(public_key: &[u8]) -> Result<String, CryptoError> {
384 let spki = public_key_to_spki(public_key)?;
385 Ok(public::kid_from_spki_der(&spki))
386}
387
388pub fn public_key_to_spki(public_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
390 let enc_vk = EncodedVerifyingKey::<MlDsa87>::try_from(public_key)
391 .map_err(|_| CryptoError::InvalidKey)?;
392 let vk = VerifyingKey::<MlDsa87>::decode(&enc_vk);
393 let spki = vk
394 .to_public_key_der()
395 .map_err(|_| CryptoError::InvalidKey)?;
396 Ok(spki.as_bytes().to_vec())
397}
398
399pub fn verify_mldsa87_spki(
401 spki_der: &[u8],
402 message_digest: &[u8],
403 digest_alg: DigestAlg,
404 signature: &[u8],
405 policy_hash: Option<&[u8]>,
406) -> Result<(), CryptoError> {
407 let public_key =
408 spki_subject_key_bytes(spki_der).map_err(|e| CryptoError::PublicKey(format!("{e}")))?;
409 if public_key.len() != mldsa87::PUBLIC_KEY_LEN {
410 return Err(CryptoError::InvalidSignature);
411 }
412 if signature.len() != mldsa87::SIGNATURE_LEN {
413 return Err(CryptoError::InvalidSignature);
414 }
415 verify_mldsa87(
416 &public_key,
417 message_digest,
418 digest_alg,
419 signature,
420 policy_hash,
421 )
422}
423pub fn is_level5_sig_alg(alg: &str) -> bool {
425 matches!(
426 alg,
427 "mldsa-87"
428 | "slh-dsa-sha2-256s"
429 | "slh-dsa-sha2-256f"
430 | "slh-dsa-shake-256s"
431 | "slh-dsa-shake-256f"
432 )
433}