use std::collections::BTreeSet;
use crate::config::{Config, SignConfig};
pub fn signature_template_suffix(template: &str) -> Option<String> {
let tail = match template.rfind("}}") {
Some(idx) => &template[idx + 2..],
None => template,
};
let tail = tail.trim();
if tail.len() < 2 || !tail.starts_with('.') {
return None;
}
Some(tail.to_string())
}
pub fn signature_asset_suffixes(cfg: &Config) -> BTreeSet<String> {
let mut suffixes = BTreeSet::new();
let mut collect = |entries: &[SignConfig], default_tmpl: &str| {
for s in entries {
if let Some(suffix) =
signature_template_suffix(s.resolved_signature_template(default_tmpl))
{
suffixes.insert(suffix);
}
if let Some(suffix) = s.certificate.as_deref().and_then(signature_template_suffix) {
suffixes.insert(suffix);
}
}
};
collect(&cfg.signs, SignConfig::DEFAULT_SIGNATURE_TEMPLATE);
collect(
&cfg.binary_signs,
SignConfig::DEFAULT_BINARY_SIGNATURE_TEMPLATE,
);
for w in cfg.workspaces.iter().flatten() {
collect(&w.signs, SignConfig::DEFAULT_SIGNATURE_TEMPLATE);
collect(
&w.binary_signs,
SignConfig::DEFAULT_BINARY_SIGNATURE_TEMPLATE,
);
}
suffixes
}
pub fn is_signature_asset(asset_name: &str, suffixes: &BTreeSet<String>) -> bool {
suffixes.iter().any(|suffix| asset_name.ends_with(suffix))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signature_template_suffix_extracts_literal_tail_after_last_expansion() {
assert_eq!(
signature_template_suffix("{{ .Artifact }}.cosign.bundle").as_deref(),
Some(".cosign.bundle")
);
assert_eq!(
signature_template_suffix("{{ .Artifact }}.sig").as_deref(),
Some(".sig")
);
assert_eq!(
signature_template_suffix("{{ .Artifact }}.asc").as_deref(),
Some(".asc")
);
}
#[test]
fn signature_template_suffix_rejects_unanchorable_templates() {
assert_eq!(signature_template_suffix("{{ .Artifact }}"), None);
assert_eq!(signature_template_suffix("{{ .Artifact }} "), None);
assert_eq!(signature_template_suffix("{{ .Artifact }}."), None);
assert_eq!(signature_template_suffix("{{ .Artifact }}sig"), None);
assert_eq!(
signature_template_suffix("{{ .Artifact }}.{{ .Format }}"),
None
);
}
#[test]
fn signature_asset_suffixes_include_keyless_certificate_template() {
let cfg = Config {
signs: vec![SignConfig {
certificate: Some("{{ .Artifact }}.pem".to_string()),
..Default::default()
}],
..Default::default()
};
let suffixes = signature_asset_suffixes(&cfg);
assert!(
suffixes.contains(".pem"),
"a configured certificate: template must contribute its suffix: {suffixes:?}"
);
}
#[test]
fn is_signature_asset_matches_any_configured_suffix() {
let mut suffixes = BTreeSet::new();
suffixes.insert(".sig".to_string());
suffixes.insert(".cosign.bundle".to_string());
assert!(is_signature_asset("app.tar.gz.sig", &suffixes));
assert!(is_signature_asset("app.tar.gz.cosign.bundle", &suffixes));
assert!(!is_signature_asset("app.tar.gz", &suffixes));
}
}