mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
feat: Symmetric encryption. No decryption, no sharing of the secret, not tested.
This commit is contained in:
20
src/e2ee.rs
20
src/e2ee.rs
@@ -59,6 +59,26 @@ impl EncryptHelper {
|
||||
Ok(ctext)
|
||||
}
|
||||
|
||||
/// TODO documentation
|
||||
pub async fn encrypt_for_broadcast(
|
||||
self,
|
||||
context: &Context,
|
||||
passphrase: &str,
|
||||
mail_to_encrypt: MimePart<'static>,
|
||||
compress: bool,
|
||||
) -> Result<String> {
|
||||
let sign_key = load_self_secret_key(context).await?;
|
||||
|
||||
let mut raw_message = Vec::new();
|
||||
let cursor = Cursor::new(&mut raw_message);
|
||||
mail_to_encrypt.clone().write_part(cursor).ok();
|
||||
|
||||
let ctext =
|
||||
pgp::encrypt_for_broadcast(raw_message, passphrase, Some(sign_key), compress).await?;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
|
||||
/// Signs the passed-in `mail` using the private key from `context`.
|
||||
/// Returns the payload and the signature.
|
||||
pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result<String> {
|
||||
|
||||
@@ -1117,18 +1117,42 @@ impl MimeFactory {
|
||||
Loaded::Mdn { .. } => true,
|
||||
};
|
||||
|
||||
// Encrypt to self unconditionally,
|
||||
// even for a single-device setup.
|
||||
let mut encryption_keyring = vec![encrypt_helper.public_key.clone()];
|
||||
encryption_keyring.extend(encryption_keys.iter().map(|(_addr, key)| (*key).clone()));
|
||||
let symmetric_key = match &self.loaded {
|
||||
Loaded::Message { chat, .. } if chat.typ == Chattype::OutBroadcast => {
|
||||
// If there is no symmetric key yet
|
||||
// (because this is an old broadcast channel,
|
||||
// created before we had symmetric encryption),
|
||||
// we just encrypt asymmetrically.
|
||||
// Symmetric encryption exists since 2025-08;
|
||||
// some time after that, we can think about requiring everyone
|
||||
// to switch to symmetrically-encrypted broadcast lists.
|
||||
chat.param.get(Param::SymmetricKey)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let encrypted = if let Some(symmetric_key) = symmetric_key {
|
||||
encrypt_helper
|
||||
.encrypt_for_broadcast(context, symmetric_key, message, compress)
|
||||
.await?
|
||||
} else {
|
||||
// Asymmetric encryption
|
||||
|
||||
// Encrypt to self unconditionally,
|
||||
// even for a single-device setup.
|
||||
let mut encryption_keyring = vec![encrypt_helper.public_key.clone()];
|
||||
encryption_keyring
|
||||
.extend(encryption_keys.iter().map(|(_addr, key)| (*key).clone()));
|
||||
|
||||
encrypt_helper
|
||||
.encrypt(context, encryption_keyring, message, compress)
|
||||
.await?
|
||||
};
|
||||
|
||||
// XXX: additional newline is needed
|
||||
// to pass filtermail at
|
||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>
|
||||
let encrypted = encrypt_helper
|
||||
.encrypt(context, encryption_keyring, message, compress)
|
||||
.await?
|
||||
+ "\n";
|
||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>:
|
||||
let encrypted = encrypted + "\n";
|
||||
|
||||
// Set the appropriate Content-Type for the outer message
|
||||
MimePart::new(
|
||||
|
||||
@@ -169,6 +169,11 @@ pub enum Param {
|
||||
/// post something to the mailing list.
|
||||
ListPost = b'p',
|
||||
|
||||
/// For Chats of type [`Chattype::OutBroadcast`] and [`Chattype::InBroadcast`]:
|
||||
/// The symmetric key shared among all chat participants,
|
||||
/// used to encrypt and decrypt messages.
|
||||
SymmetricKey = b'z',
|
||||
|
||||
/// For Contacts: If this is the List-Post address of a mailing list, contains
|
||||
/// the List-Id of the mailing list (which is also used as the group id of the chat).
|
||||
ListId = b's',
|
||||
|
||||
36
src/pgp.rs
36
src/pgp.rs
@@ -12,6 +12,7 @@ use pgp::composed::{
|
||||
SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
|
||||
StandaloneSignature, SubkeyParamsBuilder, TheRing,
|
||||
};
|
||||
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||
use pgp::crypto::ecc_curve::ECCCurve;
|
||||
use pgp::crypto::hash::HashAlgorithm;
|
||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||
@@ -322,6 +323,41 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Symmetric encryption.
|
||||
pub async fn encrypt_for_broadcast(
|
||||
plain: Vec<u8>,
|
||||
passphrase: &str,
|
||||
private_key_for_signing: Option<SignedSecretKey>,
|
||||
compress: bool,
|
||||
) -> 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 = MessageBuilder::from_bytes("", plain);
|
||||
let mut msg = msg.seipd_v2(
|
||||
&mut rng,
|
||||
SymmetricKeyAlgorithm::AES128,
|
||||
AeadAlgorithm::Ocb,
|
||||
ChunkSize::C8KiB,
|
||||
);
|
||||
msg.encrypt_with_password(&mut rng, s2k, &passphrase)?;
|
||||
|
||||
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?
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
|
||||
passphrase: &str,
|
||||
|
||||
Reference in New Issue
Block a user