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