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 Ok({
202 let test_cert = make_test_cert(ca.as_ref())?;
203 let text = test_cert.certificate.to_text().pem_string()?;
204 let ca_text = ca.certificate.to_text().pem_string()?;
205 let _debug = scopeguard::guard_on_unwind((), |_| {
206 println!("CA is\n{ca_text}");
207 println!("Certificate is\n{text}");
208 });
209
210 assert!(text.contains("Signature Algorithm: ecdsa-with-SHA256"));
211 assert!(text.contains(
212 "Issuer: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Test Root CA"
213 ));
214 assert!(
215 text.contains("Subject: C=DE, ST=Bayern, L=Munich, O=Terrazzo, CN=Terrazzo Client")
216 );
217 assert!(text.contains("X509v3 Subject Key Identifier"));
218 assert!(text.contains("X509v3 Authority Key Identifier"));
219 assert!(
221 text.contains(
222 "DirName:/C=DE/ST=Bayern/L=Munich/O=Terrazzo/CN=Terrazzo Test Root CA"
223 )
224 );
225 assert!(
226 !text
227 .to_ascii_uppercase()
228 .contains("DA:39:A3:EE:5E:6B:4B:0D:32:55:BF:EF:95:60:18:90:AF:D8:07:09")
229 );
230 })
231 }
232
233 #[test]
234 fn sign_payload() -> Result<(), Box<dyn Error>> {
235 let ca = make_test_ca()?;
236
237 Ok({
238 let test_cert = make_test_cert(ca.as_ref())?;
239
240 let signature = {
241 let mut signer = Signer::new_without_digest(&test_cert.private_key)?;
242 signer.update(DATA.as_bytes())?;
243 signer.sign_to_vec()?
244 };
245
246 assert!(validate_signature(
247 test_cert.certificate.public_key()?,
248 &signature
249 )?);
250
251 let test_cert2 = make_test_cert(ca.as_ref())?;
252 assert!(!validate_signature(
253 test_cert2.certificate.public_key()?,
254 &signature
255 )?);
256 })
257 }
258
259 fn validate_signature(
260 public_key: PKey<Public>,
261 signature: &[u8],
262 ) -> Result<bool, Box<dyn Error>> {
263 let mut verifier = Verifier::new_without_digest(&public_key)?;
264 verifier.update(DATA.as_bytes())?;
265 Ok(verifier.verify(&signature)?)
266 }
267
268 #[test]
269 fn signed_extension() -> Result<(), Box<dyn Error>> {
270 let test_case = SignedExtensionTestCase::new()?;
271 let extension = make_test_signed_extension(&test_case)?;
272 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
273 validate_test_signed_extension(&test_case, certificate)?;
274 Ok(())
275 }
276
277 #[test]
278 fn signed_extension_wrong_name() -> Result<(), Box<dyn Error>> {
279 let test_case = SignedExtensionTestCase::new()?;
280 let extension = make_test_signed_extension(&SignedExtensionTestCase {
281 common_name: format!("NOT {}", test_case.common_name),
282 ..test_case.clone()
283 })?;
284 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
285 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
286 defer_on_unwind!(eprintln!("{error}"));
287 assert_eq!(
288 error.to_string(),
289 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: common_name was 'NOT With signed extension' expected 'With signed extension'"
290 );
291 Ok(())
292 }
293
294 #[test]
295 fn signed_extension_wrong_validity_from() -> Result<(), Box<dyn Error>> {
296 let test_case = SignedExtensionTestCase::new()?;
297 let extension = make_test_signed_extension(&SignedExtensionTestCase {
298 validity: Validity {
299 from: test_case.validity.from + Duration::from_secs(123),
300 ..test_case.validity
301 },
302 ..test_case.clone()
303 })?;
304 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
305 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
306 defer_on_unwind!(eprintln!("{error}"));
307 assert!(error.to_string().starts_with(
308 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: not_before was "));
309 Ok(())
310 }
311
312 #[test]
313 fn signed_extension_wrong_validity_to() -> Result<(), Box<dyn Error>> {
314 let test_case = SignedExtensionTestCase::new()?;
315 let extension = make_test_signed_extension(&SignedExtensionTestCase {
316 validity: Validity {
317 to: test_case.validity.to + Duration::from_secs(123),
318 ..test_case.validity
319 },
320 ..test_case.clone()
321 })?;
322 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
323 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
324 defer_on_unwind!(eprintln!("{error}"));
325 assert!(error.to_string().starts_with(
326 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: not_after was "));
327 Ok(())
328 }
329
330 #[test]
331 fn signed_extension_wrong_public_key() -> Result<(), Box<dyn Error>> {
332 let test_case = SignedExtensionTestCase::new()?;
333 let extension = make_test_signed_extension(&SignedExtensionTestCase {
334 public_key: make_key()?,
335 ..test_case.clone()
336 })?;
337 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
338 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
339 defer_on_unwind!(eprintln!("{error}"));
340 assert!(error.to_string().starts_with(
341 "[VerifySignature] [CertificatePropertiesMismatch] The signed extension content hash doesn't match: public_key was '"));
342 Ok(())
343 }
344
345 #[test]
346 fn signed_extension_wrong_signer() -> Result<(), Box<dyn Error>> {
347 let test_case = SignedExtensionTestCase::new()?;
348 for (t, common_name) in [(true, "Terrazzo Client"), (false, "NOT Terrazzo")] {
349 let signer = make_named_test_cert(
350 test_case.root.as_ref(),
351 CertitficateName {
352 country: Some(['D', 'E']),
353 state_or_province: Some("Bayern"),
354 locality: Some("Munich"),
355 organization: Some("Terrazzo"),
356 common_name: Some(common_name),
357 },
358 )?;
359 let extension = make_test_signed_extension(&SignedExtensionTestCase {
360 signer,
361 ..test_case.clone()
362 })?;
363 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
364 let result = validate_test_signed_extension(&test_case, certificate);
365 if t {
366 let () = result?;
367 } else {
368 let error = result.unwrap_err();
369 defer_on_unwind!(eprintln!("{error}"));
370 assert_eq!(
371 error.to_string(),
372 "[VerifySigner] [SignerCertificateNameMismatch] The signer certificate name was: NOT Terrazzo"
373 );
374 }
375 }
376 Ok(())
377 }
378
379 #[test]
380 fn signed_extension_untrusted_signer() -> Result<(), Box<dyn Error>> {
381 let test_case = SignedExtensionTestCase::new()?;
382 let wrong_signer = make_test_cert(make_test_ca()?.as_ref())?;
383 let extension = make_test_signed_extension(&SignedExtensionTestCase {
384 signer: wrong_signer,
385 ..test_case.clone()
386 })?;
387 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
388 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
389 defer_on_unwind!(eprintln!("{error}"));
390 assert!(
391 error
392 .to_string()
393 .starts_with("[VerifySigner] [VerifySignerChainError] [ValidationFailed] ")
394 );
395 Ok(())
396 }
397
398 #[test]
399 fn signed_extension_not_a_cert() -> Result<(), Box<dyn Error>> {
400 let error = validate_signed_extension(
401 &CertificateDer::from_slice("abcd".as_bytes()),
402 &EmptyTrustedStoreConfig,
403 "",
404 )
405 .unwrap_err();
406 defer_on_unwind!(eprintln!("{error}"));
407 assert!(
408 error
409 .to_string()
410 .starts_with("[X509Certificate] Failed to parse X509Certificate: ")
411 );
412 Ok(())
413 }
414
415 #[test]
416 fn signed_extension_missing() -> Result<(), Box<dyn Error>> {
417 let test_case = SignedExtensionTestCase::new()?;
418 let test_cert = make_test_cert(test_case.root.as_ref())?;
419 let error = validate_test_signed_extension(&test_case, test_cert.certificate).unwrap_err();
420 defer_on_unwind!(eprintln!("{error}"));
421 assert!(error.to_string().starts_with("[SignedExtensionNotFound]"));
422 Ok(())
423 }
424
425 #[test]
426 fn signed_extension_invalid_intermediate() -> Result<(), Box<dyn Error>> {
427 let test_case = SignedExtensionTestCase::new()?;
428 let intermediate = make_test_intermediate(test_case.root.as_ref())?;
429 let extension = make_test_signed_extension(&SignedExtensionTestCase {
430 intermediate,
431 ..test_case.clone()
432 })?;
433 let certificate = make_test_cert_with_signed_extension(&test_case, extension)?;
434 let error = validate_test_signed_extension(&test_case, certificate).unwrap_err();
435 defer_on_unwind!(eprintln!("{error}"));
436 assert!(
437 error.to_string().starts_with(
438 "[VerifySigner] [VerifySignerChainError] [ValidationFailed] The signer certificate is invalid"
439 )
440 );
441 Ok(())
442 }
443
444 #[derive(Clone)]
445 struct SignedExtensionTestCase {
446 root: X509CertificateInfo,
447 intermediate: X509CertificateInfo,
448 signer: X509CertificateInfo,
449 common_name: String,
450 validity: Validity,
451 public_key: PKey<Private>,
452 }
453
454 impl SignedExtensionTestCase {
455 fn new() -> Result<Self, Box<dyn Error>> {
456 let root = make_test_ca()?;
457 let intermediate = make_test_intermediate(root.as_ref())?;
458 let signer = make_test_cert(intermediate.as_ref())?;
459 let validity = Validity {
460 from: SystemTime::now(),
461 to: SystemTime::now() + Duration::from_secs(1) * 3600,
462 };
463 let public_key = make_key()?;
464 Ok(Self {
465 root,
466 intermediate,
467 signer,
468 common_name: "With signed extension".to_owned(),
469 validity,
470 public_key,
471 })
472 }
473 }
474
475 fn validate_test_signed_extension(
476 test_case: &SignedExtensionTestCase,
477 certificate: X509,
478 ) -> Result<(), Box<dyn Error>> {
479 let store = {
480 let mut builder = X509StoreBuilder::new()?;
481 builder.add_cert(test_case.root.certificate.to_owned())?;
482 builder.build()
483 };
484 let () = validate_signed_extension(
485 &CertificateDer::from_slice(&certificate.to_der()?),
486 &CachedTrustedStoreConfig::from(store),
487 "Terrazzo Client",
488 )?;
489 Ok(())
490 }
491
492 fn make_test_cert_with_signed_extension(
493 test_case: &SignedExtensionTestCase,
494 extension: X509Extension,
495 ) -> Result<X509, Box<dyn Error>> {
496 let certificate = super::make_cert(
497 test_case.intermediate.as_ref(),
498 CertitficateName {
499 country: Some(['D', 'E']),
500 state_or_province: Some("Bayern"),
501 locality: Some("Munich"),
502 organization: Some("Terrazzo"),
503 common_name: Some(&test_case.common_name),
504 },
505 test_case.validity,
506 &test_case.public_key.public_key_to_pem()?.pem_string()?,
507 vec![extension],
508 )?;
509 Ok(certificate)
510 }
511
512 fn make_test_signed_extension(
513 test_case: &SignedExtensionTestCase,
514 ) -> Result<X509Extension, Box<dyn Error>> {
515 let mut intermediates = Stack::new()?;
516 intermediates.push(test_case.intermediate.certificate.clone())?;
517 let extension = make_signed_extension(
518 &test_case.common_name,
519 test_case.validity,
520 &test_case.public_key.public_key_to_der()?,
521 Some(&intermediates),
522 test_case.signer.as_ref(),
523 )?;
524 Ok(extension)
525 }
526
527 fn make_test_cert(ca: X509CertificateInfoRef) -> Result<X509CertificateInfo, Box<dyn Error>> {
528 make_named_test_cert(
529 ca,
530 CertitficateName {
531 country: Some(['D', 'E']),
532 state_or_province: Some("Bayern"),
533 locality: Some("Munich"),
534 organization: Some("Terrazzo"),
535 common_name: Some("Terrazzo Client"),
536 },
537 )
538 }
539
540 fn make_named_test_cert(
541 ca: X509CertificateInfoRef,
542 name: CertitficateName,
543 ) -> Result<X509CertificateInfo, Box<dyn Error>> {
544 let private_key = make_key()?;
545 let public_key = private_key.public_key_to_pem().pem_string()?;
546 let certificate = super::make_cert(
547 ca,
548 name,
549 Validity {
550 from: SystemTime::now(),
551 to: SystemTime::now() + Duration::from_secs(1) * 3600,
552 },
553 &public_key,
554 vec![],
555 )?;
556 Ok(CertificateInfo {
557 certificate,
558 private_key,
559 })
560 }
561
562 fn make_test_ca() -> Result<X509CertificateInfo, MakeCaError> {
563 make_ca(
564 CertitficateName {
565 country: Some(['D', 'E']),
566 state_or_province: Some("Bayern"),
567 locality: Some("Munich"),
568 organization: Some("Terrazzo"),
569 common_name: Some("Terrazzo Test Root CA"),
570 },
571 Validity {
572 from: SystemTime::now(),
573 to: SystemTime::now() + Duration::from_secs(1) * 3600,
574 },
575 )
576 }
577
578 fn make_test_intermediate(
579 root: X509CertificateInfoRef,
580 ) -> Result<X509CertificateInfo, MakeCaError> {
581 make_intermediate(
582 root,
583 CertitficateName {
584 common_name: Some("Terrazzo Test intermediate"),
585 ..CertitficateName::default()
586 },
587 Validity {
588 from: SystemTime::now(),
589 to: SystemTime::now() + Duration::from_secs(1) * 3600,
590 },
591 )
592 }
593}