Skip to content

Commit 1a6052b

Browse files
committed
lib: add more error context when loading fails
Previously the code path that read from `CertPaths` that configure a dir from the environment lacked helpful context if the dir couldn't be read. Similarly, the code path that read from a `CertPaths` file would add helpful context if `rustls_pemfile` returned an error, but not if the file open failed. This commit centralizes the job of adding useful context into `CertPaths.load()`. The `rustls_pemfile` specific part in the file path processing can then be removed. This ensures for both dirs and files that if the operation fails there's useful context as to why.
1 parent e7cbf82 commit 1a6052b

File tree

1 file changed

+76
-12
lines changed

1 file changed

+76
-12
lines changed

src/lib.rs

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,35 @@ impl CertPaths {
158158
}
159159

160160
let mut certs = match &self.file {
161-
Some(cert_file) => load_pem_certs(cert_file)?,
161+
Some(cert_file) => {
162+
load_pem_certs(cert_file).map_err(|err| Self::load_err(cert_file, err))?
163+
}
162164
None => Vec::new(),
163165
};
164166

165167
if let Some(cert_dir) = &self.dir {
166-
certs.append(&mut load_pem_certs_from_dir(cert_dir)?);
168+
certs.append(
169+
&mut load_pem_certs_from_dir(cert_dir)
170+
.map_err(|err| Self::load_err(cert_dir, err))?,
171+
);
167172
}
168173

169174
certs.sort_unstable_by(|a, b| a.cmp(b));
170175
certs.dedup();
171176

172177
Ok(Some(certs))
173178
}
179+
180+
fn load_err(path: &Path, err: Error) -> Error {
181+
Error::new(
182+
err.kind(),
183+
format!(
184+
"could not load certs from {} {}: {err}",
185+
if path.is_file() { "file" } else { "dir" },
186+
path.display()
187+
),
188+
)
189+
}
174190
}
175191

176192
/// Load certificate from certificate directory (what OpenSSL calls CAdir)
@@ -211,16 +227,7 @@ fn load_pem_certs_from_dir(dir: &Path) -> Result<Vec<CertificateDer<'static>>, E
211227
}
212228

213229
fn load_pem_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, Error> {
214-
let mut f = BufReader::new(File::open(path)?);
215-
rustls_pemfile::certs(&mut f)
216-
.map(|result| match result {
217-
Ok(der) => Ok(der),
218-
Err(err) => Err(Error::new(
219-
ErrorKind::InvalidData,
220-
format!("could not load PEM file {path:?}: {err}"),
221-
)),
222-
})
223-
.collect()
230+
rustls_pemfile::certs(&mut BufReader::new(File::open(path)?)).collect()
224231
}
225232

226233
/// Check if this is a hash-based file name for a certificate
@@ -259,7 +266,11 @@ const ENV_CERT_DIR: &str = "SSL_CERT_DIR";
259266
mod tests {
260267
use super::*;
261268

269+
#[cfg(unix)]
270+
use std::fs::Permissions;
262271
use std::io::Write;
272+
#[cfg(unix)]
273+
use std::os::unix::fs::PermissionsExt;
263274

264275
#[test]
265276
fn valid_hash_file_name() {
@@ -376,4 +387,57 @@ mod tests {
376387
let certs = load_pem_certs(Path::new("/dev/null")).unwrap();
377388
assert_eq!(certs.len(), 0);
378389
}
390+
391+
#[test]
392+
#[cfg(unix)]
393+
fn from_env_bad_dir_perms() {
394+
// Create a temp dir that we can't read from.
395+
let temp_dir = tempfile::TempDir::new().unwrap();
396+
fs::set_permissions(temp_dir.path(), Permissions::from_mode(0)).unwrap();
397+
398+
test_cert_paths_bad_perms(CertPaths {
399+
file: None,
400+
dir: Some(temp_dir.path().into()),
401+
})
402+
}
403+
404+
#[test]
405+
#[cfg(unix)]
406+
fn from_env_bad_file_perms() {
407+
// Create a tmp dir with a file inside that we can't read from.
408+
let temp_dir = tempfile::TempDir::new().unwrap();
409+
let file_path = temp_dir.path().join("unreadable.pem");
410+
let cert_file = File::create(&file_path).unwrap();
411+
cert_file
412+
.set_permissions(Permissions::from_mode(0))
413+
.unwrap();
414+
415+
test_cert_paths_bad_perms(CertPaths {
416+
file: Some(file_path.clone()),
417+
dir: None,
418+
});
419+
}
420+
421+
#[cfg(unix)]
422+
fn test_cert_paths_bad_perms(cert_paths: CertPaths) {
423+
let err = cert_paths.load().unwrap_err();
424+
425+
let affected_path = match (cert_paths.file, cert_paths.dir) {
426+
(Some(file), None) => file,
427+
(None, Some(dir)) => dir,
428+
_ => panic!("only one of file or dir should be set"),
429+
};
430+
let r#type = match affected_path.is_file() {
431+
true => "file",
432+
false => "dir",
433+
};
434+
435+
assert_eq!(err.kind(), ErrorKind::PermissionDenied);
436+
assert!(err
437+
.to_string()
438+
.contains(&format!("certs from {type}")));
439+
assert!(err
440+
.to_string()
441+
.contains(&affected_path.display().to_string()));
442+
}
379443
}

0 commit comments

Comments
 (0)