use std::path::Path;
use pgp::composed::SignedPublicKey;
use pgp::types::KeyDetails;
use crate::error::Result;
use crate::internal::{
fingerprint_to_hex, get_algorithm_name, get_key_bit_size, is_subkey_revoked, is_subkey_valid,
keyid_to_hex, parse_cert, system_time_to_datetime,
};
use crate::types::{
AvailableSubkey, CertificateInfo, KeyCipherDetails, KeyType, SubkeyInfo, UIDCertification,
UserIDInfo,
};
pub fn parse_cert_bytes(data: &[u8], allow_expired: bool) -> Result<CertificateInfo> {
let (public_key, is_secret) = parse_cert(data)?;
extract_cert_info(&public_key, is_secret, allow_expired)
}
pub fn parse_cert_file(path: impl AsRef<Path>, allow_expired: bool) -> Result<CertificateInfo> {
let data = std::fs::read(path.as_ref())?;
parse_cert_bytes(&data, allow_expired)
}
pub fn get_key_cipher_details(data: &[u8]) -> Result<Vec<KeyCipherDetails>> {
let (public_key, _) = parse_cert(data)?;
let mut details = Vec::new();
details.push(KeyCipherDetails {
fingerprint: fingerprint_to_hex(&public_key.primary_key),
algorithm: get_algorithm_name(&public_key.primary_key),
bit_length: get_key_bit_size(&public_key.primary_key),
});
for subkey in &public_key.public_subkeys {
details.push(KeyCipherDetails {
fingerprint: fingerprint_to_hex(&subkey.key),
algorithm: get_algorithm_name(&subkey.key),
bit_length: get_key_bit_size(&subkey.key),
});
}
Ok(details)
}
fn extract_cert_info(
public_key: &SignedPublicKey,
is_secret: bool,
allow_expired: bool,
) -> Result<CertificateInfo> {
let primary_fp = fingerprint_to_hex(&public_key.primary_key);
let user_ids: Vec<UserIDInfo> = public_key
.details
.users
.iter()
.map(|u| {
let value = String::from_utf8_lossy(u.id.id()).to_string();
let revocation_sig = u
.signatures
.iter()
.find(|sig| sig.typ() == Some(pgp::packet::SignatureType::CertRevocation));
let revoked = revocation_sig.is_some();
let revocation_time = revocation_sig.and_then(|sig| sig.created()).map(|ts| {
let st: std::time::SystemTime = ts.into();
system_time_to_datetime(st)
});
let certifications = u
.signatures
.iter()
.filter_map(|sig| {
let sig_type = sig.typ()?;
let cert_type_str = match sig_type {
pgp::packet::SignatureType::CertGeneric => "generic",
pgp::packet::SignatureType::CertPersona => "persona",
pgp::packet::SignatureType::CertCasual => "casual",
pgp::packet::SignatureType::CertPositive => "positive",
_ => return None,
};
let mut issuers: Vec<(String, String)> = Vec::new();
for fp in sig.issuer_fingerprint() {
let fp_hex = hex::encode_upper(fp.as_bytes());
if fp_hex == primary_fp {
return None;
}
issuers.push(("fingerprint".to_string(), fp_hex));
}
for kid in sig.issuer_key_id() {
issuers.push(("keyid".to_string(), hex::encode_upper(kid.as_ref())));
}
if issuers.is_empty() {
return None;
}
let creation_time = sig.created().map(|ts| {
let st: std::time::SystemTime = ts.into();
system_time_to_datetime(st)
});
Some(UIDCertification {
certification_type: cert_type_str.to_string(),
creation_time,
issuers,
})
})
.collect();
let is_primary = u.is_primary();
UserIDInfo {
value,
revoked,
is_primary,
revocation_time,
certifications,
}
})
.collect();
let fingerprint = fingerprint_to_hex(&public_key.primary_key);
let key_id = keyid_to_hex(&public_key.primary_key);
let creation_time = system_time_to_datetime(public_key.primary_key.created_at().into());
let expiration_time =
crate::internal::get_key_expiration(public_key).map(system_time_to_datetime);
let can_primary_sign = crate::internal::can_primary_sign(public_key);
let revocation_sig = public_key
.details
.revocation_signatures
.iter()
.find(|sig| sig.typ() == Some(pgp::packet::SignatureType::KeyRevocation));
let is_revoked = revocation_sig.is_some();
let revocation_time = revocation_sig.and_then(|sig| sig.created()).map(|ts| {
let st: std::time::SystemTime = ts.into();
system_time_to_datetime(st)
});
let subkeys = extract_subkey_info(public_key, allow_expired);
Ok(CertificateInfo {
user_ids,
fingerprint,
key_id,
is_secret,
creation_time,
expiration_time,
can_primary_sign,
is_revoked,
revocation_time,
subkeys,
})
}
fn extract_subkey_info(public_key: &SignedPublicKey, allow_expired: bool) -> Vec<SubkeyInfo> {
let mut subkeys = Vec::new();
for subkey in &public_key.public_subkeys {
let key_id = keyid_to_hex(&subkey.key);
let fingerprint = fingerprint_to_hex(&subkey.key);
let creation_time = system_time_to_datetime(subkey.key.created_at().into());
let expiration_time = subkey.signatures.first().and_then(|sig| {
sig.key_expiration_time().map(|validity| {
let creation: std::time::SystemTime = subkey.key.created_at().into();
system_time_to_datetime(creation + validity.into())
})
});
let is_revoked = is_subkey_revoked(subkey);
let algorithm = get_algorithm_name(&subkey.key);
let bit_length = get_key_bit_size(&subkey.key);
let key_type = determine_key_type(subkey);
if allow_expired || is_subkey_valid(subkey, false) {
subkeys.push(SubkeyInfo {
key_id,
fingerprint,
creation_time,
expiration_time,
key_type,
is_revoked,
algorithm,
bit_length,
});
}
}
subkeys
}
fn determine_key_type(subkey: &pgp::composed::SignedPublicSubKey) -> KeyType {
for sig in &subkey.signatures {
let flags = sig.key_flags();
if flags.encrypt_comms() || flags.encrypt_storage() {
return KeyType::Encryption;
} else if flags.sign() {
return KeyType::Signing;
} else if flags.authentication() {
return KeyType::Authentication;
} else if flags.certify() {
return KeyType::Certification;
}
}
KeyType::Unknown
}
pub fn get_available_encryption_subkeys(data: &[u8]) -> Result<Vec<AvailableSubkey>> {
get_available_subkeys_by_type(data, |flags| {
flags.encrypt_comms() || flags.encrypt_storage()
})
}
pub fn get_available_signing_subkeys(data: &[u8]) -> Result<Vec<AvailableSubkey>> {
get_available_subkeys_by_type(data, |flags| flags.sign())
}
pub fn get_available_authentication_subkeys(data: &[u8]) -> Result<Vec<AvailableSubkey>> {
get_available_subkeys_by_type(data, |flags| flags.authentication())
}
pub fn get_all_available_subkeys(data: &[u8]) -> Result<Vec<AvailableSubkey>> {
get_available_subkeys_by_type(data, |_| true)
}
fn get_available_subkeys_by_type<F>(data: &[u8], predicate: F) -> Result<Vec<AvailableSubkey>>
where
F: Fn(&pgp::packet::KeyFlags) -> bool,
{
let (public_key, _) = parse_cert(data)?;
let mut available = Vec::new();
for subkey in &public_key.public_subkeys {
if is_subkey_revoked(subkey) {
continue;
}
if !is_subkey_valid(subkey, false) {
continue;
}
let matches_predicate = subkey.signatures.iter().any(|sig| {
let flags = sig.key_flags();
predicate(&flags)
});
if !matches_predicate {
continue;
}
let key_type = determine_key_type(subkey);
let expiration_time = subkey.signatures.first().and_then(|sig| {
sig.key_expiration_time().map(|validity| {
let creation: std::time::SystemTime = subkey.key.created_at().into();
system_time_to_datetime(creation + validity.into())
})
});
available.push(AvailableSubkey {
fingerprint: fingerprint_to_hex(&subkey.key),
key_id: keyid_to_hex(&subkey.key),
creation_time: system_time_to_datetime(subkey.key.created_at().into()),
expiration_time,
key_type,
algorithm: get_algorithm_name(&subkey.key),
bit_length: get_key_bit_size(&subkey.key),
});
}
Ok(available)
}
pub fn has_available_encryption_subkey(data: &[u8]) -> Result<bool> {
Ok(!get_available_encryption_subkeys(data)?.is_empty())
}
pub fn has_available_signing_subkey(data: &[u8]) -> Result<bool> {
Ok(!get_available_signing_subkeys(data)?.is_empty())
}
#[cfg(test)]
mod tests {
}