1use std::{
8 collections::HashMap,
9 ffi::OsStr,
10 fs::{self, File},
11 io::Write as _,
12 net::IpAddr,
13 ops::DerefMut,
14 time,
15};
16
17use bytes::{BufMut as _, BytesMut};
18use openssl::{
19 asn1::{Asn1Integer, Asn1Time},
20 bn::{BigNum, MsbOption},
21 ec::{Asn1Flag, EcGroup, EcKey},
22 hash::MessageDigest,
23 nid::Nid,
24 pkey::PKey,
25 ssl::{
26 AlpnError, SslAcceptor, SslAcceptorBuilder, SslConnectorBuilder, SslContextBuilder,
27 SslFiletype, SslMethod, SslVerifyMode,
28 },
29 x509::{
30 X509, X509NameBuilder,
31 extension::SubjectAlternativeName,
32 store::{X509Store, X509StoreBuilder},
33 },
34};
35
36use crate::config::{
37 ConfigError, os_country,
38 ssl::{SslConfig, SslConfigContext, SslStore, Store},
39};
40
41impl SslStore<X509, X509Store> for Store {
42 fn get_file_certificates(path: &std::path::Path) -> Result<Vec<X509>, ConfigError> {
43 if path.is_file() {
44 match &path.extension().and_then(OsStr::to_str) {
45 Some("pem" | "crt") => match fs::read(path) {
46 Ok(pem_file) => Ok(vec![X509::from_pem(&pem_file)?]),
47 Err(io) => Err(ConfigError::IoFile(
48 path.to_str().unwrap_or_default().into(),
49 io,
50 )),
51 },
52 Some("der" | "cer") => match fs::read(path) {
53 Ok(der_file) => Ok(vec![X509::from_der(&der_file)?]),
54 Err(io) => Err(ConfigError::IoFile(
55 path.to_str().unwrap_or_default().into(),
56 io,
57 )),
58 },
59 _ => Ok(Vec::new()),
60 }
61 } else if path.is_symlink() {
62 if let Ok(link) = path.read_link() {
63 <Self as SslStore<X509, X509Store>>::get_file_certificates(&link)
64 } else {
65 Ok(Vec::new())
66 }
67 } else if let Ok(path_dir) = path.read_dir() {
68 let mut cert_list = Vec::new();
69 for dir_entry in path_dir.flatten() {
70 cert_list.append(
71 &mut <Self as SslStore<X509, X509Store>>::get_file_certificates(
72 &dir_entry.path(),
73 )?,
74 );
75 }
76
77 Ok(cert_list)
78 } else {
79 Ok(Vec::new())
80 }
81 }
82
83 fn get_store(&self) -> Result<X509Store, ConfigError> {
92 let mut store = X509StoreBuilder::new()?;
93 match self {
94 Store::System => {
95 store.set_default_paths()?;
96 Ok(store.build())
97 }
98 Store::File { path } => match glob::glob(path) {
99 Ok(certs) => {
100 for cert_path in certs.flatten() {
101 for cert in
102 <Self as SslStore<X509, X509Store>>::get_file_certificates(&cert_path)?
103 {
104 store.add_cert(cert)?;
105 }
106 }
107
108 Ok(store.build())
109 }
110 Err(e) => Err(ConfigError::WrongPathPattern(path.clone(), e)),
111 },
112 Store::Cert { certs } => {
113 for cert in certs {
114 store.add_cert(X509::from_pem(cert.as_bytes())?)?;
115 }
116
117 Ok(store.build())
118 }
119 }
120 }
121
122 fn get_certs(&self) -> Result<HashMap<String, X509>, ConfigError> {
136 match self {
137 Store::System => {
138 #[cfg(ossl300)]
139 {
140 let store: X509Store = self.get_store()?;
141 let mut certs_map = HashMap::new();
142
143 for cert in store.all_certificates() {
144 if let Some(name) = cert
145 .subject_name()
146 .entries_by_nid(Nid::COMMONNAME)
147 .last()
148 .and_then(|cn| cn.data().as_utf8().map(|cn| cn.to_string()).ok())
149 {
150 certs_map.insert(name, cert);
151 }
152 }
153
154 Ok(certs_map)
155 }
156
157 #[cfg(not(ossl300))]
158 Ok(HashMap::new())
159 }
160 Store::File { path } => match glob::glob(path) {
161 Ok(certs) => {
162 let mut certs_map = HashMap::new();
163 for cert_path in certs.flatten() {
164 let certs =
165 <Self as SslStore<X509, X509Store>>::get_file_certificates(&cert_path)?;
166 for cert in certs {
167 if let Some(name) = cert
168 .subject_name()
169 .entries_by_nid(Nid::COMMONNAME)
170 .last()
171 .and_then(|cn| cn.data().as_utf8().map(|cn| cn.to_string()).ok())
172 {
173 certs_map.insert(name, cert);
174 } else if let Some(cert_name) = cert_path.to_str().and_then(|p| {
175 p.strip_suffix(".pem").or(p
176 .strip_suffix(".crt")
177 .or(p.strip_suffix(".der").or(p.strip_suffix(".cer"))))
178 }) {
179 certs_map.insert(cert_name.into(), cert);
180 }
181 }
182 }
183
184 Ok(certs_map)
185 }
186 Err(e) => Err(ConfigError::WrongPathPattern(path.clone(), e)),
187 },
188 Store::Cert { certs } => {
189 let mut certs_map = HashMap::new();
190 for cert_pem in certs {
191 let cert = X509::from_pem(cert_pem.as_bytes())?;
192 if let Some(name) = cert
193 .subject_name()
194 .entries_by_nid(Nid::COMMONNAME)
195 .last()
196 .and_then(|cn| cn.data().as_utf8().map(|cn| cn.to_string()).ok())
197 {
198 certs_map.insert(name, cert);
199 }
200 }
201
202 Ok(certs_map)
203 }
204 }
205 }
206}
207
208pub(crate) fn init_openssl_context<B>(
210 config: &SslConfig,
211 mut context_builder: B,
212 is_server: bool,
213 host: Option<&str>,
214) -> Result<B, ConfigError>
215where
216 B: DerefMut<Target = SslContextBuilder>,
217{
218 if let Some(pkcs12_path) = &config.pkcs12 {
219 match fs::read(pkcs12_path) {
220 Ok(pkcs12_file) => {
221 let pkcs12 = ::openssl::pkcs12::Pkcs12::from_der(pkcs12_file.as_ref())?
222 .parse2(config.passphrase.as_ref().unwrap_or(&String::from("")))?;
223
224 if let Some(pkey) = pkcs12.pkey {
225 context_builder.set_private_key(&pkey)?;
226 }
227
228 if let Some(cert) = pkcs12.cert {
229 context_builder.set_certificate(&cert)?;
230 }
231
232 if let Some(ca) = pkcs12.ca {
233 for cert in ca {
234 context_builder.add_extra_chain_cert(cert)?;
235 }
236 }
237 }
238 Err(io) => return Err(ConfigError::IoFile(pkcs12_path.to_string(), io)),
239 }
240 } else if let (Some(cert_path), Some(key_path)) = (&config.cert, &config.key) {
241 context_builder.set_certificate_file(cert_path, SslFiletype::PEM)?;
242
243 match fs::read(key_path) {
244 Ok(key_file) => {
245 let pkey = if key_path.ends_with(".der") || key_path.ends_with(".cer") {
246 PKey::private_key_from_der(key_file.as_slice())?
247 } else if let Some(passphrase) = &config.passphrase {
248 PKey::private_key_from_pem_passphrase(
249 key_file.as_slice(),
250 passphrase.as_bytes(),
251 )?
252 } else {
253 PKey::private_key_from_pem(key_file.as_slice())?
254 };
255
256 context_builder.set_private_key(&pkey)?;
257 }
258 Err(io) => return Err(ConfigError::IoFile(key_path.to_string(), io)),
259 }
260 } else if is_server {
261 let mut group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
262 group.set_asn1_flag(Asn1Flag::NAMED_CURVE);
263 let pkey = PKey::from_ec_key(EcKey::generate(&group)?)?;
264 context_builder.set_private_key(&pkey)?;
265
266 let mut cert = X509::builder()?;
267 cert.set_version(2)?;
268 cert.set_pubkey(&pkey)?;
269
270 let mut serial_bn = BigNum::new()?;
271 serial_bn.pseudo_rand(64, MsbOption::MAYBE_ZERO, true)?;
272 let serial_number = Asn1Integer::from_bn(&serial_bn)?;
273 cert.set_serial_number(&serial_number)?;
274
275 let begin_valid_time =
276 Asn1Time::from_unix(time::UNIX_EPOCH.elapsed().unwrap().as_secs() as i64 - 360)?;
277 cert.set_not_before(&begin_valid_time)?;
278 let end_valid_time = Asn1Time::days_from_now(1461)?; cert.set_not_after(&end_valid_time)?;
280
281 let mut x509_name = X509NameBuilder::new()?;
282 if let Some(cn) = os_country() {
283 x509_name.append_entry_by_text("C", cn.as_str())?;
284 }
285 x509_name.append_entry_by_text("CN", "ProSA")?;
286 let x509_name = x509_name.build();
287 cert.set_subject_name(&x509_name)?;
288 cert.set_issuer_name(&x509_name)?;
289
290 if let Some(host) = host {
292 if let Ok(ip) = host.parse::<IpAddr>() {
293 if !ip.is_unspecified() && !ip.is_loopback() {
294 let mut subject_alternative_name = SubjectAlternativeName::new();
295 let x509_extension = subject_alternative_name
296 .ip(host)
297 .build(&cert.x509v3_context(None, None))?;
298 cert.append_extension2(&x509_extension)?;
299 }
300 } else {
301 let mut subject_alternative_name = SubjectAlternativeName::new();
302 let x509_extension = subject_alternative_name
303 .dns(host)
304 .build(&cert.x509v3_context(None, None))?;
305 cert.append_extension2(&x509_extension)?;
306 }
307 }
308
309 cert.sign(&pkey, MessageDigest::sha256())?;
310 let cert_x509 = cert.build();
311
312 if let Some(cert_path) = &config.cert {
314 let mut cert_file =
315 File::create(cert_path).map_err(|e| ConfigError::IoFile(cert_path.clone(), e))?;
316 if cert_path.ends_with(".der") || cert_path.ends_with(".cer") {
317 cert_file
318 .write_all(&cert_x509.to_der()?)
319 .map_err(|e| ConfigError::IoFile(cert_path.clone(), e))?;
320 } else {
321 cert_file
322 .write_all(&cert_x509.to_pem()?)
323 .map_err(|e| ConfigError::IoFile(cert_path.clone(), e))?;
324 }
325 }
326
327 context_builder.set_certificate(&cert_x509)?;
328 }
329
330 if let Some(store) = &config.store {
331 context_builder.set_cert_store(store.get_store()?);
332 if is_server {
333 context_builder.set_verify(SslVerifyMode::PEER);
334 }
335 } else if !is_server {
336 context_builder.set_cert_store(Store::default().get_store()?);
337 } else {
338 context_builder.set_verify(SslVerifyMode::NONE);
339 }
340
341 if !config.alpn.is_empty() {
342 if is_server {
343 let alpn_list = config.alpn.clone();
344 context_builder.set_alpn_select_callback(move |_ssl, alpn| {
345 let mut cli_alpn = HashMap::new();
346
347 let mut current_split = alpn;
348 while let Some(length) = current_split.first() {
349 if current_split.len() > *length as usize {
350 let (left, right) = current_split.split_at(*length as usize + 1);
351 cli_alpn.insert(String::from_utf8(left[1..].to_vec()).unwrap(), &left[1..]);
352 current_split = right;
353 } else {
354 return Err(AlpnError::ALERT_FATAL);
355 }
356 }
357
358 for alpn_name in &alpn_list {
359 if let Some(alpn) = cli_alpn.get(alpn_name) {
360 return Ok(alpn);
361 }
362 }
363
364 Err(AlpnError::NOACK)
365 });
366 } else {
367 let mut alpn_bytes = BytesMut::new();
368 for alpn in &config.alpn {
369 alpn_bytes.put_u8(alpn.len() as u8);
370 alpn_bytes.put(alpn.as_bytes());
371 }
372
373 context_builder.set_alpn_protos(&alpn_bytes)?;
374 }
375 }
376
377 Ok(context_builder)
378}
379
380impl SslConfigContext<SslConnectorBuilder, SslAcceptorBuilder> for SslConfig {
381 fn init_tls_client_context(&self) -> Result<openssl::ssl::SslConnectorBuilder, ConfigError> {
412 let mut ssl_connector = openssl::ssl::SslConnector::builder(SslMethod::tls_client())?;
413 init_openssl_context(self, ssl_connector.deref_mut(), false, None)?;
414 Ok(ssl_connector)
415 }
416
417 fn init_tls_server_context(
453 &self,
454 host: Option<&str>,
455 ) -> Result<openssl::ssl::SslAcceptorBuilder, ConfigError> {
456 let mut ssl_acceptor = SslAcceptor::mozilla_modern(SslMethod::tls_server())?;
457 init_openssl_context(self, ssl_acceptor.deref_mut(), true, host)?;
458 Ok(ssl_acceptor)
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_store() {
468 let store_le_x1_x2 = Store::Cert {
469 certs: vec![
470 "-----BEGIN CERTIFICATE-----
471MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
472TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
473cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
474WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
475ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
476MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
477h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
4780TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
479A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
480T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
481B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
482B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
483KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
484OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
485jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
486qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
487rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
488HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
489hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
490ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
4913BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
492NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
493ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
494TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
495jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
496oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4974RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
498mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
499emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
500-----END CERTIFICATE-----"
501 .to_string(),
502 "-----BEGIN CERTIFICATE-----
503MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
504CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
505R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
506MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
507ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
508EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
509+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
510ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
511AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
512zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
513tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
514/q4AaOeMSQ+2b1tbFfLn
515-----END CERTIFICATE-----"
516 .to_string(),
517 ],
518 };
519
520 let certs: HashMap<String, ::openssl::x509::X509> = store_le_x1_x2.get_certs().unwrap();
521 assert!(!certs.is_empty());
522
523 let ossl_store: ::openssl::x509::store::X509Store = store_le_x1_x2.get_store().unwrap();
524 assert!(!ossl_store.all_certificates().is_empty())
525 }
526
527 #[cfg(ossl300)]
528 #[test]
529 fn test_system_store() {
530 let certs_map: HashMap<String, X509> = Store::System.get_certs().unwrap();
531 assert!(!certs_map.is_empty());
532 }
533
534 #[test]
535 fn test_tls_server_context() {
536 let ssl_config = SslConfig::default();
537 let ssl_acceptor_builder: ::openssl::ssl::SslAcceptorBuilder =
538 ssl_config.init_tls_server_context(None).unwrap();
539 let ssl_acceptor = ssl_acceptor_builder.build();
540
541 assert!(ssl_acceptor.context().private_key().is_some());
543 assert!(ssl_acceptor.context().certificate().is_some());
544 }
545}