mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 22:36:30 +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)
|
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`.
|
/// Signs the passed-in `mail` using the private key from `context`.
|
||||||
/// Returns the payload and the signature.
|
/// Returns the payload and the signature.
|
||||||
pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result<String> {
|
pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result<String> {
|
||||||
|
|||||||
@@ -1117,18 +1117,42 @@ impl MimeFactory {
|
|||||||
Loaded::Mdn { .. } => true,
|
Loaded::Mdn { .. } => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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,
|
// Encrypt to self unconditionally,
|
||||||
// even for a single-device setup.
|
// even for a single-device setup.
|
||||||
let mut encryption_keyring = vec![encrypt_helper.public_key.clone()];
|
let mut encryption_keyring = vec![encrypt_helper.public_key.clone()];
|
||||||
encryption_keyring.extend(encryption_keys.iter().map(|(_addr, key)| (*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
|
// XXX: additional newline is needed
|
||||||
// to pass filtermail at
|
// to pass filtermail at
|
||||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>
|
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>:
|
||||||
let encrypted = encrypt_helper
|
let encrypted = encrypted + "\n";
|
||||||
.encrypt(context, encryption_keyring, message, compress)
|
|
||||||
.await?
|
|
||||||
+ "\n";
|
|
||||||
|
|
||||||
// Set the appropriate Content-Type for the outer message
|
// Set the appropriate Content-Type for the outer message
|
||||||
MimePart::new(
|
MimePart::new(
|
||||||
|
|||||||
@@ -169,6 +169,11 @@ pub enum Param {
|
|||||||
/// post something to the mailing list.
|
/// post something to the mailing list.
|
||||||
ListPost = b'p',
|
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
|
/// 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).
|
/// the List-Id of the mailing list (which is also used as the group id of the chat).
|
||||||
ListId = b's',
|
ListId = b's',
|
||||||
|
|||||||
36
src/pgp.rs
36
src/pgp.rs
@@ -12,6 +12,7 @@ use pgp::composed::{
|
|||||||
SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
|
SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
|
||||||
StandaloneSignature, SubkeyParamsBuilder, TheRing,
|
StandaloneSignature, SubkeyParamsBuilder, TheRing,
|
||||||
};
|
};
|
||||||
|
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||||
use pgp::crypto::ecc_curve::ECCCurve;
|
use pgp::crypto::ecc_curve::ECCCurve;
|
||||||
use pgp::crypto::hash::HashAlgorithm;
|
use pgp::crypto::hash::HashAlgorithm;
|
||||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||||
@@ -322,6 +323,41 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
|
|||||||
.await?
|
.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.
|
/// Symmetric decryption.
|
||||||
pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
|
pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
|
||||||
passphrase: &str,
|
passphrase: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user