use std::time;
use std::marker::PhantomData;
use crate::packet;
use crate::packet::{
Key,
key::Key4,
key::Key6,
key::UnspecifiedRole,
key::SecretKey as KeySecretKey,
key::SecretParts as KeySecretParts,
};
use crate::Profile;
use crate::Result;
use crate::packet::Signature;
use crate::packet::signature::{
self,
SignatureBuilder,
subpacket::SubpacketTag,
};
use crate::cert::prelude::*;
use crate::Error;
use crate::crypto::{Password, Signer};
use crate::types::{
Features,
HashAlgorithm,
KeyFlags,
SignatureType,
SymmetricAlgorithm,
RevocationKey,
};
mod key;
pub use key::{
KeyBuilder,
SubkeyBuilder,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum CipherSuite {
Cv25519,
Cv448,
RSA3k,
P256,
P384,
P521,
RSA2k,
RSA4k,
}
assert_send_and_sync!(CipherSuite);
impl Default for CipherSuite {
fn default() -> Self {
CipherSuite::Cv25519
}
}
impl CipherSuite {
pub fn variants() -> impl Iterator<Item=CipherSuite> {
use CipherSuite::*;
[ Cv25519, Cv448, RSA3k, P256, P384, P521, RSA2k, RSA4k ]
.into_iter()
}
pub fn is_supported(&self) -> Result<()> {
use crate::types::{Curve, PublicKeyAlgorithm};
use CipherSuite::*;
macro_rules! check_pk {
($pk: expr) => {
if ! $pk.is_supported() {
return Err(Error::UnsupportedPublicKeyAlgorithm($pk)
.into());
}
}
}
macro_rules! check_curve {
($curve: expr) => {
if ! $curve.is_supported() {
return Err(Error::UnsupportedEllipticCurve($curve)
.into());
}
}
}
match self {
Cv25519 => {
check_pk!(PublicKeyAlgorithm::EdDSA);
check_curve!(Curve::Ed25519);
check_pk!(PublicKeyAlgorithm::ECDH);
check_curve!(Curve::Cv25519);
},
Cv448 => {
check_pk!(PublicKeyAlgorithm::X448);
check_pk!(PublicKeyAlgorithm::Ed448);
},
RSA2k | RSA3k | RSA4k => {
check_pk!(PublicKeyAlgorithm::RSAEncryptSign);
},
P256 => {
check_pk!(PublicKeyAlgorithm::ECDSA);
check_curve!(Curve::NistP256);
check_pk!(PublicKeyAlgorithm::ECDH);
},
P384 => {
check_pk!(PublicKeyAlgorithm::ECDSA);
check_curve!(Curve::NistP384);
check_pk!(PublicKeyAlgorithm::ECDH);
},
P521 => {
check_pk!(PublicKeyAlgorithm::ECDSA);
check_curve!(Curve::NistP521);
check_pk!(PublicKeyAlgorithm::ECDH);
},
}
Ok(())
}
fn generate_key<K>(self, flags: K, profile: Profile)
-> Result<Key<KeySecretParts, UnspecifiedRole>>
where K: AsRef<KeyFlags>,
{
match profile {
Profile::RFC9580 => Ok(self.generate_v6_key(flags)?.into()),
Profile::RFC4880 => Ok(self.generate_v4_key(flags)?.into()),
}
}
fn generate_v4_key<K>(self, flags: K)
-> Result<Key4<KeySecretParts, UnspecifiedRole>>
where K: AsRef<KeyFlags>,
{
use crate::types::Curve;
match self {
CipherSuite::RSA2k =>
Key4::generate_rsa(2048),
CipherSuite::RSA3k =>
Key4::generate_rsa(3072),
CipherSuite::RSA4k =>
Key4::generate_rsa(4096),
CipherSuite::Cv448 => {
let flags = flags.as_ref();
let sign = flags.for_certification() || flags.for_signing()
|| flags.for_authentication();
let encrypt = flags.for_transport_encryption()
|| flags.for_storage_encryption();
match (sign, encrypt) {
(true, false) => Key4::generate_ed448(),
(false, true) => Key4::generate_x448(),
(true, true) =>
Err(Error::InvalidOperation(
"Can't use key for encryption and signing".into())
.into()),
(false, false) =>
Err(Error::InvalidOperation(
"No key flags set".into())
.into()),
}
}
CipherSuite::Cv25519 | CipherSuite::P256 |
CipherSuite::P384 | CipherSuite::P521 => {
let flags = flags.as_ref();
let sign = flags.for_certification() || flags.for_signing()
|| flags.for_authentication();
let encrypt = flags.for_transport_encryption()
|| flags.for_storage_encryption();
let curve = match self {
CipherSuite::Cv25519 if sign => Curve::Ed25519,
CipherSuite::Cv25519 if encrypt => Curve::Cv25519,
CipherSuite::Cv25519 => {
return Err(Error::InvalidOperation(
"No key flags set".into())
.into());
}
CipherSuite::P256 => Curve::NistP256,
CipherSuite::P384 => Curve::NistP384,
CipherSuite::P521 => Curve::NistP521,
_ => unreachable!(),
};
match (sign, encrypt) {
(true, false) => Key4::generate_ecc(true, curve),
(false, true) => Key4::generate_ecc(false, curve),
(true, true) =>
Err(Error::InvalidOperation(
"Can't use key for encryption and signing".into())
.into()),
(false, false) =>
Err(Error::InvalidOperation(
"No key flags set".into())
.into()),
}
},
}
}
fn generate_v6_key<K>(self, flags: K)
-> Result<Key6<KeySecretParts, UnspecifiedRole>>
where K: AsRef<KeyFlags>,
{
use crate::types::Curve;
let flags = flags.as_ref();
let sign = flags.for_certification() || flags.for_signing()
|| flags.for_authentication();
let encrypt = flags.for_transport_encryption()
|| flags.for_storage_encryption();
match self {
CipherSuite::Cv25519 => match (sign, encrypt) {
(true, false) => Key6::generate_ed25519(),
(false, true) => Key6::generate_x25519(),
(true, true) =>
Err(Error::InvalidOperation(
"Can't use key for encryption and signing".into())
.into()),
(false, false) =>
Err(Error::InvalidOperation(
"No key flags set".into())
.into()),
},
CipherSuite::RSA2k =>
Key6::generate_rsa(2048),
CipherSuite::RSA3k =>
Key6::generate_rsa(3072),
CipherSuite::RSA4k =>
Key6::generate_rsa(4096),
CipherSuite::Cv448 => match (sign, encrypt) {
(true, false) => Key6::generate_ed448(),
(false, true) => Key6::generate_x448(),
(true, true) =>
Err(Error::InvalidOperation(
"Can't use key for encryption and signing".into())
.into()),
(false, false) =>
Err(Error::InvalidOperation(
"No key flags set".into())
.into()),
},
CipherSuite::P256 | CipherSuite::P384 | CipherSuite::P521 => {
let curve = match self {
CipherSuite::Cv25519 if sign => Curve::Ed25519,
CipherSuite::Cv25519 if encrypt => Curve::Cv25519,
CipherSuite::Cv25519 => {
return Err(Error::InvalidOperation(
"No key flags set".into())
.into());
}
CipherSuite::P256 => Curve::NistP256,
CipherSuite::P384 => Curve::NistP384,
CipherSuite::P521 => Curve::NistP521,
_ => unreachable!(),
};
match (sign, encrypt) {
(true, false) => Key6::generate_ecc(true, curve),
(false, true) => Key6::generate_ecc(false, curve),
(true, true) =>
Err(Error::InvalidOperation(
"Can't use key for encryption and signing".into())
.into()),
(false, false) =>
Err(Error::InvalidOperation(
"No key flags set".into())
.into()),
}
},
}
}
}
#[derive(Clone, Debug)]
pub struct KeyBlueprint {
flags: KeyFlags,
validity: Option<time::Duration>,
ciphersuite: Option<CipherSuite>,
}
assert_send_and_sync!(KeyBlueprint);
pub struct CertBuilder<'a> {
creation_time: Option<std::time::SystemTime>,
ciphersuite: CipherSuite,
profile: Profile,
features: Features,
primary: KeyBlueprint,
subkeys: Vec<(Option<SignatureBuilder>, KeyBlueprint)>,
userids: Vec<(Option<SignatureBuilder>, packet::UserID)>,
user_attributes: Vec<(Option<SignatureBuilder>, packet::UserAttribute)>,
password: Option<Password>,
revocation_keys: Option<Vec<RevocationKey>>,
exportable: bool,
phantom: PhantomData<&'a ()>,
}
assert_send_and_sync!(CertBuilder<'_>);
impl CertBuilder<'_> {
pub fn new() -> Self {
CertBuilder {
creation_time: None,
ciphersuite: CipherSuite::default(),
profile: Default::default(),
features: Features::sequoia(),
primary: KeyBlueprint{
flags: KeyFlags::empty().set_certification(),
validity: None,
ciphersuite: None,
},
subkeys: vec![],
userids: vec![],
user_attributes: vec![],
password: None,
revocation_keys: None,
exportable: true,
phantom: PhantomData,
}
}
pub fn general_purpose<U>(userids: U) -> Self
where
U: IntoIterator,
U::Item: Into<packet::UserID>,
{
let mut builder = Self::new()
.set_primary_key_flags(KeyFlags::empty().set_certification())
.set_validity_period(
time::Duration::new(3 * 52 * 7 * 24 * 60 * 60, 0))
.add_signing_subkey()
.add_subkey(KeyFlags::empty()
.set_transport_encryption()
.set_storage_encryption(), None, None);
for u in userids {
builder = builder.add_userid(u.into());
}
builder
}
pub fn set_creation_time<T>(mut self, creation_time: T) -> Self
where T: Into<Option<std::time::SystemTime>>,
{
self.creation_time = creation_time.into();
self
}
pub fn creation_time(&self) -> Option<std::time::SystemTime>
{
self.creation_time
}
pub fn set_cipher_suite(mut self, cs: CipherSuite) -> Self {
self.ciphersuite = cs;
self
}
pub fn set_exportable(mut self, exportable: bool) -> Self {
self.exportable = exportable;
self
}
pub fn set_profile(mut self, profile: Profile) -> Result<Self> {
self.profile = profile;
Ok(self)
}
pub fn set_features(mut self, features: Features) -> Result<Self> {
self.features = features;
Ok(self)
}
pub fn add_userid<U>(mut self, uid: U) -> Self
where U: Into<packet::UserID>
{
self.userids.push((None, uid.into()));
self
}
pub fn add_userid_with<U, B>(mut self, uid: U, builder: B)
-> Result<Self>
where U: Into<packet::UserID>,
B: Into<SignatureBuilder>,
{
let builder = builder.into();
match builder.typ() {
SignatureType::GenericCertification
| SignatureType::PersonaCertification
| SignatureType::CasualCertification
| SignatureType::PositiveCertification =>
{
self.userids.push((Some(builder), uid.into()));
Ok(self)
},
t =>
Err(Error::InvalidArgument(format!(
"Signature type is not a certification: {}", t)).into()),
}
}
pub fn add_user_attribute<U>(mut self, ua: U) -> Self
where U: Into<packet::UserAttribute>
{
self.user_attributes.push((None, ua.into()));
self
}
pub fn add_user_attribute_with<U, B>(mut self, ua: U, builder: B)
-> Result<Self>
where U: Into<packet::UserAttribute>,
B: Into<SignatureBuilder>,
{
let builder = builder.into();
match builder.typ() {
SignatureType::GenericCertification
| SignatureType::PersonaCertification
| SignatureType::CasualCertification
| SignatureType::PositiveCertification =>
{
self.user_attributes.push((Some(builder), ua.into()));
Ok(self)
},
t =>
Err(Error::InvalidArgument(format!(
"Signature type is not a certification: {}", t)).into()),
}
}
pub fn add_signing_subkey(self) -> Self {
self.add_subkey(KeyFlags::empty().set_signing(), None, None)
}
pub fn add_transport_encryption_subkey(self) -> Self {
self.add_subkey(KeyFlags::empty().set_transport_encryption(),
None, None)
}
pub fn add_storage_encryption_subkey(self) -> Self {
self.add_subkey(KeyFlags::empty().set_storage_encryption(),
None, None)
}
pub fn add_certification_subkey(self) -> Self {
self.add_subkey(KeyFlags::empty().set_certification(), None, None)
}
pub fn add_authentication_subkey(self) -> Self {
self.add_subkey(KeyFlags::empty().set_authentication(), None, None)
}
pub fn add_subkey<T, C>(mut self, flags: KeyFlags, validity: T, cs: C)
-> Self
where T: Into<Option<time::Duration>>,
C: Into<Option<CipherSuite>>,
{
self.subkeys.push((None, KeyBlueprint {
flags,
validity: validity.into(),
ciphersuite: cs.into(),
}));
self
}
pub fn add_subkey_with<T, C, B>(mut self, flags: KeyFlags, validity: T,
cs: C, builder: B) -> Result<Self>
where T: Into<Option<time::Duration>>,
C: Into<Option<CipherSuite>>,
B: Into<SignatureBuilder>,
{
let builder = builder.into();
match builder.typ() {
SignatureType::SubkeyBinding => {
self.subkeys.push((Some(builder), KeyBlueprint {
flags,
validity: validity.into(),
ciphersuite: cs.into(),
}));
Ok(self)
},
t =>
Err(Error::InvalidArgument(format!(
"Signature type is not a subkey binding: {}", t)).into()),
}
}
pub fn set_primary_key_flags(mut self, flags: KeyFlags) -> Self {
self.primary.flags = flags;
self
}
pub fn set_password(mut self, password: Option<Password>) -> Self {
self.password = password;
self
}
pub fn set_validity_period<T>(mut self, validity: T) -> Self
where T: Into<Option<time::Duration>>
{
self.primary.validity = validity.into();
self
}
pub fn set_revocation_keys(mut self, revocation_keys: Vec<RevocationKey>)
-> Self
{
self.revocation_keys = Some(revocation_keys);
self
}
pub fn generate(mut self) -> Result<(Cert, Signature)> {
use crate::Packet;
use crate::types::ReasonForRevocation;
let creation_time =
self.creation_time.unwrap_or_else(|| {
use crate::packet::signature::SIG_BACKDATE_BY;
crate::now() -
time::Duration::new(SIG_BACKDATE_BY, 0)
});
let (primary, sig, mut signer) = self.primary_key(creation_time)?;
let cert = Cert::try_from(vec![
Packet::SecretKey({
let mut primary = primary.clone();
if let Some(ref password) = self.password {
let (k, mut secret) = primary.take_secret();
secret.encrypt_in_place(&k, password)?;
primary = k.add_secret(secret).0;
}
primary
}),
])?;
let mut acc = vec![
Packet::from(sig),
];
let have_primary_user_thing = {
let is_primary = |osig: &Option<SignatureBuilder>| -> bool {
osig.as_ref().and_then(|s| s.primary_userid()).unwrap_or(false)
};
self.userids.iter().map(|(s, _)| s).any(is_primary)
|| self.user_attributes.iter().map(|(s, _)| s).any(is_primary)
};
let mut emitted_primary_user_thing = false;
for (template, uid) in std::mem::take(&mut self.userids).into_iter() {
let sig = template.unwrap_or_else(
|| SignatureBuilder::new(SignatureType::PositiveCertification));
let sig = Self::signature_common(sig, creation_time,
self.exportable)?;
let mut sig = self.add_primary_key_metadata(sig)?;
if emitted_primary_user_thing {
sig = sig.modify_hashed_area(|mut a| {
a.remove_all(SubpacketTag::PrimaryUserID);
Ok(a)
})?;
} else if have_primary_user_thing {
emitted_primary_user_thing |=
sig.primary_userid().unwrap_or(false);
} else {
sig = sig.set_primary_userid(true)?;
emitted_primary_user_thing = true;
}
let signature = uid.bind(&mut signer, &cert, sig)?;
acc.push(uid.into());
acc.push(signature.into());
}
for (template, ua) in std::mem::take(&mut self.user_attributes) {
let sig = template.unwrap_or_else(
|| SignatureBuilder::new(SignatureType::PositiveCertification));
let sig = Self::signature_common(
sig, creation_time, self.exportable)?;
let mut sig = self.add_primary_key_metadata(sig)?;
if emitted_primary_user_thing {
sig = sig.modify_hashed_area(|mut a| {
a.remove_all(SubpacketTag::PrimaryUserID);
Ok(a)
})?;
} else if have_primary_user_thing {
emitted_primary_user_thing |=
sig.primary_userid().unwrap_or(false);
} else {
sig = sig.set_primary_userid(true)?;
emitted_primary_user_thing = true;
}
let signature = ua.bind(&mut signer, &cert, sig)?;
acc.push(ua.into());
acc.push(signature.into());
}
for (template, blueprint) in self.subkeys {
let flags = &blueprint.flags;
let mut subkey = blueprint.ciphersuite
.unwrap_or(self.ciphersuite)
.generate_key(flags, self.profile)?
.role_into_subordinate();
subkey.set_creation_time(creation_time)?;
let sig = template.unwrap_or_else(
|| SignatureBuilder::new(SignatureType::SubkeyBinding));
let sig = Self::signature_common(
sig, creation_time, self.exportable)?;
let mut builder = sig
.set_key_flags(flags.clone())?
.set_key_validity_period(blueprint.validity.or(self.primary.validity))?;
if flags.for_certification() || flags.for_signing()
|| flags.for_authentication()
{
let mut subkey_signer = subkey.clone().into_keypair().unwrap();
let backsig =
signature::SignatureBuilder::new(SignatureType::PrimaryKeyBinding)
.set_signature_creation_time(creation_time)?
.set_hash_algo(HashAlgorithm::SHA512)
.sign_primary_key_binding(&mut subkey_signer, &primary,
&subkey)?;
builder = builder.set_embedded_signature(backsig)?;
}
let signature = subkey.bind(&mut signer, &cert, builder)?;
if let Some(ref password) = self.password {
let (k, mut secret) = subkey.take_secret();
secret.encrypt_in_place(&k, password)?;
subkey = k.add_secret(secret).0;
}
acc.push(subkey.into());
acc.push(signature.into());
}
let cert = cert.insert_packets(acc)?.0;
let revocation = CertRevocationBuilder::new()
.set_signature_creation_time(creation_time)?
.set_reason_for_revocation(
ReasonForRevocation::Unspecified, b"Unspecified")?
.build(&mut signer, &cert, None)?;
assert!(cert.bad.is_empty());
assert!(cert.unknowns.is_empty());
Ok((cert, revocation))
}
fn primary_key(&self, creation_time: std::time::SystemTime)
-> Result<(KeySecretKey, Signature, Box<dyn Signer>)>
{
let mut key = self.primary.ciphersuite
.unwrap_or(self.ciphersuite)
.generate_key(KeyFlags::empty().set_certification(),
self.profile)?
.role_into_primary();
key.set_creation_time(creation_time)?;
let sig = SignatureBuilder::new(SignatureType::DirectKey);
let sig = Self::signature_common(
sig, creation_time, self.exportable)?;
let mut sig = self.add_primary_key_metadata(sig)?;
if let Some(ref revocation_keys) = self.revocation_keys {
for k in revocation_keys.into_iter().cloned() {
sig = sig.add_revocation_key(k)?;
}
}
let mut signer = key.clone().into_keypair()
.expect("key generated above has a secret");
let sig = sig.sign_direct_key(&mut signer, key.parts_as_public())?;
Ok((key, sig, Box::new(signer)))
}
fn signature_common(builder: SignatureBuilder,
creation_time: time::SystemTime,
exportable: bool)
-> Result<SignatureBuilder>
{
let mut builder = builder
.set_hash_algo(HashAlgorithm::SHA512)
.set_signature_creation_time(creation_time)?;
if ! exportable {
builder = builder.set_exportable_certification(false)?;
}
Ok(builder)
}
fn add_primary_key_metadata(&self,
builder: SignatureBuilder)
-> Result<SignatureBuilder>
{
builder
.set_features(self.features.clone())?
.set_key_flags(self.primary.flags.clone())?
.set_key_validity_period(self.primary.validity)?
.set_preferred_hash_algorithms(vec![
HashAlgorithm::SHA512,
HashAlgorithm::SHA256,
])?
.set_preferred_symmetric_algorithms(vec![
SymmetricAlgorithm::AES256,
SymmetricAlgorithm::AES128,
])
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use crate::Fingerprint;
use crate::crypto::mpi::PublicKey;
use crate::packet::signature::subpacket::{SubpacketTag, SubpacketValue};
use crate::types::Curve;
use crate::types::PublicKeyAlgorithm;
use crate::parse::Parse;
use crate::policy::StandardPolicy as P;
use crate::serialize::Serialize;
#[test]
fn all_opts() {
let p = &P::new();
let (cert, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_userid("test1@example.com")
.add_userid("test2@example.com")
.add_signing_subkey()
.add_transport_encryption_subkey()
.add_certification_subkey()
.generate().unwrap();
let mut userids = cert.userids().with_policy(p, None)
.map(|u| String::from_utf8_lossy(u.userid().value()).into_owned())
.collect::<Vec<String>>();
userids.sort();
assert_eq!(userids,
&[ "test1@example.com",
"test2@example.com",
][..]);
assert_eq!(cert.subkeys().count(), 3);
}
#[test]
fn direct_key_sig() {
let p = &P::new();
let (cert, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_signing_subkey()
.add_transport_encryption_subkey()
.add_certification_subkey()
.generate().unwrap();
assert_eq!(cert.userids().count(), 0);
assert_eq!(cert.subkeys().count(), 3);
let sig =
cert.primary_key().with_policy(p, None).unwrap().binding_signature();
assert_eq!(sig.typ(), crate::types::SignatureType::DirectKey);
assert!(sig.features().unwrap().supports_seipdv1());
}
#[test]
fn setter() {
let (cert1, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.set_cipher_suite(CipherSuite::RSA3k)
.set_cipher_suite(CipherSuite::Cv25519)
.generate().unwrap();
assert_eq!(cert1.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA);
let (cert2, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::RSA3k)
.add_userid("test2@example.com")
.add_transport_encryption_subkey()
.generate().unwrap();
assert_eq!(cert2.primary_key().key().pk_algo(),
PublicKeyAlgorithm::RSAEncryptSign);
assert_eq!(cert2.subkeys().next().unwrap().key().pk_algo(),
PublicKeyAlgorithm::RSAEncryptSign);
}
#[test]
fn defaults() {
let p = &P::new();
let (cert1, _) = CertBuilder::new()
.add_userid("test2@example.com")
.generate().unwrap();
assert_eq!(cert1.primary_key().key().pk_algo(),
PublicKeyAlgorithm::EdDSA);
assert!(cert1.subkeys().next().is_none());
assert!(cert1.with_policy(p, None).unwrap().primary_userid().unwrap()
.binding_signature().features().unwrap().supports_seipdv1());
}
#[test]
fn not_always_certify() {
let p = &P::new();
let (cert1, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.set_primary_key_flags(KeyFlags::empty())
.add_transport_encryption_subkey()
.generate().unwrap();
assert!(! cert1.primary_key().with_policy(p, None).unwrap().for_certification());
assert_eq!(cert1.keys().subkeys().count(), 1);
}
#[test]
fn gen_wired_subkeys() {
let (cert1, _) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.set_primary_key_flags(KeyFlags::empty())
.add_subkey(KeyFlags::empty().set_certification(), None, None)
.generate().unwrap();
let sig_pkts = cert1.subkeys().next().unwrap().bundle()
.self_signatures().next().unwrap().hashed_area();
match sig_pkts.subpacket(SubpacketTag::KeyFlags).unwrap().value() {
SubpacketValue::KeyFlags(ref ks) => assert!(ks.for_certification()),
v => panic!("Unexpected subpacket: {:?}", v),
}
assert_eq!(cert1.subkeys().count(), 1);
}
#[test]
fn generate_revocation_certificate() {
let p = &P::new();
use crate::types::RevocationStatus;
let (cert, revocation) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.generate().unwrap();
assert_eq!(cert.revocation_status(p, None),
RevocationStatus::NotAsFarAsWeKnow);
let cert = cert.insert_packets(revocation.clone()).unwrap().0;
assert_eq!(cert.revocation_status(p, None),
RevocationStatus::Revoked(vec![ &revocation ]));
}
#[test]
fn builder_roundtrip() {
use std::convert::TryFrom;
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.add_signing_subkey()
.generate().unwrap();
let pile = cert.clone().into_packet_pile().into_children().collect::<Vec<_>>();
let exp = Cert::try_from(pile).unwrap();
assert_eq!(cert, exp);
}
#[test]
fn encrypted_secrets() {
let (cert,_) = CertBuilder::new()
.set_cipher_suite(CipherSuite::Cv25519)
.set_password(Some(String::from("streng geheim").into()))
.generate().unwrap();
assert!(cert.primary_key().key().optional_secret().unwrap().is_encrypted());
}
#[test]
fn all_ciphersuites() {
for cs in CipherSuite::variants()
.into_iter().filter(|cs| cs.is_supported().is_ok())
{
assert!(CertBuilder::new()
.set_cipher_suite(cs)
.generate().is_ok());
}
}
#[test]
fn validity_periods() {
let p = &P::new();
let now = crate::now();
let s = std::time::Duration::new(1, 0);
let (cert,_) = CertBuilder::new()
.set_creation_time(now)
.set_validity_period(600 * s)
.add_subkey(KeyFlags::empty().set_signing(),
300 * s, None)
.add_subkey(KeyFlags::empty().set_authentication(),
None, None)
.generate().unwrap();
let key = cert.primary_key().key();
let sig = &cert.primary_key().bundle().self_signatures().next().unwrap();
assert!(sig.key_alive(key, now).is_ok());
assert!(sig.key_alive(key, now + 590 * s).is_ok());
assert!(! sig.key_alive(key, now + 610 * s).is_ok());
let ka = cert.keys().with_policy(p, now).alive().revoked(false)
.for_signing().next().unwrap();
assert!(ka.alive().is_ok());
assert!(ka.clone().with_policy(p, now + 290 * s).unwrap().alive().is_ok());
assert!(! ka.clone().with_policy(p, now + 310 * s).unwrap().alive().is_ok());
let ka = cert.keys().with_policy(p, now).alive().revoked(false)
.for_authentication().next().unwrap();
assert!(ka.alive().is_ok());
assert!(ka.clone().with_policy(p, now + 590 * s).unwrap().alive().is_ok());
assert!(! ka.clone().with_policy(p, now + 610 * s).unwrap().alive().is_ok());
}
#[test]
fn creation_time() {
let p = &P::new();
use std::time::UNIX_EPOCH;
let (cert, rev) = CertBuilder::new()
.set_creation_time(UNIX_EPOCH)
.set_cipher_suite(CipherSuite::Cv25519)
.add_userid("foo")
.add_signing_subkey()
.generate().unwrap();
assert_eq!(cert.primary_key().key().creation_time(), UNIX_EPOCH);
assert_eq!(cert.primary_key().with_policy(p, None).unwrap()
.binding_signature()
.signature_creation_time().unwrap(), UNIX_EPOCH);
assert_eq!(cert.primary_key().with_policy(p, None).unwrap()
.direct_key_signature().unwrap()
.signature_creation_time().unwrap(), UNIX_EPOCH);
assert_eq!(rev.signature_creation_time().unwrap(), UNIX_EPOCH);
assert_eq!(cert.keys().with_policy(p, None).count(), 2);
for ka in cert.keys().with_policy(p, None) {
assert_eq!(ka.key().creation_time(), UNIX_EPOCH);
assert_eq!(ka.binding_signature()
.signature_creation_time().unwrap(), UNIX_EPOCH);
}
assert_eq!(cert.userids().count(), 1);
for ui in cert.userids().with_policy(p, None) {
assert_eq!(ui.binding_signature()
.signature_creation_time().unwrap(), UNIX_EPOCH);
}
}
#[test]
fn designated_revokers() -> Result<()> {
use std::collections::HashSet;
let p = &P::new();
let fpr1 = "C03F A641 1B03 AE12 5764 6118 7223 B566 78E0 2528";
let fpr2 = "50E6 D924 308D BF22 3CFB 510A C2B8 1905 6C65 2598";
let revokers = vec![
RevocationKey::new(PublicKeyAlgorithm::RSAEncryptSign,
Fingerprint::from_str(fpr1)?,
false),
RevocationKey::new(PublicKeyAlgorithm::ECDSA,
Fingerprint::from_str(fpr2)?,
false)
];
let (cert,_)
= CertBuilder::general_purpose(Some("alice@example.org"))
.set_revocation_keys(revokers.clone())
.generate()?;
let cert = cert.with_policy(p, None)?;
assert_eq!(cert.revocation_keys().collect::<HashSet<_>>(),
revokers.iter().collect::<HashSet<_>>());
let (cert,_) = CertBuilder::new()
.set_revocation_keys(revokers.clone())
.generate()?;
let cert = cert.with_policy(p, None)?;
assert!(cert.primary_userid().is_err());
assert_eq!(cert.revocation_keys().collect::<HashSet<_>>(),
revokers.iter().collect::<HashSet<_>>());
let now = crate::types::Timestamp::now();
let then = now.checked_add(crate::types::Duration::days(1)?).unwrap();
let (cert,_) = CertBuilder::new()
.set_revocation_keys(revokers.clone())
.set_creation_time(now)
.generate()?;
use crate::crypto::hash::Hash;
let mut primary_signer =
cert.primary_key().key().clone().parts_into_secret()?
.into_keypair()?;
let mut hash = HashAlgorithm::SHA512.context()?
.for_signature(primary_signer.public().version());
cert.primary_key().key().hash(&mut hash)?;
let sig = signature::SignatureBuilder::new(SignatureType::DirectKey)
.set_signature_creation_time(then)?
.sign_hash(&mut primary_signer, hash)?;
let cert = cert.insert_packets(sig)?.0;
assert!(cert.with_policy(p, then)?.primary_userid().is_err());
assert_eq!(cert.revocation_keys(p).collect::<HashSet<_>>(),
revokers.iter().collect::<HashSet<_>>());
Ok(())
}
#[test]
fn primary_user_things() -> Result<()> {
fn count_primary_user_things(c: Cert) -> usize {
c.into_packets().map(|p| match p {
Packet::Signature(s) if s.primary_userid().unwrap_or(false)
=> 1,
_ => 0,
}).sum()
}
use crate::packet::{prelude::*, user_attribute::Subpacket};
let ua_foo =
UserAttribute::new(&[Subpacket::Unknown(7, vec![7; 7].into())])?;
let ua_bar =
UserAttribute::new(&[Subpacket::Unknown(11, vec![11; 11].into())])?;
let p = &P::new();
let positive = SignatureType::PositiveCertification;
let (c, _) = CertBuilder::new().generate()?;
assert_eq!(count_primary_user_things(c), 0);
let (c, _) = CertBuilder::new()
.add_userid("foo")
.generate()?;
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_userid("foo")
.add_userid("bar")
.generate()?;
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_user_attribute(ua_foo.clone())
.generate()?;
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_user_attribute(ua_foo.clone())
.add_user_attribute(ua_bar.clone())
.generate()?;
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_userid("foo")
.add_user_attribute(ua_foo.clone())
.generate()?;
let vc = c.with_policy(p, None)?;
assert_eq!(vc.primary_userid()?.binding_signature().primary_userid(),
Some(true));
assert_eq!(vc.primary_user_attribute()?.binding_signature().primary_userid(),
None);
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_user_attribute(ua_foo.clone())
.add_userid("foo")
.generate()?;
let vc = c.with_policy(p, None)?;
assert_eq!(vc.primary_userid()?.binding_signature().primary_userid(),
Some(true));
assert_eq!(vc.primary_user_attribute()?.binding_signature().primary_userid(),
None);
assert_eq!(count_primary_user_things(c), 1);
let (c, _) = CertBuilder::new()
.add_userid("foo")
.add_userid_with(
"buz",
SignatureBuilder::new(positive).set_primary_userid(false)?)?
.add_userid_with(
"bar",
SignatureBuilder::new(positive).set_primary_userid(true)?)?
.add_userid_with(
"baz",
SignatureBuilder::new(positive).set_primary_userid(true)?)?
.generate()?;
let vc = c.with_policy(p, None)?;
assert_eq!(vc.primary_userid()?.userid().value(), b"bar");
assert_eq!(count_primary_user_things(c), 1);
Ok(())
}
#[test]
fn non_exportable_cert() -> Result<()> {
let (cert, _) =
CertBuilder::general_purpose(Some("alice@example.org"))
.set_exportable(false)
.generate()?;
let (bob, _) =
CertBuilder::general_purpose(Some("bob@example.org"))
.generate()?;
let mut keypair = bob.primary_key().key().clone()
.parts_into_secret()?.into_keypair()?;
let certification = cert.userids().nth(0).unwrap()
.userid()
.certify(&mut keypair, &cert,
SignatureType::PositiveCertification,
None, None)?;
let cert = cert.insert_packets(certification)?.0;
macro_rules! check {
($cert: expr, $export: ident, $expected: expr) => {
let mut exported = Vec::new();
$cert.$export(&mut exported)?;
let certs = CertParser::from_bytes(&exported)?
.collect::<Result<Vec<Cert>>>()?;
assert_eq!(certs.len(), $expected);
if $expected == 0 {
assert_eq!(exported.len(), 0,
"{}", String::from_utf8_lossy(&exported));
} else {
assert!(exported.len() > 0);
}
}
}
check!(cert, export, 0);
check!(cert, serialize, 1);
check!(cert.as_tsk(), export, 0);
check!(cert.as_tsk(), serialize, 1);
check!(cert.armored(), export, 0);
check!(cert.armored(), serialize, 1);
check!(cert.as_tsk().armored(), export, 0);
check!(cert.as_tsk().armored(), serialize, 1);
let mut keypair = cert.primary_key().key().clone()
.parts_into_secret()?.into_keypair()?;
let certification = cert.userids().nth(0).unwrap()
.userid()
.certify(&mut keypair, &cert,
SignatureType::PositiveCertification,
None, None)?;
let cert = cert.insert_packets(certification)?.0;
macro_rules! check {
($cert: expr, $export: ident, $expected: expr) => {
let mut exported = Vec::new();
$cert.$export(&mut exported)?;
let certs = CertParser::from_bytes(&exported)?
.collect::<Result<Vec<Cert>>>()?;
assert_eq!(certs.len(), $expected);
if $expected == 0 {
assert_eq!(exported.len(), 0,
"{}", String::from_utf8_lossy(&exported));
} else {
assert!(exported.len() > 0);
}
}
}
check!(cert, export, 1);
check!(cert, serialize, 1);
check!(cert.as_tsk(), export, 1);
check!(cert.as_tsk(), serialize, 1);
check!(cert.armored(), export, 1);
check!(cert.armored(), serialize, 1);
check!(cert.as_tsk().armored(), export, 1);
check!(cert.as_tsk().armored(), serialize, 1);
Ok(())
}
#[test]
fn check_algos() {
for (cipher_suite, profile, algos, curves) in [
(CipherSuite::Cv25519,
Profile::RFC4880,
&[ PublicKeyAlgorithm::EdDSA, PublicKeyAlgorithm::ECDH ][..],
&[ Curve::Ed25519, Curve::Cv25519, ][..]),
(CipherSuite::Cv25519,
Profile::RFC9580,
&[ PublicKeyAlgorithm::Ed25519, PublicKeyAlgorithm::X25519 ],
&[]),
(CipherSuite::Cv448,
Profile::RFC4880,
&[ PublicKeyAlgorithm::Ed448, PublicKeyAlgorithm::X448 ],
&[]),
(CipherSuite::Cv448,
Profile::RFC9580,
&[ PublicKeyAlgorithm::Ed448, PublicKeyAlgorithm::X448 ],
&[]),
(CipherSuite::RSA2k,
Profile::RFC4880,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::RSA2k,
Profile::RFC9580,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::RSA3k,
Profile::RFC4880,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::RSA3k,
Profile::RFC9580,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::RSA4k,
Profile::RFC4880,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::RSA4k,
Profile::RFC9580,
&[ PublicKeyAlgorithm::RSAEncryptSign ],
&[]),
(CipherSuite::P256,
Profile::RFC4880,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP256 ]),
(CipherSuite::P256,
Profile::RFC9580,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP256 ]),
(CipherSuite::P384,
Profile::RFC4880,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP384 ]),
(CipherSuite::P384,
Profile::RFC9580,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP384 ]),
(CipherSuite::P521,
Profile::RFC4880,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP521 ]),
(CipherSuite::P521,
Profile::RFC9580,
&[ PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ECDH ],
&[ Curve::NistP521 ]),
]
{
eprintln!("Testing that generating {:?}, {:?} results in \
algorithms: {}; curves: {}",
cipher_suite, profile,
algos
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", "),
if curves.is_empty() {
"none".to_string()
} else {
curves
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
});
if let Err(err) = cipher_suite.is_supported() {
eprintln!("Skipping, cipher suite is not supported: {}", err);
continue;
}
let (cert, _) = CertBuilder::general_purpose(Some("x@example.org"))
.set_cipher_suite(cipher_suite)
.set_profile(profile)
.expect("Profile is supported")
.generate()
.expect("Cipher suite is supported");
let mut algos_got = cert.keys()
.map(|ka| ka.key().pk_algo())
.collect::<Vec<_>>();
algos_got.sort();
algos_got.dedup();
let mut algos_expected = algos.to_vec();
algos_expected.sort();
assert_eq!(&algos_expected, &algos_got,
"\n\
algos expected: {}\n\
algos got: {}",
algos_expected
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", "),
algos_got
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", "));
let mut curves_got = cert.keys()
.filter_map(|ka| match ka.key().mpis() {
PublicKey::EdDSA { curve, .. }
| PublicKey::ECDSA { curve, .. }
| PublicKey::ECDH { curve, .. } =>
{
Some(curve.clone())
}
_ => None,
})
.collect::<Vec<_>>();
curves_got.sort();
curves_got.dedup();
let mut curves_expected = curves.to_vec();
curves_expected.sort();
assert_eq!(&curves_expected, &curves_got,
"\n\
curves expected: {}\n\
curves got: {}",
if curves_expected.is_empty() {
"(none)".to_string()
} else {
curves_expected
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
},
if curves_got.is_empty() {
"(none)".to_string()
} else {
curves_got
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
});
}
}
}