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
21pub fn make_ca(
23 subject_name: CertitficateName,
24 validity: Validity,
25) -> Result<X509CertificateInfo, MakeCaError> {
26 make_impl(None, subject_name, validity)
27}
28
29pub 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}