mirror of
https://github.com/chatmail/core.git
synced 2026-05-06 06:46:35 +03:00
build: update to rPGP 0.16.0 (#6719)
Co-authored-by: Heiko Schaefer <heiko@schaefer.name> Co-authored-by: link2xt <link2xt@testrun.org>
This commit is contained in:
committed by
GitHub
parent
42975b2ff3
commit
5c2af42cdd
218
src/pgp.rs
218
src/pgp.rs
@@ -1,19 +1,22 @@
|
||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::io::Cursor;
|
||||
use std::io::{BufRead, Cursor};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use anyhow::{Context as _, Result};
|
||||
use chrono::SubsecRound;
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use pgp::armor::BlockType;
|
||||
use pgp::composed::{
|
||||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||||
SignedPublicSubKey, SignedSecretKey, StandaloneSignature, SubkeyParamsBuilder,
|
||||
ArmorOptions, Deserializable, KeyType as PgpKeyType, Message, MessageBuilder,
|
||||
SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
|
||||
StandaloneSignature, SubkeyParamsBuilder, TheRing,
|
||||
};
|
||||
use pgp::crypto::ecc_curve::ECCCurve;
|
||||
use pgp::crypto::hash::HashAlgorithm;
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
use pgp::types::{CompressionAlgorithm, PublicKeyTrait, StringToKey};
|
||||
use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
|
||||
use pgp::types::{CompressionAlgorithm, KeyDetails, Password, PublicKeyTrait, StringToKey};
|
||||
use rand::thread_rng;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
@@ -28,7 +31,7 @@ pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||||
const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
|
||||
|
||||
/// Preferred cryptographic hash.
|
||||
const HASH_ALGORITHM: HashAlgorithm = HashAlgorithm::SHA2_256;
|
||||
const HASH_ALGORITHM: HashAlgorithm = HashAlgorithm::Sha256;
|
||||
|
||||
/// Split data from PGP Armored Data as defined in <https://tools.ietf.org/html/rfc4880#section-6.2>.
|
||||
///
|
||||
@@ -91,7 +94,7 @@ impl KeyPair {
|
||||
/// 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) -> Result<KeyPair> {
|
||||
let signing_key_type = PgpKeyType::EdDSALegacy;
|
||||
let signing_key_type = PgpKeyType::Ed25519Legacy;
|
||||
let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
|
||||
|
||||
let user_id = format!("<{addr}>");
|
||||
@@ -107,11 +110,10 @@ pub(crate) fn create_keypair(addr: EmailAddress) -> Result<KeyPair> {
|
||||
SymmetricKeyAlgorithm::AES128,
|
||||
])
|
||||
.preferred_hash_algorithms(smallvec![
|
||||
HashAlgorithm::SHA2_256,
|
||||
HashAlgorithm::SHA2_384,
|
||||
HashAlgorithm::SHA2_512,
|
||||
HashAlgorithm::SHA2_224,
|
||||
HashAlgorithm::SHA1,
|
||||
HashAlgorithm::Sha256,
|
||||
HashAlgorithm::Sha384,
|
||||
HashAlgorithm::Sha512,
|
||||
HashAlgorithm::Sha224,
|
||||
])
|
||||
.preferred_compression_algorithms(smallvec![
|
||||
CompressionAlgorithm::ZLIB,
|
||||
@@ -132,7 +134,7 @@ pub(crate) fn create_keypair(addr: EmailAddress) -> Result<KeyPair> {
|
||||
let secret_key = key_params
|
||||
.generate(&mut rng)
|
||||
.context("failed to generate the key")?
|
||||
.sign(&mut rng, || "".into())
|
||||
.sign(&mut rng, &Password::empty())
|
||||
.context("failed to sign secret key")?;
|
||||
secret_key
|
||||
.verify()
|
||||
@@ -160,55 +162,73 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey
|
||||
/// Encrypts `plain` text using `public_keys_for_encryption`
|
||||
/// and signs it using `private_key_for_signing`.
|
||||
pub async fn pk_encrypt(
|
||||
plain: &[u8],
|
||||
plain: Vec<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<&SignedPublicSubKey> = public_keys_for_encryption
|
||||
.iter()
|
||||
.filter_map(select_pk_for_encryption)
|
||||
.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)?
|
||||
} else {
|
||||
lit_msg.encrypt_to_keys_seipdv1(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys)?
|
||||
};
|
||||
let pkeys = public_keys_for_encryption
|
||||
.iter()
|
||||
.filter_map(select_pk_for_encryption);
|
||||
|
||||
let encoded_msg = encrypted_msg.to_armored_string(Default::default())?;
|
||||
let msg = MessageBuilder::from_bytes("", plain);
|
||||
let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
|
||||
for pkey in pkeys {
|
||||
msg.encrypt_to_key(&mut rng, &pkey)?;
|
||||
}
|
||||
|
||||
if let Some(ref skey) = private_key_for_signing {
|
||||
msg.sign(&**skey, Password::empty(), HASH_ALGORITHM);
|
||||
if compress {
|
||||
msg.compression(CompressionAlgorithm::ZLIB);
|
||||
}
|
||||
}
|
||||
|
||||
let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Signs `plain` text using `private_key_for_signing`.
|
||||
/// Produces a detached signature for `plain` text using `private_key_for_signing`.
|
||||
pub fn pk_calc_signature(
|
||||
plain: &[u8],
|
||||
plain: Vec<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 rng = thread_rng();
|
||||
|
||||
let mut config = SignatureConfig::from_key(
|
||||
rng,
|
||||
&private_key_for_signing.primary_key,
|
||||
SignatureType::Binary,
|
||||
)?;
|
||||
let signature = msg.into_signature().to_armored_string(Default::default())?;
|
||||
Ok(signature)
|
||||
|
||||
config.hashed_subpackets = vec![
|
||||
Subpacket::regular(SubpacketData::IssuerFingerprint(
|
||||
private_key_for_signing.fingerprint(),
|
||||
))?,
|
||||
Subpacket::critical(SubpacketData::SignatureCreationTime(
|
||||
chrono::Utc::now().trunc_subsecs(0),
|
||||
))?,
|
||||
];
|
||||
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(
|
||||
private_key_for_signing.key_id(),
|
||||
))?];
|
||||
|
||||
let signature = config.sign(
|
||||
&private_key_for_signing.primary_key,
|
||||
&Password::empty(),
|
||||
plain.as_slice(),
|
||||
)?;
|
||||
|
||||
let sig = StandaloneSignature::new(signature);
|
||||
|
||||
Ok(sig.to_armored_string(ArmorOptions::default())?)
|
||||
}
|
||||
|
||||
/// Decrypts the message with keys from the private key keyring.
|
||||
@@ -218,16 +238,27 @@ pub fn pk_calc_signature(
|
||||
pub fn pk_decrypt(
|
||||
ctext: Vec<u8>,
|
||||
private_keys_for_decryption: &[SignedSecretKey],
|
||||
) -> Result<pgp::composed::Message> {
|
||||
) -> Result<pgp::composed::Message<'static>> {
|
||||
let cursor = Cursor::new(ctext);
|
||||
let (msg, _headers) = Message::from_armor_single(cursor)?;
|
||||
let (msg, _headers) = Message::from_armor(cursor)?;
|
||||
|
||||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
|
||||
let empty_pw = Password::empty();
|
||||
|
||||
let (msg, _key_ids) = msg.decrypt(|| "".into(), &skeys[..])?;
|
||||
let ring = TheRing {
|
||||
secret_keys: skeys,
|
||||
key_passwords: vec![&empty_pw],
|
||||
message_password: vec![],
|
||||
session_keys: vec![],
|
||||
allow_legacy: false,
|
||||
};
|
||||
let (msg, ring_result) = msg.decrypt_the_ring(ring, true)?;
|
||||
anyhow::ensure!(
|
||||
!ring_result.secret_keys.is_empty(),
|
||||
"decryption failed, no matching secret keys"
|
||||
);
|
||||
|
||||
// get_content() will decompress the message if needed,
|
||||
// but this avoids decompressing it again to check signatures
|
||||
// remove one layer of compression
|
||||
let msg = msg.decompress()?;
|
||||
|
||||
Ok(msg)
|
||||
@@ -243,9 +274,9 @@ pub fn valid_signature_fingerprints(
|
||||
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 {
|
||||
if msg.is_signed() {
|
||||
for pkey in public_keys_for_validation {
|
||||
if signed_msg.verify(&pkey.primary_key).is_ok() {
|
||||
if msg.verify(&pkey.primary_key).is_ok() {
|
||||
let fp = pkey.dc_fingerprint();
|
||||
ret_signature_fingerprints.insert(fp);
|
||||
}
|
||||
@@ -274,21 +305,17 @@ pub fn pk_validate(
|
||||
}
|
||||
|
||||
/// 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();
|
||||
pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
|
||||
let passphrase = Password::from(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 builder = MessageBuilder::from_bytes("", plain);
|
||||
let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
|
||||
builder.encrypt_with_password(s2k, &passphrase)?;
|
||||
|
||||
let encoded_msg = msg.to_armored_string(Default::default())?;
|
||||
let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
@@ -296,20 +323,18 @@ pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
pub async fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
|
||||
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)?;
|
||||
let (enc_msg, _) = Message::from_armor(ctext)?;
|
||||
let password = Password::from(passphrase);
|
||||
|
||||
match msg.get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
let msg = enc_msg.decrypt_with_password(&password)?;
|
||||
let res = msg.decompress()?.as_data_vec()?;
|
||||
Ok(res)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -322,16 +347,21 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{alice_keypair, bob_keypair};
|
||||
|
||||
fn pk_decrypt_and_validate(
|
||||
ctext: Vec<u8>,
|
||||
private_keys_for_decryption: &[SignedSecretKey],
|
||||
fn pk_decrypt_and_validate<'a>(
|
||||
ctext: &'a [u8],
|
||||
private_keys_for_decryption: &'a [SignedSecretKey],
|
||||
public_keys_for_validation: &[SignedPublicKey],
|
||||
) -> Result<(pgp::composed::Message, HashSet<Fingerprint>)> {
|
||||
let msg = pk_decrypt(ctext, private_keys_for_decryption)?;
|
||||
) -> Result<(
|
||||
pgp::composed::Message<'static>,
|
||||
HashSet<Fingerprint>,
|
||||
Vec<u8>,
|
||||
)> {
|
||||
let mut msg = pk_decrypt(ctext.to_vec(), private_keys_for_decryption)?;
|
||||
let content = msg.as_data_vec()?;
|
||||
let ret_signature_fingerprints =
|
||||
valid_signature_fingerprints(&msg, public_keys_for_validation)?;
|
||||
|
||||
Ok((msg, ret_signature_fingerprints))
|
||||
Ok((msg, ret_signature_fingerprints, content))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -407,7 +437,7 @@ mod tests {
|
||||
let compress = true;
|
||||
|
||||
pk_encrypt(
|
||||
CLEARTEXT,
|
||||
CLEARTEXT.to_vec(),
|
||||
keyring,
|
||||
Some(KEYS.alice_secret.clone()),
|
||||
compress,
|
||||
@@ -425,7 +455,7 @@ mod tests {
|
||||
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
|
||||
let compress = true;
|
||||
|
||||
pk_encrypt(CLEARTEXT, keyring, None, compress)
|
||||
pk_encrypt(CLEARTEXT.to_vec(), keyring, None, compress)
|
||||
.await
|
||||
.unwrap()
|
||||
})
|
||||
@@ -453,35 +483,34 @@ mod tests {
|
||||
// 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(),
|
||||
let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
|
||||
ctext_signed().await.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||||
assert_eq!(content, 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(),
|
||||
let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
|
||||
ctext_signed().await.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||||
assert_eq!(content, 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);
|
||||
let (_msg, valid_signatures, content) =
|
||||
pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[]).unwrap();
|
||||
assert_eq!(content, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
|
||||
@@ -490,26 +519,23 @@ mod tests {
|
||||
// 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(),
|
||||
let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
|
||||
ctext_signed().await.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(msg.get_content().unwrap().unwrap(), CLEARTEXT);
|
||||
assert_eq!(content, 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);
|
||||
let (_msg, valid_signatures, content) =
|
||||
pk_decrypt_and_validate(ctext_unsigned().await.as_bytes(), &decrypt_keyring, &[])
|
||||
.unwrap();
|
||||
assert_eq!(content, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user