trz_gateway_common/x509/
name.rs

1use 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}