use std::io::Cursor;
use std::path::Path;
use pgp::composed::{Deserializable, SignedPublicKey};
use pgp::packet::Signature;
use pgp::ser::Serialize;
use pgp::types::KeyDetails;
use crate::error::{Error, Result};
use crate::internal::{fingerprint_to_hex, parse_cert, public_key_to_armored};
use crate::parse::parse_cert_bytes;
use crate::types::CertificateInfo;
pub fn parse_keyring_file(path: impl AsRef<Path>) -> Result<Vec<(CertificateInfo, Vec<u8>)>> {
let keyring_data = std::fs::read(path.as_ref())?;
parse_keyring_bytes(&keyring_data)
}
pub fn parse_keyring_bytes(data: &[u8]) -> Result<Vec<(CertificateInfo, Vec<u8>)>> {
let mut results = Vec::new();
let cursor = Cursor::new(data);
let (keys_iter, _headers) =
SignedPublicKey::from_reader_many(cursor).map_err(|e| Error::Parse(e.to_string()))?;
for key_result in keys_iter {
match key_result {
Ok(key) => {
let bytes = key.to_bytes().map_err(|e| Error::Crypto(e.to_string()))?;
let info = parse_cert_bytes(&bytes, true)?;
results.push((info, bytes));
}
Err(e) => {
eprintln!("Warning: failed to parse certificate: {}", e);
}
}
}
Ok(results)
}
pub fn export_keyring_file(certs: &[&[u8]], output: impl AsRef<Path>) -> Result<()> {
let mut keyring_data = Vec::new();
for cert_data in certs {
let (public_key, _is_secret) = parse_cert(cert_data)?;
let bytes = public_key
.to_bytes()
.map_err(|e| Error::Crypto(e.to_string()))?;
keyring_data.extend_from_slice(&bytes);
}
std::fs::write(output.as_ref(), keyring_data)?;
Ok(())
}
pub fn export_keyring_armored(certs: &[&[u8]]) -> Result<String> {
let mut all_armored = String::new();
for cert_data in certs {
let (public_key, _is_secret) = parse_cert(cert_data)?;
let armored = public_key_to_armored(&public_key)?;
all_armored.push_str(&armored);
all_armored.push('\n');
}
Ok(all_armored)
}
pub fn merge_keys(cert_data: &[u8], update_data: &[u8], force: bool) -> Result<Vec<u8>> {
let (mut orig, _) = parse_cert(cert_data)?;
let (update, _) = parse_cert(update_data)?;
let fp1 = fingerprint_to_hex(&orig.primary_key);
let fp2 = fingerprint_to_hex(&update.primary_key);
if fp1 != fp2 && !force {
return Err(Error::InvalidInput(format!(
"Certificate fingerprints do not match: {} vs {}",
fp1, fp2
)));
}
merge_cert(&mut orig, update);
orig.to_bytes().map_err(|e| Error::Crypto(e.to_string()))
}
fn merge_cert(orig: &mut SignedPublicKey, update: SignedPublicKey) {
merge_signatures(&mut orig.details.direct_signatures, update.details.direct_signatures);
merge_signatures(
&mut orig.details.revocation_signatures,
update.details.revocation_signatures,
);
for sk_update in update.public_subkeys {
if let Some(existing) = orig
.public_subkeys
.iter_mut()
.find(|sk| sk.fingerprint() == sk_update.fingerprint())
{
merge_signatures(&mut existing.signatures, sk_update.signatures);
} else {
orig.public_subkeys.push(sk_update);
}
}
for uid_update in update.details.users {
if let Some(existing) = orig
.details
.users
.iter_mut()
.find(|u| u.id.id() == uid_update.id.id())
{
merge_signatures(&mut existing.signatures, uid_update.signatures);
} else {
orig.details.users.push(uid_update);
}
}
for attr_update in update.details.user_attributes {
if let Some(existing) = orig
.details
.user_attributes
.iter_mut()
.find(|a| a.attr == attr_update.attr)
{
merge_signatures(&mut existing.signatures, attr_update.signatures);
} else {
orig.details.user_attributes.push(attr_update);
}
}
}
fn signature_bytes_eq(a: &Signature, b: &Signature) -> bool {
if let (Some(sb1), Some(sb2)) = (a.signature(), b.signature()) {
sb1 == sb2
} else {
a == b
}
}
fn merge_signatures(target: &mut Vec<Signature>, updates: Vec<Signature>) {
for upd in updates {
if let Some(existing) = target.iter_mut().find(|s| signature_bytes_eq(s, &upd)) {
merge_unhashed(existing, &upd);
} else {
target.push(upd);
}
}
}
fn merge_unhashed(target: &mut Signature, source: &Signature) {
let mut inserts = Vec::new();
if let (Some(c1), Some(c2)) = (target.config(), source.config()) {
for (pos, sub) in c2.unhashed_subpackets.iter().enumerate() {
if !c1.unhashed_subpackets.contains(sub) {
inserts.push((pos, sub.clone()));
}
}
}
for (pos, sp) in inserts {
let _ = target.unhashed_subpacket_insert(pos, sp);
}
}
#[cfg(test)]
mod tests {
}