use crate::declare_detector;
use crate::diagnostic::{Action, Diagnostic, FilesystemAction, WatchAction};
use crate::watch::COMMON_PGPSIGURL_MANGLES;
use crate::{Certainty, FixerError, FixerPreferences, LintianIssue, Visibility};
use debian_watch::{mangle, Release};
use debian_workspace::Workspace;
use sequoia_openpgp as openpgp;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
const NUM_KEYS_TO_CHECK: usize = 5;
const RELEASES_TO_INSPECT: usize = 5;
#[derive(Debug)]
enum VerificationStatus {
Unverified,
Verified,
Failed,
}
#[derive(Debug)]
struct SignatureInfo {
verification_status: VerificationStatus,
keys: HashSet<String>,
mangle: Option<String>,
}
fn verify_signature(
sig_data: &[u8],
data: &[u8],
keyring_data: &[u8],
) -> Result<bool, Box<dyn std::error::Error>> {
use openpgp::parse::Parse;
use openpgp::policy::StandardPolicy;
let policy = StandardPolicy::new();
let cert_parser = openpgp::cert::CertParser::from_bytes(keyring_data)?;
let certs: Vec<_> = cert_parser.filter_map(|r| r.ok()).collect();
if certs.is_empty() {
return Err("No valid certificates in keyring".into());
}
let packets = openpgp::PacketPile::from_bytes(sig_data)?;
for cert in &certs {
for packet in packets.descendants() {
let sig = match packet {
openpgp::Packet::Signature(sig) => sig,
_ => continue,
};
for key_amalg in cert.keys().with_policy(&policy, None) {
let key_handle = key_amalg.key();
let key_fingerprint = key_handle.fingerprint();
let is_issuer = sig.issuer_fingerprints().any(|fp| fp == &key_fingerprint);
if !is_issuer {
continue;
}
match sig.clone().verify_message(key_handle, data) {
Ok(_) => {
tracing::debug!(
"Signature verified successfully with key {}",
key_fingerprint
);
return Ok(true);
}
Err(e) => {
tracing::debug!("Signature verification failed: {}", e);
}
}
}
}
}
Ok(false)
}
fn probe_signature(
release: &Release,
pgpsigurlmangle: Option<&str>,
keyring_data: &[u8],
) -> Result<Option<SignatureInfo>, Box<dyn std::error::Error>> {
let mangles: Vec<&str> = if let Some(mangle) = pgpsigurlmangle {
vec![mangle]
} else {
COMMON_PGPSIGURL_MANGLES.to_vec()
};
for mangle in mangles {
let sig_url = if let Some(ref pgpsigurl) = release.pgpsigurl {
pgpsigurl.clone()
} else {
match mangle::apply_mangle(mangle, &release.url) {
Ok(url) => url,
Err(e) => {
tracing::debug!(
"Failed to apply mangle '{}' to '{}': {}",
mangle,
release.url,
e
);
continue;
}
}
};
tracing::debug!(
"Trying signature URL: {} (from release URL: {})",
sig_url,
release.url
);
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()?;
let sig_response = match client.get(&sig_url).send() {
Ok(resp) if resp.status().is_success() => {
tracing::debug!("Successfully downloaded signature from {}", sig_url);
resp
}
Ok(resp) => {
tracing::debug!(
"Signature URL {} returned status {}",
sig_url,
resp.status()
);
continue;
}
Err(e) => {
tracing::debug!("Failed to fetch signature from {}: {}", sig_url, e);
continue;
}
};
let sig_data = sig_response.bytes()?;
let release_data = match release.download_blocking() {
Ok(data) => {
tracing::debug!("Downloaded release tarball ({} bytes)", data.len());
data
}
Err(e) => {
tracing::debug!("Failed to download release: {}", e);
continue;
}
};
use openpgp::parse::Parse;
let packets = match openpgp::PacketPile::from_bytes(&sig_data) {
Ok(packets) => packets,
Err(e) => {
tracing::debug!("Failed to parse signature packets: {}", e);
continue;
}
};
let mut fingerprints = Vec::new();
for packet in packets.descendants() {
if let openpgp::Packet::Signature(sig) = packet {
if let Some(fp) = sig.issuer_fingerprints().next() {
let fp_hex = fp.to_hex();
tracing::debug!("Found issuer fingerprint in signature: {}", fp_hex);
fingerprints.push(fp_hex);
}
}
}
if fingerprints.is_empty() {
tracing::debug!("No fingerprints found in signature");
continue;
}
let mut keys = HashSet::new();
for fp in &fingerprints {
keys.insert(fp.clone());
}
let verification_status = if !keyring_data.is_empty() {
match verify_signature(&sig_data, &release_data, keyring_data) {
Ok(true) => {
tracing::debug!("Signature verification succeeded");
VerificationStatus::Verified
}
Ok(false) => {
tracing::debug!(
"Signature verification failed - signature does not match keyring"
);
VerificationStatus::Failed
}
Err(e) => {
tracing::debug!("Error during signature verification: {}", e);
VerificationStatus::Unverified
}
}
} else {
tracing::debug!("No keyring available, discovery mode");
VerificationStatus::Unverified
};
tracing::debug!(
"Found signature with {} key(s), status={:?}",
keys.len(),
verification_status
);
return Ok(Some(SignatureInfo {
verification_status,
keys,
mangle: Some(mangle.to_string()),
}));
}
Ok(None)
}
fn analyze_mangles(used_mangles: &[Option<String>]) -> (HashSet<Option<String>>, HashSet<String>) {
let found_common_mangles: HashSet<Option<String>> =
used_mangles.iter().take(5).cloned().collect();
let active_common_mangles: HashSet<String> = found_common_mangles
.iter()
.filter_map(|x| x.as_ref().cloned())
.collect();
(found_common_mangles, active_common_mangles)
}
fn determine_pgpmode(
found_common_mangles: &HashSet<Option<String>>,
) -> (debian_watch::PgpMode, String) {
if found_common_mangles.len() == 1 {
(
debian_watch::PgpMode::Mangle,
"Check upstream PGP signatures.".to_string(),
)
} else {
(
debian_watch::PgpMode::Auto,
"Opportunistically check upstream PGP signatures.".to_string(),
)
}
}
fn export_cert_armored(cert: &openpgp::Cert) -> Result<Vec<u8>, String> {
use openpgp::serialize::Serialize;
let mut key_output = Vec::new();
{
let mut writer =
openpgp::armor::Writer::new(&mut key_output, openpgp::armor::Kind::PublicKey)
.map_err(|e| format!("Failed to create armor writer: {}", e))?;
cert.serialize(&mut writer)
.map_err(|e| format!("Failed to serialize certificate: {}", e))?;
writer
.finalize()
.map_err(|e| format!("Failed to finalize armor: {}", e))?;
}
Ok(key_output)
}
pub fn detect(
ws: &dyn Workspace,
preferences: &FixerPreferences,
) -> Result<Vec<Diagnostic>, FixerError> {
let package = ws.package().unwrap_or("").to_string();
tracing::debug!("Running pubkey detect for package {}", package);
let watch_rel = PathBuf::from("debian/watch");
let watch_file = match ws.parsed_watch() {
Ok(w) => w,
Err(debian_workspace::Error::NotFound) => {
tracing::debug!("No debian/watch file found");
return Ok(Vec::new());
}
Err(e) => {
return Err(FixerError::Other(format!(
"Failed to parse debian/watch: {}",
e
)))
}
};
if !preferences.net_access.unwrap_or(false) {
tracing::debug!("Network access not enabled, skipping");
return Ok(Vec::new());
}
let (has_keys, keyring_data): (bool, Vec<u8>) = {
let mut found = None;
for path in &[
"debian/upstream/signing-key.asc",
"debian/upstream/signing-key.pgp",
] {
if let Some(data) = ws.read_file(Path::new(path))? {
tracing::debug!("Loaded existing keyring from {}", path);
found = Some(data);
break;
}
}
match found {
Some(d) => (true, d.into_owned()),
None => (false, Vec::new()),
}
};
let mut needed_keys: HashSet<String> = HashSet::new();
let mut description: Option<String> = None;
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let mut watch_actions: Vec<Action> = Vec::new();
for entry in watch_file.entries() {
let pgpsigurlmangle = entry.get_option("pgpsigurlmangle");
if pgpsigurlmangle.is_some() && has_keys {
tracing::debug!("Entry already has pgpsigurlmangle and keys, skipping");
continue;
}
let pgpmode = entry
.get_option("pgpmode")
.unwrap_or_else(|| "default".to_string());
if entry.get_option("pgpmode").is_some() && preferences.diligence.unwrap_or(0) == 0 {
tracing::debug!("pgpmode already set and diligence=0, skipping");
continue;
}
if matches!(pgpmode.as_str(), "gittag" | "previous" | "next" | "self") {
tracing::debug!("Unsupported pgpmode: {}, skipping", pgpmode);
return Ok(Vec::new());
}
tracing::debug!("Discovering releases for package {}", package);
let releases = match entry.discover_blocking(|| package.to_string()) {
Ok(mut rels) => {
rels.sort_by(|a, b| b.cmp(a)); tracing::debug!("Found {} releases", rels.len());
rels
}
Err(e) => {
if matches!(e, debian_watch::discover::DiscoveryError::HttpError(_)) {
tracing::debug!("HTTP error accessing discovery URL: {}", e);
return Ok(Vec::new());
}
return Err(FixerError::Other(format!(
"Failed to discover releases: {}",
e
)));
}
};
let mut verification_statuses = Vec::new();
let mut used_mangles: Vec<Option<String>> = Vec::new();
let mut has_verification_failure = false;
tracing::debug!(
"Checking signatures for up to {} releases",
RELEASES_TO_INSPECT
);
for release in releases.iter().take(RELEASES_TO_INSPECT) {
tracing::debug!("Probing signature for release {}", release.version);
match probe_signature(release, pgpsigurlmangle.as_deref(), &keyring_data) {
Ok(Some(sig_info)) => {
tracing::debug!(
"Found signature with mangle: {:?}, status: {:?}",
sig_info.mangle,
sig_info.verification_status
);
if matches!(sig_info.verification_status, VerificationStatus::Failed) {
has_verification_failure = true;
}
verification_statuses.push(sig_info.verification_status);
used_mangles.push(sig_info.mangle.clone());
needed_keys.extend(sig_info.keys);
}
Ok(None) => {
tracing::debug!("No signature found for release {}", release.version);
used_mangles.push(None);
}
Err(e) => {
tracing::debug!("Error probing signature: {}", e);
used_mangles.push(None);
}
}
}
if has_keys && has_verification_failure {
tracing::warn!(
"Signatures do not match existing keyring at debian/upstream/signing-key.*. \
Not updating watch file or fetching different keys. \
If upstream changed their signing key, manually update the keyring."
);
return Ok(Vec::new());
}
let successful_probes = verification_statuses.len();
if successful_probes < NUM_KEYS_TO_CHECK.min(releases.len()) {
tracing::debug!(
"Not enough signatures found ({} < {}), skipping",
successful_probes,
NUM_KEYS_TO_CHECK
);
return Ok(Vec::new());
}
let (found_common_mangles, active_common_mangles) = analyze_mangles(&used_mangles);
tracing::debug!(
"Found {} common mangles, {} active",
found_common_mangles.len(),
active_common_mangles.len()
);
if pgpsigurlmangle.is_none() && !active_common_mangles.is_empty() {
let entry_url = entry.url();
let mut entry_actions: Vec<Action> = Vec::new();
if active_common_mangles.len() == 1 {
let new_mangle = active_common_mangles.iter().next().unwrap().clone();
tracing::debug!("Setting pgpsigurlmangle to: {}", new_mangle);
entry_actions.push(Action::Watch(WatchAction::SetEntryOption {
file: watch_rel.clone(),
url: entry_url.clone(),
option: "pgpsigurlmangle".into(),
value: new_mangle,
}));
}
let (pgpmode_value, mut desc) = determine_pgpmode(&found_common_mangles);
tracing::debug!("Setting pgpmode to: {:?}", pgpmode_value);
entry_actions.push(Action::Watch(WatchAction::SetEntryOption {
file: watch_rel.clone(),
url: entry_url,
option: "pgpmode".into(),
value: pgpmode_value.to_string(),
}));
if !needed_keys.is_empty() {
let fingerprints: Vec<String> = needed_keys.iter().cloned().collect();
desc = format!(
"{} ({})",
desc.trim_end_matches('.'),
fingerprints.join(", ")
);
}
description = Some(desc.clone());
let issue = LintianIssue::source_with_info(
"debian-watch-does-not-check-openpgp-signature",
Visibility::Pedantic,
vec!["[debian/watch]".to_string()],
);
watch_actions.extend(entry_actions.iter().cloned());
diagnostics.push(
Diagnostic::with_actions(
issue,
"debian/watch does not check the OpenPGP signature.",
desc,
entry_actions,
)
.with_certainty(Certainty::Certain),
);
}
}
let _ = watch_actions;
if !has_keys && !needed_keys.is_empty() {
tracing::debug!("Need to fetch {} keys", needed_keys.len());
let mut keyfile_content = Vec::new();
let keys_vec: Vec<String> = needed_keys.iter().cloned().collect();
let keyserver = preferences
.extra_env
.as_ref()
.and_then(|e| e.get("KEYSERVER").cloned())
.unwrap_or_else(|| "https://keys.openpgp.org".to_string());
let mut fetch_failed = false;
for fingerprint in &keys_vec {
tracing::debug!("Fetching key with fingerprint: {}", fingerprint);
let url = format!("{}/vks/v1/by-fingerprint/{}", keyserver, fingerprint);
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| FixerError::Other(format!("Failed to build HTTP client: {}", e)))?;
let response = match client.get(&url).send() {
Ok(resp) if resp.status().is_success() => resp,
Ok(resp) => {
tracing::debug!(
"Keyserver returned status {} for key {}",
resp.status(),
fingerprint
);
fetch_failed = true;
break;
}
Err(e) => {
tracing::debug!("Failed to fetch key {}: {}", fingerprint, e);
fetch_failed = true;
break;
}
};
let key_data = response
.bytes()
.map_err(|e| FixerError::Other(format!("Failed to read key data: {}", e)))?;
use openpgp::parse::Parse;
let cert = openpgp::Cert::from_reader(std::io::Cursor::new(&key_data[..]))
.map_err(|e| FixerError::Other(format!("Failed to parse certificate: {}", e)))?;
let key_output = export_cert_armored(&cert).map_err(FixerError::Other)?;
keyfile_content.extend_from_slice(&key_output);
keyfile_content.push(b'\n');
}
if !fetch_failed && !keyfile_content.is_empty() {
let issue = LintianIssue::source_with_info(
"debian-watch-file-pubkey-file-is-missing",
Visibility::Error,
vec!["[debian/watch]".to_string()],
);
let key_desc = format!(
"Add upstream signing keys ({}).",
needed_keys.iter().cloned().collect::<Vec<_>>().join(", ")
);
if description.is_none() {
description = Some(key_desc.clone());
}
diagnostics.push(
Diagnostic::with_actions(
issue,
"debian/watch references signing keys that are not present.",
key_desc,
vec![Action::Filesystem(FilesystemAction::Write {
file: PathBuf::from("debian/upstream/signing-key.asc"),
content: keyfile_content,
})],
)
.with_certainty(Certainty::Certain),
);
}
}
let _ = description; Ok(diagnostics)
}
declare_detector! {
name: "pubkey",
tags: [
"debian-watch-does-not-check-openpgp-signature",
"debian-watch-file-pubkey-file-is-missing"
],
triggers: [
debian_workspace::Trigger::Watch(debian_workspace::WatchAspect::Source),
debian_workspace::Trigger::Watch(debian_workspace::WatchAspect::Option(
"pgpsigurlmangle",
)),
debian_workspace::Trigger::Watch(debian_workspace::WatchAspect::Option(
"pgpmode",
)),
debian_workspace::Trigger::File("debian/upstream/signing-key.asc"),
debian_workspace::Trigger::File("debian/upstream/signing-key.pgp"),
],
cost: crate::detector::DetectorCost::Network,
detect: |ws, prefs| detect(ws, prefs),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_common_mangles() {
assert!(COMMON_PGPSIGURL_MANGLES.contains(&"s/$/.asc/"));
assert!(COMMON_PGPSIGURL_MANGLES.contains(&"s/$/.sig/"));
assert!(COMMON_PGPSIGURL_MANGLES.contains(&"s/$/.gpg/"));
assert_eq!(COMMON_PGPSIGURL_MANGLES.len(), 5);
}
#[test]
fn test_analyze_mangles_all_same() {
let mangles = vec![
Some("s/$/.asc/".to_string()),
Some("s/$/.asc/".to_string()),
Some("s/$/.asc/".to_string()),
];
let (found, active) = analyze_mangles(&mangles);
assert_eq!(found.len(), 1);
assert!(found.contains(&Some("s/$/.asc/".to_string())));
assert_eq!(active.len(), 1);
assert!(active.contains("s/$/.asc/"));
}
#[test]
fn test_analyze_mangles_mixed() {
let mangles = vec![
Some("s/$/.asc/".to_string()),
None,
Some("s/$/.asc/".to_string()),
];
let (found, active) = analyze_mangles(&mangles);
assert_eq!(found.len(), 2); assert!(found.contains(&Some("s/$/.asc/".to_string())));
assert!(found.contains(&None));
assert_eq!(active.len(), 1); assert!(active.contains("s/$/.asc/"));
}
#[test]
fn test_analyze_mangles_all_none() {
let mangles = vec![None, None, None];
let (found, active) = analyze_mangles(&mangles);
assert_eq!(found.len(), 1);
assert!(found.contains(&None));
assert_eq!(active.len(), 0);
}
#[test]
fn test_analyze_mangles_different_mangles() {
let mangles = vec![
Some("s/$/.asc/".to_string()),
Some("s/$/.sig/".to_string()),
Some("s/$/.asc/".to_string()),
];
let (found, active) = analyze_mangles(&mangles);
assert_eq!(found.len(), 2);
assert_eq!(active.len(), 2);
assert!(active.contains("s/$/.asc/"));
assert!(active.contains("s/$/.sig/"));
}
#[test]
fn test_determine_pgpmode_all_signed() {
let mut mangles = HashSet::new();
mangles.insert(Some("s/$/.asc/".to_string()));
let (mode, desc) = determine_pgpmode(&mangles);
assert_eq!(mode, debian_watch::PgpMode::Mangle);
assert_eq!(desc, "Check upstream PGP signatures.");
}
#[test]
fn test_determine_pgpmode_mixed() {
let mut mangles = HashSet::new();
mangles.insert(Some("s/$/.asc/".to_string()));
mangles.insert(None);
let (mode, desc) = determine_pgpmode(&mangles);
assert_eq!(mode, debian_watch::PgpMode::Auto);
assert_eq!(desc, "Opportunistically check upstream PGP signatures.");
}
#[test]
fn test_determine_pgpmode_multiple_mangles() {
let mut mangles = HashSet::new();
mangles.insert(Some("s/$/.asc/".to_string()));
mangles.insert(Some("s/$/.sig/".to_string()));
let (mode, desc) = determine_pgpmode(&mangles);
assert_eq!(mode, debian_watch::PgpMode::Auto);
assert_eq!(desc, "Opportunistically check upstream PGP signatures.");
}
#[test]
fn test_verify_signature_with_empty_keyring() {
let sig_data = b"fake signature data";
let data = b"fake release data";
let keyring_data = b"";
let result = verify_signature(sig_data, data, keyring_data);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No valid certificates"));
}
#[test]
fn test_verify_signature_with_invalid_keyring() {
let sig_data = b"fake signature data";
let data = b"fake release data";
let keyring_data = b"not a valid keyring";
let result = verify_signature(sig_data, data, keyring_data);
assert!(result.is_err());
}
#[test]
fn test_export_cert_armored_with_test_key() {
use openpgp::cert::CertBuilder;
let (cert, _) = CertBuilder::new()
.add_userid("Test User <test@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate");
let result = export_cert_armored(&cert);
assert!(result.is_ok());
let exported = result.unwrap();
let exported_str = String::from_utf8_lossy(&exported);
assert!(exported_str.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----"));
assert!(exported_str.contains("-----END PGP PUBLIC KEY BLOCK-----"));
}
#[test]
fn test_verify_signature_roundtrip() {
use openpgp::cert::CertBuilder;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::*;
let policy = StandardPolicy::new();
let (cert, _) = CertBuilder::new()
.add_userid("Test User <test@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate");
let keypair = cert
.keys()
.with_policy(&policy, None)
.alive()
.revoked(false)
.for_signing()
.secret()
.next()
.expect("No signing key found")
.key()
.clone()
.into_keypair()
.expect("Failed to convert to keypair");
let data = b"Hello, world!";
let mut sig_data = Vec::new();
{
let message = Message::new(&mut sig_data);
let signer = Signer::new(message, keypair)
.expect("Failed to create signer")
.detached()
.build()
.expect("Failed to build signer");
let mut writer = signer;
std::io::copy(&mut std::io::Cursor::new(data), &mut writer)
.expect("Failed to write data");
writer.finalize().expect("Failed to finalize signature");
}
let keyring_data = export_cert_armored(&cert).expect("Failed to export cert");
let result = verify_signature(&sig_data, data, &keyring_data);
assert_eq!(result.unwrap(), true);
}
#[test]
fn test_verify_signature_wrong_data() {
use openpgp::cert::CertBuilder;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::*;
let policy = StandardPolicy::new();
let (cert, _) = CertBuilder::new()
.add_userid("Test User <test@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate");
let keypair = cert
.keys()
.with_policy(&policy, None)
.alive()
.revoked(false)
.for_signing()
.secret()
.next()
.expect("No signing key found")
.key()
.clone()
.into_keypair()
.expect("Failed to convert to keypair");
let data = b"Hello, world!";
let mut sig_data = Vec::new();
{
let message = Message::new(&mut sig_data);
let signer = Signer::new(message, keypair)
.expect("Failed to create signer")
.detached()
.build()
.expect("Failed to build signer");
let mut writer = signer;
std::io::copy(&mut std::io::Cursor::new(data), &mut writer)
.expect("Failed to write data");
writer.finalize().expect("Failed to finalize signature");
}
let keyring_data = export_cert_armored(&cert).expect("Failed to export cert");
let wrong_data = b"Different data!";
let result = verify_signature(&sig_data, wrong_data, &keyring_data);
assert_eq!(result.unwrap(), false);
}
#[test]
fn test_verification_status_unverified_when_no_keyring() {
use openpgp::cert::CertBuilder;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::*;
let policy = StandardPolicy::new();
let (cert, _) = CertBuilder::new()
.add_userid("Test User <test@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate");
let keypair = cert
.keys()
.with_policy(&policy, None)
.alive()
.revoked(false)
.for_signing()
.secret()
.next()
.expect("No signing key found")
.key()
.clone()
.into_keypair()
.expect("Failed to convert to keypair");
let data = b"Hello, world!";
let mut sig_data = Vec::new();
{
let message = Message::new(&mut sig_data);
let signer = Signer::new(message, keypair)
.expect("Failed to create signer")
.detached()
.build()
.expect("Failed to build signer");
let mut writer = signer;
std::io::copy(&mut std::io::Cursor::new(data), &mut writer)
.expect("Failed to write data");
writer.finalize().expect("Failed to finalize signature");
}
let empty_keyring = b"";
let result = verify_signature(&sig_data, data, empty_keyring);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No valid certificates"));
}
#[test]
fn test_verification_status_verified_with_correct_keyring() {
use openpgp::cert::CertBuilder;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::*;
let policy = StandardPolicy::new();
let (cert, _) = CertBuilder::new()
.add_userid("Test User <test@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate");
let keypair = cert
.keys()
.with_policy(&policy, None)
.alive()
.revoked(false)
.for_signing()
.secret()
.next()
.expect("No signing key found")
.key()
.clone()
.into_keypair()
.expect("Failed to convert to keypair");
let data = b"Hello, world!";
let mut sig_data = Vec::new();
{
let message = Message::new(&mut sig_data);
let signer = Signer::new(message, keypair)
.expect("Failed to create signer")
.detached()
.build()
.expect("Failed to build signer");
let mut writer = signer;
std::io::copy(&mut std::io::Cursor::new(data), &mut writer)
.expect("Failed to write data");
writer.finalize().expect("Failed to finalize signature");
}
let keyring_data = export_cert_armored(&cert).expect("Failed to export cert");
let result = verify_signature(&sig_data, data, &keyring_data);
assert!(result.unwrap());
}
#[test]
fn test_verification_status_failed_with_wrong_keyring() {
use openpgp::cert::CertBuilder;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::stream::*;
let policy = StandardPolicy::new();
let (cert1, _) = CertBuilder::new()
.add_userid("Test User 1 <test1@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate 1");
let (cert2, _) = CertBuilder::new()
.add_userid("Test User 2 <test2@example.com>")
.add_signing_subkey()
.generate()
.expect("Failed to generate test certificate 2");
let keypair1 = cert1
.keys()
.with_policy(&policy, None)
.alive()
.revoked(false)
.for_signing()
.secret()
.next()
.expect("No signing key found")
.key()
.clone()
.into_keypair()
.expect("Failed to convert to keypair");
let data = b"Hello, world!";
let mut sig_data = Vec::new();
{
let message = Message::new(&mut sig_data);
let signer = Signer::new(message, keypair1)
.expect("Failed to create signer")
.detached()
.build()
.expect("Failed to build signer");
let mut writer = signer;
std::io::copy(&mut std::io::Cursor::new(data), &mut writer)
.expect("Failed to write data");
writer.finalize().expect("Failed to finalize signature");
}
let wrong_keyring = export_cert_armored(&cert2).expect("Failed to export cert");
let result = verify_signature(&sig_data, data, &wrong_keyring);
assert!(!result.unwrap());
}
}