mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 13:36:30 +03:00
648 lines
20 KiB
Rust
648 lines
20 KiB
Rust
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
||
|
||
use std::collections::{BTreeMap, HashSet};
|
||
use std::io;
|
||
use std::io::Cursor;
|
||
|
||
use anyhow::{bail, Context as _, Result};
|
||
use deltachat_contact_tools::EmailAddress;
|
||
use pgp::armor::BlockType;
|
||
use pgp::composed::{
|
||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||
SignedPublicSubKey, SignedSecretKey, StandaloneSignature, SubkeyParamsBuilder,
|
||
};
|
||
use pgp::crypto::ecc_curve::ECCCurve;
|
||
use pgp::crypto::hash::HashAlgorithm;
|
||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||
use pgp::types::{CompressionAlgorithm, PublicKeyTrait, SignatureBytes, StringToKey};
|
||
use rand::{thread_rng, CryptoRng, Rng};
|
||
use tokio::runtime::Handle;
|
||
|
||
use crate::constants::KeyGenType;
|
||
use crate::key::{DcKey, Fingerprint};
|
||
|
||
#[allow(missing_docs)]
|
||
#[cfg(test)]
|
||
pub(crate) const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
|
||
|
||
#[allow(missing_docs)]
|
||
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||
|
||
/// Preferred symmetric encryption algorithm.
|
||
const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
|
||
|
||
/// Preferred cryptographic hash.
|
||
const HASH_ALGORITHM: HashAlgorithm = HashAlgorithm::SHA2_256;
|
||
|
||
/// A wrapper for rPGP public key types
|
||
#[derive(Debug)]
|
||
enum SignedPublicKeyOrSubkey<'a> {
|
||
Key(&'a SignedPublicKey),
|
||
Subkey(&'a SignedPublicSubKey),
|
||
}
|
||
|
||
impl PublicKeyTrait for SignedPublicKeyOrSubkey<'_> {
|
||
fn version(&self) -> pgp::types::KeyVersion {
|
||
match self {
|
||
Self::Key(k) => k.version(),
|
||
Self::Subkey(k) => k.version(),
|
||
}
|
||
}
|
||
|
||
fn fingerprint(&self) -> pgp::types::Fingerprint {
|
||
match self {
|
||
Self::Key(k) => k.fingerprint(),
|
||
Self::Subkey(k) => k.fingerprint(),
|
||
}
|
||
}
|
||
|
||
fn key_id(&self) -> pgp::types::KeyId {
|
||
match self {
|
||
Self::Key(k) => k.key_id(),
|
||
Self::Subkey(k) => k.key_id(),
|
||
}
|
||
}
|
||
|
||
fn algorithm(&self) -> pgp::crypto::public_key::PublicKeyAlgorithm {
|
||
match self {
|
||
Self::Key(k) => k.algorithm(),
|
||
Self::Subkey(k) => k.algorithm(),
|
||
}
|
||
}
|
||
|
||
fn created_at(&self) -> &chrono::DateTime<chrono::Utc> {
|
||
match self {
|
||
Self::Key(k) => k.created_at(),
|
||
Self::Subkey(k) => k.created_at(),
|
||
}
|
||
}
|
||
|
||
fn expiration(&self) -> Option<u16> {
|
||
match self {
|
||
Self::Key(k) => k.expiration(),
|
||
Self::Subkey(k) => k.expiration(),
|
||
}
|
||
}
|
||
|
||
fn verify_signature(
|
||
&self,
|
||
hash: HashAlgorithm,
|
||
data: &[u8],
|
||
sig: &SignatureBytes,
|
||
) -> pgp::errors::Result<()> {
|
||
match self {
|
||
Self::Key(k) => k.verify_signature(hash, data, sig),
|
||
Self::Subkey(k) => k.verify_signature(hash, data, sig),
|
||
}
|
||
}
|
||
|
||
fn encrypt<R: Rng + CryptoRng>(
|
||
&self,
|
||
rng: R,
|
||
plain: &[u8],
|
||
typ: pgp::types::EskType,
|
||
) -> pgp::errors::Result<pgp::types::PkeskBytes> {
|
||
match self {
|
||
Self::Key(k) => k.encrypt(rng, plain, typ),
|
||
Self::Subkey(k) => k.encrypt(rng, plain, typ),
|
||
}
|
||
}
|
||
|
||
fn serialize_for_hashing(&self, writer: &mut impl io::Write) -> pgp::errors::Result<()> {
|
||
match self {
|
||
Self::Key(k) => k.serialize_for_hashing(writer),
|
||
Self::Subkey(k) => k.serialize_for_hashing(writer),
|
||
}
|
||
}
|
||
|
||
fn public_params(&self) -> &pgp::types::PublicParams {
|
||
match self {
|
||
Self::Key(k) => k.public_params(),
|
||
Self::Subkey(k) => k.public_params(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Split data from PGP Armored Data as defined in <https://tools.ietf.org/html/rfc4880#section-6.2>.
|
||
///
|
||
/// Returns (type, headers, base64 encoded body).
|
||
pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>)> {
|
||
use std::io::Read;
|
||
|
||
let cursor = Cursor::new(buf);
|
||
let mut dearmor = pgp::armor::Dearmor::new(cursor);
|
||
|
||
let mut bytes = Vec::with_capacity(buf.len());
|
||
|
||
dearmor.read_to_end(&mut bytes)?;
|
||
let typ = dearmor.typ.context("failed to parse type")?;
|
||
|
||
// normalize headers
|
||
let headers = dearmor
|
||
.headers
|
||
.into_iter()
|
||
.map(|(key, values)| {
|
||
(
|
||
key.trim().to_lowercase(),
|
||
values
|
||
.last()
|
||
.map_or_else(String::new, |s| s.trim().to_string()),
|
||
)
|
||
})
|
||
.collect();
|
||
|
||
Ok((typ, headers, bytes))
|
||
}
|
||
|
||
/// A PGP keypair.
|
||
///
|
||
/// This has it's own struct to be able to keep the public and secret
|
||
/// keys together as they are one unit.
|
||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||
pub struct KeyPair {
|
||
/// Public key.
|
||
pub public: SignedPublicKey,
|
||
|
||
/// Secret key.
|
||
pub secret: SignedSecretKey,
|
||
}
|
||
|
||
impl KeyPair {
|
||
/// Creates new keypair from a secret key.
|
||
///
|
||
/// Public key is split off the secret key.
|
||
pub fn new(secret: SignedSecretKey) -> Result<Self> {
|
||
use crate::key::DcSecretKey;
|
||
|
||
let public = secret.split_public_key()?;
|
||
Ok(Self { public, secret })
|
||
}
|
||
}
|
||
|
||
/// Create a new key pair.
|
||
///
|
||
/// Both secret and public key consist of signing primary key and encryption subkey
|
||
/// as [described in the Autocrypt standard](https://autocrypt.org/level1.html#openpgp-based-key-data).
|
||
pub(crate) fn create_keypair(addr: EmailAddress, keygen_type: KeyGenType) -> Result<KeyPair> {
|
||
let (signing_key_type, encryption_key_type) = match keygen_type {
|
||
KeyGenType::Rsa2048 => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)),
|
||
KeyGenType::Rsa4096 => (PgpKeyType::Rsa(4096), PgpKeyType::Rsa(4096)),
|
||
KeyGenType::Ed25519 | KeyGenType::Default => (
|
||
PgpKeyType::EdDSALegacy,
|
||
PgpKeyType::ECDH(ECCCurve::Curve25519),
|
||
),
|
||
};
|
||
|
||
let user_id = format!("<{addr}>");
|
||
let key_params = SecretKeyParamsBuilder::default()
|
||
.key_type(signing_key_type)
|
||
.can_certify(true)
|
||
.can_sign(true)
|
||
.primary_user_id(user_id)
|
||
.passphrase(None)
|
||
.preferred_symmetric_algorithms(smallvec![
|
||
SymmetricKeyAlgorithm::AES256,
|
||
SymmetricKeyAlgorithm::AES192,
|
||
SymmetricKeyAlgorithm::AES128,
|
||
])
|
||
.preferred_hash_algorithms(smallvec![
|
||
HashAlgorithm::SHA2_256,
|
||
HashAlgorithm::SHA2_384,
|
||
HashAlgorithm::SHA2_512,
|
||
HashAlgorithm::SHA2_224,
|
||
HashAlgorithm::SHA1,
|
||
])
|
||
.preferred_compression_algorithms(smallvec![
|
||
CompressionAlgorithm::ZLIB,
|
||
CompressionAlgorithm::ZIP,
|
||
])
|
||
.subkey(
|
||
SubkeyParamsBuilder::default()
|
||
.key_type(encryption_key_type)
|
||
.can_encrypt(true)
|
||
.passphrase(None)
|
||
.build()
|
||
.context("failed to build subkey parameters")?,
|
||
)
|
||
.build()
|
||
.context("failed to build key parameters")?;
|
||
|
||
let mut rng = thread_rng();
|
||
let secret_key = key_params
|
||
.generate(&mut rng)
|
||
.context("failed to generate the key")?
|
||
.sign(&mut rng, || "".into())
|
||
.context("failed to sign secret key")?;
|
||
secret_key
|
||
.verify()
|
||
.context("invalid secret key generated")?;
|
||
|
||
let key_pair = KeyPair::new(secret_key)?;
|
||
key_pair
|
||
.public
|
||
.verify()
|
||
.context("invalid public key generated")?;
|
||
Ok(key_pair)
|
||
}
|
||
|
||
/// Select public key or subkey to use for encryption.
|
||
///
|
||
/// First, tries to use subkeys. If none of the subkeys are suitable
|
||
/// for encryption, tries to use primary key. Returns `None` if the public
|
||
/// key cannot be used for encryption.
|
||
///
|
||
/// TODO: take key flags and expiration dates into account
|
||
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<SignedPublicKeyOrSubkey> {
|
||
key.public_subkeys
|
||
.iter()
|
||
.find(|subkey| subkey.is_encryption_key())
|
||
.map_or_else(
|
||
|| {
|
||
// No usable subkey found, try primary key
|
||
if key.is_encryption_key() {
|
||
Some(SignedPublicKeyOrSubkey::Key(key))
|
||
} else {
|
||
None
|
||
}
|
||
},
|
||
|subkey| Some(SignedPublicKeyOrSubkey::Subkey(subkey)),
|
||
)
|
||
}
|
||
|
||
/// Encrypts `plain` text using `public_keys_for_encryption`
|
||
/// and signs it using `private_key_for_signing`.
|
||
pub async fn pk_encrypt(
|
||
plain: &[u8],
|
||
public_keys_for_encryption: Vec<SignedPublicKey>,
|
||
private_key_for_signing: Option<SignedSecretKey>,
|
||
compress: bool,
|
||
) -> Result<String> {
|
||
let lit_msg = Message::new_literal_bytes("", plain);
|
||
|
||
Handle::current()
|
||
.spawn_blocking(move || {
|
||
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
|
||
.iter()
|
||
.filter_map(select_pk_for_encryption)
|
||
.collect();
|
||
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
|
||
|
||
let mut rng = thread_rng();
|
||
|
||
let encrypted_msg = if let Some(ref skey) = private_key_for_signing {
|
||
let signed_msg = lit_msg.sign(&mut rng, skey, || "".into(), HASH_ALGORITHM)?;
|
||
let compressed_msg = if compress {
|
||
signed_msg.compress(CompressionAlgorithm::ZLIB)?
|
||
} else {
|
||
signed_msg
|
||
};
|
||
compressed_msg.encrypt_to_keys_seipdv1(
|
||
&mut rng,
|
||
SYMMETRIC_KEY_ALGORITHM,
|
||
&pkeys_refs,
|
||
)?
|
||
} else {
|
||
lit_msg.encrypt_to_keys_seipdv1(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
|
||
};
|
||
|
||
let encoded_msg = encrypted_msg.to_armored_string(Default::default())?;
|
||
|
||
Ok(encoded_msg)
|
||
})
|
||
.await?
|
||
}
|
||
|
||
/// Signs `plain` text using `private_key_for_signing`.
|
||
pub fn pk_calc_signature(
|
||
plain: &[u8],
|
||
private_key_for_signing: &SignedSecretKey,
|
||
) -> Result<String> {
|
||
let mut rng = thread_rng();
|
||
let msg = Message::new_literal_bytes("", plain).sign(
|
||
&mut rng,
|
||
private_key_for_signing,
|
||
|| "".into(),
|
||
HASH_ALGORITHM,
|
||
)?;
|
||
let signature = msg.into_signature().to_armored_string(Default::default())?;
|
||
Ok(signature)
|
||
}
|
||
|
||
/// Decrypts the message with keys from the private key keyring.
|
||
///
|
||
/// Receiver private keys are provided in
|
||
/// `private_keys_for_decryption`.
|
||
pub fn pk_decrypt(
|
||
ctext: Vec<u8>,
|
||
private_keys_for_decryption: &[SignedSecretKey],
|
||
) -> Result<pgp::composed::Message> {
|
||
let cursor = Cursor::new(ctext);
|
||
let (msg, _headers) = Message::from_armor_single(cursor)?;
|
||
|
||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
|
||
|
||
let (msg, _key_ids) = msg.decrypt(|| "".into(), &skeys[..])?;
|
||
|
||
// get_content() will decompress the message if needed,
|
||
// but this avoids decompressing it again to check signatures
|
||
let msg = msg.decompress()?;
|
||
|
||
Ok(msg)
|
||
}
|
||
|
||
/// Returns fingerprints
|
||
/// of all keys from the `public_keys_for_validation` keyring that
|
||
/// have valid signatures there.
|
||
///
|
||
/// If the message is wrongly signed, HashSet will be empty.
|
||
pub fn valid_signature_fingerprints(
|
||
msg: &pgp::composed::Message,
|
||
public_keys_for_validation: &[SignedPublicKey],
|
||
) -> Result<HashSet<Fingerprint>> {
|
||
let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
|
||
if let signed_msg @ pgp::composed::Message::Signed { .. } = msg {
|
||
for pkey in public_keys_for_validation {
|
||
if signed_msg.verify(&pkey.primary_key).is_ok() {
|
||
let fp = pkey.dc_fingerprint();
|
||
ret_signature_fingerprints.insert(fp);
|
||
}
|
||
}
|
||
}
|
||
Ok(ret_signature_fingerprints)
|
||
}
|
||
|
||
/// Validates detached signature.
|
||
pub fn pk_validate(
|
||
content: &[u8],
|
||
signature: &[u8],
|
||
public_keys_for_validation: &[SignedPublicKey],
|
||
) -> Result<HashSet<Fingerprint>> {
|
||
let mut ret: HashSet<Fingerprint> = Default::default();
|
||
|
||
let standalone_signature = StandaloneSignature::from_armor_single(Cursor::new(signature))?.0;
|
||
|
||
// Remove trailing CRLF before the delimiter.
|
||
// According to RFC 3156 it is considered to be part of the MIME delimiter for the purpose of
|
||
// OpenPGP signature calculation.
|
||
let content = content
|
||
.get(..content.len().saturating_sub(2))
|
||
.context("index is out of range")?;
|
||
|
||
for pkey in public_keys_for_validation {
|
||
if standalone_signature.verify(pkey, content).is_ok() {
|
||
let fp = pkey.dc_fingerprint();
|
||
ret.insert(fp);
|
||
}
|
||
}
|
||
Ok(ret)
|
||
}
|
||
|
||
/// Symmetric encryption.
|
||
pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||
let lit_msg = Message::new_literal_bytes("", plain);
|
||
let passphrase = passphrase.to_string();
|
||
|
||
tokio::task::spawn_blocking(move || {
|
||
let mut rng = thread_rng();
|
||
let s2k = StringToKey::new_default(&mut rng);
|
||
let msg = lit_msg.encrypt_with_password_seipdv1(
|
||
&mut rng,
|
||
s2k,
|
||
SYMMETRIC_KEY_ALGORITHM,
|
||
|| passphrase,
|
||
)?;
|
||
|
||
let encoded_msg = msg.to_armored_string(Default::default())?;
|
||
|
||
Ok(encoded_msg)
|
||
})
|
||
.await?
|
||
}
|
||
|
||
/// Symmetric decryption.
|
||
pub async fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||
passphrase: &str,
|
||
ctext: T,
|
||
) -> Result<Vec<u8>> {
|
||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||
|
||
let passphrase = passphrase.to_string();
|
||
tokio::task::spawn_blocking(move || {
|
||
let msg = enc_msg.decrypt_with_password(|| passphrase)?;
|
||
|
||
match msg.get_content()? {
|
||
Some(content) => Ok(content),
|
||
None => bail!("Decrypted message is empty"),
|
||
}
|
||
})
|
||
.await?
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use once_cell::sync::Lazy;
|
||
use tokio::sync::OnceCell;
|
||
|
||
use super::*;
|
||
use crate::test_utils::{alice_keypair, bob_keypair};
|
||
|
||
fn pk_decrypt_and_validate(
|
||
ctext: Vec<u8>,
|
||
private_keys_for_decryption: &[SignedSecretKey],
|
||
public_keys_for_validation: &[SignedPublicKey],
|
||
) -> Result<(pgp::composed::Message, HashSet<Fingerprint>)> {
|
||
let msg = pk_decrypt(ctext, private_keys_for_decryption)?;
|
||
let ret_signature_fingerprints =
|
||
valid_signature_fingerprints(&msg, public_keys_for_validation)?;
|
||
|
||
Ok((msg, ret_signature_fingerprints))
|
||
}
|
||
|
||
#[test]
|
||
fn test_split_armored_data_1() {
|
||
let (typ, _headers, base64) = split_armored_data(
|
||
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE-----",
|
||
)
|
||
.unwrap();
|
||
|
||
assert_eq!(typ, BlockType::Message);
|
||
assert!(!base64.is_empty());
|
||
assert_eq!(
|
||
std::string::String::from_utf8(base64).unwrap(),
|
||
"hello world"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_split_armored_data_2() {
|
||
let (typ, headers, base64) = split_armored_data(
|
||
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
|
||
)
|
||
.unwrap();
|
||
|
||
assert_eq!(typ, BlockType::PrivateKey);
|
||
assert!(!base64.is_empty());
|
||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||
}
|
||
|
||
#[test]
|
||
fn test_create_keypair() {
|
||
let keypair0 = create_keypair(
|
||
EmailAddress::new("foo@bar.de").unwrap(),
|
||
KeyGenType::Default,
|
||
)
|
||
.unwrap();
|
||
let keypair1 = create_keypair(
|
||
EmailAddress::new("two@zwo.de").unwrap(),
|
||
KeyGenType::Default,
|
||
)
|
||
.unwrap();
|
||
assert_ne!(keypair0.public, keypair1.public);
|
||
}
|
||
|
||
/// [SignedSecretKey] and [SignedPublicKey] objects
|
||
/// to use in tests.
|
||
struct TestKeys {
|
||
alice_secret: SignedSecretKey,
|
||
alice_public: SignedPublicKey,
|
||
bob_secret: SignedSecretKey,
|
||
bob_public: SignedPublicKey,
|
||
}
|
||
|
||
impl TestKeys {
|
||
fn new() -> TestKeys {
|
||
let alice = alice_keypair();
|
||
let bob = bob_keypair();
|
||
TestKeys {
|
||
alice_secret: alice.secret.clone(),
|
||
alice_public: alice.public,
|
||
bob_secret: bob.secret.clone(),
|
||
bob_public: bob.public,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The original text of [CTEXT_SIGNED]
|
||
static CLEARTEXT: &[u8] = b"This is a test";
|
||
|
||
/// Initialised [TestKeys] for tests.
|
||
static KEYS: Lazy<TestKeys> = Lazy::new(TestKeys::new);
|
||
|
||
static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
|
||
static CTEXT_UNSIGNED: OnceCell<String> = OnceCell::const_new();
|
||
|
||
/// A ciphertext encrypted to Alice & Bob, signed by Alice.
|
||
async fn ctext_signed() -> &'static String {
|
||
CTEXT_SIGNED
|
||
.get_or_init(|| async {
|
||
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
|
||
let compress = true;
|
||
|
||
pk_encrypt(
|
||
CLEARTEXT,
|
||
keyring,
|
||
Some(KEYS.alice_secret.clone()),
|
||
compress,
|
||
)
|
||
.await
|
||
.unwrap()
|
||
})
|
||
.await
|
||
}
|
||
|
||
/// A ciphertext encrypted to Alice & Bob, not signed.
|
||
async fn ctext_unsigned() -> &'static String {
|
||
CTEXT_UNSIGNED
|
||
.get_or_init(|| async {
|
||
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
|
||
let compress = true;
|
||
|
||
pk_encrypt(CLEARTEXT, keyring, None, compress)
|
||
.await
|
||
.unwrap()
|
||
})
|
||
.await
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_encrypt_signed() {
|
||
assert!(!ctext_signed().await.is_empty());
|
||
assert!(ctext_signed()
|
||
.await
|
||
.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_encrypt_unsigned() {
|
||
assert!(!ctext_unsigned().await.is_empty());
|
||
assert!(ctext_unsigned()
|
||
.await
|
||
.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_decrypt_singed() {
|
||
// Check decrypting as Alice
|
||
let decrypt_keyring = vec![KEYS.alice_secret.clone()];
|
||
let sig_check_keyring = vec![KEYS.alice_public.clone()];
|
||
let (msg, valid_signatures) = pk_decrypt_and_validate(
|
||
ctext_signed().await.as_bytes().to_vec(),
|
||
&decrypt_keyring,
|
||
&sig_check_keyring,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||
assert_eq!(valid_signatures.len(), 1);
|
||
|
||
// Check decrypting as Bob
|
||
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
|
||
let sig_check_keyring = vec![KEYS.alice_public.clone()];
|
||
let (msg, valid_signatures) = pk_decrypt_and_validate(
|
||
ctext_signed().await.as_bytes().to_vec(),
|
||
&decrypt_keyring,
|
||
&sig_check_keyring,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||
assert_eq!(valid_signatures.len(), 1);
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_decrypt_no_sig_check() {
|
||
let keyring = vec![KEYS.alice_secret.clone()];
|
||
let (msg, valid_signatures) =
|
||
pk_decrypt_and_validate(ctext_signed().await.as_bytes().to_vec(), &keyring, &[])
|
||
.unwrap();
|
||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||
assert_eq!(valid_signatures.len(), 0);
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_decrypt_signed_no_key() {
|
||
// The validation does not have the public key of the signer.
|
||
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
|
||
let sig_check_keyring = vec![KEYS.bob_public.clone()];
|
||
let (msg, valid_signatures) = pk_decrypt_and_validate(
|
||
ctext_signed().await.as_bytes().to_vec(),
|
||
&decrypt_keyring,
|
||
&sig_check_keyring,
|
||
)
|
||
.unwrap();
|
||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||
assert_eq!(valid_signatures.len(), 0);
|
||
}
|
||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||
async fn test_decrypt_unsigned() {
|
||
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
|
||
let (msg, valid_signatures) = pk_decrypt_and_validate(
|
||
ctext_unsigned().await.as_bytes().to_vec(),
|
||
&decrypt_keyring,
|
||
&[],
|
||
)
|
||
.unwrap();
|
||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||
assert_eq!(valid_signatures.len(), 0);
|
||
}
|
||
}
|