use std::io::{BufReader, Cursor, Read};
use std::path::Path;
use pgp::armor::Dearmor;
use pgp::composed::{MessageBuilder, SignedPublicKey};
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::packet::{Packet, PacketParser, PublicKeyEncryptedSessionKey};
use pgp::types::KeyDetails;
use rand::thread_rng;
use crate::error::{Error, Result};
use crate::internal::{is_subkey_valid, parse_public_key};
pub fn encrypt_bytes(recipient_cert: &[u8], plaintext: &[u8], armor: bool) -> Result<Vec<u8>> {
encrypt_bytes_to_multiple(&[recipient_cert], plaintext, armor)
}
pub fn encrypt_bytes_to_multiple(
recipient_certs: &[&[u8]],
plaintext: &[u8],
armor: bool,
) -> Result<Vec<u8>> {
if recipient_certs.is_empty() {
return Err(Error::InvalidInput("No recipients specified".to_string()));
}
let mut rng = thread_rng();
let mut encryption_keys = Vec::new();
for cert_data in recipient_certs {
let public_key = parse_public_key(cert_data)?;
let subkeys = find_valid_encryption_subkeys(&public_key)?;
encryption_keys.extend(subkeys);
}
if encryption_keys.is_empty() {
return Err(Error::NoEncryptionSubkey);
}
let mut builder = MessageBuilder::from_bytes("", plaintext.to_vec())
.seipd_v1(&mut rng, SymmetricKeyAlgorithm::AES256);
for key in &encryption_keys {
builder.encrypt_to_key(&mut rng, key)
.map_err(|e| Error::Crypto(e.to_string()))?;
}
if armor {
let armored = builder.to_armored_string(&mut rng, None.into())
.map_err(|e| Error::Crypto(e.to_string()))?;
Ok(armored.into_bytes())
} else {
builder.to_vec(&mut rng)
.map_err(|e| Error::Crypto(e.to_string()))
}
}
pub fn encrypt_file(
recipient_cert: &[u8],
input: impl AsRef<Path>,
output: impl AsRef<Path>,
armor: bool,
) -> Result<()> {
encrypt_file_to_multiple(&[recipient_cert], input, output, armor)
}
pub fn encrypt_file_to_multiple(
recipient_certs: &[&[u8]],
input: impl AsRef<Path>,
output: impl AsRef<Path>,
armor: bool,
) -> Result<()> {
let plaintext = std::fs::read(input.as_ref())?;
let ciphertext = encrypt_bytes_to_multiple(recipient_certs, &plaintext, armor)?;
std::fs::write(output.as_ref(), ciphertext)?;
Ok(())
}
pub fn encrypt_reader_to_file<R: Read>(
recipient_certs: &[&[u8]],
mut reader: R,
output: impl AsRef<Path>,
armor: bool,
) -> Result<()> {
let mut plaintext = Vec::new();
reader.read_to_end(&mut plaintext)?;
let ciphertext = encrypt_bytes_to_multiple(recipient_certs, &plaintext, armor)?;
std::fs::write(output.as_ref(), ciphertext)?;
Ok(())
}
pub fn bytes_encrypted_for(ciphertext: &[u8]) -> Result<Vec<String>> {
let mut key_ids = Vec::new();
let data = if ciphertext.starts_with(b"-----BEGIN PGP") {
let cursor = Cursor::new(ciphertext);
let dearmor = Dearmor::new(cursor);
let mut buf = Vec::new();
let mut reader = BufReader::new(dearmor);
reader.read_to_end(&mut buf)?;
buf
} else {
ciphertext.to_vec()
};
let parser = PacketParser::new(Cursor::new(&data));
for packet_result in parser {
match packet_result {
Ok(packet) => {
if let Packet::PublicKeyEncryptedSessionKey(pkesk) = packet {
let key_id = match pkesk {
PublicKeyEncryptedSessionKey::V3 { id, .. } => {
format!("{}", id).to_uppercase()
}
PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
if let Some(fp) = fingerprint {
format!("{}", fp).to_uppercase()
} else {
continue;
}
}
PublicKeyEncryptedSessionKey::Other { .. } => {
continue;
}
};
key_ids.push(key_id);
}
}
Err(_) => {
break;
}
}
}
Ok(key_ids)
}
pub fn file_encrypted_for(path: impl AsRef<Path>) -> Result<Vec<String>> {
let ciphertext = std::fs::read(path.as_ref())?;
bytes_encrypted_for(&ciphertext)
}
fn find_valid_encryption_subkeys(key: &SignedPublicKey) -> Result<Vec<pgp::composed::SignedPublicSubKey>> {
let mut valid_keys = Vec::new();
for subkey in &key.public_subkeys {
if !subkey.key.algorithm().can_encrypt() {
continue;
}
let has_encryption_flag = subkey.signatures.iter().any(|sig| {
let flags = sig.key_flags();
flags.encrypt_comms() || flags.encrypt_storage()
});
if !has_encryption_flag {
continue;
}
if !is_subkey_valid(subkey, false) {
continue;
}
valid_keys.push(subkey.clone());
}
if valid_keys.is_empty() {
return Err(Error::NoEncryptionSubkey);
}
Ok(valid_keys)
}
#[cfg(test)]
mod tests {
}