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 }