This commit is contained in:
Mateusz Gruszczyński committed 2026-10-04 09:29:10 +02:00
1 parent 2e4aa4df4d
commit 87e571c170
106 files changed
+17952 -6185

No files matched your search

+50 -11
View File
@@ -59,7 +59,10 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
metadata.is_file() && !metadata.file_type().is_symlink(),
"secret store master key must be a regular file"
);
ensure!(metadata.len() == KEY_BYTES as u64, "invalid secret store master key size");
ensure!(
metadata.len() == KEY_BYTES as u64,
"invalid secret store master key size"
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
@@ -79,7 +82,10 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
let mut file = options.open(path)?;
file.read_exact(bytes.as_mut())?;
let mut extra = [0u8; 1];
ensure!(file.read(&mut extra)? == 0, "invalid secret store master key size");
ensure!(
file.read(&mut extra)? == 0,
"invalid secret store master key size"
);
Ok(bytes)
}
@@ -158,7 +164,10 @@ pub async fn put(
metadata: serde_json::Value,
) -> Result<()> {
validate_identity(secret_id, provider)?;
ensure!(!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES, "invalid secret size");
ensure!(
!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES,
"invalid secret size"
);
let metadata = serde_json::to_string(&metadata)?;
ensure!(metadata.len() <= 4096, "secret metadata is too large");
initialize(data_dir)?;
@@ -212,12 +221,16 @@ pub async fn get(
}
validate_identity(secret_id, &provider)?;
let version: i64 = row.try_get("encryption_version")?;
ensure!(version == ENCRYPTION_VERSION, "unsupported secret encryption version");
ensure!(
version == ENCRYPTION_VERSION,
"unsupported secret encryption version"
);
let nonce: Vec<u8> = row.try_get("nonce")?;
ensure!(nonce.len() == NONCE_BYTES, "invalid secret nonce");
let mut ciphertext = Zeroizing::new(row.try_get::<Vec<u8>, _>("ciphertext")?);
ensure!(
ciphertext.len() >= AES_256_GCM.tag_len() && ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
ciphertext.len() >= AES_256_GCM.tag_len()
&& ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
"invalid encrypted secret size"
);
let mut nonce_bytes = [0u8; NONCE_BYTES];
@@ -287,7 +300,10 @@ pub async fn migrate_acme_credentials(
return Ok(0);
}
let root_meta = fs::symlink_metadata(&root)?;
ensure!(root_meta.is_dir() && !root_meta.file_type().is_symlink(), "invalid ACME directory");
ensure!(
root_meta.is_dir() && !root_meta.file_type().is_symlink(),
"invalid ACME directory"
);
let mut migrated = 0usize;
for entry in fs::read_dir(&root)? {
let entry = entry?;
@@ -301,7 +317,10 @@ pub async fn migrate_acme_credentials(
continue;
}
let profile: serde_json::Value = serde_json::from_slice(&fs::read(&profile_path)?)?;
let provider = profile.get("provider").and_then(serde_json::Value::as_str).unwrap_or("");
let provider = profile
.get("provider")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
let certificate_id = profile
.get("certificate_id")
.and_then(serde_json::Value::as_str)
@@ -341,7 +360,8 @@ pub async fn migrate_acme_credentials(
)
.await?;
plaintext.zeroize();
fs::remove_file(&credential_path).context("legacy ACME credential was encrypted but could not be removed")?;
fs::remove_file(&credential_path)
.context("legacy ACME credential was encrypted but could not be removed")?;
migrated += 1;
}
Ok(migrated)
@@ -413,7 +433,10 @@ mod tests {
.unwrap();
assert_eq!(value.as_slice(), secret);
drop(value);
assert_eq!(read_key(&master_key_path(dir.path())).unwrap().len(), KEY_BYTES);
assert_eq!(
read_key(&master_key_path(dir.path())).unwrap().len(),
KEY_BYTES
);
}
#[tokio::test]
@@ -425,11 +448,27 @@ mod tests {
let store = crate::db::Store::open(&database, &crate::config::Config::default())
.await
.unwrap();
put(&store, dir.path(), "acme/site/cloudflare/credential", "cloudflare", b"secret-value", serde_json::json!({})).await.unwrap();
put(
&store,
dir.path(),
"acme/site/cloudflare/credential",
"cloudflare",
b"secret-value",
serde_json::json!({}),
)
.await
.unwrap();
sqlx::query("UPDATE secret_store SET ciphertext = zeroblob(length(ciphertext))")
.execute(&store.pool)
.await
.unwrap();
assert!(get(&store, dir.path(), "acme/site/cloudflare/credential", Some("cloudflare")).await.is_err());
assert!(get(
&store,
dir.path(),
"acme/site/cloudflare/credential",
Some("cloudflare")
)
.await
.is_err());
}
}