trz_gateway_common/x509/
name.rs1use axum::http::StatusCode;
2use nameth::NamedEnumValues as _;
3use nameth::nameth;
4use openssl::error::ErrorStack;
5use openssl::nid::Nid;
6use openssl::x509::X509Name;
7use openssl::x509::X509NameBuilder;
8
9use crate::http_error::IsHttpError;
10
11pub(super) fn make_name(args: CertitficateName) -> Result<X509Name, MakeNameError> {
12 let mut name = X509NameBuilder::new().map_err(MakeNameError::NewBuilder)?;
13 let mut set = |nid, value| {
14 let Some(value) = value else {
15 return Ok::<_, MakeNameError>(());
16 };
17 match name.append_entry_by_nid(nid, value) {
18 Ok(()) => Ok(()),
19 Err(error) => {
20 let nid = nid
21 .long_name()
22 .map_err(|error| MakeNameError::InvalidField { error, nid })?
23 .to_owned();
24 let value = value.to_owned();
25 Err(Box::new(InvalidValueError { error, nid, value }).into())
26 }
27 }
28 };
29 let country = args.country.map(String::from_iter);
30 set(Nid::COUNTRYNAME, country.as_deref())?;
31 set(Nid::STATEORPROVINCENAME, args.state_or_province)?;
32 set(Nid::LOCALITYNAME, args.locality)?;
33 set(Nid::ORGANIZATIONNAME, args.organization)?;
34 set(Nid::COMMONNAME, args.common_name)?;
35 Ok(name.build())
36}
37
38#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
39pub struct CertitficateName<'t> {
40 pub country: Option<[char; 2]>,
41 pub state_or_province: Option<&'t str>,
42 pub locality: Option<&'t str>,
43 pub organization: Option<&'t str>,
44 pub common_name: Option<&'t str>,
45}
46
47#[nameth]
48#[derive(thiserror::Error, Debug)]
49pub enum MakeNameError {
50 #[error("[{n}] Failed to create a new LDAP Name builder: {0}", n = self.name())]
51 NewBuilder(ErrorStack),
52
53 #[error("[{n}] {0}", n = self.name())]
54 InvalidValue(#[from] Box<InvalidValueError>),
55
56 #[error("[{n}] Invalid LDAP field NID={nid}: {error}", n = self.name(), nid = nid.as_raw())]
57 InvalidField { error: ErrorStack, nid: Nid },
58}
59
60#[nameth]
61#[derive(thiserror::Error, Debug)]
62#[error("Failed to set LDAP field {nid} = '{value}': {error}")]
63pub struct InvalidValueError {
64 error: ErrorStack,
65 nid: String,
66 value: String,
67}
68
69impl IsHttpError for MakeNameError {
70 fn status_code(&self) -> StatusCode {
71 match self {
72 Self::NewBuilder { .. } => StatusCode::INTERNAL_SERVER_ERROR,
73 Self::InvalidValue { .. } => StatusCode::BAD_REQUEST,
74 Self::InvalidField { .. } => StatusCode::INTERNAL_SERVER_ERROR,
75 }
76 }
77}
78#[cfg(test)]
79mod tests {
80 #[test]
81 fn common_name() {
82 let name = super::make_name(super::CertitficateName {
83 common_name: Some("Test cert"),
84 ..Default::default()
85 })
86 .unwrap();
87 let name: String = name.entries().map(|entry| format!("{entry:?}")).collect();
88 assert_eq!("\"commonName = \\\"Test cert\\\"\"", format!("{name:?}"));
89 }
90
91 #[test]
92 fn country() {
93 let name = super::make_name(super::CertitficateName {
94 common_name: Some("Test cert"),
95 country: Some(['F', 'R']),
96 ..Default::default()
97 })
98 .unwrap();
99 let name: String = name.entries().map(|entry| format!("{entry:?}")).collect();
100 assert_eq!(
101 "\"countryName = \\\"FR\\\"commonName = \\\"Test cert\\\"\"",
102 format!("{name:?}")
103 );
104 }
105
106 #[test]
107 fn error() {
108 let too_long: String = (0..200).map(|_| 'X').collect();
109 let Err(error) = super::make_name(super::CertitficateName {
110 common_name: Some(&too_long),
111 ..Default::default()
112 }) else {
113 panic!();
114 };
115 let super::MakeNameError::InvalidValue(invalid_value) = &error else {
116 panic!();
117 };
118 assert_eq!(too_long, invalid_value.value);
119 assert_eq!("commonName", invalid_value.nid);
120 assert!(error.to_string().starts_with(&format!(
121 "[InvalidValue] Failed to set LDAP field commonName = '{too_long}': "
122 ),));
123 }
124}