darkbio_crypto/xdsa/
cert.rs

1// crypto-rs: cryptography primitives and wrappers
2// Copyright 2025 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! CMLDSA + x509 cryptography wrappers and parametrization.
8
9use crate::x509;
10use crate::xdsa::{PublicKey, SecretKey};
11use const_oid::ObjectIdentifier;
12use der::Encode;
13use std::error::Error;
14use x509_cert::Certificate;
15
16// Implement the needed subject trait for the public key.
17impl x509::Subject for PublicKey {
18    type Bytes = [u8; 1984];
19
20    /// Returns the public key bytes.
21    fn to_bytes(&self) -> Self::Bytes {
22        self.to_bytes()
23    }
24
25    /// Returns the id-MLDSA65-Ed25519-SHA512 OID, 1.3.6.1.5.5.7.6.48.
26    fn algorithm_oid(&self) -> ObjectIdentifier {
27        ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.6.48")
28    }
29}
30
31impl PublicKey {
32    /// from_cert_pem parses a public key out of a PEM encoded, authenticated
33    /// certificate, verifying the signature and returning both the key, and the
34    /// validity interval.
35    pub fn from_cert_pem(pem: &str, signer: PublicKey) -> Result<(Self, u64, u64), Box<dyn Error>> {
36        let (_, der) = x509_parser::pem::parse_x509_pem(pem.as_bytes())?;
37        PublicKey::from_cert_der(der.contents.as_slice(), signer)
38    }
39
40    /// from_cert_der parses a public key out of a DER encoded, authenticated
41    /// certificate, verifying the signature and returning both the key, and the
42    /// validity interval.
43    pub fn from_cert_der(
44        der: &[u8],
45        signer: PublicKey,
46    ) -> Result<(Self, u64, u64), Box<dyn Error>> {
47        // Parse the certificate
48        let (_, cert) = x509_parser::parse_x509_certificate(der)?;
49
50        // Validate the content against the provided signer (composite signature)
51        let tbs = cert.tbs_certificate.as_ref();
52        let sig_bytes: [u8; 3373] = cert
53            .signature_value
54            .data
55            .as_ref()
56            .try_into()
57            .map_err(|_| "invalid signature length")?;
58        let sig = super::Signature::from_bytes(&sig_bytes);
59        signer.verify(tbs, &sig)?;
60
61        // Extract the embedded public key (ML-DSA-65 1952 bytes || Ed25519 32 bytes)
62        let key = PublicKey::from_bytes(
63            cert.tbs_certificate
64                .subject_pki
65                .subject_public_key
66                .data
67                .as_ref()
68                .try_into()?,
69        )?;
70        // Extract the validity period
71        let start = cert.tbs_certificate.validity.not_before.timestamp() as u64;
72        let until = cert.tbs_certificate.validity.not_after.timestamp() as u64;
73
74        Ok((key, start, until))
75    }
76
77    /// to_cert_pem generates a PEM encoded X.509 certificate for this public
78    /// key, signed by an xDSA issuer.
79    pub fn to_cert_pem(
80        &self,
81        signer: &SecretKey,
82        params: &x509::Params,
83    ) -> Result<String, Box<dyn Error>> {
84        let cert = self.to_cert(signer, params)?;
85        let der = cert.to_der()?;
86        let pem_str = der::pem::encode_string("CERTIFICATE", der::pem::LineEnding::LF, &der)
87            .map_err(|e| format!("PEM encoding error: {:?}", e))?;
88        Ok(pem_str)
89    }
90
91    /// to_cert_der generates a DER encoded X.509 certificate for this public
92    /// key, signed by an xDSA issuer.
93    pub fn to_cert_der(
94        &self,
95        signer: &SecretKey,
96        params: &x509::Params,
97    ) -> Result<Vec<u8>, Box<dyn Error>> {
98        Ok(self.to_cert(signer, params)?.to_der()?)
99    }
100
101    /// to_cert generates an X.509 certificate for this public key, signed by an
102    /// xDSA issuer.
103    pub fn to_cert(
104        &self,
105        signer: &SecretKey,
106        params: &x509::Params,
107    ) -> Result<Certificate, Box<dyn Error>> {
108        x509::new(self, signer, params)
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use crate::x509::Params;
115    use crate::xdsa::{PublicKey, SecretKey};
116    use std::time::{SystemTime, UNIX_EPOCH};
117
118    // Tests that certificates can be created and then parsed and verified.
119    #[test]
120    fn test_cert_parse() {
121        // Create the keys for Alice (subject) and Bobby (issuer)
122        let alice_secret = SecretKey::generate();
123        let bobby_secret = SecretKey::generate();
124        let alice_public = alice_secret.public_key();
125        let bobby_public = bobby_secret.public_key();
126
127        // Create a certificate for alice, signed by bobby
128        let start = SystemTime::now()
129            .duration_since(UNIX_EPOCH)
130            .unwrap()
131            .as_secs();
132        let until = start + 3600;
133
134        // Test PEM roundtrip (end-entity cert)
135        let pem = alice_public
136            .to_cert_pem(
137                &bobby_secret,
138                &Params {
139                    subject_name: "Alice",
140                    issuer_name: "Bobby",
141                    not_before: start,
142                    not_after: until,
143                    is_ca: false,
144                    path_len: None,
145                },
146            )
147            .unwrap();
148        let (parsed_key, parsed_start, parsed_until) =
149            PublicKey::from_cert_pem(pem.as_str(), bobby_public.clone()).unwrap();
150        assert_eq!(parsed_key.to_bytes(), alice_public.to_bytes());
151        assert_eq!(parsed_start, start);
152        assert_eq!(parsed_until, until);
153
154        // Test DER roundtrip (CA cert with path_len=0)
155        let der = alice_public
156            .to_cert_der(
157                &bobby_secret,
158                &Params {
159                    subject_name: "Alice",
160                    issuer_name: "Bobby",
161                    not_before: start,
162                    not_after: until,
163                    is_ca: true,
164                    path_len: Some(0),
165                },
166            )
167            .unwrap();
168        let (parsed_key, parsed_start, parsed_until) =
169            PublicKey::from_cert_der(der.as_slice(), bobby_public.clone()).unwrap();
170        assert_eq!(parsed_key.to_bytes(), alice_public.to_bytes());
171        assert_eq!(parsed_start, start);
172        assert_eq!(parsed_until, until);
173    }
174
175    // Tests that certificates signed by one key cannot be verified by another.
176    #[test]
177    fn test_cert_invalid_signer() {
178        // Create the keys for Alice (subject), Bobby (issuer) and Wrong (3rd party)
179        let alice_secret = SecretKey::generate();
180        let bobby_secret = SecretKey::generate();
181        let wrong_secret = SecretKey::generate();
182
183        let alice_public = alice_secret.public_key();
184
185        // Create a certificate for alice, signed by bobby
186        let start = SystemTime::now()
187            .duration_since(UNIX_EPOCH)
188            .unwrap()
189            .as_secs();
190        let until = start + 3600;
191
192        // Sign a new certificate and verify with the wrong signer
193        let pem = alice_public
194            .to_cert_pem(
195                &bobby_secret,
196                &Params {
197                    subject_name: "Alice",
198                    issuer_name: "Bobby",
199                    not_before: start,
200                    not_after: until,
201                    is_ca: false,
202                    path_len: None,
203                },
204            )
205            .unwrap();
206        let result = PublicKey::from_cert_pem(pem.as_str(), wrong_secret.public_key());
207        assert!(result.is_err());
208    }
209}