This commit is contained in:
Mateusz Gruszczyński committed 2026-10-08 10:36:34 +02:00
1 parent 3b3f0db377
commit d940f9995f
47 files changed
+542 -187

No files matched your search

+77 -6
View File
@@ -16,6 +16,7 @@ use std::{
use zeroize::Zeroizing;
const MAX_PEM: u64 = 131_072;
const MAX_CERTIFICATE_PATH: usize = 4096;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct Settings {
@@ -324,7 +325,68 @@ pub fn safe_name(name: &str) -> Result<()> {
);
Ok(())
}
/// Validate how a configured certificate pair is addressed. Legacy/simple names
/// remain private to `<data-dir>/certs`; an explicitly selected external pair uses
/// two absolute paths. Mixing the two modes is rejected so the source is never
/// ambiguous.
pub fn certificate_files_are_external(cert_file: &str, key_file: &str) -> Result<bool> {
let cert_external = Path::new(cert_file).is_absolute();
let key_external = Path::new(key_file).is_absolute();
ensure!(
cert_external == key_external,
"certificate and private key must both use filenames from the RustProxy certificate directory or both use absolute external paths"
);
if cert_external {
ensure!(
!cert_file.is_empty()
&& !key_file.is_empty()
&& cert_file.len() <= MAX_CERTIFICATE_PATH
&& key_file.len() <= MAX_CERTIFICATE_PATH,
"external certificate paths must be absolute paths up to 4096 bytes"
);
Ok(true)
} else {
safe_name(cert_file)?;
safe_name(key_file)?;
Ok(false)
}
}
fn configured_certificate_path(directory: &Path, value: &str) -> Result<PathBuf> {
if Path::new(value).is_absolute() {
ensure!(
!value.is_empty() && value.len() <= MAX_CERTIFICATE_PATH,
"external certificate path must be an absolute path up to 4096 bytes"
);
// Certbot's live/ entries are symlinks to archive/. Resolve them on every
// reload, then open the final regular file with O_NOFOLLOW in read_pem.
return fs::canonicalize(value)
.with_context(|| format!("cannot resolve external certificate path {value}"));
}
safe_name(value)?;
Ok(directory.join(value))
}
fn read_configured_pem(
directory: &Path,
value: &str,
private: bool,
) -> Result<Zeroizing<Vec<u8>>> {
let external = Path::new(value).is_absolute();
let path = configured_certificate_path(directory, value)?;
read_pem_with_policy(&path, private, external)
}
fn read_pem(path: &Path, private: bool) -> Result<Zeroizing<Vec<u8>>> {
read_pem_with_policy(path, private, false)
}
fn read_pem_with_policy(
path: &Path,
private: bool,
external_group_read: bool,
) -> Result<Zeroizing<Vec<u8>>> {
let mut options = OpenOptions::new();
options.read(true);
#[cfg(unix)]
@@ -343,14 +405,23 @@ fn read_pem(path: &Path, private: bool) -> Result<Zeroizing<Vec<u8>>> {
#[cfg(unix)]
if private {
use std::os::unix::fs::PermissionsExt;
ensure!(
meta.permissions().mode() & 0o077 == 0,
"private key permissions must be 600: {}",
path.display()
);
let mode = meta.permissions().mode();
if external_group_read {
ensure!(
mode & 0o137 == 0,
"external private key may be owner/group-readable but must not be executable, group-writable, or world-accessible: {}",
path.display()
);
} else {
ensure!(
mode & 0o077 == 0,
"private key permissions must not grant group/world access: {}",
path.display()
);
}
}
#[cfg(not(unix))]
let _ = private;
let _ = (private, external_group_read);
let mut data = Zeroizing::new(Vec::new());
file.take(MAX_PEM + 1).read_to_end(&mut data)?;
ensure!(