fa835e234f
- Add `Sq::key_revoke`, and change the `sq key revoke` integration tests to use it.
332 lines
10 KiB
Rust
332 lines
10 KiB
Rust
use std::time::Duration;
|
|
|
|
use openpgp::parse::Parse;
|
|
use openpgp::types::ReasonForRevocation;
|
|
use openpgp::types::RevocationStatus;
|
|
use openpgp::types::SignatureType;
|
|
use openpgp::Cert;
|
|
use openpgp::Result;
|
|
use sequoia_openpgp as openpgp;
|
|
|
|
mod common;
|
|
use common::compare_notations;
|
|
use common::FileOrKeyHandle;
|
|
use common::Sq;
|
|
use common::STANDARD_POLICY;
|
|
|
|
#[test]
|
|
fn sq_key_revoke() -> Result<()> {
|
|
let sq = Sq::new();
|
|
|
|
let time = sq.now();
|
|
|
|
let (cert, cert_path, _cert_rev)
|
|
= sq.key_generate(&[], &["alice"]);
|
|
|
|
let message = "message";
|
|
|
|
// revoke for various reasons, with or without notations added, or
|
|
// with a revocation whose reference time is one hour after the
|
|
// creation of the certificate
|
|
for (reason, reason_str, notations, revocation_time) in [
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[][..],
|
|
None,
|
|
),
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::KeyRetired, "retired", &[][..], None),
|
|
(
|
|
ReasonForRevocation::KeyRetired,
|
|
"retired",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::KeySuperseded, "superseded", &[][..], None),
|
|
(
|
|
ReasonForRevocation::KeySuperseded,
|
|
"superseded",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::Unspecified, "unspecified", &[][..], None),
|
|
(
|
|
ReasonForRevocation::Unspecified,
|
|
"unspecified",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::Unspecified,
|
|
"unspecified",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
] {
|
|
eprintln!("==========================");
|
|
eprintln!("reason: {}, message: {}, notations: {:?}, time: {:?}",
|
|
reason, reason_str, notations, revocation_time);
|
|
|
|
for keystore in [false, true].into_iter() {
|
|
eprintln!("--------------------------");
|
|
eprintln!("keystore: {}", keystore);
|
|
|
|
let revocation = sq.scratch_file(Some(&format!(
|
|
"revocation_{}_{}_{}.rev",
|
|
reason_str,
|
|
if notations.is_empty() {
|
|
"no_notations"
|
|
} else {
|
|
"notations"
|
|
},
|
|
if revocation_time.is_some() {
|
|
"time"
|
|
} else {
|
|
"no_time"
|
|
}
|
|
)[..]));
|
|
|
|
if keystore {
|
|
// When using the keystore, we need to import the key.
|
|
sq.key_import(&cert_path);
|
|
}
|
|
|
|
let updated = sq.key_revoke(
|
|
if keystore {
|
|
FileOrKeyHandle::from(cert.key_handle())
|
|
} else {
|
|
FileOrKeyHandle::from(&cert_path)
|
|
},
|
|
None,
|
|
reason_str,
|
|
message,
|
|
None,
|
|
notations,
|
|
Some(revocation.as_path()));
|
|
|
|
if let RevocationStatus::Revoked(sigs)
|
|
= updated.revocation_status(STANDARD_POLICY, None)
|
|
{
|
|
assert_eq!(sigs.len(), 1);
|
|
let sig = sigs.into_iter().next().unwrap();
|
|
|
|
// the issuer is the certificate owner
|
|
assert_eq!(
|
|
sig.get_issuers().into_iter().next(),
|
|
Some(cert.key_handle())
|
|
);
|
|
|
|
let revoked_cert = cert.clone().insert_packets(sig.clone()).unwrap();
|
|
let status = revoked_cert
|
|
.revocation_status(
|
|
STANDARD_POLICY,
|
|
revocation_time.map(Into::into));
|
|
|
|
println!("{:?}", sig);
|
|
println!("{:?}", status);
|
|
// Verify the revocation.
|
|
assert!(matches!(status, RevocationStatus::Revoked(_)));
|
|
|
|
// it is a key revocation
|
|
assert_eq!(sig.typ(), SignatureType::KeyRevocation);
|
|
|
|
// our reason for revocation and message matches
|
|
assert_eq!(
|
|
sig.reason_for_revocation(),
|
|
Some((reason, message.as_bytes()))
|
|
);
|
|
|
|
// the notations of the revocation match the ones
|
|
// we passed in
|
|
compare_notations(sig, notations)?;
|
|
} else {
|
|
panic!("Not revoked");
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn sq_key_revoke_thirdparty() -> Result<()> {
|
|
let sq = Sq::new();
|
|
|
|
let time = sq.now();
|
|
|
|
let (cert, cert_path, _cert_rev)
|
|
= sq.key_generate(&[], &["alice"]);
|
|
|
|
let (thirdparty_cert, thirdparty_path, _cert_rev)
|
|
= sq.key_generate(&[], &["bob <bob@example.org>"]);
|
|
|
|
let thirdparty_valid_cert = thirdparty_cert
|
|
.with_policy(STANDARD_POLICY, Some(time.into()))?;
|
|
let thirdparty_fingerprint = &thirdparty_valid_cert.clone().fingerprint();
|
|
|
|
let message = "message";
|
|
|
|
// revoke for various reasons, with or without notations added, or with
|
|
// a revocation whose reference time is one hour after the creation of the
|
|
// certificate
|
|
for (reason, reason_str, notations, revocation_time) in [
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[][..],
|
|
None,
|
|
),
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::KeyCompromised,
|
|
"compromised",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::KeyRetired, "retired", &[][..], None),
|
|
(
|
|
ReasonForRevocation::KeyRetired,
|
|
"retired",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::KeyRetired,
|
|
"retired",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::KeySuperseded, "superseded", &[][..], None),
|
|
(
|
|
ReasonForRevocation::KeySuperseded,
|
|
"superseded",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::KeySuperseded,
|
|
"superseded",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
(ReasonForRevocation::Unspecified, "unspecified", &[][..], None),
|
|
(
|
|
ReasonForRevocation::Unspecified,
|
|
"unspecified",
|
|
&[][..],
|
|
Some(time + Duration::new(60 * 60, 0)),
|
|
),
|
|
(
|
|
ReasonForRevocation::Unspecified,
|
|
"unspecified",
|
|
&[("foo", "bar"), ("hallo@sequoia-pgp.org", "VALUE")][..],
|
|
None,
|
|
),
|
|
] {
|
|
for keystore in [false, true].into_iter() {
|
|
let revocation = sq.scratch_file(Some(&format!(
|
|
"revocation_{}_{}_{}.rev",
|
|
reason_str,
|
|
if ! notations.is_empty() {
|
|
"no_notations"
|
|
} else {
|
|
"notations"
|
|
},
|
|
if revocation_time.is_some() {
|
|
"time"
|
|
} else {
|
|
"no_time"
|
|
}
|
|
)[..]));
|
|
|
|
if keystore {
|
|
// When using the keystore, we need to import the key.
|
|
|
|
sq.cert_import(&cert_path);
|
|
sq.key_import(&thirdparty_path);
|
|
}
|
|
|
|
let revocation_cert = sq.key_revoke(
|
|
if keystore {
|
|
FileOrKeyHandle::from(cert.key_handle())
|
|
} else {
|
|
FileOrKeyHandle::from(&cert_path)
|
|
},
|
|
if keystore {
|
|
FileOrKeyHandle::from(thirdparty_cert.key_handle())
|
|
} else {
|
|
FileOrKeyHandle::from(&thirdparty_path)
|
|
},
|
|
reason_str,
|
|
message,
|
|
None,
|
|
notations,
|
|
Some(revocation.as_path()));
|
|
|
|
let revocation_cert = Cert::from_file(&revocation)?;
|
|
assert!(! revocation_cert.is_tsk());
|
|
|
|
// evaluate revocation status
|
|
let status = revocation_cert.revocation_status(
|
|
STANDARD_POLICY, revocation_time.map(Into::into));
|
|
if let RevocationStatus::CouldBe(sigs) = status {
|
|
// there is only one signature packet
|
|
assert_eq!(sigs.len(), 1);
|
|
let sig = sigs.into_iter().next().unwrap();
|
|
|
|
// it is a key revocation
|
|
assert_eq!(sig.typ(), SignatureType::KeyRevocation);
|
|
|
|
// the issuer is a thirdparty revoker
|
|
assert_eq!(
|
|
sig.get_issuers().into_iter().next().as_ref(),
|
|
Some(&thirdparty_fingerprint.clone().into())
|
|
);
|
|
|
|
// the revocation can be verified
|
|
if sig
|
|
.clone()
|
|
.verify_primary_key_revocation(
|
|
&thirdparty_cert.primary_key(),
|
|
&cert.primary_key(),
|
|
)
|
|
.is_err()
|
|
{
|
|
panic!("revocation is not valid")
|
|
}
|
|
|
|
// our reason for revocation and message matches
|
|
assert_eq!(
|
|
sig.reason_for_revocation(),
|
|
Some((reason, message.as_bytes()))
|
|
);
|
|
|
|
// the notations of the revocation match the ones
|
|
// we passed in
|
|
compare_notations(sig, notations)?;
|
|
} else {
|
|
panic!("there are no signatures in {:?}", status);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|