use super::{LibraryError, fetch};
use std::io::Write;
use std::path::Path;
const DEFAULT_OIDC_ISSUER: &str = "https://token.actions.githubusercontent.com";
pub fn verify_manifest_signature(
manifest_url: &str,
manifest_body: &[u8],
require_signature: bool,
identity_regexp: Option<&str>,
oidc_issuer: Option<&str>,
) -> Result<(), LibraryError> {
if !require_signature {
return Ok(());
}
let tmpdir = tempfile::tempdir().map_err(LibraryError::Io)?;
let manifest_path = tmpdir.path().join("manifest.json");
std::fs::write(&manifest_path, manifest_body).map_err(LibraryError::Io)?;
let sig_url = format!("{}.sig", manifest_url);
let cert_url = format!("{}.pem", manifest_url);
let sig_bytes = match fetch::fetch_bounded(&sig_url, fetch::MAX_ITEM_BYTES) {
Ok(b) => b,
Err(e) => {
return Err(LibraryError::SignatureCompanionsMissing {
url: crate::network::redact_credentials(manifest_url).into_owned(),
reason: format!("could not fetch {sig_url}: {e}"),
});
}
};
let cert_bytes = match fetch::fetch_bounded(&cert_url, fetch::MAX_ITEM_BYTES) {
Ok(b) => b,
Err(e) => {
return Err(LibraryError::SignatureCompanionsMissing {
url: crate::network::redact_credentials(manifest_url).into_owned(),
reason: format!("could not fetch {cert_url}: {e}"),
});
}
};
let sig_path = manifest_path.with_extension("json.sig");
let cert_path = manifest_path.with_extension("json.pem");
write_atomic(&sig_path, &sig_bytes)?;
write_atomic(&cert_path, &cert_bytes)?;
let identity = identity_regexp.unwrap_or(".*");
let issuer = oidc_issuer.unwrap_or(DEFAULT_OIDC_ISSUER);
match crate::update::signature::verify_sigstore_signature_with_identity(
&manifest_path,
identity,
issuer,
) {
Ok(crate::update::signature::SignatureOutcome::Verified) => Ok(()),
Ok(crate::update::signature::SignatureOutcome::CosignMissing) => {
Err(LibraryError::CosignMissing {
url: crate::network::redact_credentials(manifest_url).into_owned(),
})
}
Ok(crate::update::signature::SignatureOutcome::SignatureFilesMissing) => {
Err(LibraryError::SignatureCompanionsMissing {
url: crate::network::redact_credentials(manifest_url).into_owned(),
reason: "cosign reports sig/cert files missing after temp write".to_string(),
})
}
Ok(crate::update::signature::SignatureOutcome::Rejected(stderr)) => {
Err(LibraryError::SignatureRejected {
url: crate::network::redact_credentials(manifest_url).into_owned(),
reason: stderr,
})
}
Err(e) => Err(LibraryError::SignatureRejected {
url: crate::network::redact_credentials(manifest_url).into_owned(),
reason: e.to_string(),
}),
}
}
fn write_atomic(path: &Path, content: &[u8]) -> Result<(), LibraryError> {
let mut f = std::fs::File::create(path).map_err(LibraryError::Io)?;
f.write_all(content).map_err(LibraryError::Io)?;
f.flush().map_err(LibraryError::Io)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn require_signature_false_short_circuits() {
let res = verify_manifest_signature(
"https://cdn.example/lib/manifest.json",
b"{}",
false,
None,
None,
);
assert!(res.is_ok());
}
}