use std::fmt;
use std::time::{SystemTime, Duration};
use std::u32;
use anyhow::Context;
use crate::{
cert::prelude::*,
Error,
Packet,
packet::{
key,
Signature,
signature::subpacket::{
SubpacketTag,
SubpacketValue,
},
Tag,
},
Result,
types::{
AEADAlgorithm,
HashAlgorithm,
SignatureType,
SymmetricAlgorithm,
Timestamp,
},
};
#[macro_use] mod cutofflist;
use cutofflist::{
CutoffList,
REJECT,
ACCEPT,
};
pub trait Policy : fmt::Debug {
fn signature(&self, _sig: &Signature) -> Result<()> {
Ok(())
}
fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>)
-> Result<()>
{
Ok(())
}
fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> {
Ok(())
}
fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> {
Ok(())
}
fn packet(&self, _packet: &Packet) -> Result<()> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct StandardPolicy<'a> {
time: Option<Timestamp>,
hash_algos_normal: NormalHashCutoffList,
hash_algos_revocation: RevocationHashCutoffList,
critical_subpackets: SubpacketTagCutoffList,
good_critical_notations: &'a [&'a str],
packet_tags: PacketTagCutoffList,
symmetric_algos: SymmetricAlgorithmCutoffList,
aead_algos: AEADAlgorithmCutoffList,
asymmetric_algos: AsymmetricAlgorithmCutoffList,
}
impl<'a> Default for StandardPolicy<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> From<&'a StandardPolicy<'a>> for Option<&'a dyn Policy> {
fn from(p: &'a StandardPolicy<'a>) -> Self {
Some(p as &dyn Policy)
}
}
a_cutoff_list!(NormalHashCutoffList, HashAlgorithm, 12,
[
REJECT,
Some(Timestamp::Y1997),
Some(Timestamp::Y2013),
Some(Timestamp::Y2013),
REJECT,
REJECT,
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(RevocationHashCutoffList, HashAlgorithm, 12,
[
REJECT,
Some(Timestamp::Y2004),
Some(Timestamp::Y2020),
Some(Timestamp::Y2020),
REJECT,
REJECT,
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(SubpacketTagCutoffList, SubpacketTag, 36,
[
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
REJECT,
REJECT,
ACCEPT,
REJECT,
ACCEPT,
REJECT,
ACCEPT,
ACCEPT,
REJECT,
REJECT,
REJECT,
ACCEPT,
REJECT,
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(AsymmetricAlgorithmCutoffList, AsymmetricAlgorithm, 18,
[
Some(Timestamp::Y2014),
ACCEPT,
ACCEPT,
ACCEPT,
Some(Timestamp::Y2014),
ACCEPT,
ACCEPT,
ACCEPT,
Some(Timestamp::Y2014),
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(SymmetricAlgorithmCutoffList, SymmetricAlgorithm, 14,
[
REJECT,
ACCEPT,
Some(Timestamp::Y2017),
ACCEPT,
ACCEPT,
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(AEADAlgorithmCutoffList, AEADAlgorithm, 3,
[
REJECT,
ACCEPT,
ACCEPT,
]);
a_cutoff_list!(PacketTagCutoffList, Tag, 21,
[
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
Some(Timestamp::Y2004),
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
REJECT,
REJECT,
ACCEPT,
ACCEPT,
ACCEPT,
ACCEPT,
]);
fn system_time_cutoff_to_timestamp(t: SystemTime) -> Option<Timestamp> {
let t = t
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::new(0, 0));
let t = t.as_secs();
if t > u32::MAX as u64 {
None
} else {
Some((t as u32).into())
}
}
impl<'a> StandardPolicy<'a> {
pub const fn new() -> Self {
const EMPTY_LIST: &'static [&'static str] = &[];
Self {
time: None,
hash_algos_normal: NormalHashCutoffList::Default(),
hash_algos_revocation: RevocationHashCutoffList::Default(),
critical_subpackets: SubpacketTagCutoffList::Default(),
good_critical_notations: EMPTY_LIST,
asymmetric_algos: AsymmetricAlgorithmCutoffList::Default(),
symmetric_algos: SymmetricAlgorithmCutoffList::Default(),
aead_algos: AEADAlgorithmCutoffList::Default(),
packet_tags: PacketTagCutoffList::Default(),
}
}
pub fn at(time: SystemTime) -> Self {
let mut p = Self::new();
p.time = Some(system_time_cutoff_to_timestamp(time)
.unwrap_or(Timestamp::MAX));
p
}
pub fn time(&self) -> Option<SystemTime> {
self.time.map(Into::into)
}
pub fn accept_hash(&mut self, h: HashAlgorithm) {
self.hash_algos_normal.set(h, ACCEPT);
self.hash_algos_revocation.set(h, ACCEPT);
}
pub fn reject_hash(&mut self, h: HashAlgorithm) {
self.hash_algos_normal.set(h, REJECT);
self.hash_algos_revocation.set(h, REJECT);
}
pub fn reject_hash_at<N, R>(&mut self, h: HashAlgorithm,
normal: N, revocation: R)
where N: Into<Option<SystemTime>>,
R: Into<Option<SystemTime>>,
{
self.hash_algos_normal.set(
h,
normal.into().and_then(system_time_cutoff_to_timestamp));
self.hash_algos_revocation.set(
h,
revocation.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn hash_cutoffs(&self, h: HashAlgorithm)
-> (Option<SystemTime>, Option<SystemTime>)
{
(self.hash_algos_normal.cutoff(h).map(|t| t.into()),
self.hash_algos_revocation.cutoff(h).map(|t| t.into()))
}
pub fn accept_critical_subpacket(&mut self, s: SubpacketTag) {
self.critical_subpackets.set(s, ACCEPT);
}
pub fn reject_critical_subpacket(&mut self, s: SubpacketTag) {
self.critical_subpackets.set(s, REJECT);
}
pub fn reject_critical_subpacket_at<C>(&mut self, s: SubpacketTag,
cutoff: C)
where C: Into<Option<SystemTime>>,
{
self.critical_subpackets.set(
s,
cutoff.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn critical_subpacket_cutoff(&self, s: SubpacketTag)
-> Option<SystemTime> {
self.critical_subpackets.cutoff(s).map(|t| t.into())
}
pub fn good_critical_notations(&mut self, good_list: &'a [&'a str]) {
self.good_critical_notations = good_list;
}
pub fn accept_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) {
self.asymmetric_algos.set(a, ACCEPT);
}
pub fn reject_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) {
self.asymmetric_algos.set(a, REJECT);
}
pub fn reject_asymmetric_algo_at<C>(&mut self, a: AsymmetricAlgorithm,
cutoff: C)
where C: Into<Option<SystemTime>>,
{
self.asymmetric_algos.set(
a,
cutoff.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn asymmetric_algo_cutoff(&self, a: AsymmetricAlgorithm)
-> Option<SystemTime> {
self.asymmetric_algos.cutoff(a).map(|t| t.into())
}
pub fn accept_symmetric_algo(&mut self, s: SymmetricAlgorithm) {
self.symmetric_algos.set(s, ACCEPT);
}
pub fn reject_symmetric_algo(&mut self, s: SymmetricAlgorithm) {
self.symmetric_algos.set(s, REJECT);
}
pub fn reject_symmetric_algo_at<C>(&mut self, s: SymmetricAlgorithm,
cutoff: C)
where C: Into<Option<SystemTime>>,
{
self.symmetric_algos.set(
s,
cutoff.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn symmetric_algo_cutoff(&self, s: SymmetricAlgorithm)
-> Option<SystemTime> {
self.symmetric_algos.cutoff(s).map(|t| t.into())
}
pub fn accept_aead_algo(&mut self, a: AEADAlgorithm) {
self.aead_algos.set(a, ACCEPT);
}
pub fn reject_aead_algo(&mut self, a: AEADAlgorithm) {
self.aead_algos.set(a, REJECT);
}
pub fn reject_aead_algo_at<C>(&mut self, a: AEADAlgorithm,
cutoff: C)
where C: Into<Option<SystemTime>>,
{
self.aead_algos.set(
a,
cutoff.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn aead_algo_cutoff(&self, a: AEADAlgorithm)
-> Option<SystemTime> {
self.aead_algos.cutoff(a).map(|t| t.into())
}
pub fn accept_packet_tag(&mut self, tag: Tag) {
self.packet_tags.set(tag, ACCEPT);
}
pub fn reject_packet_tag(&mut self, tag: Tag) {
self.packet_tags.set(tag, REJECT);
}
pub fn reject_packet_tag_at<C>(&mut self, tag: Tag, cutoff: C)
where C: Into<Option<SystemTime>>,
{
self.packet_tags.set(
tag,
cutoff.into().and_then(system_time_cutoff_to_timestamp));
}
pub fn packet_tag_cutoff(&self, tag: Tag) -> Option<SystemTime> {
self.packet_tags.cutoff(tag).map(|t| t.into())
}
}
impl<'a> Policy for StandardPolicy<'a> {
fn signature(&self, sig: &Signature) -> Result<()> {
let time = self.time.unwrap_or_else(Timestamp::now);
match sig.typ() {
t @ SignatureType::KeyRevocation
| t @ SignatureType::SubkeyRevocation
| t @ SignatureType::CertificationRevocation =>
{
self.hash_algos_revocation.check(sig.hash_algo(), time)
.context(format!(
"Policy rejected revocation signature ({})", t))?
}
t =>
{
self.hash_algos_normal.check(sig.hash_algo(), time)
.context(format!(
"Policy rejected non-revocation signature ({})", t))?
}
}
for csp in sig.hashed_area().iter().filter(|sp| sp.critical()) {
self.critical_subpackets.check(csp.tag(), time)
.context("Policy rejected critical signature subpacket")?;
if let SubpacketValue::NotationData(n) = csp.value() {
if ! self.good_critical_notations.contains(&n.name()) {
return Err(Error::PolicyViolation(
format!("Policy rejected critical notation {:?}",
n.name()), None).into());
}
}
}
Ok(())
}
fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>)
-> Result<()>
{
use self::AsymmetricAlgorithm::{*, Unknown};
use crate::types::PublicKeyAlgorithm::*;
use crate::crypto::mpis::PublicKey;
#[allow(deprecated)]
let a = match (ka.pk_algo(), ka.mpis().bits()) {
(RSAEncryptSign, Some(b))
| (RSAEncrypt, Some(b))
| (RSASign, Some(b)) if b < 2048 => RSA1024,
(RSAEncryptSign, Some(b))
| (RSAEncrypt, Some(b))
| (RSASign, Some(b)) if b < 3072 => RSA2048,
(RSAEncryptSign, Some(b))
| (RSAEncrypt, Some(b))
| (RSASign, Some(b)) if b < 4096 => RSA3072,
(RSAEncryptSign, Some(_))
| (RSAEncrypt, Some(_))
| (RSASign, Some(_)) => RSA4096,
(RSAEncryptSign, None)
| (RSAEncrypt, None)
| (RSASign, None) => unreachable!(),
(ElGamalEncryptSign, Some(b))
| (ElGamalEncrypt, Some(b)) if b < 2048 => ElGamal1024,
(ElGamalEncryptSign, Some(b))
| (ElGamalEncrypt, Some(b)) if b < 3072 => ElGamal2048,
(ElGamalEncryptSign, Some(b))
| (ElGamalEncrypt, Some(b)) if b < 4096 => ElGamal3072,
(ElGamalEncryptSign, Some(_))
| (ElGamalEncrypt, Some(_)) => ElGamal4096,
(ElGamalEncryptSign, None)
| (ElGamalEncrypt, None) => unreachable!(),
(DSA, Some(b)) if b < 2048 => DSA1024,
(DSA, Some(b)) if b < 3072 => DSA2048,
(DSA, Some(b)) if b < 4096 => DSA3072,
(DSA, Some(_)) => DSA4096,
(DSA, None) => unreachable!(),
(ECDH, _) | (ECDSA, _) | (EdDSA, _) => {
let curve = match ka.mpis() {
PublicKey::EdDSA { curve, .. } => curve,
PublicKey::ECDSA { curve, .. } => curve,
PublicKey::ECDH { curve, .. } => curve,
_ => unreachable!(),
};
use crate::types::Curve;
match curve {
Curve::NistP256 => NistP256,
Curve::NistP384 => NistP384,
Curve::NistP521 => NistP521,
Curve::BrainpoolP256 => BrainpoolP256,
Curve::BrainpoolP512 => BrainpoolP512,
Curve::Ed25519 => Cv25519,
Curve::Cv25519 => Cv25519,
Curve::Unknown(_) => Unknown,
Curve::__Nonexhaustive => unreachable!(),
}
},
_ => Unknown,
};
let time = self.time.unwrap_or_else(Timestamp::now);
self.asymmetric_algos.check(a, time)
.context("Policy rejected encryption algorithm")
}
fn packet(&self, packet: &Packet) -> Result<()> {
let time = self.time.unwrap_or_else(Timestamp::now);
self.packet_tags.check(packet.tag(), time)
.context("Policy rejected packet type")
}
fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()> {
let time = self.time.unwrap_or_else(Timestamp::now);
self.symmetric_algos.check(algo, time)
.context("Policy rejected symmetric encryption algorithm")
}
fn aead_algorithm(&self, algo: AEADAlgorithm) -> Result<()> {
let time = self.time.unwrap_or_else(Timestamp::now);
self.aead_algos.check(algo, time)
.context("Policy rejected authenticated encryption algorithm")
}
}
#[derive(Clone, Debug)]
pub enum AsymmetricAlgorithm {
RSA1024,
RSA2048,
RSA3072,
RSA4096,
ElGamal1024,
ElGamal2048,
ElGamal3072,
ElGamal4096,
DSA1024,
DSA2048,
DSA3072,
DSA4096,
NistP256,
NistP384,
NistP521,
BrainpoolP256,
BrainpoolP512,
Cv25519,
Unknown,
#[doc(hidden)] __Nonexhaustive,
}
impl std::fmt::Display for AsymmetricAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl From<AsymmetricAlgorithm> for u8 {
fn from(a: AsymmetricAlgorithm) -> Self {
use self::AsymmetricAlgorithm::*;
match a {
RSA1024 => 0,
RSA2048 => 1,
RSA3072 => 2,
RSA4096 => 3,
ElGamal1024 => 4,
ElGamal2048 => 5,
ElGamal3072 => 6,
ElGamal4096 => 7,
DSA1024 => 8,
DSA2048 => 9,
DSA3072 => 10,
DSA4096 => 11,
NistP256 => 12,
NistP384 => 13,
NistP521 => 14,
BrainpoolP256 => 15,
BrainpoolP512 => 16,
Cv25519 => 17,
Unknown => 255,
__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Debug)]
pub struct NullPolicy {
}
impl NullPolicy {
pub const fn new() -> Self {
NullPolicy {}
}
}
impl Policy for NullPolicy {
}
#[cfg(test)]
mod test {
use std::io::Read;
use std::time::Duration;
use super::*;
use crate::Error;
use crate::Fingerprint;
use crate::crypto::SessionKey;
use crate::packet::key::Key4;
use crate::packet::signature;
use crate::packet::{PKESK, SKESK};
use crate::parse::Parse;
use crate::parse::stream::DecryptionHelper;
use crate::parse::stream::Decryptor;
use crate::parse::stream::DetachedVerifier;
use crate::parse::stream::MessageLayer;
use crate::parse::stream::MessageStructure;
use crate::parse::stream::VerificationHelper;
use crate::parse::stream::Verifier;
use crate::policy::StandardPolicy as P;
use crate::types::Curve;
use crate::types::KeyFlags;
use crate::types::SymmetricAlgorithm;
const _A_STANDARD_POLICY: StandardPolicy = StandardPolicy::new();
#[test]
fn binding_signature() {
let p = &P::new();
let (cert, _) = CertBuilder::new()
.add_signing_subkey()
.add_transport_encryption_subkey()
.generate().unwrap();
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
#[derive(Debug)]
struct NoDirectKeySigs;
impl Policy for NoDirectKeySigs {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
DirectKey => Err(anyhow::anyhow!("direct key!")),
_ => Ok(()),
}
}
}
let p = &NoDirectKeySigs {};
assert_eq!(cert.keys().with_policy(p, None).count(), 0);
#[derive(Debug)]
struct NoSubkeySigs;
impl Policy for NoSubkeySigs {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
SubkeyBinding => Err(anyhow::anyhow!("subkey signature!")),
_ => Ok(()),
}
}
}
let p = &NoSubkeySigs {};
assert_eq!(cert.keys().with_policy(p, None).count(), 1);
}
#[test]
fn revocation() -> Result<()> {
use crate::cert::prelude::*;
use crate::types::SignatureType;
use crate::types::ReasonForRevocation;
let p = &P::new();
let (cert, _) = CertBuilder::new()
.add_userid("Alice")
.add_signing_subkey()
.add_transport_encryption_subkey()
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
assert_eq!(cert.userids().with_policy(p, None).count(), 1);
#[derive(Debug)]
struct NoPositiveCertifications;
impl Policy for NoPositiveCertifications {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
PositiveCertification =>
Err(anyhow::anyhow!("positive certification!")),
_ => Ok(()),
}
}
}
let p = &NoPositiveCertifications {};
assert_eq!(cert.userids().with_policy(p, None).count(), 0);
let mut keypair = cert.primary_key().key().clone()
.parts_into_secret()?.into_keypair()?;
let ca = cert.userids().nth(0).unwrap();
let revocation =
UserIDRevocationBuilder::new()
.set_reason_for_revocation(
ReasonForRevocation::KeyRetired,
b"Left example.org.")?
.build(&mut keypair, &cert, ca.userid(), None)?;
assert_eq!(revocation.typ(), SignatureType::CertificationRevocation);
let cert = cert.merge_packets(vec![revocation.clone().into()])?;
assert_eq!(cert.userids().with_policy(p, None).revoked(false).count(), 0);
#[derive(Debug)]
struct NoCertificationRevocation;
impl Policy for NoCertificationRevocation {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
CertificationRevocation =>
Err(anyhow::anyhow!("certification certification!")),
_ => Ok(()),
}
}
}
let p = &NoCertificationRevocation {};
assert_eq!(cert.userids().with_policy(p, None).revoked(false).count(), 1);
let subkey = cert.keys().subkeys().nth(0).unwrap();
let revocation =
SubkeyRevocationBuilder::new()
.set_reason_for_revocation(
ReasonForRevocation::KeyRetired,
b"Smells funny.").unwrap()
.build(&mut keypair, &cert, subkey.key(), None)?;
assert_eq!(revocation.typ(), SignatureType::SubkeyRevocation);
assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 3);
let cert = cert.merge_packets(vec![revocation.clone().into()])?;
assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 2);
#[derive(Debug)]
struct NoSubkeyRevocation;
impl Policy for NoSubkeyRevocation {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
SubkeyRevocation =>
Err(anyhow::anyhow!("subkey revocation!")),
_ => Ok(()),
}
}
}
let p = &NoSubkeyRevocation {};
assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 3);
Ok(())
}
#[test]
fn binary_signature() {
#[derive(PartialEq, Debug)]
struct VHelper {
good: usize,
errors: usize,
keys: Vec<Cert>,
}
impl VHelper {
fn new(keys: Vec<Cert>) -> Self {
VHelper {
good: 0,
errors: 0,
keys,
}
}
}
impl VerificationHelper for VHelper {
fn get_public_keys(&mut self, _ids: &[crate::KeyHandle])
-> Result<Vec<Cert>>
{
Ok(self.keys.clone())
}
fn check(&mut self, structure: MessageStructure) -> Result<()>
{
for layer in structure.iter() {
match layer {
MessageLayer::SignatureGroup { ref results } =>
for result in results {
eprintln!("result: {:?}", result);
match result {
Ok(_) => self.good += 1,
Err(_) => self.errors += 1,
}
}
MessageLayer::Compression { .. } => (),
_ => unreachable!(),
}
}
Ok(())
}
}
impl DecryptionHelper for VHelper {
fn decrypt<D>(&mut self, _: &[PKESK], _: &[SKESK],
_: Option<SymmetricAlgorithm>,_: D)
-> Result<Option<Fingerprint>>
where D: FnMut(SymmetricAlgorithm, &SessionKey) -> Result<()>
{
unreachable!();
}
}
#[derive(Debug)]
struct NoBinarySigantures;
impl Policy for NoBinarySigantures {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
eprintln!("{:?}", sig.typ());
match sig.typ() {
Binary =>
Err(anyhow::anyhow!("binary!")),
_ => Ok(()),
}
}
}
let no_binary_signatures = &NoBinarySigantures {};
#[derive(Debug)]
struct NoSubkeySigs;
impl Policy for NoSubkeySigs {
fn signature(&self, sig: &Signature) -> Result<()> {
use crate::types::SignatureType::*;
match sig.typ() {
SubkeyBinding => Err(anyhow::anyhow!("subkey signature!")),
_ => Ok(()),
}
}
}
let no_subkey_signatures = &NoSubkeySigs {};
let standard = &P::new();
let keys = [
"neal.pgp",
].iter()
.map(|f| Cert::from_bytes(crate::tests::key(f)).unwrap())
.collect::<Vec<_>>();
let data = "messages/signed-1.gpg";
let reference = crate::tests::manifesto();
let h = VHelper::new(keys.clone());
let mut v =
match Verifier::from_bytes(standard, crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 1);
assert_eq!(v.helper_ref().errors, 0);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
let h = VHelper::new(keys.clone());
let mut v = match Verifier::from_bytes(no_subkey_signatures,
crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 0);
assert_eq!(v.helper_ref().errors, 1);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
let h = VHelper::new(keys.clone());
let mut v =
match Verifier::from_bytes(no_binary_signatures,
crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 0);
assert_eq!(v.helper_ref().errors, 1);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
let h = VHelper::new(keys.clone());
let mut v =
match Decryptor::from_bytes(standard, crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 1);
assert_eq!(v.helper_ref().errors, 0);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
let h = VHelper::new(keys.clone());
let mut v = match Decryptor::from_bytes(no_subkey_signatures,
crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 0);
assert_eq!(v.helper_ref().errors, 1);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
let h = VHelper::new(keys.clone());
let mut v =
match Decryptor::from_bytes(no_binary_signatures,
crate::tests::file(data), h,
crate::frozen_time()) {
Ok(v) => v,
Err(e) => panic!("{}", e),
};
assert!(v.message_processed());
assert_eq!(v.helper_ref().good, 0);
assert_eq!(v.helper_ref().errors, 1);
let mut content = Vec::new();
v.read_to_end(&mut content).unwrap();
assert_eq!(reference.len(), content.len());
assert_eq!(reference, &content[..]);
}
#[test]
fn hash_algo() -> Result<()> {
use crate::types::RevocationStatus;
use crate::types::ReasonForRevocation;
const SECS_IN_YEAR : u64 = 365 * 24 * 60 * 60;
const DEFAULT : StandardPolicy = StandardPolicy::new();
let (cert, _) = CertBuilder::new()
.add_userid("Alice")
.generate()?;
let algo = cert.primary_key()
.binding_signature(&DEFAULT, None).unwrap().hash_algo();
eprintln!("{:?}", algo);
let mut keypair = cert.primary_key().key().clone()
.parts_into_secret()?.into_keypair()?;
let cert_revoked = cert.clone().revoke_in_place(
&mut keypair,
ReasonForRevocation::KeyCompromised,
b"It was the maid :/")?;
match cert_revoked.revoked(&DEFAULT, None) {
RevocationStatus::Revoked(sigs) => {
assert_eq!(sigs.len(), 1);
assert_eq!(sigs[0].hash_algo(), algo);
}
_ => panic!("not revoked"),
}
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash(algo);
assert!(cert.primary_key()
.binding_signature(&reject, None).is_err());
assert_match!(RevocationStatus::NotAsFarAsWeKnow
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash_at(
algo,
SystemTime::now() + Duration::from_secs(SECS_IN_YEAR),
SystemTime::now() + Duration::from_secs(SECS_IN_YEAR));
cert.primary_key().binding_signature(&reject, None)?;
assert_match!(RevocationStatus::Revoked(_)
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash_at(
algo,
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR),
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR));
assert!(cert.primary_key()
.binding_signature(&reject, None).is_err());
assert_match!(RevocationStatus::NotAsFarAsWeKnow
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash_at(
algo,
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR),
SystemTime::now() + Duration::from_secs(SECS_IN_YEAR));
assert!(cert.primary_key()
.binding_signature(&reject, None).is_err());
assert_match!(RevocationStatus::Revoked(_)
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
let algo_u8 : u8 = algo.into();
assert!(algo_u8 != 0u8);
reject.reject_hash_at(
(algo_u8 - 1).into(),
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR),
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR));
reject.reject_hash_at(
(algo_u8 + 1).into(),
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR),
SystemTime::now() - Duration::from_secs(SECS_IN_YEAR));
cert.primary_key().binding_signature(&reject, None)?;
assert_match!(RevocationStatus::Revoked(_)
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash_at(
algo,
SystemTime::UNIX_EPOCH - Duration::from_secs(SECS_IN_YEAR),
SystemTime::UNIX_EPOCH - Duration::from_secs(SECS_IN_YEAR));
assert!(cert.primary_key()
.binding_signature(&reject, None).is_err());
assert_match!(RevocationStatus::NotAsFarAsWeKnow
= cert_revoked.revoked(&reject, None));
let mut reject : StandardPolicy = StandardPolicy::new();
reject.reject_hash_at(
algo,
SystemTime::UNIX_EPOCH + Duration::from_secs(500 * SECS_IN_YEAR),
SystemTime::UNIX_EPOCH + Duration::from_secs(500 * SECS_IN_YEAR));
cert.primary_key().binding_signature(&reject, None)?;
assert_match!(RevocationStatus::Revoked(_)
= cert_revoked.revoked(&reject, None));
Ok(())
}
#[test]
fn key_verify_self_signature() -> Result<()> {
let p = &P::new();
#[derive(Debug)]
struct NoRsa;
impl Policy for NoRsa {
fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>)
-> Result<()>
{
use crate::types::PublicKeyAlgorithm::*;
eprintln!("algo: {}", ka.key().pk_algo());
if ka.key().pk_algo() == RSAEncryptSign {
Err(anyhow::anyhow!("RSA!"))
} else {
Ok(())
}
}
}
let norsa = &NoRsa {};
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::RSA4k)
.add_signing_subkey()
.add_signing_subkey()
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 0);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_err());
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_signing_subkey()
.generate()?;
let pk = cert.primary_key().key().parts_as_secret()?;
let subkey: key::SecretSubkey
= Key4::generate_rsa(4096)?.into();
let binding = signature::Builder::new(SignatureType::SubkeyBinding)
.set_key_flags(&KeyFlags::default().set_transport_encryption(true))?
.set_issuer_fingerprint(cert.fingerprint())?
.set_issuer(cert.keyid())?
.sign_subkey_binding(&mut pk.clone().into_keypair()?,
&pk, &subkey)?;
let cert = cert.merge_packets(vec![ subkey.into(), binding.into() ])?;
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 2);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_ok());
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::RSA4k)
.add_signing_subkey()
.generate()?;
let pk = cert.primary_key().key().parts_as_secret()?;
let subkey: key::SecretSubkey
= key::Key4::generate_ecc(true, Curve::Ed25519)?.into();
let binding = signature::Builder::new(SignatureType::SubkeyBinding)
.set_key_flags(&KeyFlags::default().set_transport_encryption(true))?
.set_issuer_fingerprint(cert.fingerprint())?
.set_issuer(cert.keyid())?
.sign_subkey_binding(&mut pk.clone().into_keypair()?,
&pk, &subkey)?;
let cert = cert.merge_packets(vec![ subkey.into(), binding.into() ])?;
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 0);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_err());
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_signing_subkey()
.add_signing_subkey()
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 3);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 3);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_ok());
Ok(())
}
#[test]
fn key_verify_binary_signature() -> Result<()> {
use crate::packet::signature;
use crate::serialize::Serialize;
use crate::Packet;
use crate::types::KeyFlags;
let p = &P::new();
#[derive(Debug)]
struct NoRsa;
impl Policy for NoRsa {
fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>)
-> Result<()>
{
use crate::types::PublicKeyAlgorithm::*;
eprintln!("algo: {} is {}",
ka.fingerprint(), ka.key().pk_algo());
if ka.key().pk_algo() == RSAEncryptSign {
Err(anyhow::anyhow!("RSA!"))
} else {
Ok(())
}
}
}
let norsa = &NoRsa {};
#[derive(PartialEq, Debug)]
struct VHelper {
good: usize,
errors: usize,
keys: Vec<Cert>,
}
impl VHelper {
fn new(keys: Vec<Cert>) -> Self {
VHelper {
good: 0,
errors: 0,
keys,
}
}
}
impl VerificationHelper for VHelper {
fn get_public_keys(&mut self, _ids: &[crate::KeyHandle])
-> Result<Vec<Cert>>
{
Ok(self.keys.clone())
}
fn check(&mut self, structure: MessageStructure) -> Result<()>
{
for layer in structure.iter() {
match layer {
MessageLayer::SignatureGroup { ref results } =>
for result in results {
match result {
Ok(_) => self.good += 1,
Err(_) => self.errors += 1,
}
}
MessageLayer::Compression { .. } => (),
_ => unreachable!(),
}
}
Ok(())
}
}
impl DecryptionHelper for VHelper {
fn decrypt<D>(&mut self, _: &[PKESK], _: &[SKESK],
_: Option<SymmetricAlgorithm>,_: D)
-> Result<Option<Fingerprint>>
where D: FnMut(SymmetricAlgorithm, &SessionKey) -> Result<()>
{
unreachable!();
}
}
fn sign_and_verify(p: &dyn Policy, cert: &Cert, good: bool) {
eprintln!("Expect verification to be {}",
if good { "good" } else { "bad" });
for (i, k) in cert.keys().enumerate() {
eprintln!(" {}. {}", i, k.fingerprint());
}
let msg = b"Hello, World";
let key = cert.keys().nth(1).unwrap().key();
let mut keypair = key.clone()
.parts_into_secret().unwrap()
.into_keypair().unwrap();
let sig = signature::Builder::new(SignatureType::Binary)
.set_signature_creation_time(
std::time::SystemTime::now()).unwrap()
.set_issuer_fingerprint(key.fingerprint()).unwrap()
.set_issuer(key.keyid()).unwrap()
.sign_message(&mut keypair, msg).unwrap();
sig.verify_message(key, msg).unwrap();
let sig = {
let mut v = Vec::new();
let sig : Packet = sig.into();
sig.serialize(&mut v).unwrap();
v
};
let h = VHelper::new(vec![ cert.clone() ]);
let mut v = DetachedVerifier::from_bytes(p, &sig, h, None).unwrap();
v.verify_bytes(msg).unwrap();
assert_eq!(v.helper_ref().good, if good { 1 } else { 0 });
assert_eq!(v.helper_ref().errors, if good { 0 } else { 1 });
}
eprintln!("Trying ECC primary, ECC sub:");
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_subkey(KeyFlags::default().set_signing(true), None,
None)
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 2);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 2);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_ok());
sign_and_verify(p, &cert, true);
sign_and_verify(norsa, &cert, true);
eprintln!("Trying RSA primary, ECC sub:");
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::RSA4k)
.add_subkey(KeyFlags::default().set_signing(true), None,
CipherSuite::Cv25519)
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 2);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 0);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_err());
sign_and_verify(p, &cert, true);
sign_and_verify(norsa, &cert, false);
eprintln!("Trying ECC primary, RSA sub:");
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_subkey(KeyFlags::default().set_signing(true), None,
CipherSuite::RSA4k)
.generate()?;
assert_eq!(cert.keys().with_policy(p, None).count(), 2);
assert_eq!(cert.keys().with_policy(norsa, None).count(), 1);
assert!(cert.primary_key().with_policy(p, None).is_ok());
assert!(cert.primary_key().with_policy(norsa, None).is_ok());
sign_and_verify(p, &cert, true);
sign_and_verify(norsa, &cert, false);
Ok(())
}
#[test]
fn reject_seip_packet() {
#[derive(PartialEq, Debug)]
struct Helper {}
impl VerificationHelper for Helper {
fn get_public_keys(&mut self, _: &[crate::KeyHandle])
-> Result<Vec<Cert>> {
unreachable!()
}
fn check(&mut self, _: MessageStructure) -> Result<()> {
unreachable!()
}
}
impl DecryptionHelper for Helper {
fn decrypt<D>(&mut self, _: &[PKESK], _: &[SKESK],
_: Option<SymmetricAlgorithm>, _: D)
-> Result<Option<Fingerprint>>
where D: FnMut(SymmetricAlgorithm, &SessionKey) -> Result<()> {
Ok(None)
}
}
let p = &P::new();
let r = Decryptor::from_bytes(
p, crate::tests::message("encrypted-to-testy.gpg"),
Helper {}, crate::frozen_time());
match r {
Ok(_) => panic!(),
Err(e) => assert_match!(Error::MissingSessionKey(_)
= e.downcast().unwrap()),
}
let p = &mut P::new();
p.reject_packet_tag(Tag::SEIP);
let r = Decryptor::from_bytes(
p, crate::tests::message("encrypted-to-testy.gpg"),
Helper {}, crate::frozen_time());
match r {
Ok(_) => panic!(),
Err(e) => assert_match!(Error::PolicyViolation(_, _)
= e.downcast().unwrap()),
}
}
#[test]
fn reject_cipher() {
struct Helper {}
impl VerificationHelper for Helper {
fn get_public_keys(&mut self, _: &[crate::KeyHandle])
-> Result<Vec<Cert>> {
Ok(Default::default())
}
fn check(&mut self, _: MessageStructure) -> Result<()> {
Ok(())
}
}
impl DecryptionHelper for Helper {
fn decrypt<D>(&mut self, pkesks: &[PKESK], _: &[SKESK],
algo: Option<SymmetricAlgorithm>, mut decrypt: D)
-> Result<Option<Fingerprint>>
where D: FnMut(SymmetricAlgorithm, &SessionKey) -> Result<()>
{
let p = &P::new();
let mut pair = Cert::from_bytes(
crate::tests::key("testy-private.pgp"))?
.keys().with_policy(p, None)
.for_transport_encryption().secret().nth(0).unwrap()
.key().clone().into_keypair()?;
pkesks[0].decrypt(&mut pair, algo)
.and_then(|(algo, session_key)| decrypt(algo, &session_key))
.map(|_| None)
}
}
let p = &P::new();
Decryptor::from_bytes(
p, crate::tests::message("encrypted-to-testy.gpg"),
Helper {}, crate::frozen_time()).unwrap();
let p = &mut P::new();
p.reject_symmetric_algo(SymmetricAlgorithm::AES256);
let r = Decryptor::from_bytes(
p, crate::tests::message("encrypted-to-testy.gpg"),
Helper {}, crate::frozen_time());
match r {
Ok(_) => panic!(),
Err(e) => assert_match!(Error::PolicyViolation(_, _)
= e.downcast().unwrap()),
}
}
#[test]
fn reject_asymmetric_algos() -> Result<()> {
let cert = Cert::from_bytes(crate::tests::key("neal.pgp"))?;
let p = &mut P::new();
let t = crate::frozen_time();
assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 4);
p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA1024);
assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 4);
p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA2048);
assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 1);
Ok(())
}
}