use chrono::{DateTime, Utc};
use pgp::composed::{
EncryptionCaps, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedSecretKey,
SubkeyParamsBuilder,
};
use pgp::packet::{PacketTrait, SignatureConfig, SignatureType, Subpacket, SubpacketData, UserId};
use pgp::types::{KeyDetails, KeyVersion, PacketHeaderVersion, Password, SignedUser, Timestamp};
use rand::thread_rng;
use std::time::SystemTime;
use crate::error::{Error, Result};
use crate::internal::{
fingerprint_to_hex, parse_public_key, parse_secret_key, public_key_to_armored,
secret_key_to_bytes,
};
use crate::types::{CertificationType, CipherSuite, GeneratedKey, SubkeyFlags};
#[allow(clippy::too_many_arguments)]
pub fn create_key(
password: &str,
user_ids: &[&str],
cipher: CipherSuite,
creation_time: Option<DateTime<Utc>>,
expiration_time: Option<DateTime<Utc>>,
subkeys_expiration: Option<DateTime<Utc>>,
which_keys: SubkeyFlags,
can_primary_sign: bool,
can_primary_expire: bool,
) -> Result<GeneratedKey> {
if user_ids.is_empty() {
return Err(Error::InvalidInput(
"At least one user ID is required".to_string(),
));
}
let mut rng = thread_rng();
let primary_key_type = cipher.primary_key_type();
let encryption_key_type = cipher.encryption_key_type();
let creation_timestamp = match creation_time {
Some(dt) => {
let systime: SystemTime = dt.into();
Some(Timestamp::try_from(systime)
.map_err(|e| Error::InvalidInput(format!("Invalid creation time: {}", e)))?)
}
None => None,
};
let mut subkeys = Vec::new();
if which_keys.encryption {
let mut enc_builder = SubkeyParamsBuilder::default();
enc_builder
.key_type(encryption_key_type)
.can_encrypt(EncryptionCaps::All)
.can_sign(false)
.can_authenticate(false);
if let Some(ts) = creation_timestamp {
enc_builder.created_at(ts);
}
if !password.is_empty() {
enc_builder.passphrase(Some(password.to_string()));
}
subkeys.push(enc_builder.build().map_err(|e| Error::Crypto(e.to_string()))?);
}
if which_keys.signing {
let mut sign_builder = SubkeyParamsBuilder::default();
sign_builder
.key_type(primary_key_type.clone())
.can_encrypt(EncryptionCaps::None)
.can_sign(true)
.can_authenticate(false);
if let Some(ts) = creation_timestamp {
sign_builder.created_at(ts);
}
if !password.is_empty() {
sign_builder.passphrase(Some(password.to_string()));
}
subkeys.push(sign_builder.build().map_err(|e| Error::Crypto(e.to_string()))?);
}
if which_keys.authentication {
let mut auth_builder = SubkeyParamsBuilder::default();
auth_builder
.key_type(primary_key_type.clone())
.can_encrypt(EncryptionCaps::None)
.can_sign(false)
.can_authenticate(true);
if let Some(ts) = creation_timestamp {
auth_builder.created_at(ts);
}
if !password.is_empty() {
auth_builder.passphrase(Some(password.to_string()));
}
subkeys.push(auth_builder.build().map_err(|e| Error::Crypto(e.to_string()))?);
}
let mut key_params = SecretKeyParamsBuilder::default();
key_params
.key_type(primary_key_type)
.can_certify(true)
.can_sign(can_primary_sign)
.can_encrypt(EncryptionCaps::None)
.primary_user_id(user_ids[0].to_string());
if let Some(ts) = creation_timestamp {
key_params.created_at(ts);
}
if user_ids.len() > 1 {
let additional_uids: Vec<String> = user_ids[1..].iter().map(|s| s.to_string()).collect();
key_params.user_ids(additional_uids);
}
if !password.is_empty() {
key_params.passphrase(Some(password.to_string()));
}
key_params.subkeys(subkeys);
let secret_key_params = key_params.build().map_err(|e| Error::Crypto(e.to_string()))?;
let secret_key = secret_key_params
.generate(&mut rng)
.map_err(|e| Error::Crypto(e.to_string()))?;
let public_key = secret_key.to_public_key();
let fingerprint = fingerprint_to_hex(&public_key.primary_key);
let mut final_secret_key_bytes = secret_key_to_bytes(&secret_key)?;
if can_primary_expire {
if let Some(exp_time) = expiration_time {
final_secret_key_bytes = update_primary_expiry(&final_secret_key_bytes, exp_time, password)?;
}
}
if let Some(subkey_exp_time) = subkeys_expiration {
let current_info = crate::parse::parse_cert_bytes(&final_secret_key_bytes, true)?;
let subkey_fps: Vec<String> = current_info.subkeys.iter()
.map(|s| s.fingerprint.clone())
.collect();
let subkey_fp_refs: Vec<&str> = subkey_fps.iter()
.map(|s| s.as_str())
.collect();
if !subkey_fp_refs.is_empty() {
final_secret_key_bytes = update_subkeys_expiry(
&final_secret_key_bytes, &subkey_fp_refs, subkey_exp_time, password)?;
}
}
let final_secret = parse_secret_key(&final_secret_key_bytes)?;
let final_public = final_secret.to_public_key();
let public_key_armored = public_key_to_armored(&final_public)?;
Ok(GeneratedKey {
public_key: public_key_armored,
secret_key: final_secret_key_bytes,
fingerprint,
})
}
pub fn create_key_simple(password: &str, user_ids: &[&str]) -> Result<GeneratedKey> {
create_key(
password,
user_ids,
CipherSuite::Cv25519,
None,
None,
None,
SubkeyFlags::all(),
false,
true,
)
}
pub fn get_pub_key(cert_data: &[u8]) -> Result<String> {
let secret_key = parse_secret_key(cert_data)?;
let public_key = SignedPublicKey::from(secret_key);
public_key_to_armored(&public_key)
}
pub fn update_subkeys_expiry(
cert_data: &[u8],
fingerprints: &[&str],
expiry_time: DateTime<Utc>,
password: &str,
) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let secret_key = parse_secret_key(cert_data)?;
let password = Password::from(password);
let normalized_fps: Vec<String> = fingerprints
.iter()
.map(|fp| fp.to_uppercase().replace(" ", ""))
.collect();
let mut new_public_subkeys = Vec::new();
for subkey in &secret_key.public_subkeys {
let subkey_fp = fingerprint_to_hex(&subkey.key);
let should_update = normalized_fps.iter().any(|fp| subkey_fp.contains(fp) || fp.contains(&subkey_fp));
if should_update {
let creation_systime: SystemTime = subkey.key.created_at().into();
let subkey_creation: DateTime<Utc> = creation_systime.into();
let duration = expiry_time.signed_duration_since(subkey_creation);
if duration.num_seconds() <= 0 {
return Err(Error::InvalidInput(
"Expiry time must be after subkey creation time".to_string(),
));
}
let expiry_duration = pgp::types::Duration::from_secs(duration.num_seconds() as u32);
let key_flags = subkey
.signatures
.first()
.map(|sig| sig.key_flags())
.unwrap_or_default();
let hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(
secret_key.primary_key.fingerprint(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::KeyFlags(key_flags))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
];
let mut config = SignatureConfig::from_key(
&mut rng,
&secret_key.primary_key,
SignatureType::SubkeyBinding,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
config.hashed_subpackets = hashed_subpackets;
if secret_key.primary_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
secret_key.primary_key.legacy_key_id(),
))
.map_err(|e| Error::Crypto(e.to_string()))?];
}
let sig = config
.sign_subkey_binding(
&secret_key.primary_key,
&secret_key.primary_key.public_key(),
&password,
&subkey.key,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut new_sigs = vec![sig];
for existing_sig in &subkey.signatures {
if existing_sig.typ() != Some(SignatureType::SubkeyBinding) {
new_sigs.push(existing_sig.clone());
}
}
new_public_subkeys.push(pgp::composed::SignedPublicSubKey {
key: subkey.key.clone(),
signatures: new_sigs,
});
} else {
new_public_subkeys.push(subkey.clone());
}
}
let mut new_secret_subkeys = Vec::new();
for subkey in &secret_key.secret_subkeys {
let subkey_fp = fingerprint_to_hex(&subkey.key);
let should_update = normalized_fps.iter().any(|fp| subkey_fp.contains(fp) || fp.contains(&subkey_fp));
if should_update {
let creation_systime: SystemTime = subkey.key.created_at().into();
let subkey_creation: DateTime<Utc> = creation_systime.into();
let duration = expiry_time.signed_duration_since(subkey_creation);
if duration.num_seconds() <= 0 {
return Err(Error::InvalidInput(
"Expiry time must be after subkey creation time".to_string(),
));
}
let expiry_duration = pgp::types::Duration::from_secs(duration.num_seconds() as u32);
let key_flags = subkey
.signatures
.first()
.map(|sig| sig.key_flags())
.unwrap_or_default();
let hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(
secret_key.primary_key.fingerprint(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::KeyFlags(key_flags))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
];
let mut config = SignatureConfig::from_key(
&mut rng,
&secret_key.primary_key,
SignatureType::SubkeyBinding,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
config.hashed_subpackets = hashed_subpackets;
if secret_key.primary_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
secret_key.primary_key.legacy_key_id(),
))
.map_err(|e| Error::Crypto(e.to_string()))?];
}
let sig = config
.sign_subkey_binding(
&secret_key.primary_key,
&secret_key.primary_key.public_key(),
&password,
&subkey.key.public_key(),
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut new_sigs = vec![sig];
for existing_sig in &subkey.signatures {
if existing_sig.typ() != Some(SignatureType::SubkeyBinding) {
new_sigs.push(existing_sig.clone());
}
}
new_secret_subkeys.push(pgp::composed::SignedSecretSubKey {
key: subkey.key.clone(),
signatures: new_sigs,
});
} else {
new_secret_subkeys.push(subkey.clone());
}
}
let updated_key = SignedSecretKey::new(
secret_key.primary_key.clone(),
secret_key.details.clone(),
new_public_subkeys,
new_secret_subkeys,
);
secret_key_to_bytes(&updated_key)
}
pub fn update_primary_expiry(
cert_data: &[u8],
expiry_time: DateTime<Utc>,
password: &str,
) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let secret_key = parse_secret_key(cert_data)?;
let password = Password::from(password);
let creation_systime: SystemTime = secret_key.primary_key.created_at().into();
let key_creation: DateTime<Utc> = creation_systime.into();
let duration = expiry_time.signed_duration_since(key_creation);
if duration.num_seconds() <= 0 {
return Err(Error::InvalidInput(
"Expiry time must be after key creation time".to_string(),
));
}
let expiry_duration = pgp::types::Duration::from_secs(duration.num_seconds() as u32);
let mut new_direct_signatures: Vec<pgp::packet::Signature> = Vec::new();
for existing_sig in &secret_key.details.direct_signatures {
if existing_sig.typ() == Some(SignatureType::Key) {
let existing_config = existing_sig
.config()
.ok_or_else(|| Error::Crypto("Cannot read existing direct signature config".to_string()))?;
let mut new_hashed_subpackets: Vec<Subpacket> = Vec::new();
let mut has_creation_time = false;
let mut has_expiry_time = false;
for subpacket in existing_config.hashed_subpackets() {
match &subpacket.data {
SubpacketData::SignatureCreationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_creation_time = true;
}
SubpacketData::KeyExpirationTime(_) => {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
has_expiry_time = true;
}
_ => {
new_hashed_subpackets.push(subpacket.clone());
}
}
}
if !has_creation_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
if !has_expiry_time {
new_hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
let new_unhashed_subpackets: Vec<Subpacket> = existing_config
.unhashed_subpackets()
.cloned()
.collect();
let mut config = SignatureConfig::from_key(&mut rng, &secret_key.primary_key, SignatureType::Key)
.map_err(|e| Error::Crypto(e.to_string()))?;
config.hashed_subpackets = new_hashed_subpackets;
config.unhashed_subpackets = new_unhashed_subpackets;
let sig = config
.sign_key(&secret_key.primary_key, &password, &secret_key.primary_key.public_key())
.map_err(|e| Error::Crypto(e.to_string()))?;
new_direct_signatures.push(sig);
} else {
new_direct_signatures.push(existing_sig.clone());
}
}
let mut new_users: Vec<SignedUser> = Vec::new();
for signed_user in &secret_key.details.users {
let mut hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(
secret_key.primary_key.fingerprint(),
))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::KeyExpirationTime(expiry_duration))
.map_err(|e| Error::Crypto(e.to_string()))?,
];
if let Some(existing_sig) = signed_user.signatures.first() {
let flags = existing_sig.key_flags();
hashed_subpackets.push(
Subpacket::regular(SubpacketData::KeyFlags(flags))
.map_err(|e| Error::Crypto(e.to_string()))?,
);
}
let mut config = SignatureConfig::from_key(&mut rng, &secret_key.primary_key, SignatureType::CertPositive)
.map_err(|e| Error::Crypto(e.to_string()))?;
config.hashed_subpackets = hashed_subpackets;
if secret_key.primary_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
secret_key.primary_key.legacy_key_id(),
))
.map_err(|e| Error::Crypto(e.to_string()))?];
}
let sig = config
.sign_certification(
&secret_key.primary_key,
&secret_key.primary_key.public_key(),
&password,
signed_user.id.tag(),
&signed_user.id,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut combined_sigs = vec![sig];
for existing_sig in &signed_user.signatures {
if existing_sig.typ() != Some(SignatureType::CertPositive) {
combined_sigs.push(existing_sig.clone());
}
}
new_users.push(SignedUser::new(signed_user.id.clone(), combined_sigs));
}
let updated_key = SignedSecretKey::new(
secret_key.primary_key.clone(),
pgp::composed::SignedKeyDetails::new(
secret_key.details.revocation_signatures.clone(),
new_direct_signatures,
new_users,
secret_key.details.user_attributes.clone(),
),
secret_key.public_subkeys.clone(),
secret_key.secret_subkeys.clone(),
);
secret_key_to_bytes(&updated_key)
}
pub fn add_uid(cert_data: &[u8], uid: &str, password: &str) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let secret_key = parse_secret_key(cert_data)?;
let password = Password::from(password);
let new_uid = UserId::from_str(PacketHeaderVersion::New, uid)
.map_err(|e| Error::Crypto(e.to_string()))?;
let signed_user = new_uid
.sign(
&mut rng,
&secret_key.primary_key,
secret_key.primary_key.public_key(),
&password,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut users = secret_key.details.users.clone();
users.push(signed_user);
let new_details = SignedKeyDetails::new(
secret_key.details.revocation_signatures.clone(),
secret_key.details.direct_signatures.clone(),
users,
secret_key.details.user_attributes.clone(),
);
let new_secret_key = SignedSecretKey::new(
secret_key.primary_key.clone(),
new_details,
secret_key.public_subkeys.clone(),
secret_key.secret_subkeys.clone(),
);
secret_key_to_bytes(&new_secret_key)
}
pub fn revoke_uid(cert_data: &[u8], uid: &str, password: &str) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let secret_key = parse_secret_key(cert_data)?;
let password = Password::from(password);
let uid_bytes = uid.as_bytes();
let uid_index = secret_key
.details
.users
.iter()
.position(|u| u.id.id() == uid_bytes)
.ok_or_else(|| Error::InvalidInput(format!("User ID '{}' not found in key", uid)))?;
let mut config = SignatureConfig::from_key(&mut rng, &secret_key.primary_key, SignatureType::CertRevocation)
.map_err(|e| Error::Crypto(e.to_string()))?;
config.hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))
.map_err(|e| Error::Crypto(e.to_string()))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(secret_key.primary_key.fingerprint()))
.map_err(|e| Error::Crypto(e.to_string()))?,
];
if secret_key.primary_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
secret_key.primary_key.legacy_key_id(),
))
.map_err(|e| Error::Crypto(e.to_string()))?];
}
let user_to_revoke = &secret_key.details.users[uid_index];
let revocation_sig = config
.sign_certification_third_party(
&secret_key.primary_key,
&password,
secret_key.primary_key.public_key(),
user_to_revoke.id.tag(),
&user_to_revoke.id,
)
.map_err(|e| Error::Crypto(e.to_string()))?;
let mut users = secret_key.details.users.clone();
users[uid_index].signatures.push(revocation_sig);
let new_details = SignedKeyDetails::new(
secret_key.details.revocation_signatures.clone(),
secret_key.details.direct_signatures.clone(),
users,
secret_key.details.user_attributes.clone(),
);
let new_secret_key = SignedSecretKey::new(
secret_key.primary_key.clone(),
new_details,
secret_key.public_subkeys.clone(),
secret_key.secret_subkeys.clone(),
);
secret_key_to_bytes(&new_secret_key)
}
pub fn update_password(
cert_data: &[u8],
old_password: &str,
new_password: &str,
) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let secret_key = parse_secret_key(cert_data)?;
let old_pw = Password::from(old_password);
let new_pw = Password::from(new_password);
let mut new_primary_key = secret_key.primary_key.clone();
new_primary_key
.remove_password(&old_pw)
.map_err(|e| Error::Crypto(format!("Failed to unlock primary key: {}", e)))?;
new_primary_key
.set_password(&mut rng, &new_pw)
.map_err(|e| Error::Crypto(format!("Failed to set new password on primary key: {}", e)))?;
let mut new_secret_subkeys = Vec::new();
for subkey in &secret_key.secret_subkeys {
let mut new_subkey = subkey.clone();
new_subkey
.key
.remove_password(&old_pw)
.map_err(|e| Error::Crypto(format!("Failed to unlock subkey: {}", e)))?;
new_subkey
.key
.set_password(&mut rng, &new_pw)
.map_err(|e| Error::Crypto(format!("Failed to set new password on subkey: {}", e)))?;
new_secret_subkeys.push(new_subkey);
}
let new_secret_key = SignedSecretKey::new(
new_primary_key,
secret_key.details.clone(),
secret_key.public_subkeys.clone(),
new_secret_subkeys,
);
secret_key_to_bytes(&new_secret_key)
}
pub fn certify_key(
certifier_data: &[u8],
target_data: &[u8],
certification_type: CertificationType,
user_ids: Option<&[&str]>,
password: &str,
) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let certifier = parse_secret_key(certifier_data)?;
let password = Password::from(password);
let target = parse_public_key(target_data)?;
let sig_type = match certification_type {
CertificationType::Generic => SignatureType::CertGeneric,
CertificationType::Persona => SignatureType::CertPersona,
CertificationType::Casual => SignatureType::CertCasual,
CertificationType::Positive => SignatureType::CertPositive,
};
let uids_to_certify: Vec<&str> = match user_ids {
Some(uids) => uids.to_vec(),
None => {
target
.details
.users
.iter()
.map(|u| std::str::from_utf8(u.id.id()).unwrap_or(""))
.filter(|s| !s.is_empty())
.collect()
}
};
let mut new_users: Vec<SignedUser> = Vec::new();
for signed_user in &target.details.users {
let uid_str = std::str::from_utf8(signed_user.id.id()).unwrap_or("");
let should_certify = uids_to_certify.contains(&uid_str);
if should_certify {
let certified_user = signed_user.id.sign_third_party(
&mut rng,
&certifier.primary_key,
&password,
&target.primary_key,
sig_type,
)?;
let mut combined_sigs = signed_user.signatures.clone();
combined_sigs.extend(certified_user.signatures);
new_users.push(SignedUser::new(signed_user.id.clone(), combined_sigs));
} else {
new_users.push(signed_user.clone());
}
}
let certified_key = SignedPublicKey {
primary_key: target.primary_key.clone(),
details: pgp::composed::SignedKeyDetails::new(
target.details.revocation_signatures.clone(),
target.details.direct_signatures.clone(),
new_users,
target.details.user_attributes.clone(),
),
public_subkeys: target.public_subkeys.clone(),
};
public_key_to_armored(&certified_key).map(|s| s.into_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subkey_flags() {
let flags = SubkeyFlags::all();
assert!(flags.encryption);
assert!(flags.signing);
assert!(flags.authentication);
let flags = SubkeyFlags::from_bitmask(3);
assert!(flags.encryption);
assert!(flags.signing);
assert!(!flags.authentication);
}
}