Skip to main content

kernoid_e2e/
message.rs

1// SPDX-License-Identifier: MIT
2//! Encrypting data to keys and decrypting it (RFC 0006): OpenPGP messages, AEAD (SEIPDv2, AES-256-OCB) when every
3//! recipient's key is v6, otherwise SEIPDv1 as v4 programs read it; signed with the sender's key if one is given.
4
5use pgp::{
6    composed::{ArmorOptions, Message, MessageBuilder},
7    crypto::{aead::AeadAlgorithm, hash::HashAlgorithm, sym::SymmetricKeyAlgorithm},
8    types::{KeyDetails, KeyVersion, Password},
9};
10use rand::rngs::OsRng;
11
12use crate::{Error, PublicKey, Result, SecretKey, keys::hex};
13
14/// Encrypts `data` to every encryption subkey of each recipient. Armored for mail, binary for storage.
15pub fn encrypt(data: &[u8], recipients: &[&PublicKey], signer: Option<&SecretKey>, armor: bool) -> Result<Vec<u8>> {
16    if recipients.is_empty() {
17        return Err(Error::NoEncryptionKey);
18    }
19    let subkeys: Vec<_> = recipients.iter().map(|r| r.encryption_subkeys()).collect();
20    if subkeys.iter().any(Vec::is_empty) {
21        return Err(Error::NoEncryptionKey);
22    }
23    let v6 = recipients.iter().all(|r| r.0.version() == KeyVersion::V6);
24    let mut builder = MessageBuilder::from_bytes("", data.to_vec());
25    if let Some(signer) = signer {
26        builder.sign(&signer.0.primary_key, Password::empty(), HashAlgorithm::Sha512);
27    }
28    let out = if v6 {
29        let mut builder =
30            builder.seipd_v2(OsRng, SymmetricKeyAlgorithm::AES256, AeadAlgorithm::Ocb, Default::default());
31        for subkey in subkeys.iter().flatten() {
32            builder.encrypt_to_key(OsRng, subkey)?;
33        }
34        if armor {
35            builder.to_armored_string(OsRng, ArmorOptions::default())?.into_bytes()
36        } else {
37            builder.to_vec(OsRng)?
38        }
39    } else {
40        let mut builder = builder.seipd_v1(OsRng, SymmetricKeyAlgorithm::AES256);
41        for subkey in subkeys.iter().flatten() {
42            builder.encrypt_to_key(OsRng, subkey)?;
43        }
44        if armor {
45            builder.to_armored_string(OsRng, ArmorOptions::default())?.into_bytes()
46        } else {
47            builder.to_vec(OsRng)?
48        }
49    };
50    Ok(out)
51}
52
53/// What a decryption gave.
54#[derive(Debug, Clone)]
55pub struct Decrypted {
56    pub data: Vec<u8>,
57    /// Whether the message was signed at all.
58    pub signed: bool,
59    /// The fingerprint of the key among `senders` whose signature is valid, if any.
60    pub verified_by: Option<String>,
61}
62
63/// Decrypts an OpenPGP message (armored or binary) with any of `keys`, and checks its signature against `senders`.
64pub fn decrypt(data: &[u8], keys: &[&SecretKey], senders: &[&PublicKey]) -> Result<Decrypted> {
65    let message = if is_armored(data) {
66        Message::from_armor(data).map_err(|_| Error::NotEncrypted)?.0
67    } else {
68        Message::from_bytes(data).map_err(|_| Error::NotEncrypted)?
69    };
70    if !message.is_encrypted() {
71        return Err(Error::NotEncrypted);
72    }
73    let passwords: Vec<Password> = keys.iter().map(|_| Password::empty()).collect();
74    let mut message = message
75        .decrypt_with_keys(passwords.iter().collect(), keys.iter().map(|k| &k.0).collect())
76        .map_err(|_| Error::Decrypt)?;
77    if message.is_compressed() {
78        message = message.decompress().map_err(|_| Error::Decrypt)?;
79    }
80    let data = message.as_data_vec().map_err(|_| Error::Decrypt)?;
81    let signed = matches!(message, Message::Signed { .. });
82    let verified_by = signed
83        .then(|| {
84            senders.iter().find_map(|sender| {
85                let keys = std::iter::once(&sender.0 as &dyn pgp::types::VerifyingKey)
86                    .chain(sender.0.public_subkeys.iter().map(|s| s as &dyn pgp::types::VerifyingKey));
87                keys.into_iter().find(|k| message.verify(*k).is_ok()).map(|_| hex(sender.0.fingerprint().as_bytes()))
88            })
89        })
90        .flatten();
91    Ok(Decrypted { data, signed, verified_by })
92}
93
94pub(crate) fn is_armored(data: &[u8]) -> bool {
95    let start = data.iter().position(|b| !b.is_ascii_whitespace()).unwrap_or(0);
96    data[start..].starts_with(b"-----BEGIN PGP")
97}