agate

Simple gemini server for static files
git clone https://github.com/mbrubeck/agate.git
Log | Files | Refs | README

certificates.rs (8340B)


      1 use {
      2     std::{
      3         ffi::OsStr,
      4         fmt::{Display, Formatter},
      5         path::Path,
      6         sync::Arc,
      7     },
      8     tokio_rustls::rustls::{
      9         self,
     10         crypto::ring::sign::any_supported_type,
     11         pki_types::{self, CertificateDer, PrivateKeyDer},
     12         server::{ClientHello, ResolvesServerCert},
     13         sign::{CertifiedKey, SigningKey},
     14     },
     15 };
     16 
     17 /// A struct that holds all loaded certificates and the respective domain
     18 /// names.
     19 #[derive(Debug)]
     20 pub(crate) struct CertStore {
     21     /// Stores the certificates and the domains they apply to, sorted by domain
     22     /// names, longest matches first
     23     certs: Vec<(String, Arc<CertifiedKey>)>,
     24 }
     25 
     26 pub static CERT_FILE_NAME: &str = "cert.der";
     27 pub static KEY_FILE_NAME: &str = "key.der";
     28 
     29 #[derive(Debug)]
     30 pub enum CertLoadError {
     31     /// could not access the certificate root directory
     32     NoReadCertDir,
     33     /// no certificates or keys were found
     34     Empty,
     35     /// the key file for the specified domain is bad (e.g. does not contain a
     36     /// key or is invalid)
     37     BadKey(String, rustls::Error),
     38     /// the key file for the specified domain is missing (but a certificate
     39     /// file was present)
     40     MissingKey(String),
     41     /// the certificate file for the specified domain is missing (but a key
     42     /// file was present)
     43     MissingCert(String),
     44     /// neither a key file nor a certificate file were present for the given
     45     /// domain (but a folder was present)
     46     EmptyDomain(String),
     47 }
     48 
     49 impl Display for CertLoadError {
     50     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
     51         match self {
     52             Self::NoReadCertDir => write!(f, "Could not read from certificate directory."),
     53             Self::Empty => write!(f, "No keys or certificates were found in the given directory.\nSpecify the --hostname option to generate these automatically."),
     54             Self::BadKey(domain, err) => write!(f, "The key file for {domain} is malformed: {err:?}"),
     55             Self::MissingKey(domain) => write!(f, "The key file for {domain} is missing."),
     56             Self::MissingCert(domain) => {
     57                 write!(f, "The certificate file for {domain} is missing.")
     58             }
     59             Self::EmptyDomain(domain) => write!(
     60                 f,
     61                 "A folder for {domain} exists, but there is no certificate or key file."
     62             ),
     63         }
     64     }
     65 }
     66 
     67 impl std::error::Error for CertLoadError {}
     68 
     69 fn load_domain(certs_dir: &Path, domain: String) -> Result<CertifiedKey, CertLoadError> {
     70     let mut path = certs_dir.to_path_buf();
     71     path.push(&domain);
     72     // load certificate from file
     73     path.push(CERT_FILE_NAME);
     74     if !path.is_file() {
     75         return Err(if !path.with_file_name(KEY_FILE_NAME).is_file() {
     76             CertLoadError::EmptyDomain(domain)
     77         } else {
     78             CertLoadError::MissingCert(domain)
     79         });
     80     }
     81     let cert = CertificateDer::from(
     82         std::fs::read(&path).map_err(|_| CertLoadError::MissingCert(domain.clone()))?,
     83     );
     84 
     85     // load key from file
     86     path.set_file_name(KEY_FILE_NAME);
     87     let Ok(der) = std::fs::read(&path) else {
     88         return Err(CertLoadError::MissingKey(domain));
     89     };
     90 
     91     // transform key to correct format
     92     let key = der_to_private_key(&der).map_err(|e| CertLoadError::BadKey(domain.clone(), e))?;
     93 
     94     Ok(CertifiedKey::new(vec![cert], key))
     95 }
     96 
     97 /// We don't know the key type of the private key DER file, so try each
     98 /// possible type until we find one that works.
     99 ///
    100 /// We should probably stop doing this and use a PEM file instead:
    101 /// <https://github.com/rustls/rustls/issues/1661>
    102 fn der_to_private_key(der: &[u8]) -> Result<Arc<dyn SigningKey>, rustls::Error> {
    103     let keys = [
    104         PrivateKeyDer::Pkcs1(pki_types::PrivatePkcs1KeyDer::from(der)),
    105         PrivateKeyDer::Sec1(pki_types::PrivateSec1KeyDer::from(der)),
    106         PrivateKeyDer::Pkcs8(pki_types::PrivatePkcs8KeyDer::from(der)),
    107     ];
    108 
    109     let mut err = None;
    110     for key in keys {
    111         match any_supported_type(&key) {
    112             Ok(key) => return Ok(key),
    113             Err(e) => err = Some(e),
    114         }
    115     }
    116     Err(err.unwrap())
    117 }
    118 
    119 impl CertStore {
    120     /// Load certificates from a certificate directory.
    121     /// Certificates should be stored in a folder for each hostname, for example
    122     /// the certificate and key for `example.com` should be in the files
    123     /// `certs_dir/example.com/{cert.der,key.der}` respectively.
    124     ///
    125     /// If there are `cert.der` and `key.der` directly in `certs_dir`, these
    126     /// will be loaded as default certificates.
    127     pub fn load_from(certs_dir: &Path) -> Result<Self, CertLoadError> {
    128         // load all certificates from directories
    129         let mut certs = vec![];
    130 
    131         // Try to load fallback certificate and key directly from the top level
    132         // certificate directory.
    133         match load_domain(certs_dir, String::new()) {
    134             Err(CertLoadError::EmptyDomain(_)) => { /* there are no fallback keys */ }
    135             Err(CertLoadError::Empty) | Err(CertLoadError::NoReadCertDir) => unreachable!(),
    136             Err(CertLoadError::BadKey(_, e)) => {
    137                 return Err(CertLoadError::BadKey("fallback".to_string(), e))
    138             }
    139             Err(CertLoadError::MissingKey(_)) => {
    140                 return Err(CertLoadError::MissingKey("fallback".to_string()))
    141             }
    142             Err(CertLoadError::MissingCert(_)) => {
    143                 return Err(CertLoadError::MissingCert("fallback".to_string()))
    144             }
    145             // For the fallback keys there is no domain name to verify them
    146             // against, so we can skip that step and only have to do it for the
    147             // other keys below.
    148             Ok(key) => certs.push((String::new(), Arc::new(key))),
    149         }
    150 
    151         for file in certs_dir
    152             .read_dir()
    153             .or(Err(CertLoadError::NoReadCertDir))?
    154             .filter_map(Result::ok)
    155             .filter(|x| x.path().is_dir())
    156         {
    157             let path = file.path();
    158 
    159             // the filename should be the domain name
    160             let filename = path
    161                 .file_name()
    162                 .and_then(OsStr::to_str)
    163                 .unwrap()
    164                 .to_string();
    165 
    166             let key = load_domain(certs_dir, filename.clone())?;
    167 
    168             certs.push((filename, Arc::new(key)));
    169         }
    170 
    171         if certs.is_empty() {
    172             return Err(CertLoadError::Empty);
    173         }
    174 
    175         certs.sort_unstable_by(|(a, _), (b, _)| {
    176             // Try to match as many domain segments as possible. If one is a
    177             // substring of the other, the `zip` will only compare the smaller
    178             // length of either a or b and the for loop will not decide.
    179             for (a_part, b_part) in a.split('.').rev().zip(b.split('.').rev()) {
    180                 if a_part != b_part {
    181                     // Here we have to make sure that the empty string will
    182                     // always be sorted to the end, so we reverse the usual
    183                     // ordering of str.
    184                     return a_part.cmp(b_part).reverse();
    185                 }
    186             }
    187             // Sort longer domains first.
    188             a.len().cmp(&b.len()).reverse()
    189         });
    190 
    191         log::debug!(
    192             "certs loaded for {:?}",
    193             certs.iter().map(|t| &t.0).collect::<Vec<_>>()
    194         );
    195 
    196         Ok(Self { certs })
    197     }
    198 
    199     /// Checks if a certificate fitting a specific domain has been loaded.
    200     /// The same rules about using a certificate at the level above apply.
    201     pub fn has_domain(&self, domain: &str) -> bool {
    202         self.certs.iter().any(|(s, _)| domain.ends_with(s))
    203     }
    204 }
    205 
    206 impl ResolvesServerCert for CertStore {
    207     fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
    208         if let Some(name) = client_hello.server_name() {
    209             let name: &str = name;
    210             // The certificate list is sorted so the longest match will always
    211             // appear first. We have to find the first that is either this
    212             // domain or a parent domain of the current one.
    213             self.certs
    214                 .iter()
    215                 .find(|(s, _)| name.ends_with(s))
    216                 // only the key is interesting
    217                 .map(|(_, k)| k)
    218                 .cloned()
    219         } else {
    220             // This kind of resolver requires SNI.
    221             None
    222         }
    223     }
    224 }