use std::time::SystemTime;
use pgp::composed::{SignedKeyDetails, SignedPublicKey, SignedPublicSubKey};
use pgp::packet::SignatureType;
use pgp::types::KeyDetails;
use crate::error::{Error, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(dead_code)] pub(crate) enum SigningKeyUsage {
DataSignature,
KeyMaintenance,
}
pub(crate) fn is_key_expired(creation_time: SystemTime, validity_seconds: Option<u64>) -> bool {
if let Some(validity) = validity_seconds {
if validity == 0 {
return false; }
let expiration = creation_time + std::time::Duration::from_secs(validity);
expiration < SystemTime::now()
} else {
false }
}
pub(crate) fn is_subkey_revoked(subkey: &SignedPublicSubKey) -> bool {
subkey
.signatures
.iter()
.any(|sig| sig.typ() == Some(SignatureType::SubkeyRevocation))
}
pub(crate) fn can_subkey_sign(subkey: &SignedPublicSubKey) -> bool {
most_recent_binding_sig(subkey)
.map(|sig| sig.key_flags().sign())
.unwrap_or(false)
}
pub(crate) fn most_recent_binding_sig(
subkey: &SignedPublicSubKey,
) -> Option<&pgp::packet::Signature> {
subkey
.signatures
.iter()
.filter(|sig| sig.typ() == Some(SignatureType::SubkeyBinding))
.max_by_key(|sig| sig.created().map(|t| t.as_secs()).unwrap_or(0))
}
pub(crate) fn can_subkey_encrypt(subkey: &SignedPublicSubKey) -> bool {
most_recent_binding_sig(subkey)
.map(|sig| {
let flags = sig.key_flags();
flags.encrypt_comms() || flags.encrypt_storage()
})
.unwrap_or(false)
}
pub(crate) fn is_subkey_valid(subkey: &SignedPublicSubKey, allow_expired: bool) -> bool {
if is_subkey_revoked(subkey) {
return false;
}
if !allow_expired {
let most_recent_sig = subkey
.signatures
.iter()
.filter(|sig| sig.key_expiration_time().is_some())
.max_by_key(|sig| sig.created().map(|t| t.as_secs()).unwrap_or(0));
if let Some(sig) = most_recent_sig {
if let Some(validity) = sig.key_expiration_time() {
let creation_time: SystemTime = subkey.key.created_at().into();
if is_key_expired(creation_time, Some(validity.as_secs() as u64)) {
return false;
}
}
}
}
true
}
fn most_recent_self_sig(
user: &pgp::types::SignedUser,
) -> Option<&pgp::packet::Signature> {
user.signatures
.iter()
.filter(|sig| {
matches!(
sig.typ(),
Some(SignatureType::CertGeneric)
| Some(SignatureType::CertPersona)
| Some(SignatureType::CertCasual)
| Some(SignatureType::CertPositive)
)
})
.max_by_key(|sig| sig.created().map(|t| t.as_secs()).unwrap_or(0))
}
pub(crate) fn can_details_sign(details: &SignedKeyDetails) -> bool {
for user in &details.users {
if let Some(sig) = most_recent_self_sig(user) {
if sig.key_flags().sign() {
return true;
}
}
}
false
}
pub(crate) fn can_primary_sign(key: &SignedPublicKey) -> bool {
can_details_sign(&key.details)
}
#[cfg(feature = "card")]
pub(crate) fn can_primary_certify(key: &SignedPublicKey) -> bool {
key.details
.users
.iter()
.any(|u| {
most_recent_self_sig(u)
.map(|sig| sig.key_flags().certify())
.unwrap_or(false)
})
}
pub(crate) fn is_details_revoked(details: &SignedKeyDetails) -> bool {
details
.revocation_signatures
.iter()
.any(|sig| sig.typ() == Some(SignatureType::KeyRevocation))
}
pub(crate) fn is_primary_key_revoked(key: &SignedPublicKey) -> bool {
is_details_revoked(&key.details)
}
pub(crate) fn is_primary_key_valid_for_verification(key: &SignedPublicKey) -> bool {
!is_primary_key_revoked(key)
}
pub(crate) fn primary_expiration_from_details(
creation_time: SystemTime,
details: &SignedKeyDetails,
) -> Option<SystemTime> {
let mut newest_created: Option<SystemTime> = None;
let mut newest_expiration = None;
for user in &details.users {
let self_sigs = user.signatures.iter().filter(|sig| {
matches!(
sig.typ(),
Some(SignatureType::CertGeneric)
| Some(SignatureType::CertPersona)
| Some(SignatureType::CertCasual)
| Some(SignatureType::CertPositive)
)
});
for sig in self_sigs {
if let Some(validity) = sig.key_expiration_time() {
let sig_created: Option<SystemTime> = sig.created().map(|ts| ts.into());
let is_newer = match (&newest_created, &sig_created) {
(Some(prev), Some(cur)) => cur > prev,
(None, Some(_)) => true,
(None, None) => newest_expiration.is_none(),
_ => false,
};
if is_newer {
newest_created = sig_created;
newest_expiration = Some(creation_time + validity.into());
}
}
}
}
newest_expiration
}
pub(crate) fn validate_signing_usage(
creation_time: SystemTime,
details: &SignedKeyDetails,
usage: SigningKeyUsage,
) -> Result<()> {
if is_details_revoked(details) {
return Err(Error::KeyRevoked);
}
if matches!(usage, SigningKeyUsage::DataSignature) {
if let Some(exp) = primary_expiration_from_details(creation_time, details) {
if exp < SystemTime::now() {
return Err(Error::KeyExpired);
}
}
}
Ok(())
}
#[cfg(feature = "card")]
pub(crate) fn validate_primary_key_signing_usage(
key: &SignedPublicKey,
usage: SigningKeyUsage,
) -> Result<()> {
validate_signing_usage(key.primary_key.created_at().into(), &key.details, usage)
}
pub(crate) fn get_key_expiration(key: &SignedPublicKey) -> Option<SystemTime> {
primary_expiration_from_details(key.primary_key.created_at().into(), &key.details)
}