trz_gateway_common/x509/
ca.rs

1use nameth::NamedEnumValues as _;
2use nameth::nameth;
3use openssl::error::ErrorStack;
4use openssl::x509::X509Builder;
5use openssl::x509::extension::BasicConstraints;
6use openssl::x509::extension::KeyUsage;
7
8use super::common_fields::SetCommonFieldsError;
9use super::common_fields::set_akid;
10use super::common_fields::set_common_fields;
11use super::key::MakeKeyError;
12use super::key::make_key;
13use super::name::CertitficateName;
14use super::name::MakeNameError;
15use super::name::make_name;
16use super::validity::Validity;
17use crate::certificate_info::CertificateInfo;
18use crate::certificate_info::X509CertificateInfo;
19use crate::certificate_info::X509CertificateInfoRef;
20
21/// Makes a Root CA certificate.
22pub fn make_ca(
23    subject_name: CertitficateName,
24    validity: Validity,
25) -> Result<X509CertificateInfo, MakeCaError> {
26    make_impl(None, subject_name, validity)
27}
28
29/// Makes an intermediate CA certificate.
30pub fn make_intermediate(
31    root: X509CertificateInfoRef,
32    subject_name: CertitficateName,
33    validity: Validity,
34) -> Result<X509CertificateInfo, MakeCaError> {
35    make_impl(Some(root), subject_name, validity)
36}
37
38fn make_impl(
39    issuer: Option<X509CertificateInfoRef>,
40    subject_name: CertitficateName,
41    validity: Validity,
42) -> Result<X509CertificateInfo, MakeCaError> {
43    let key = make_key()?;
44
45    let mut builder = X509Builder::new().map_err(MakeCaError::NewBuilder)?;
46
47    builder
48        .set_pubkey(&key)
49        .map_err(MakeCaError::SetPublicKey)?;
50    let subject_name = make_name(subject_name)?;
51    let (issuer_name, issuer_key) = issuer
52        .map(|issuer| (issuer.certificate.subject_name(), issuer.private_key))
53        .unwrap_or((&subject_name, &key));
54    set_common_fields(&mut builder, issuer_name, &subject_name, validity)?;
55
56    (|| {
57        let basic_constraints = BasicConstraints::new().critical().ca().build()?;
58        builder.append_extension(basic_constraints)?;
59        Ok(())
60    })()
61    .map_err(MakeCaError::BasicConstraints)?;
62
63    (|| {
64        let key_usage = KeyUsage::new()
65            .critical()
66            .key_cert_sign()
67            .crl_sign()
68            .build()?;
69        builder.append_extension(key_usage)?;
70        Ok(())
71    })()
72    .map_err(MakeCaError::KeyUsage)?;
73
74    if let Some(issuer) = issuer {
75        set_akid(issuer.certificate, &mut builder).map_err(MakeCaError::AuthorityKeyIdentifier)?;
76    }
77
78    builder
79        .sign(issuer_key, openssl::hash::MessageDigest::sha256())
80        .map_err(MakeCaError::Sign)?;
81
82    let root_cert = builder.build();
83
84    Ok(CertificateInfo {
85        certificate: root_cert,
86        private_key: key,
87    })
88}
89
90#[nameth]
91#[derive(thiserror::Error, Debug)]
92pub enum MakeCaError {
93    #[error("[{n}] {0}", n = self.name())]
94    MakeKey(#[from] MakeKeyError),
95
96    #[error("[{n}] Failed to create a new X509 Certificate builder: {0}", n = self.name())]
97    NewBuilder(ErrorStack),
98
99    #[error("[{n}] {0}", n = self.name())]
100    MakeName(#[from] MakeNameError),
101
102    #[error("[{n}] Failed to set the public key: {0}", n = self.name())]
103    SetPublicKey(ErrorStack),
104
105    #[error("[{n}] Failed to set AKID: {0}", n = self.name())]
106    AuthorityKeyIdentifier(ErrorStack),
107
108    #[error("[{n}] {0}", n = self.name())]
109    SetCommonFieldsError(#[from] SetCommonFieldsError),
110
111    #[error("[{n}] Failed to set basic constraints: {0}", n = self.name())]
112    BasicConstraints(ErrorStack),
113
114    #[error("[{n}] Failed to set key usage: {0}", n = self.name())]
115    KeyUsage(ErrorStack),
116
117    #[error("[{n}] Failed to sign the certificate: {0}", n = self.name())]
118    Sign(ErrorStack),
119}
120
121#[cfg(test)]
122mod tests {
123    use std::error::Error;
124    use std::time::Duration;
125    use std::time::SystemTime;
126
127    use openssl::sign::Signer;
128    use openssl::sign::Verifier;
129
130    use super::super::name::CertitficateName;
131    use super::MakeCaError;
132    use crate::x509::PemString as _;
133    use crate::x509::validity::Validity;
134
135    #[test]
136    fn make_ca() -> Result<(), Box<dyn Error>> {
137        Ok({
138            let ca = super::make_ca(
139                CertitficateName {
140                    country: Some(['D', 'E']),
141                    state_or_province: Some("Bayern"),
142                    locality: Some("Munich"),
143                    organization: Some("Terrazzo"),
144                    common_name: Some("Terrazzo Test Root CA"),
145                },
146                Validity {
147                    from: SystemTime::now(),
148                    to: SystemTime::now() + Duration::from_secs(1) * 3600,
149                },
150            )?;
151            let text = ca.certificate.to_text().pem_string().unwrap();
152            let _debug = scopeguard::guard_on_unwind((), |_| {
153                println!("Certificate is\n{text}");
154            });
155
156            assert!(text.contains("Signature Algorithm: ecdsa-with-SHA256"));
157            assert!(text.contains(
158                "Issuer: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Test Root CA"
159            ));
160            assert!(text.contains(
161                "Issuer: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Test Root CA"
162            ));
163            assert!(text.contains("CA:TRUE"));
164            assert!(text.contains("X509v3 Subject Key Identifier"));
165            assert!(
166                !text
167                    .to_ascii_uppercase()
168                    .contains("DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09")
169            );
170        })
171    }
172
173    #[test]
174    fn sign_payload() -> Result<(), Box<dyn Error>> {
175        const DATA: &str = "Hello, world! 😃";
176
177        Ok({
178            let ca = super::make_ca(
179                CertitficateName {
180                    country: Some(['D', 'E']),
181                    state_or_province: Some("Bayern"),
182                    locality: Some("Munich"),
183                    organization: Some("Terrazzo"),
184                    common_name: Some("Terrazzo Test Root CA"),
185                },
186                Validity {
187                    from: SystemTime::now(),
188                    to: SystemTime::now() + Duration::from_secs(1) * 3600,
189                },
190            )?;
191            let public_key = ca.certificate.public_key()?;
192
193            let signature = {
194                let mut signer = Signer::new_without_digest(&ca.private_key)?;
195                signer.update(DATA.as_bytes())?;
196                signer.sign_to_vec()?
197            };
198
199            {
200                let mut verifier = Verifier::new_without_digest(&public_key)?;
201                verifier.update(DATA.as_bytes())?;
202                assert!(verifier.verify(&signature)?);
203            }
204
205            let ca = super::make_ca(
206                CertitficateName {
207                    country: Some(['D', 'E']),
208                    state_or_province: Some("Bayern"),
209                    locality: Some("Munich"),
210                    organization: Some("Terrazzo"),
211                    common_name: Some("Terrazzo Test Root CA"),
212                },
213                Validity {
214                    from: SystemTime::now(),
215                    to: SystemTime::now() + Duration::from_secs(1) * 3600,
216                },
217            )?;
218            let public_key = ca.certificate.public_key()?;
219            let mut verifier = Verifier::new_without_digest(&public_key)?;
220            verifier.update(DATA.as_bytes())?;
221            assert_eq!(false, verifier.verify(&signature)?);
222        })
223    }
224
225    #[test]
226    fn invalid_name() -> Result<(), Box<dyn Error>> {
227        Ok({
228            let too_long: String = (0..200).map(|_| 'X').collect();
229            let Err(MakeCaError::MakeName(..)) = super::make_ca(
230                CertitficateName {
231                    country: Some(['D', 'E']),
232                    state_or_province: Some("Bayern"),
233                    locality: Some("Munich"),
234                    organization: Some("Terrazzo"),
235                    common_name: Some(&too_long),
236                },
237                Validity {
238                    from: SystemTime::now(),
239                    to: SystemTime::now() + Duration::from_secs(1) * 3600,
240                },
241            ) else {
242                panic!()
243            };
244        })
245    }
246}