quantcrypt/asn1/
public_key.rs

1use crate::asn1::asn_util::{is_composite_kem_or_dsa_oid, is_valid_kem_or_dsa_oid};
2use crate::dsa::common::dsa_trait::Dsa;
3use crate::dsa::dsa_manager::DsaManager;
4use crate::errors;
5use crate::kem::common::kem_trait::Kem;
6use crate::kem::kem_manager::KemManager;
7use der::{asn1::BitString, Document};
8use der::{Decode, Encode};
9use pem::EncodeConfig;
10use pkcs8::ObjectIdentifier;
11use pkcs8::{spki::AlgorithmIdentifierWithOid, EncodePublicKey};
12
13use crate::asn1::composite_public_key::CompositePublicKey;
14
15use crate::asn1::public_key_info::PublicKeyInfo;
16
17use super::asn_util::{is_dsa_oid, is_kem_oid};
18use errors::QuantCryptError;
19
20type Result<T> = std::result::Result<T, QuantCryptError>;
21
22#[derive(Clone)]
23/// A raw public key for use with the certificate builder
24pub struct PublicKey {
25    /// The OID for the DSA / KEM
26    oid: String,
27    /// The key material
28    key: Vec<u8>,
29    /// Is it a composite key
30    is_composite: bool,
31}
32
33impl PublicKey {
34    /// Create a new public key
35    ///
36    /// # Arguments
37    ///
38    /// * `oid` - The OID for the DSA / KEM
39    /// * `key` - The key material
40    ///
41    /// # Returns
42    ///
43    /// A new public key
44    ///
45    /// # Errors
46    ///
47    /// `KeyError::InvalidPublicKey` will be returned if the OID is invalid
48    /// or the key is invalid
49    pub fn new(oid: &str, key: &[u8]) -> Result<Self> {
50        if !is_valid_kem_or_dsa_oid(&oid.to_string()) {
51            return Err(errors::QuantCryptError::InvalidPublicKey);
52        }
53        let is_composite = is_composite_kem_or_dsa_oid(oid);
54        Ok(Self {
55            oid: oid.to_string(),
56            key: key.to_vec(),
57            is_composite,
58        })
59    }
60
61    /// Create a new public key from a composite public key
62    ///
63    /// # Arguments
64    ///
65    /// * `composite_pk` - The composite public key
66    ///
67    /// # Returns
68    ///
69    /// A new public key
70    ///
71    /// # Errors
72    ///
73    /// `KeyError::InvalidPublicKey` will be returned if the public key is invalid
74    pub fn from_composite(composite_pk: &CompositePublicKey) -> Result<Self> {
75        Ok(Self {
76            oid: composite_pk.get_oid().to_string(),
77            key: composite_pk
78                .to_der()
79                .map_err(|_| errors::QuantCryptError::InvalidPublicKey)?,
80            is_composite: true,
81        })
82    }
83
84    /// Get the OID for the DSA / KEM public key algorithm
85    ///
86    /// # Returns
87    ///
88    /// The OID for the DSA / KEM public key algorithm
89    pub fn get_oid(&self) -> &str {
90        &self.oid
91    }
92
93    /// Get the key material
94    ///
95    /// # Returns
96    ///
97    /// The key material
98    pub fn get_key(&self) -> &[u8] {
99        &self.key
100    }
101
102    /// Is it a composite key
103    ///
104    /// # Returns
105    ///
106    /// True if it is a composite key, false otherwise
107    pub fn is_composite(&self) -> bool {
108        self.is_composite
109    }
110
111    /// Convert the public key to a PEM-encoded string
112    ///
113    /// # Returns
114    ///
115    /// The PEM-encoded public key
116    ///
117    /// # Errors
118    ///
119    /// `KeyError::InvalidPublicKey` will be returned if the public key is invalid
120    pub fn to_pem(&self) -> Result<String> {
121        let der = self
122            .to_der()
123            .map_err(|_| errors::QuantCryptError::InvalidPublicKey)?;
124        let pem_obj = pem::Pem::new("PUBLIC KEY", der);
125        let encode_conf = EncodeConfig::default().set_line_ending(pem::LineEnding::LF);
126        Ok(pem::encode_config(&pem_obj, encode_conf))
127    }
128
129    /// Get's the raw public key as a BitString such that it can be used in a OneAsymmetricKey structure
130    ///
131    /// # Returns
132    ///
133    /// The public key as a BitString
134    pub(crate) fn to_bitstring(&self) -> Result<BitString> {
135        let pk_bs = BitString::from_bytes(&self.key)
136            .map_err(|_| errors::QuantCryptError::InvalidPublicKey)?;
137        Ok(pk_bs)
138    }
139
140    /// Convert the public key to a DER-encoded byte array. The raw public key is wrapped in a
141    /// SubjectPublicKeyInfo structure.
142    ///
143    /// # Returns
144    ///
145    /// The DER-encoded byte array
146    ///
147    /// # Errors
148    ///
149    /// `KeyError::InvalidPublicKey` will be returned if the public key is invalid
150    pub fn to_der(&self) -> Result<Vec<u8>> {
151        let pk_bs = self.to_bitstring()?;
152
153        let oid: ObjectIdentifier = self
154            .oid
155            .parse()
156            .map_err(|_| QuantCryptError::InvalidPublicKey)?;
157
158        let pub_key_info = PublicKeyInfo {
159            algorithm: AlgorithmIdentifierWithOid {
160                oid,
161                parameters: None,
162            },
163            public_key: pk_bs,
164        };
165        let der = pub_key_info
166            .to_der()
167            .map_err(|_| errors::QuantCryptError::InvalidPublicKey)?;
168        Ok(der)
169    }
170
171    /// Create a new public key from a PEM-encoded string
172    ///
173    /// # Arguments
174    ///
175    /// * `pem` - The PEM-encoded public key
176    ///
177    /// # Returns
178    ///
179    /// A new public key
180    ///
181    /// # Errors
182    ///
183    /// `KeyError::InvalidPublicKey` will be returned if the public key is invalid
184    pub fn from_pem(pem: &str) -> Result<Self> {
185        let pem = pem::parse(pem).map_err(|_| errors::QuantCryptError::InvalidPublicKey)?;
186        // Header should be "PUBLIC KEY"
187        if pem.tag() != "PUBLIC KEY" {
188            return Err(errors::QuantCryptError::InvalidPublicKey);
189        }
190
191        let der = pem.contents();
192        Self::from_der(der)
193    }
194
195    /// Create a new public key from a DER-encoded byte array
196    ///
197    /// # Arguments
198    ///
199    /// * `der` - The DER-encoded public key
200    ///
201    /// # Returns
202    ///
203    /// A new public key
204    ///
205    /// # Errors
206    ///
207    /// `KeyError::InvalidPublicKey` will be returned if the public key is invalid
208    pub fn from_der(der: &[u8]) -> Result<Self> {
209        let pub_key_info =
210            PublicKeyInfo::from_der(der).map_err(|_| errors::QuantCryptError::InvalidPublicKey)?;
211        let pk_bytes = if let Some(pk_bytes) = pub_key_info.public_key.as_bytes() {
212            pk_bytes
213        } else {
214            return Err(errors::QuantCryptError::InvalidPublicKey);
215        };
216
217        let oid = pub_key_info.algorithm.oid.to_string();
218
219        // Check if oid is valid
220        if !is_valid_kem_or_dsa_oid(&oid) {
221            return Err(errors::QuantCryptError::InvalidPublicKey);
222        }
223
224        let is_composite = is_composite_kem_or_dsa_oid(&oid);
225
226        Ok(Self {
227            oid,
228            key: pk_bytes.to_vec(),
229            is_composite,
230        })
231    }
232
233    /// Verify a signature
234    ///
235    /// # Arguments
236    ///
237    /// * `message` - The message to verify
238    /// * `signature` - The signature
239    ///
240    /// # Returns
241    ///
242    /// A boolean indicating if the signature is valid
243    ///
244    /// # Errors
245    ///
246    /// `QuantCryptError::UnsupportedOperation` will be returned if the OID is not a DSA key
247    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
248        // Check if this is a DSA key
249        if !is_dsa_oid(&self.oid) {
250            return Err(errors::QuantCryptError::UnsupportedOperation);
251        }
252
253        let dsa =
254            DsaManager::new_from_oid(&self.oid).map_err(|_| errors::QuantCryptError::InvalidOid)?;
255
256        let verified = dsa
257            .verify(self.get_key(), message, signature)
258            .unwrap_or(false);
259
260        Ok(verified)
261    }
262
263    /// Encapsulate to get a shared secret and a ciphertext based on this public key
264    ///
265    /// # Returns
266    ///
267    /// A tuple containing the ciphertext and the shared secret (ct, ss)
268    pub fn encap(&self) -> Result<(Vec<u8>, Vec<u8>)> {
269        // Check if this is a KEM key
270        if !is_kem_oid(&self.oid) {
271            return Err(errors::QuantCryptError::UnsupportedOperation);
272        }
273
274        let mut kem =
275            KemManager::new_from_oid(&self.oid).map_err(|_| errors::QuantCryptError::InvalidOid)?;
276
277        let (ct, ss) = kem.encap(self.get_key())?;
278
279        Ok((ct, ss))
280    }
281}
282
283impl EncodePublicKey for PublicKey {
284    fn to_public_key_der(&self) -> std::result::Result<Document, pkcs8::spki::Error> {
285        let der = self
286            .to_der()
287            .map_err(|_| pkcs8::spki::Error::KeyMalformed)?;
288        let doc = Document::try_from(der)?;
289        Ok(doc)
290    }
291}
292
293#[cfg(test)]
294mod test {
295    use crate::dsa::common::config::oids::Oid;
296    use crate::dsa::common::dsa_type::DsaType;
297
298    use super::*;
299
300    #[test]
301    fn test_composite_public_key() {
302        let pem_bytes = include_bytes!("../../test/data/mldsa44_ecdsa_p256_sha256_pk.pem");
303        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
304        let pk = PublicKey::from_pem(pem).unwrap();
305
306        assert!(pk.is_composite());
307        assert_eq!(pk.get_oid(), DsaType::MlDsa44EcdsaP256SHA256.get_oid());
308
309        let key_bytes = pk.get_key();
310        let pk2 = CompositePublicKey::from_der(&pk.oid, &key_bytes).unwrap();
311
312        assert_eq!(pk.oid, pk2.get_oid());
313
314        let pk2 = PublicKey::from_composite(&pk2).unwrap();
315        let pem2 = pk2.to_pem().unwrap();
316        assert_eq!(pem, pem2.trim());
317
318        let oid = DsaType::MlDsa44EcdsaP256SHA256.get_oid();
319        assert_eq!(pk.oid, oid);
320    }
321
322    #[test]
323    fn test_pk_no_headers() {
324        let pem_bytes = include_bytes!("../../test/data/bad/no_headers.pem");
325        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
326        let pk = PublicKey::from_pem(pem);
327
328        assert!(pk.is_err());
329        assert!(matches!(
330            pk.err().unwrap(),
331            errors::QuantCryptError::InvalidPublicKey
332        ));
333    }
334
335    #[test]
336    fn test_pk_bad_base64() {
337        let pem_bytes = include_bytes!("../../test/data/bad/bad_base64.pem");
338        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
339        let pk = PublicKey::from_pem(pem);
340
341        assert!(pk.is_err());
342        assert!(matches!(
343            pk.err().unwrap(),
344            errors::QuantCryptError::InvalidPublicKey
345        ));
346    }
347
348    #[test]
349    fn test_pk_empty() {
350        let pem_bytes = include_bytes!("../../test/data/bad/empty.pem");
351        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
352        let pk = PublicKey::from_pem(pem);
353
354        assert!(pk.is_err());
355        assert!(matches!(
356            pk.err().unwrap(),
357            errors::QuantCryptError::InvalidPublicKey
358        ));
359    }
360
361    #[test]
362    fn test_pk_bad_tag() {
363        let pem_bytes = include_bytes!("../../test/data/bad/bad_tag.pem");
364        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
365        let pk = PublicKey::from_pem(pem);
366
367        assert!(pk.is_err());
368        assert!(matches!(
369            pk.err().unwrap(),
370            errors::QuantCryptError::InvalidPublicKey
371        ));
372    }
373
374    #[test]
375    fn test_pk_bad_algorithm() {
376        let pem_bytes = include_bytes!("../../test/data/bad/public_rsa_2048.pem");
377        let pem = std::str::from_utf8(pem_bytes).unwrap().trim();
378        let pk = PublicKey::from_pem(pem);
379
380        assert!(pk.is_err());
381        assert!(matches!(
382            pk.err().unwrap(),
383            errors::QuantCryptError::InvalidPublicKey
384        ));
385    }
386}