Skip to main content

aws_lc_rs/
pqdsa.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4pub(crate) mod key_pair;
5pub(crate) mod signature;
6
7use crate::aws_lc::{EVP_PKEY, EVP_PKEY_PQDSA, NID_MLDSA44, NID_MLDSA65, NID_MLDSA87};
8use crate::error::{KeyRejected, Unspecified};
9use crate::ptr::LcPtr;
10use core::ffi::c_int;
11
12#[derive(Debug, Eq, PartialEq)]
13#[allow(non_camel_case_types)]
14pub(crate) enum AlgorithmID {
15    ML_DSA_44,
16    ML_DSA_65,
17    ML_DSA_87,
18}
19
20impl AlgorithmID {
21    #[allow(dead_code)]
22    pub(crate) const fn from_nid(nid: c_int) -> Result<Self, Unspecified> {
23        match nid {
24            NID_MLDSA44 => Ok(Self::ML_DSA_44),
25            NID_MLDSA65 => Ok(Self::ML_DSA_65),
26            NID_MLDSA87 => Ok(Self::ML_DSA_87),
27            _ => Err(Unspecified),
28        }
29    }
30
31    pub(crate) const fn nid(&self) -> c_int {
32        match self {
33            Self::ML_DSA_44 => NID_MLDSA44,
34            Self::ML_DSA_65 => NID_MLDSA65,
35            Self::ML_DSA_87 => NID_MLDSA87,
36        }
37    }
38
39    #[allow(dead_code)]
40    pub(crate) const fn priv_key_size_bytes(&self) -> usize {
41        match self {
42            Self::ML_DSA_44 => 2560,
43            Self::ML_DSA_65 => 4032,
44            Self::ML_DSA_87 => 4896,
45        }
46    }
47
48    pub(crate) const fn pub_key_size_bytes(&self) -> usize {
49        match self {
50            Self::ML_DSA_44 => 1312,
51            Self::ML_DSA_65 => 1952,
52            Self::ML_DSA_87 => 2592,
53        }
54    }
55
56    pub(crate) const fn seed_size_bytes(&self) -> usize {
57        // All ML-DSA variants use 32-byte seeds per FIPS 204
58        match self {
59            Self::ML_DSA_44 | Self::ML_DSA_65 | Self::ML_DSA_87 => 32,
60        }
61    }
62
63    pub(crate) const fn signature_size_bytes(&self) -> usize {
64        match self {
65            Self::ML_DSA_44 => 2420,
66            Self::ML_DSA_65 => 3309,
67            Self::ML_DSA_87 => 4627,
68        }
69    }
70}
71
72pub(crate) fn validate_pqdsa_evp_key(
73    evp_pkey: &LcPtr<EVP_PKEY>,
74    id: &'static AlgorithmID,
75) -> Result<(), KeyRejected> {
76    if evp_pkey.as_const().key_size_bytes() == id.pub_key_size_bytes() {
77        Ok(())
78    } else {
79        Err(KeyRejected::unspecified())
80    }
81}
82
83pub(crate) fn parse_pqdsa_public_key(
84    key_bytes: &[u8],
85    id: &'static AlgorithmID,
86) -> Result<LcPtr<EVP_PKEY>, KeyRejected> {
87    LcPtr::<EVP_PKEY>::parse_rfc5280_public_key(key_bytes, EVP_PKEY_PQDSA)
88        .or(LcPtr::<EVP_PKEY>::parse_raw_public_key(
89            key_bytes,
90            EVP_PKEY_PQDSA,
91        ))
92        .and_then(|key| validate_pqdsa_evp_key(&key, id).map(|()| key))
93}
94
95#[cfg(test)]
96mod tests {
97    use crate::aws_lc::{
98        EVP_PKEY_cmp, EVP_PKEY, EVP_PKEY_PQDSA, NID_MLDSA44, NID_MLDSA65, NID_MLDSA87,
99    };
100
101    use crate::evp_pkey::*;
102
103    use crate::pkcs8::Version;
104    use crate::pqdsa::key_pair::evp_key_pqdsa_generate;
105    use crate::pqdsa::AlgorithmID;
106    use crate::ptr::LcPtr;
107
108    #[test]
109    fn test_keygen() {
110        for nid in [NID_MLDSA44, NID_MLDSA65, NID_MLDSA87] {
111            let key = evp_key_pqdsa_generate(nid).unwrap();
112            println!("key size: {:?}", key.as_const().key_size_bytes());
113            test_serialization_for(&key, &AlgorithmID::from_nid(nid).unwrap());
114            test_signing_for(&key, &AlgorithmID::from_nid(nid).unwrap());
115        }
116    }
117
118    fn test_serialization_for(evp_pkey: &LcPtr<EVP_PKEY>, id: &AlgorithmID) {
119        let public_buffer = evp_pkey.as_const().marshal_rfc5280_public_key().unwrap();
120        println!("public marshall: {public_buffer:?}");
121        let key_public =
122            LcPtr::<EVP_PKEY>::parse_rfc5280_public_key(&public_buffer, EVP_PKEY_PQDSA).unwrap();
123
124        let private_buffer = evp_pkey
125            .as_const()
126            .marshal_rfc5208_private_key(Version::V1)
127            .unwrap();
128        println!("private marshall: {private_buffer:?}");
129        let key_private =
130            LcPtr::<EVP_PKEY>::parse_rfc5208_private_key(&private_buffer, EVP_PKEY_PQDSA).unwrap();
131
132        let raw_public_buffer = key_public.as_const().marshal_raw_public_key().unwrap();
133        assert_eq!(raw_public_buffer.len(), id.pub_key_size_bytes());
134        println!("raw public size: {}", raw_public_buffer.len());
135        let key_public2 =
136            LcPtr::<EVP_PKEY>::parse_raw_public_key(&raw_public_buffer, EVP_PKEY_PQDSA).unwrap();
137
138        assert_eq!(1, unsafe {
139            EVP_PKEY_cmp(key_public.as_const_ptr(), key_public2.as_const_ptr())
140        });
141
142        let raw_private_buffer = key_private.as_const().marshal_raw_private_key().unwrap();
143        assert_eq!(raw_private_buffer.len(), id.priv_key_size_bytes());
144        println!("raw private size: {}", raw_private_buffer.len());
145        let key_private2 =
146            LcPtr::<EVP_PKEY>::parse_raw_private_key(&raw_private_buffer, EVP_PKEY_PQDSA).unwrap();
147        assert_eq!(1, unsafe {
148            EVP_PKEY_cmp(key_private.as_const_ptr(), key_private2.as_const_ptr())
149        });
150    }
151
152    fn test_signing_for(evp_pkey: &LcPtr<EVP_PKEY>, id: &AlgorithmID) {
153        let message = b"hello world";
154        let signature = evp_pkey
155            .sign(message, None, No_EVP_PKEY_CTX_consumer)
156            .unwrap();
157        println!("signature size: {}", signature.len());
158        assert_eq!(signature.len(), evp_pkey.as_const().signature_size_bytes());
159        assert_eq!(signature.len(), id.signature_size_bytes());
160        evp_pkey
161            .verify(message, None, No_EVP_PKEY_CTX_consumer, &signature)
162            .unwrap();
163        println!("verified: {signature:?}");
164    }
165}