diff --git a/Cargo.lock b/Cargo.lock index f1016bd057..0348f05727 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,10 +439,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "bitfield" -version = "0.17.0" +name = "bitfields" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d84268bbf9b487d31fe4b849edbefcd3911422d7a07de855a2da1f70ab3d1c" +dependencies = [ + "bitfields-impl", +] + +[[package]] +name = "bitfields-impl" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" +checksum = "07c93edde7bb4416c35c85048e34f78999dcb47d199bde3b1d79286156f3e2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "thiserror 2.0.12", +] [[package]] name = "bitflags" @@ -456,6 +471,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -623,16 +650,6 @@ dependencies = [ "alloc-stdlib", ] -[[package]] -name = "bstr" -version = "1.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "buffer-redux" version = "1.0.2" @@ -1141,7 +1158,7 @@ dependencies = [ "crypto_secretbox", "curve25519-dalek", "salsa20", - "serdect", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -1200,6 +1217,23 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "cx448" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c0cf476284b03eb6c10e78787b21c7abb7d7d43cb2f02532ba6b831ed892fa" +dependencies = [ + "crypto-bigint", + "elliptic-curve", + "pkcs8", + "rand_core 0.6.4", + "serdect 0.3.0", + "sha3", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "darling" version = "0.20.10" @@ -1526,7 +1560,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1541,6 +1584,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "unicode-xid", +] + [[package]] name = "des" version = "0.8.1" @@ -1735,6 +1790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", + "base64ct", "crypto-bigint", "digest", "ff", @@ -1745,7 +1801,10 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", + "serde_json", + "serdect 0.2.0", "subtle", + "tap", "zeroize", ] @@ -1930,6 +1989,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -1965,6 +2025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2002,6 +2063,12 @@ dependencies = [ name = "format-flowed" version = "1.0.0" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -2867,7 +2934,7 @@ dependencies = [ "crypto_box", "data-encoding", "der", - "derive_more", + "derive_more 1.0.0", "ed25519-dalek", "futures-buffered", "futures-util", @@ -2921,7 +2988,7 @@ checksum = "f91ac4aaab68153d726c4e6b39c30f9f9253743f0e25664e52f4caeb46f48d11" dependencies = [ "curve25519-dalek", "data-encoding", - "derive_more", + "derive_more 1.0.0", "ed25519-dalek", "rand_core 0.6.4", "serde", @@ -2951,7 +3018,7 @@ dependencies = [ "anyhow", "async-channel 2.3.1", "bytes", - "derive_more", + "derive_more 1.0.0", "ed25519-dalek", "futures-concurrency", "futures-lite", @@ -3062,7 +3129,7 @@ dependencies = [ "bytes", "cfg_aliases", "data-encoding", - "derive_more", + "derive_more 1.0.0", "getrandom 0.3.3", "hickory-resolver", "http 1.1.0", @@ -3110,12 +3177,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "iter-read" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" - [[package]] name = "itertools" version = "0.10.5" @@ -3216,6 +3277,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -3416,7 +3486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" dependencies = [ "cfg_aliases", - "derive_more", + "derive_more 1.0.0", "futures-buffered", "futures-lite", "futures-util", @@ -3564,7 +3634,7 @@ dependencies = [ "atomic-waker", "bytes", "cfg_aliases", - "derive_more", + "derive_more 1.0.0", "iroh-quinn-udp", "js-sys", "libc", @@ -4059,21 +4129,22 @@ dependencies = [ [[package]] name = "pgp" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30249ac8a98b356b473b04bc5358c75a260aa96a295d0743ce752fe7b173f235" +checksum = "f91d320242d9b686612b15526fe38711afdf856e112eaa4775ce25b0d9b12b11" dependencies = [ + "aead", "aes", "aes-gcm", "aes-kw", "argon2", "base64", - "bitfield", + "bitfields", "block-padding", "blowfish", - "bstr", "buffer-redux", "byteorder", + "bytes", "camellia", "cast5", "cfb-mode", @@ -4082,8 +4153,9 @@ dependencies = [ "const-oid", "crc24", "curve25519-dalek", + "cx448", "derive_builder", - "derive_more", + "derive_more 2.0.1", "des", "digest", "dsa", @@ -4096,7 +4168,6 @@ dependencies = [ "hex", "hkdf", "idea", - "iter-read", "k256", "log", "md-5", @@ -4109,6 +4180,7 @@ dependencies = [ "p384", "p521", "rand 0.8.5", + "regex", "ripemd", "rsa", "sha1", @@ -4117,7 +4189,7 @@ dependencies = [ "sha3", "signature", "smallvec", - "thiserror 2.0.12", + "snafu", "twofish", "x25519-dalek", "zeroize", @@ -4343,7 +4415,7 @@ checksum = "7d6db66007eac4a0ec8331d0d20c734bd64f6445d64bbaf0d0a27fea7a054e36" dependencies = [ "base64", "bytes", - "derive_more", + "derive_more 1.0.0", "futures-lite", "futures-util", "hyper-util", @@ -4640,6 +4712,12 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -5198,6 +5276,7 @@ dependencies = [ "der", "generic-array", "pkcs8", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -5339,6 +5418,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -5786,6 +5875,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.19.1" @@ -7051,6 +7146,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -7288,6 +7392,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "zlib-rs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 3584b97ee3..cf734b12ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ num-derive = "0.4" num-traits = { workspace = true } parking_lot = "0.12" percent-encoding = "2.3" -pgp = { version = "0.15.0", default-features = false } +pgp = { version = "0.16.0", default-features = false } pin-project = "1" qrcodegen = "1.7.0" quick-xml = "0.37" diff --git a/deny.toml b/deny.toml index 5910ebc5f1..e8072d0ebd 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,8 @@ ignore = [ skip = [ { name = "async-channel", version = "1.9.0" }, { name = "bitflags", version = "1.3.2" }, + { name = "derive_more-impl", version = "1.0.0" }, + { name = "derive_more", version = "1.0.0" }, { name = "event-listener", version = "2.5.3" }, { name = "generator", version = "0.7.5" }, { name = "getrandom", version = "0.2.12" }, @@ -38,6 +40,7 @@ skip = [ { name = "regex-automata", version = "0.1.10" }, { name = "regex-syntax", version = "0.6.29" }, { name = "rustix", version = "0.38.44" }, + { name = "serdect", version = "0.2.0" }, { name = "spin", version = "0.9.8" }, { name = "strum_macros", version = "0.26.2" }, { name = "strum", version = "0.26.2" }, diff --git a/src/context.rs b/src/context.rs index 5af9b2ee0c..75b71f9d65 100644 --- a/src/context.rs +++ b/src/context.rs @@ -10,8 +10,8 @@ use std::time::Duration; use anyhow::{bail, ensure, Context as _, Result}; use async_channel::{self as channel, Receiver, Sender}; +use pgp::composed::SignedPublicKey; use pgp::types::PublicKeyTrait; -use pgp::SignedPublicKey; use ratelimit::Ratelimit; use tokio::sync::{Mutex, Notify, RwLock}; @@ -1074,7 +1074,7 @@ impl Context { res += &format!("db_size_bytes {db_size}\n"); let secret_key = &load_self_secret_key(self).await?.primary_key; - let key_created = secret_key.created_at().timestamp(); + let key_created = secret_key.public_key().created_at().timestamp(); res += &format!("key_created {key_created}\n"); // how many of the chats active in the last months are: diff --git a/src/decrypt.rs b/src/decrypt.rs index 7be616c309..fa23c9e97b 100644 --- a/src/decrypt.rs +++ b/src/decrypt.rs @@ -15,10 +15,10 @@ use crate::pgp; /// Tries to decrypt a message, but only if it is structured as an Autocrypt message. /// /// If successful and the message is encrypted, returns decrypted body. -pub fn try_decrypt( - mail: &ParsedMail<'_>, - private_keyring: &[SignedSecretKey], -) -> Result> { +pub fn try_decrypt<'a>( + mail: &'a ParsedMail<'a>, + private_keyring: &'a [SignedSecretKey], +) -> Result>> { let Some(encrypted_data_part) = get_encrypted_mime(mail) else { return Ok(None); }; diff --git a/src/e2ee.rs b/src/e2ee.rs index 011e8b8c06..39a259f043 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -143,7 +143,7 @@ impl EncryptHelper { let cursor = Cursor::new(&mut raw_message); mail_to_encrypt.clone().write_part(cursor).ok(); - let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key), compress).await?; + let ctext = pgp::pk_encrypt(raw_message, keyring, Some(sign_key), compress).await?; Ok(ctext) } @@ -153,9 +153,8 @@ impl EncryptHelper { pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result { let sign_key = load_self_secret_key(context).await?; let mut buffer = Vec::new(); - let cursor = Cursor::new(&mut buffer); - mail.clone().write_part(cursor).ok(); - let signature = pgp::pk_calc_signature(&buffer, &sign_key)?; + mail.clone().write_part(&mut buffer)?; + let signature = pgp::pk_calc_signature(buffer, &sign_key)?; Ok(signature) } } diff --git a/src/imex.rs b/src/imex.rs index dd8a589029..a8c5571fc3 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -4,7 +4,6 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::pin::Pin; -use ::pgp::types::PublicKeyTrait; use anyhow::{bail, ensure, format_err, Context as _, Result}; use futures::TryStreamExt; use futures_lite::FutureExt; @@ -32,6 +31,7 @@ use crate::tools::{ mod key_transfer; mod transfer; +use ::pgp::types::KeyDetails; pub use key_transfer::{continue_key_transfer, initiate_key_transfer}; pub use transfer::{get_backup, BackupProvider}; @@ -173,7 +173,11 @@ async fn set_self_key(context: &Context, armored: &str) -> Result<()> { }; key::store_self_keypair(context, &keypair).await?; - info!(context, "stored self key: {:?}", keypair.secret.key_id()); + info!( + context, + "stored self key: {:?}", + keypair.secret.public_key().key_id() + ); Ok(()) } diff --git a/src/imex/key_transfer.rs b/src/imex/key_transfer.rs index 88f61742ed..d7726e8d8e 100644 --- a/src/imex/key_transfer.rs +++ b/src/imex/key_transfer.rs @@ -1,4 +1,6 @@ //! # Key transfer via Autocrypt Setup Message. +use std::io::BufReader; + use rand::{thread_rng, Rng}; use anyhow::{bail, ensure, Result}; @@ -71,7 +73,7 @@ pub async fn continue_key_transfer( if let Some(filename) = msg.get_file(context) { let file = open_file_std(context, filename)?; let sc = normalize_setup_code(setup_code); - let armored_key = decrypt_setup_file(&sc, file).await?; + let armored_key = decrypt_setup_file(&sc, BufReader::new(file)).await?; set_self_key(context, &armored_key).await?; context.set_config_bool(Config::BccSelf, true).await?; @@ -96,7 +98,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result Some(("Autocrypt-Prefer-Encrypt", "mutual")), }; let private_key_asc = private_key.to_asc(ac_headers); - let encr = pgp::symm_encrypt(passphrase, private_key_asc.as_bytes()) + let encr = pgp::symm_encrypt(passphrase, private_key_asc.into_bytes()) .await? .replace('\n', "\r\n"); @@ -155,7 +157,7 @@ fn create_setup_code(_context: &Context) -> String { ret } -async fn decrypt_setup_file( +async fn decrypt_setup_file( passphrase: &str, file: T, ) -> Result { @@ -258,11 +260,10 @@ mod tests { assert!(!base64.is_empty()); - let setup_file = S_EM_SETUPFILE.to_string(); - let decrypted = - decrypt_setup_file(S_EM_SETUPCODE, std::io::Cursor::new(setup_file.as_bytes())) - .await - .unwrap(); + let setup_file = S_EM_SETUPFILE; + let decrypted = decrypt_setup_file(S_EM_SETUPCODE, setup_file.as_bytes()) + .await + .unwrap(); let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap(); @@ -278,14 +279,13 @@ mod tests { /// "Implementations MUST NOT use plaintext in Symmetrically Encrypted Data packets". #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decrypt_plaintext_autocrypt_setup_message() { - let setup_file = S_PLAINTEXT_SETUPFILE.to_string(); + let setup_file = S_PLAINTEXT_SETUPFILE; let incorrect_setupcode = "0000-0000-0000-0000-0000-0000-0000-0000-0000"; - assert!(decrypt_setup_file( - incorrect_setupcode, - std::io::Cursor::new(setup_file.as_bytes()), - ) - .await - .is_err()); + assert!( + decrypt_setup_file(incorrect_setupcode, setup_file.as_bytes(),) + .await + .is_err() + ); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/src/key.rs b/src/key.rs index 29a2a5d9d5..b829829fb7 100644 --- a/src/key.rs +++ b/src/key.rs @@ -10,7 +10,7 @@ use deltachat_contact_tools::EmailAddress; use pgp::composed::Deserializable; pub use pgp::composed::{SignedPublicKey, SignedSecretKey}; use pgp::ser::Serialize; -use pgp::types::{PublicKeyTrait, SecretKeyTrait}; +use pgp::types::{KeyDetails, KeyId, Password}; use rand::thread_rng; use tokio::runtime::Handle; @@ -24,7 +24,7 @@ use crate::tools::{self, time_elapsed}; /// This trait is implemented for rPGP's [SignedPublicKey] and /// [SignedSecretKey] types and makes working with them a little /// easier in the deltachat world. -pub(crate) trait DcKey: Serialize + Deserializable + PublicKeyTrait + Clone { +pub(crate) trait DcKey: Serialize + Deserializable + Clone { /// Create a key from some bytes. fn from_slice(bytes: &[u8]) -> Result { let res = ::from_bytes(Cursor::new(bytes)); @@ -78,7 +78,7 @@ pub(crate) trait DcKey: Serialize + Deserializable + PublicKeyTrait + Clone { let bytes = data.as_bytes(); let res = Self::from_armor_single(Cursor::new(bytes)); let (key, headers) = match res { - Err(pgp::errors::Error::NoMatchingPacket) => match Self::is_private() { + Err(pgp::errors::Error::NoMatchingPacket { .. }) => match Self::is_private() { true => bail!("No private key packet found"), false => bail!("No public key packet found"), }, @@ -123,11 +123,10 @@ pub(crate) trait DcKey: Serialize + Deserializable + PublicKeyTrait + Clone { fn to_asc(&self, header: Option<(&str, &str)>) -> String; /// The fingerprint for the key. - fn dc_fingerprint(&self) -> Fingerprint { - PublicKeyTrait::fingerprint(self).into() - } + fn dc_fingerprint(&self) -> Fingerprint; fn is_private() -> bool; + fn key_id(&self) -> KeyId; } pub(crate) async fn load_self_public_key(context: &Context) -> Result { @@ -230,6 +229,14 @@ impl DcKey for SignedPublicKey { fn is_private() -> bool { false } + + fn dc_fingerprint(&self) -> Fingerprint { + self.fingerprint().into() + } + + fn key_id(&self) -> KeyId { + KeyDetails::key_id(self) + } } impl DcKey for SignedSecretKey { @@ -249,6 +256,14 @@ impl DcKey for SignedSecretKey { fn is_private() -> bool { true } + + fn dc_fingerprint(&self) -> Fingerprint { + self.fingerprint().into() + } + + fn key_id(&self) -> KeyId { + KeyDetails::key_id(&**self) + } } /// Deltachat extension trait for secret keys. @@ -262,9 +277,14 @@ pub(crate) trait DcSecretKey { impl DcSecretKey for SignedSecretKey { fn split_public_key(&self) -> Result { self.verify()?; - let unsigned_pubkey = SecretKeyTrait::public_key(self); + let unsigned_pubkey = self.public_key(); let mut rng = thread_rng(); - let signed_pubkey = unsigned_pubkey.sign(&mut rng, self, || "".into())?; + let signed_pubkey = unsigned_pubkey.sign( + &mut rng, + &self.primary_key, + self.primary_key.public_key(), + &Password::empty(), + )?; Ok(signed_pubkey) } } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index f6c9c13279..2edd2e417d 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -342,10 +342,10 @@ impl MimeMessage { let mail_raw; // Memory location for a possible decrypted message. let decrypted_msg; // Decrypted signed OpenPGP message. - let (mail, encrypted) = + let (mail, is_encrypted) = match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring)) { - Ok(Some(msg)) => { - mail_raw = msg.get_content()?.unwrap_or_default(); + Ok(Some(mut msg)) => { + mail_raw = msg.as_data_vec().unwrap_or_default(); let decrypted_mail = mailparse::parse_mail(&mail_raw)?; if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { @@ -434,7 +434,7 @@ impl MimeMessage { signatures.extend(signatures_detached); content }); - if let (Ok(mail), true) = (mail, encrypted) { + if let (Ok(mail), true) = (mail, is_encrypted) { if !signatures.is_empty() { // Remove unsigned opportunistically protected headers from messages considered // Autocrypt-encrypted / displayed with padlock. @@ -529,7 +529,7 @@ impl MimeMessage { } } } - if !encrypted { + if !is_encrypted { signatures.clear(); } if let Some(peerstate) = &mut peerstate { diff --git a/src/pgp.rs b/src/pgp.rs index de363a7d46..9051e78aed 100644 --- a/src/pgp.rs +++ b/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 . /// @@ -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 { - 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 { 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 { 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, public_keys_for_encryption: Vec, private_key_for_signing: Option, compress: bool, ) -> Result { - let lit_msg = Message::new_literal_bytes("", plain); - Handle::current() .spawn_blocking(move || { - let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption + let mut rng = thread_rng(); + + let pkeys = public_keys_for_encryption .iter() - .filter_map(select_pk_for_encryption) - .collect(); + .filter_map(select_pk_for_encryption); - let mut rng = thread_rng(); + 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)?; + } - 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)? - }; + 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 = encrypted_msg.to_armored_string(Default::default())?; + 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, private_key_for_signing: &SignedSecretKey, ) -> Result { - 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, private_keys_for_decryption: &[SignedSecretKey], -) -> Result { +) -> Result> { 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 (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 empty_pw = Password::empty(); + + 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" + ); + + // 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> { let mut ret_signature_fingerprints: HashSet = 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 { - let lit_msg = Message::new_literal_bytes("", plain); - let passphrase = passphrase.to_string(); +pub async fn symm_encrypt(passphrase: &str, plain: Vec) -> Result { + 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 { } /// Symmetric decryption. -pub async fn symm_decrypt( +pub async fn symm_decrypt( passphrase: &str, ctext: T, ) -> Result> { - 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, - 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)> { - let msg = pk_decrypt(ctext, private_keys_for_decryption)?; + ) -> Result<( + pgp::composed::Message<'static>, + HashSet, + Vec, + )> { + 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); } } diff --git a/src/push.rs b/src/push.rs index 31112bd9fd..ee72744182 100644 --- a/src/push.rs +++ b/src/push.rs @@ -11,9 +11,8 @@ use std::sync::Arc; use anyhow::{Context as _, Result}; use base64::Engine as _; -use pgp::crypto::aead::AeadAlgorithm; +use pgp::crypto::aead::{AeadAlgorithm, ChunkSize}; use pgp::crypto::sym::SymmetricKeyAlgorithm; -use pgp::ser::Serialize; use rand::thread_rng; use tokio::sync::RwLock; @@ -81,18 +80,17 @@ pub(crate) fn encrypt_device_token(device_token: &str) -> Result { .first() .context("No encryption subkey found")?; let padded_device_token = pad_device_token(device_token); - let literal_message = pgp::composed::Message::new_literal("", &padded_device_token); let mut rng = thread_rng(); - let chunk_size = 8; - let encrypted_message = literal_message.encrypt_to_keys_seipdv2( + let mut msg = pgp::composed::MessageBuilder::from_bytes("", padded_device_token).seipd_v2( &mut rng, SymmetricKeyAlgorithm::AES128, AeadAlgorithm::Ocb, - chunk_size, - &[&encryption_subkey], - )?; - let encoded_message = encrypted_message.to_bytes()?; + ChunkSize::C8KiB, + ); + msg.encrypt_to_key(&mut rng, &encryption_subkey)?; + let encoded_message = msg.to_vec(&mut rng)?; + Ok(format!( "openpgp:{}", base64::engine::general_purpose::STANDARD.encode(encoded_message)