Skip to main content

trz_gateway_server/server/
root_ca_configuration.rs

1//! Self-signed Root CA.
2//!
3//! This Root CA is not used as the trust anchor, it is used to issue client
4//! certificates but the security comes from the signed extension.
5
6use std::io::ErrorKind;
7use std::path::Path;
8
9use nameth::NamedEnumValues as _;
10use nameth::nameth;
11use trz_gateway_common::certificate_info::CertificateError;
12use trz_gateway_common::certificate_info::CertificateInfo;
13use trz_gateway_common::security_configuration::certificate::pem::PemCertificate;
14use trz_gateway_common::x509::PemAsStringError;
15use trz_gateway_common::x509::PemString as _;
16use trz_gateway_common::x509::ca::MakeCaError;
17use trz_gateway_common::x509::ca::make_ca;
18use trz_gateway_common::x509::name::CertitficateName;
19use trz_gateway_common::x509::validity::Validity;
20
21/// Default implementation to load or create a Root CA stored as PEM file on disk.
22pub fn load_root_ca(
23    name: CertitficateName,
24    root_ca_path: CertificateInfo<impl AsRef<Path>>,
25    default_validity: Validity,
26) -> Result<PemCertificate, RootCaConfigError> {
27    let root_ca_path = root_ca_path.as_ref();
28    match root_ca_path.map(|path| path.exists()) {
29        CertificateInfo {
30            certificate: true,
31            private_key: true,
32        } => {
33            let root_ca = root_ca_path
34                .try_map(std::fs::read_to_string)
35                .map_err(|error| RootCaConfigError::Load(error, format!("{root_ca_path:?}")))?;
36            Ok(root_ca.into())
37        }
38        CertificateInfo {
39            certificate: false,
40            private_key: false,
41        } => {
42            let root_ca = make_ca(name, default_validity).map_err(Box::new)?;
43            let root_ca_pem = CertificateInfo {
44                certificate: root_ca.certificate.to_pem(),
45                private_key: root_ca.private_key.private_key_to_pem_pkcs8(),
46            }
47            .try_map(|maybe_pem| maybe_pem.pem_string())?;
48            let _: CertificateInfo<()> = root_ca_path
49                .zip(root_ca_pem.as_ref())
50                .try_map(write_pem_file)
51                .map_err(|error| RootCaConfigError::Store(error, format!("{root_ca_path:?}")))?;
52
53            #[cfg(unix)]
54            {
55                use std::fs::Permissions;
56                use std::os::unix::fs::PermissionsExt as _;
57                let permissions = Permissions::from_mode(0o600);
58                std::fs::set_permissions(root_ca_path.private_key, permissions)
59                    .map_err(RootCaConfigError::SetPrivateKeyFilePermissions)?;
60            }
61
62            Ok(root_ca_pem.into())
63        }
64        CertificateInfo {
65            certificate: root_ca_exists,
66            private_key: private_key_exists,
67        } => {
68            return Err(RootCaConfigError::InconsistentState {
69                root_ca_exists,
70                private_key_exists,
71            });
72        }
73    }
74}
75
76#[nameth]
77#[derive(thiserror::Error, Debug)]
78pub enum RootCaConfigError {
79    #[error("[{n}] Failed to load certificate from '{1}': {0}", n = self.name())]
80    Load(CertificateError<std::io::Error>, String),
81
82    #[error("[{n}] Failed to store certificate into '{1}': {0}", n = self.name())]
83    Store(CertificateError<std::io::Error>, String),
84
85    #[error("[{n}] Inconsistent state: root_ca_exists:{root_ca_exists} private_key_exists:{private_key_exists}", n = self.name())]
86    InconsistentState {
87        root_ca_exists: bool,
88        private_key_exists: bool,
89    },
90
91    #[error("[{n}] {0}", n = self.name())]
92    MakeCa(#[from] Box<MakeCaError>),
93
94    #[error("[{n}] {0}", n = self.name())]
95    PemString(#[from] CertificateError<PemAsStringError>),
96
97    #[cfg(unix)]
98    #[error("[{n}] {0}", n = self.name())]
99    SetPrivateKeyFilePermissions(std::io::Error),
100}
101
102fn write_pem_file((path, pem): (&Path, &str)) -> Result<(), std::io::Error> {
103    let parent_dir = path.parent().ok_or_else(|| {
104        std::io::Error::new(
105            ErrorKind::InvalidInput,
106            format!("Failed to get parent folder of: {path:?}"),
107        )
108    })?;
109    std::fs::create_dir_all(parent_dir)?;
110    std::fs::write(path, pem)
111}