/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-native-certs-0.8.4/src/lib.rs
Line | Count | Source |
1 | | //! rustls-native-certs allows rustls to use the platform's native certificate |
2 | | //! store when operating as a TLS client. |
3 | | //! |
4 | | //! It provides a single function [`load_native_certs()`], which returns a |
5 | | //! collection of certificates found by reading the platform-native |
6 | | //! certificate store. |
7 | | //! |
8 | | //! If the SSL_CERT_FILE environment variable is set, certificates (in PEM |
9 | | //! format) are read from that file instead. |
10 | | //! |
11 | | //! If you want to load these certificates into a `rustls::RootCertStore`, |
12 | | //! you'll likely want to do something like this: |
13 | | //! |
14 | | //! ```no_run |
15 | | //! let mut roots = rustls::RootCertStore::empty(); |
16 | | //! for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { |
17 | | //! roots.add(cert).unwrap(); |
18 | | //! } |
19 | | //! ``` |
20 | | |
21 | | // Enable documentation for all features on docs.rs |
22 | | #![cfg_attr(rustls_native_certs_docsrs, feature(doc_cfg))] |
23 | | |
24 | | use std::error::Error as StdError; |
25 | | use std::path::{Path, PathBuf}; |
26 | | use std::{env, fmt, fs, io}; |
27 | | |
28 | | use pki_types::pem::{self, PemObject}; |
29 | | use pki_types::CertificateDer; |
30 | | |
31 | | #[cfg(all(unix, not(target_os = "macos")))] |
32 | | mod unix; |
33 | | #[cfg(all(unix, not(target_os = "macos")))] |
34 | | use unix as platform; |
35 | | |
36 | | #[cfg(windows)] |
37 | | mod windows; |
38 | | #[cfg(windows)] |
39 | | use windows as platform; |
40 | | |
41 | | #[cfg(target_os = "macos")] |
42 | | mod macos; |
43 | | #[cfg(target_os = "macos")] |
44 | | use macos as platform; |
45 | | |
46 | | /// Load root certificates found in the platform's native certificate store. |
47 | | /// |
48 | | /// ## Environment Variables |
49 | | /// |
50 | | /// | Env. Var. | Description | |
51 | | /// |---------------|---------------------------------------------------------------------------------------| |
52 | | /// | SSL_CERT_FILE | File containing an arbitrary number of certificates in PEM format. | |
53 | | /// | SSL_CERT_DIR | Colon separated list of directories containing certificate files. | |
54 | | /// |
55 | | /// If **either** (or **both**) are set, certificates are only loaded from |
56 | | /// the locations specified via environment variables and not the platform- |
57 | | /// native certificate store. |
58 | | /// |
59 | | /// ## Certificate Validity |
60 | | /// |
61 | | /// All certificates are expected to be in PEM format. A file may contain |
62 | | /// multiple certificates. |
63 | | /// |
64 | | /// Example: |
65 | | /// |
66 | | /// ```text |
67 | | /// -----BEGIN CERTIFICATE----- |
68 | | /// MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw |
69 | | /// CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg |
70 | | /// R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 |
71 | | /// MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT |
72 | | /// ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw |
73 | | /// EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW |
74 | | /// +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 |
75 | | /// ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T |
76 | | /// AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI |
77 | | /// zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW |
78 | | /// tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 |
79 | | /// /q4AaOeMSQ+2b1tbFfLn |
80 | | /// -----END CERTIFICATE----- |
81 | | /// -----BEGIN CERTIFICATE----- |
82 | | /// MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 |
83 | | /// MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g |
84 | | /// Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG |
85 | | /// A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg |
86 | | /// Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl |
87 | | /// ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j |
88 | | /// QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr |
89 | | /// ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr |
90 | | /// BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM |
91 | | /// YyRIHN8wfdVoOw== |
92 | | /// -----END CERTIFICATE----- |
93 | | /// |
94 | | /// ``` |
95 | | /// |
96 | | /// For reasons of compatibility, an attempt is made to skip invalid sections |
97 | | /// of a certificate file but this means it's also possible for a malformed |
98 | | /// certificate to be skipped. |
99 | | /// |
100 | | /// If a certificate isn't loaded, and no error is reported, check if: |
101 | | /// |
102 | | /// 1. the certificate is in PEM format (see example above) |
103 | | /// 2. *BEGIN CERTIFICATE* line starts with exactly five hyphens (`'-'`) |
104 | | /// 3. *END CERTIFICATE* line ends with exactly five hyphens (`'-'`) |
105 | | /// 4. there is a line break after the certificate. |
106 | | /// |
107 | | /// ## Errors |
108 | | /// |
109 | | /// This function fails in a platform-specific way, expressed in a `std::io::Error`. |
110 | | /// |
111 | | /// ## Caveats |
112 | | /// |
113 | | /// This function can be expensive: on some platforms it involves loading |
114 | | /// and parsing a ~300KB disk file. It's therefore prudent to call |
115 | | /// this sparingly. |
116 | | /// |
117 | | /// [c_rehash]: https://www.openssl.org/docs/manmaster/man1/c_rehash.html |
118 | 1 | pub fn load_native_certs() -> CertificateResult { |
119 | 1 | let paths = CertPaths::from_env(); |
120 | 1 | match (&paths.dirs, &paths.file) { |
121 | 1 | (v, _) if !v.is_empty() => paths.load(), |
122 | 0 | (_, Some(_)) => paths.load(), |
123 | 1 | _ => platform::load_native_certs(), |
124 | | } |
125 | 1 | } |
126 | | |
127 | | /// Results from trying to load certificates from the platform's native store. |
128 | | #[non_exhaustive] |
129 | | #[derive(Debug, Default)] |
130 | | pub struct CertificateResult { |
131 | | /// Any certificates that were successfully loaded. |
132 | | pub certs: Vec<CertificateDer<'static>>, |
133 | | /// Any errors encountered while loading certificates. |
134 | | pub errors: Vec<Error>, |
135 | | } |
136 | | |
137 | | impl CertificateResult { |
138 | | /// Return the found certificates if no error occurred, otherwise panic. |
139 | | #[track_caller] |
140 | 0 | pub fn expect(self, msg: &str) -> Vec<CertificateDer<'static>> { |
141 | 0 | match self.errors.is_empty() { |
142 | 0 | true => self.certs, |
143 | 0 | false => panic!("{msg}: {:?}", self.errors), |
144 | | } |
145 | 0 | } |
146 | | |
147 | | /// Return the found certificates if no error occurred, otherwise panic. |
148 | | #[track_caller] |
149 | 0 | pub fn unwrap(self) -> Vec<CertificateDer<'static>> { |
150 | 0 | match self.errors.is_empty() { |
151 | 0 | true => self.certs, |
152 | 0 | false => panic!( |
153 | 0 | "errors occurred while loading certificates: {:?}", |
154 | | self.errors |
155 | | ), |
156 | | } |
157 | 0 | } |
158 | | |
159 | 0 | fn pem_error(&mut self, err: pem::Error, path: &Path) { |
160 | 0 | self.errors.push(Error { |
161 | | context: "failed to read PEM from file", |
162 | 0 | kind: match err { |
163 | 0 | pem::Error::Io(err) => ErrorKind::Io { |
164 | 0 | inner: err, |
165 | 0 | path: path.to_owned(), |
166 | 0 | }, |
167 | 0 | _ => ErrorKind::Pem(err), |
168 | | }, |
169 | | }); |
170 | 0 | } |
171 | | |
172 | 0 | fn io_error(&mut self, err: io::Error, path: &Path, context: &'static str) { |
173 | 0 | self.errors.push(Error { |
174 | 0 | context, |
175 | 0 | kind: ErrorKind::Io { |
176 | 0 | inner: err, |
177 | 0 | path: path.to_owned(), |
178 | 0 | }, |
179 | 0 | }); |
180 | 0 | } |
181 | | |
182 | | #[cfg(any(windows, target_os = "macos"))] |
183 | | fn os_error(&mut self, err: Box<dyn StdError + Send + Sync + 'static>, context: &'static str) { |
184 | | self.errors.push(Error { |
185 | | context, |
186 | | kind: ErrorKind::Os(err), |
187 | | }); |
188 | | } |
189 | | } |
190 | | |
191 | | /// Certificate paths from `SSL_CERT_FILE` and/or `SSL_CERT_DIR`. |
192 | | struct CertPaths { |
193 | | file: Option<PathBuf>, |
194 | | dirs: Vec<PathBuf>, |
195 | | } |
196 | | |
197 | | impl CertPaths { |
198 | 1 | fn from_env() -> Self { |
199 | | Self { |
200 | 1 | file: env::var_os(ENV_CERT_FILE).map(PathBuf::from), |
201 | | // Read `SSL_CERT_DIR`, split it on the platform delimiter (`:` on Unix, `;` on Windows), |
202 | | // and return the entries as `PathBuf`s. |
203 | | // |
204 | | // See <https://docs.openssl.org/3.5/man1/openssl-rehash/#options> |
205 | 1 | dirs: match env::var_os(ENV_CERT_DIR) { |
206 | 0 | Some(dirs) => env::split_paths(&dirs) |
207 | 0 | .filter(|p| !p.as_os_str().is_empty()) |
208 | 0 | .collect(), |
209 | 1 | None => Vec::new(), |
210 | | }, |
211 | | } |
212 | 1 | } |
213 | | |
214 | | /// Load certificates from the paths. |
215 | | /// |
216 | | /// See [`load_certs_from_paths()`]. |
217 | 1 | fn load(&self) -> CertificateResult { |
218 | 1 | load_certs_from_paths_internal(self.file.as_deref(), &self.dirs) |
219 | 1 | } |
220 | | } |
221 | | |
222 | | /// Load certificates from the given paths. |
223 | | /// |
224 | | /// If both are `None`, returns an empty [`CertificateResult`]. |
225 | | /// |
226 | | /// If `file` is `Some`, it is always used, so it must be a path to an existing, |
227 | | /// accessible file from which certificates can be loaded successfully. While parsing, |
228 | | /// the rustls-pki-types PEM parser will ignore parts of the file which are |
229 | | /// not considered part of a certificate. Certificates which are not in the right |
230 | | /// format (PEM) or are otherwise corrupted may get ignored silently. |
231 | | /// |
232 | | /// If `dir` is defined, a directory must exist at this path, and all files |
233 | | /// contained in it must be loaded successfully, subject to the rules outlined above for `file`. |
234 | | /// The directory is not scanned recursively and may be empty. |
235 | 0 | pub fn load_certs_from_paths(file: Option<&Path>, dir: Option<&Path>) -> CertificateResult { |
236 | 0 | let dir = match dir { |
237 | 0 | Some(d) => vec![d], |
238 | 0 | None => Vec::new(), |
239 | | }; |
240 | | |
241 | 0 | load_certs_from_paths_internal(file, dir.as_ref()) |
242 | 0 | } |
243 | | |
244 | 1 | fn load_certs_from_paths_internal( |
245 | 1 | file: Option<&Path>, |
246 | 1 | dir: &[impl AsRef<Path>], |
247 | 1 | ) -> CertificateResult { |
248 | 1 | let mut out = CertificateResult::default(); |
249 | 1 | if file.is_none() && dir.is_empty() { |
250 | 0 | return out; |
251 | 1 | } |
252 | | |
253 | 1 | if let Some(cert_file) = file { |
254 | 1 | load_pem_certs(cert_file, &mut out); |
255 | 1 | } |
256 | | |
257 | 1 | for cert_dir in dir.iter() { |
258 | 1 | load_pem_certs_from_dir(cert_dir.as_ref(), &mut out); |
259 | 1 | } |
260 | | |
261 | 1 | out.certs |
262 | 5.59k | .sort_unstable_by(|a, b| a.cmp(b)); rustls_native_certs::load_certs_from_paths_internal::<std::path::PathBuf>::{closure#0}Line | Count | Source | 262 | 5.59k | .sort_unstable_by(|a, b| a.cmp(b)); |
Unexecuted instantiation: rustls_native_certs::load_certs_from_paths_internal::<&std::path::Path>::{closure#0} |
263 | 1 | out.certs.dedup(); |
264 | 1 | out |
265 | 1 | } rustls_native_certs::load_certs_from_paths_internal::<std::path::PathBuf> Line | Count | Source | 244 | 1 | fn load_certs_from_paths_internal( | 245 | 1 | file: Option<&Path>, | 246 | 1 | dir: &[impl AsRef<Path>], | 247 | 1 | ) -> CertificateResult { | 248 | 1 | let mut out = CertificateResult::default(); | 249 | 1 | if file.is_none() && dir.is_empty() { | 250 | 0 | return out; | 251 | 1 | } | 252 | | | 253 | 1 | if let Some(cert_file) = file { | 254 | 1 | load_pem_certs(cert_file, &mut out); | 255 | 1 | } | 256 | | | 257 | 1 | for cert_dir in dir.iter() { | 258 | 1 | load_pem_certs_from_dir(cert_dir.as_ref(), &mut out); | 259 | 1 | } | 260 | | | 261 | 1 | out.certs | 262 | 1 | .sort_unstable_by(|a, b| a.cmp(b)); | 263 | 1 | out.certs.dedup(); | 264 | 1 | out | 265 | 1 | } |
Unexecuted instantiation: rustls_native_certs::load_certs_from_paths_internal::<&std::path::Path> |
266 | | |
267 | | /// Load certificate from certificate directory (what OpenSSL calls CAdir) |
268 | 1 | fn load_pem_certs_from_dir(dir: &Path, out: &mut CertificateResult) { |
269 | 1 | let dir_reader = match fs::read_dir(dir) { |
270 | 1 | Ok(reader) => reader, |
271 | 0 | Err(err) => { |
272 | 0 | out.io_error(err, dir, "opening directory"); |
273 | 0 | return; |
274 | | } |
275 | | }; |
276 | | |
277 | 294 | for entry in dir_reader { |
278 | 293 | let entry = match entry { |
279 | 293 | Ok(entry) => entry, |
280 | 0 | Err(err) => { |
281 | 0 | out.io_error(err, dir, "reading directory entries"); |
282 | 0 | continue; |
283 | | } |
284 | | }; |
285 | | |
286 | 293 | let path = entry.path(); |
287 | | |
288 | | // `openssl rehash` used to create this directory uses symlinks. So, |
289 | | // make sure we resolve them. |
290 | 293 | let metadata = match fs::metadata(&path) { |
291 | 293 | Ok(metadata) => metadata, |
292 | 0 | Err(e) if e.kind() == io::ErrorKind::NotFound => { |
293 | | // Dangling symlink |
294 | 0 | continue; |
295 | | } |
296 | 0 | Err(e) => { |
297 | 0 | out.io_error(e, &path, "failed to open file"); |
298 | 0 | continue; |
299 | | } |
300 | | }; |
301 | | |
302 | 293 | if metadata.is_file() { |
303 | 293 | load_pem_certs(&path, out); |
304 | 293 | } |
305 | | } |
306 | 1 | } |
307 | | |
308 | 294 | fn load_pem_certs(path: &Path, out: &mut CertificateResult) { |
309 | 294 | let iter = match CertificateDer::pem_file_iter(path) { |
310 | 294 | Ok(iter) => iter, |
311 | 0 | Err(err) => { |
312 | 0 | out.pem_error(err, path); |
313 | 0 | return; |
314 | | } |
315 | | }; |
316 | | |
317 | 878 | for result in iter { |
318 | 584 | match result { |
319 | 584 | Ok(cert) => out.certs.push(cert), |
320 | 0 | Err(err) => out.pem_error(err, path), |
321 | | } |
322 | | } |
323 | 294 | } |
324 | | |
325 | | #[derive(Debug)] |
326 | | pub struct Error { |
327 | | pub context: &'static str, |
328 | | pub kind: ErrorKind, |
329 | | } |
330 | | |
331 | | impl StdError for Error { |
332 | 0 | fn source(&self) -> Option<&(dyn StdError + 'static)> { |
333 | 0 | Some(match &self.kind { |
334 | 0 | ErrorKind::Io { inner, .. } => inner, |
335 | 0 | ErrorKind::Os(err) => &**err, |
336 | 0 | ErrorKind::Pem(err) => err, |
337 | | }) |
338 | 0 | } |
339 | | } |
340 | | |
341 | | impl fmt::Display for Error { |
342 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
343 | 0 | f.write_str(self.context)?; |
344 | 0 | f.write_str(": ")?; |
345 | 0 | match &self.kind { |
346 | 0 | ErrorKind::Io { inner, path } => { |
347 | 0 | write!(f, "{inner} at '{}'", path.display()) |
348 | | } |
349 | 0 | ErrorKind::Os(err) => err.fmt(f), |
350 | 0 | ErrorKind::Pem(err) => err.fmt(f), |
351 | | } |
352 | 0 | } |
353 | | } |
354 | | |
355 | | #[non_exhaustive] |
356 | | #[derive(Debug)] |
357 | | pub enum ErrorKind { |
358 | | Io { inner: io::Error, path: PathBuf }, |
359 | | Os(Box<dyn StdError + Send + Sync + 'static>), |
360 | | Pem(pem::Error), |
361 | | } |
362 | | |
363 | | const ENV_CERT_FILE: &str = "SSL_CERT_FILE"; |
364 | | const ENV_CERT_DIR: &str = "SSL_CERT_DIR"; |
365 | | |
366 | | #[cfg(test)] |
367 | | mod tests { |
368 | | use super::*; |
369 | | |
370 | | use std::fs::File; |
371 | | #[cfg(unix)] |
372 | | use std::fs::Permissions; |
373 | | use std::io::Write; |
374 | | #[cfg(unix)] |
375 | | use std::os::unix::fs::PermissionsExt; |
376 | | |
377 | | #[test] |
378 | | fn deduplication() { |
379 | | let temp_dir = tempfile::TempDir::new().unwrap(); |
380 | | let cert1 = include_str!("../tests/badssl-com-chain.pem"); |
381 | | let cert2 = include_str!("../integration-tests/one-existing-ca.pem"); |
382 | | let file_path = temp_dir |
383 | | .path() |
384 | | .join("ca-certificates.crt"); |
385 | | let dir_path = temp_dir.path().to_path_buf(); |
386 | | |
387 | | { |
388 | | let mut file = File::create(&file_path).unwrap(); |
389 | | write!(file, "{}", cert1).unwrap(); |
390 | | write!(file, "{}", cert2).unwrap(); |
391 | | } |
392 | | |
393 | | { |
394 | | // Duplicate (already in `file_path`) |
395 | | let mut file = File::create(dir_path.join("71f3bb26.0")).unwrap(); |
396 | | write!(file, "{}", cert1).unwrap(); |
397 | | } |
398 | | |
399 | | { |
400 | | // Duplicate (already in `file_path`) |
401 | | let mut file = File::create(dir_path.join("912e7cd5.0")).unwrap(); |
402 | | write!(file, "{}", cert2).unwrap(); |
403 | | } |
404 | | |
405 | | let result = CertPaths { |
406 | | file: Some(file_path.clone()), |
407 | | dirs: vec![], |
408 | | } |
409 | | .load(); |
410 | | assert_eq!(result.certs.len(), 2); |
411 | | |
412 | | let result = CertPaths { |
413 | | file: None, |
414 | | dirs: vec![dir_path.clone()], |
415 | | } |
416 | | .load(); |
417 | | assert_eq!(result.certs.len(), 2); |
418 | | |
419 | | let result = CertPaths { |
420 | | file: Some(file_path), |
421 | | dirs: vec![dir_path], |
422 | | } |
423 | | .load(); |
424 | | assert_eq!(result.certs.len(), 2); |
425 | | } |
426 | | |
427 | | #[test] |
428 | | fn malformed_file_from_env() { |
429 | | // Certificate parser tries to extract certs from file ignoring |
430 | | // invalid sections. |
431 | | let mut result = CertificateResult::default(); |
432 | | load_pem_certs(Path::new(file!()), &mut result); |
433 | | assert_eq!(result.certs.len(), 0); |
434 | | assert!(result.errors.is_empty()); |
435 | | } |
436 | | |
437 | | #[test] |
438 | | fn from_env_missing_file() { |
439 | | let mut result = CertificateResult::default(); |
440 | | load_pem_certs(Path::new("no/such/file"), &mut result); |
441 | | match &first_error(&result).kind { |
442 | | ErrorKind::Io { inner, .. } => assert_eq!(inner.kind(), io::ErrorKind::NotFound), |
443 | | _ => panic!("unexpected error {:?}", result.errors), |
444 | | } |
445 | | } |
446 | | |
447 | | #[test] |
448 | | fn from_env_missing_dir() { |
449 | | let mut result = CertificateResult::default(); |
450 | | load_pem_certs_from_dir(Path::new("no/such/directory"), &mut result); |
451 | | match &first_error(&result).kind { |
452 | | ErrorKind::Io { inner, .. } => assert_eq!(inner.kind(), io::ErrorKind::NotFound), |
453 | | _ => panic!("unexpected error {:?}", result.errors), |
454 | | } |
455 | | } |
456 | | |
457 | | #[test] |
458 | | #[cfg(unix)] |
459 | | fn from_env_with_non_regular_and_empty_file() { |
460 | | let mut result = CertificateResult::default(); |
461 | | load_pem_certs(Path::new("/dev/null"), &mut result); |
462 | | assert_eq!(result.certs.len(), 0); |
463 | | assert!(result.errors.is_empty()); |
464 | | } |
465 | | |
466 | | #[test] |
467 | | #[cfg(unix)] |
468 | | fn from_env_bad_dir_perms() { |
469 | | // Create a temp dir that we can't read from. |
470 | | let temp_dir = tempfile::TempDir::new().unwrap(); |
471 | | fs::set_permissions(temp_dir.path(), Permissions::from_mode(0o000)).unwrap(); |
472 | | |
473 | | test_cert_paths_bad_perms(CertPaths { |
474 | | file: None, |
475 | | dirs: vec![temp_dir.path().into()], |
476 | | }) |
477 | | } |
478 | | |
479 | | #[test] |
480 | | #[cfg(unix)] |
481 | | fn from_env_bad_file_perms() { |
482 | | // Create a tmp dir with a file inside that we can't read from. |
483 | | let temp_dir = tempfile::TempDir::new().unwrap(); |
484 | | let file_path = temp_dir.path().join("unreadable.pem"); |
485 | | let cert_file = File::create(&file_path).unwrap(); |
486 | | cert_file |
487 | | .set_permissions(Permissions::from_mode(0o000)) |
488 | | .unwrap(); |
489 | | |
490 | | test_cert_paths_bad_perms(CertPaths { |
491 | | file: Some(file_path.clone()), |
492 | | dirs: vec![], |
493 | | }); |
494 | | } |
495 | | |
496 | | #[cfg(unix)] |
497 | | fn test_cert_paths_bad_perms(cert_paths: CertPaths) { |
498 | | let result = cert_paths.load(); |
499 | | |
500 | | if let (None, true) = (cert_paths.file, cert_paths.dirs.is_empty()) { |
501 | | panic!("only one of file or dir should be set"); |
502 | | }; |
503 | | |
504 | | let error = first_error(&result); |
505 | | match &error.kind { |
506 | | ErrorKind::Io { inner, .. } => { |
507 | | assert_eq!(inner.kind(), io::ErrorKind::PermissionDenied); |
508 | | inner |
509 | | } |
510 | | _ => panic!("unexpected error {:?}", result.errors), |
511 | | }; |
512 | | } |
513 | | |
514 | | fn first_error(result: &CertificateResult) -> &Error { |
515 | | result.errors.first().unwrap() |
516 | | } |
517 | | } |