1use axum::http::StatusCode;
2use nameth::NamedEnumValues as _;
3use nameth::nameth;
4use openssl::error::ErrorStack;
5use openssl::pkey::PKey;
6use openssl::x509::X509;
7use openssl::x509::X509Builder;
8use openssl::x509::X509Extension;
9use openssl::x509::extension::BasicConstraints;
10use openssl::x509::extension::ExtendedKeyUsage;
11use openssl::x509::extension::KeyUsage;
12use openssl::x509::extension::SubjectAlternativeName;
13
14use super::common_fields::SetCommonFieldsError;
15use super::common_fields::set_akid;
16use super::common_fields::set_common_fields;
17use super::name::CertitficateName;
18use super::name::MakeNameError;
19use super::name::make_name;
20use super::validity::Validity;
21use crate::certificate_info::X509CertificateInfoRef;
22use crate::http_error::IsHttpError;
23
24pub fn make_cert(
26 issuer: X509CertificateInfoRef,
27 name: CertitficateName,
28 validity: Validity,
29 public_key: &str,
30 extensions: Vec<X509Extension>,
31) -> Result<X509, MakeCertError> {
32 let mut builder = X509Builder::new().map_err(MakeCertError::NewBuilder)?;
33
34 let public_key =
35 PKey::public_key_from_pem(public_key.as_bytes()).map_err(MakeCertError::ParsePublicKey)?;
36 builder
37 .set_pubkey(&public_key)
38 .map_err(MakeCertError::SetPublicKey)?;
39
40 {
41 let name = make_name(name)?;
42 set_common_fields(
43 &mut builder,
44 issuer.certificate.subject_name(),
45 &name,
46 validity,
47 )?;
48 }
49
50 (|| {
51 let basic_constraints = BasicConstraints::new().critical().build()?;
52 builder.append_extension(basic_constraints)?;
53 Ok(())
54 })()
55 .map_err(MakeCertError::BasicConstraints)?;
56
57 (|| {
58 let key_usage = KeyUsage::new().critical().digital_signature().build()?;
59 builder.append_extension(key_usage)?;
60 Ok(())
61 })()
62 .map_err(MakeCertError::KeyUsage)?;
63
64 (|| {
65 let key_usage = ExtendedKeyUsage::new()
66 .critical()
67 .server_auth()
68 .client_auth()
69 .build()?;
70 builder.append_extension(key_usage)?;
71 Ok(())
72 })()
73 .map_err(MakeCertError::ExtendedKeyUsage)?;
74
75 set_akid(issuer.certificate, &mut builder).map_err(MakeCertError::AuthorityKeyIdentifier)?;
76
77 if let Some(common_name) = name.common_name {
78 (|| {
79 builder.append_extension(
80 SubjectAlternativeName::new()
81 .dns(common_name)
82 .build(&builder.x509v3_context(Some(issuer.certificate), None))?,
83 )?;
84 Ok(())
85 })()
86 .map_err(MakeCertError::SubjectAlternativeName)?;
87 }
88
89 for extension in extensions {
90 builder
91 .append_extension(extension)
92 .map_err(MakeCertError::AppendCustomExtension)?;
93 }
94
95 builder
96 .sign(issuer.private_key, openssl::hash::MessageDigest::sha256())
97 .map_err(MakeCertError::Sign)?;
98
99 let certificate = builder.build();
100
101 Ok(certificate)
102}
103
104#[nameth]
105#[derive(thiserror::Error, Debug)]
106pub enum MakeCertError {
107 #[error("[{n}] Failed to create a new X509 Certificate builder: {0}", n = self.name())]
108 NewBuilder(ErrorStack),
109
110 #[error("[{n}] Failed to parse PEM public key: {0}", n = self.name())]
111 ParsePublicKey(ErrorStack),
112
113 #[error("[{n}] Failed to set the public key: {0}", n = self.name())]
114 SetPublicKey(ErrorStack),
115
116 #[error("[{n}] {0}", n = self.name())]
117 MakeName(#[from] MakeNameError),
118
119 #[error("[{n}] {0}", n = self.name())]
120 SetCommonFieldsError(#[from] SetCommonFieldsError),
121
122 #[error("[{n}] Failed to set basic constraints: {0}", n = self.name())]
123 BasicConstraints(ErrorStack),
124
125 #[error("[{n}] Failed to set key usage: {0}", n = self.name())]
126 KeyUsage(ErrorStack),
127
128 #[error("[{n}] Failed to set extended key usage: {0}", n = self.name())]
129 ExtendedKeyUsage(ErrorStack),
130
131 #[error("[{n}] Failed to set AKID: {0}", n = self.name())]
132 AuthorityKeyIdentifier(ErrorStack),
133
134 #[error("[{n}] Failed to set subject alternative name: {0}", n = self.name())]
135 SubjectAlternativeName(ErrorStack),
136
137 #[error("[{n}] Failed add custom extension: {0}", n = self.name())]
138 AppendCustomExtension(ErrorStack),
139
140 #[error("[{n}] Failed to sign the certificate: {0}", n = self.name())]
141 Sign(ErrorStack),
142}
143
144impl IsHttpError for MakeCertError {
145 fn status_code(&self) -> StatusCode {
146 match self {
147 Self::ParsePublicKey { .. } => StatusCode::BAD_REQUEST,
148 Self::MakeName(error) => error.status_code(),
149 Self::SetCommonFieldsError(error) => error.status_code(),
150 Self::AppendCustomExtension { .. } => StatusCode::BAD_REQUEST,
151 Self::NewBuilder { .. }
152 | Self::SetPublicKey { .. }
153 | Self::BasicConstraints { .. }
154 | Self::KeyUsage { .. }
155 | Self::ExtendedKeyUsage { .. }
156 | Self::AuthorityKeyIdentifier { .. }
157 | Self::SubjectAlternativeName { .. }
158 | Self::Sign { .. } => StatusCode::INTERNAL_SERVER_ERROR,
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use std::error::Error;
166 use std::time::Duration;
167 use std::time::SystemTime;
168
169 use openssl::pkey::PKey;
170 use openssl::pkey::Private;
171 use openssl::pkey::Public;
172 use openssl::sign::Signer;
173 use openssl::sign::Verifier;
174 use openssl::stack::Stack;
175 use openssl::x509::X509;
176 use openssl::x509::X509Extension;
177 use openssl::x509::store::X509StoreBuilder;
178 use rustls::pki_types::CertificateDer;
179 use scopeguard::defer_on_unwind;
180
181 use super::super::name::CertitficateName;
182 use crate::certificate_info::CertificateInfo;
183 use crate::certificate_info::X509CertificateInfo;
184 use crate::certificate_info::X509CertificateInfoRef;
185 use crate::security_configuration::trusted_store::cache::CachedTrustedStoreConfig;
186 use crate::security_configuration::trusted_store::empty::EmptyTrustedStoreConfig;
187 use crate::x509::PemString as _;
188 use crate::x509::ca::MakeCaError;
189 use crate::x509::ca::make_ca;
190 use crate::x509::ca::make_intermediate;
191 use crate::x509::key::make_key;
192 use crate::x509::signed_extension::make_signed_extension;
193 use crate::x509::signed_extension::validate_signed_extension;
194 use crate::x509::validity::Validity;
195
196 const DATA: &str = "Hello, world! 😃";
197
198 #[test]
199 fn make_cert() -> Result<(), Box<dyn Error>> {
200 let ca = make_test_ca()?;
201 let test_cert = make_test_cert(ca.as_ref())?;
202 let text = test_cert.certificate.to_text().pem_string()?;
203 let ca_text = ca.certificate.to_text().pem_string()?;
204 let _debug = scopeguard::guard_on_unwind((), |_| {
205 println!("CA is\n{ca_text}");
206 println!("Certificate is\n{text}");
207 });
208
209 assert!(text.contains("Signature Algorithm: ecdsa-with-SHA256"));
210 assert!(
211 text.contains(
212 "Issuer: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Test Root CA"
213 )
214 );
215 assert!(
216 text.contains("Subject: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Client")
217 );
218 assert!(text.contains("X509v3 Subject Key Identifier"));
219 assert!(text.contains("X509v3 Authority Key Identifier"));
220 assert!(
222 text.contains("DirName:/C=DE/ST=Bayern/L=Munich/O=Terrazzo/CN=Terrazzo Test Root CA")
223 );
224 assert!(
225 !text
226 .to_ascii_uppercase()
227 .contains("DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09")
228 );
229 Ok(())
230 }
231
232 #[test]
233 fn sign_payload() -> Result<(), Box<dyn Error>> {
234 let ca = make_test_ca()?;
235
236 let test_cert = make_test_cert(ca.as_ref())?;
237
238 let signature = {
239 let mut signer = Signer::new_without_digest(&test_cert.private_key)?;
240 signer.update(DATA.as_bytes())?;
241 signer.sign_to_vec()?
242 };
243
244 assert!(validate_signature(
245 test_cert.certificate.public_key()?,
246 &signature
247 )?);
248
249 let test_cert2 = make_test_cert(ca.as_ref())?;
250 assert!(!validate_signature(
251 test_cert2.certificate.public_key()?,
252 &signature
253 )?);
254 Ok(())
255 }
256
257 fn validate_signature(
258 public_key: PKey<Public>,
259 signature: &[u8],
260 ) -> Result<bool, Box<dyn Error>> {
261 let mut verifier = Verifier::new_without_digest(&public_key)?;
262 verifier.update(DATA.as_bytes())?;
263 Ok(verifier.verify(signature)?)
264 }
265
266 #[test]
267 fn signed_extension() -> Result<(), Box<dyn Error>> {
268 let test_case = SignedExtensionTestCase::new()?;
269 let extension = make_test_signed_extension(&test_case)?;
270 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
271 validate_test_signed_extension(&test_case, certificate)?;
272 Ok(())
273 }
274
275 #[test]
276 fn signed_extension_wrong_name() -> Result<(), Box<dyn Error>> {
277 let test_case = SignedExtensionTestCase::new()?;
278 let extension = make_test_signed_extension(&SignedExtensionTestCase {
279 common_name: format!("NOT {}", test_case.common_name),
280 ..test_case.clone()
281 })?;
282 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
283 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
284 defer_on_unwind!(eprintln!("{error}"));
285 assert_eq!(
286 error.to_string(),
287 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: common_name was 'NOT With signed extension' expected 'With signed extension'"
288 );
289 Ok(())
290 }
291
292 #[test]
293 fn signed_extension_wrong_validity_from() -> Result<(), Box<dyn Error>> {
294 let test_case = SignedExtensionTestCase::new()?;
295 let extension = make_test_signed_extension(&SignedExtensionTestCase {
296 validity: Validity {
297 from: test_case.validity.from + Duration::from_secs(123),
298 ..test_case.validity
299 },
300 ..test_case.clone()
301 })?;
302 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
303 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
304 defer_on_unwind!(eprintln!("{error}"));
305 assert!(error.to_string().starts_with(
306 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: not_before was "));
307 Ok(())
308 }
309
310 #[test]
311 fn signed_extension_wrong_validity_to() -> Result<(), Box<dyn Error>> {
312 let test_case = SignedExtensionTestCase::new()?;
313 let extension = make_test_signed_extension(&SignedExtensionTestCase {
314 validity: Validity {
315 to: test_case.validity.to + Duration::from_secs(123),
316 ..test_case.validity
317 },
318 ..test_case.clone()
319 })?;
320 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
321 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
322 defer_on_unwind!(eprintln!("{error}"));
323 assert!(error.to_string().starts_with(
324 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: not_after was "));
325 Ok(())
326 }
327
328 #[test]
329 fn signed_extension_wrong_public_key() -> Result<(), Box<dyn Error>> {
330 let test_case = SignedExtensionTestCase::new()?;
331 let extension = make_test_signed_extension(&SignedExtensionTestCase {
332 public_key: make_key()?,
333 ..test_case.clone()
334 })?;
335 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
336 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
337 defer_on_unwind!(eprintln!("{error}"));
338 assert!(error.to_string().starts_with(
339 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: public_key was '"));
340 Ok(())
341 }
342
343 #[test]
344 fn signed_extension_wrong_signer() -> Result<(), Box<dyn Error>> {
345 let test_case = SignedExtensionTestCase::new()?;
346 for (t, common_name) in [(true, "Terrazzo Client"), (false, "NOT Terrazzo")] {
347 let signer = make_named_test_cert(
348 test_case.root.as_ref(),
349 CertitficateName {
350 country: Some(['D', 'E']),
351 state_or_province: Some("Bayern"),
352 locality: Some("Munich"),
353 organization: Some("Terrazzo"),
354 common_name: Some(common_name),
355 },
356 )?;
357 let extension = make_test_signed_extension(&SignedExtensionTestCase {
358 signer,
359 ..test_case.clone()
360 })?;
361 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
362 let result = validate_test_signed_extension(&test_case, certificate);
363 if t {
364 let () = result?;
365 } else {
366 let error = result.unwrap_err();
367 defer_on_unwind!(eprintln!("{error}"));
368 assert_eq!(
369 error.to_string(),
370 "[VerifySigner] [SignerCertificateNameMismatch] The signer certificate name was: NOT Terrazzo"
371 );
372 }
373 }
374 Ok(())
375 }
376
377 #[test]
378 fn signed_extension_untrusted_signer() -> Result<(), Box<dyn Error>> {
379 let test_case = SignedExtensionTestCase::new()?;
380 let wrong_signer = make_test_cert(make_test_ca()?.as_ref())?;
381 let extension = make_test_signed_extension(&SignedExtensionTestCase {
382 signer: wrong_signer,
383 ..test_case.clone()
384 })?;
385 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
386 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
387 defer_on_unwind!(eprintln!("{error}"));
388 assert!(
389 error
390 .to_string()
391 .starts_with("[VerifySigner] [VerifySignerChainError] [ValidationFailed] ")
392 );
393 Ok(())
394 }
395
396 #[test]
397 fn signed_extension_not_a_cert() -> Result<(), Box<dyn Error>> {
398 let error = validate_signed_extension(
399 &CertificateDer::from_slice("abcd".as_bytes()),
400 &EmptyTrustedStoreConfig,
401 "",
402 )
403 .unwrap_err();
404 defer_on_unwind!(eprintln!("{error}"));
405 assert!(
406 error
407 .to_string()
408 .starts_with("[X509Certificate] Failed to parse X509Certificate: ")
409 );
410 Ok(())
411 }
412
413 #[test]
414 fn signed_extension_missing() -> Result<(), Box<dyn Error>> {
415 let test_case = SignedExtensionTestCase::new()?;
416 let test_cert = make_test_cert(test_case.root.as_ref())?;
417 let error = validate_test_signed_extension(&test_case, test_cert.certificate).unwrap_err();
418 defer_on_unwind!(eprintln!("{error}"));
419 assert!(error.to_string().starts_with("[SignedExtensionNotFound]"));
420 Ok(())
421 }
422
423 #[test]
424 fn signed_extension_invalid_intermediate() -> Result<(), Box<dyn Error>> {
425 let test_case = SignedExtensionTestCase::new()?;
426 let intermediate = make_test_intermediate(test_case.root.as_ref())?;
427 let extension = make_test_signed_extension(&SignedExtensionTestCase {
428 intermediate,
429 ..test_case.clone()
430 })?;
431 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
432 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
433 defer_on_unwind!(eprintln!("{error}"));
434 assert!(
435 error.to_string().starts_with(
436 "[VerifySigner] [VerifySignerChainError] [ValidationFailed] The signer certificate is invalid"
437 )
438 );
439 Ok(())
440 }
441
442 #[derive(Clone)]
443 struct SignedExtensionTestCase {
444 root: X509CertificateInfo,
445 intermediate: X509CertificateInfo,
446 signer: X509CertificateInfo,
447 common_name: String,
448 validity: Validity,
449 public_key: PKey<Private>,
450 }
451
452 impl SignedExtensionTestCase {
453 fn new() -> Result<Self, Box<dyn Error>> {
454 let root = make_test_ca()?;
455 let intermediate = make_test_intermediate(root.as_ref())?;
456 let signer = make_test_cert(intermediate.as_ref())?;
457 let validity = Validity {
458 from: SystemTime::now(),
459 to: SystemTime::now() + Duration::from_secs(1) * 3600,
460 };
461 let public_key = make_key()?;
462 Ok(Self {
463 root,
464 intermediate,
465 signer,
466 common_name: "With signed extension".to_owned(),
467 validity,
468 public_key,
469 })
470 }
471 }
472
473 fn validate_test_signed_extension(
474 test_case: &SignedExtensionTestCase,
475 certificate: X509,
476 ) -> Result<(), Box<dyn Error>> {
477 let store = {
478 let mut builder = X509StoreBuilder::new()?;
479 builder.add_cert(test_case.root.certificate.to_owned())?;
480 builder.build()
481 };
482 let () = validate_signed_extension(
483 &CertificateDer::from_slice(&certificate.to_der()?),
484 &CachedTrustedStoreConfig::from(store),
485 "Terrazzo Client",
486 )?;
487 Ok(())
488 }
489
490 fn make_test_cert_with_signed_extension(
491 test_case: &SignedExtensionTestCase,
492 extension: X509Extension,
493 ) -> Result<X509, Box<dyn Error>> {
494 let certificate = super::make_cert(
495 test_case.intermediate.as_ref(),
496 CertitficateName {
497 country: Some(['D', 'E']),
498 state_or_province: Some("Bayern"),
499 locality: Some("Munich"),
500 organization: Some("Terrazzo"),
501 common_name: Some(&test_case.common_name),
502 },
503 test_case.validity,
504 &test_case.public_key.public_key_to_pem()?.pem_string()?,
505 vec![extension],
506 )?;
507 Ok(certificate)
508 }
509
510 fn make_test_signed_extension(
511 test_case: &SignedExtensionTestCase,
512 ) -> Result<X509Extension, Box<dyn Error>> {
513 let mut intermediates = Stack::new()?;
514 intermediates.push(test_case.intermediate.certificate.clone())?;
515 let extension = make_signed_extension(
516 &test_case.common_name,
517 test_case.validity,
518 &test_case.public_key.public_key_to_der()?,
519 Some(&intermediates),
520 test_case.signer.as_ref(),
521 )?;
522 Ok(extension)
523 }
524
525 fn make_test_cert(ca: X509CertificateInfoRef) -> Result<X509CertificateInfo, Box<dyn Error>> {
526 make_named_test_cert(
527 ca,
528 CertitficateName {
529 country: Some(['D', 'E']),
530 state_or_province: Some("Bayern"),
531 locality: Some("Munich"),
532 organization: Some("Terrazzo"),
533 common_name: Some("Terrazzo Client"),
534 },
535 )
536 }
537
538 fn make_named_test_cert(
539 ca: X509CertificateInfoRef,
540 name: CertitficateName,
541 ) -> Result<X509CertificateInfo, Box<dyn Error>> {
542 let private_key = make_key()?;
543 let public_key = private_key.public_key_to_pem().pem_string()?;
544 let certificate = super::make_cert(
545 ca,
546 name,
547 Validity {
548 from: SystemTime::now(),
549 to: SystemTime::now() + Duration::from_secs(1) * 3600,
550 },
551 &public_key,
552 vec![],
553 )?;
554 Ok(CertificateInfo {
555 certificate,
556 private_key,
557 })
558 }
559
560 fn make_test_ca() -> Result<X509CertificateInfo, MakeCaError> {
561 make_ca(
562 CertitficateName {
563 country: Some(['D', 'E']),
564 state_or_province: Some("Bayern"),
565 locality: Some("Munich"),
566 organization: Some("Terrazzo"),
567 common_name: Some("Terrazzo Test Root CA"),
568 },
569 Validity {
570 from: SystemTime::now(),
571 to: SystemTime::now() + Duration::from_secs(1) * 3600,
572 },
573 )
574 }
575
576 fn make_test_intermediate(
577 root: X509CertificateInfoRef,
578 ) -> Result<X509CertificateInfo, MakeCaError> {
579 make_intermediate(
580 root,
581 CertitficateName {
582 common_name: Some("Terrazzo Test intermediate"),
583 ..CertitficateName::default()
584 },
585 Validity {
586 from: SystemTime::now(),
587 to: SystemTime::now() + Duration::from_secs(1) * 3600,
588 },
589 )
590 }
591}