diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8d217eb4e..5076f9d5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,7 +100,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.WRITE_GITHUB_TOKEN }} MAILGUN_API_TOKEN: ${{ secrets.MAILGUN_API_TOKEN }} - EMAIL_ENCRYPTION_KEY: ${{ secrets.EMAIL_ENCRYPTION_KEY }} + EMAIL_PRIVATE_KEY: ${{ secrets.EMAIL_PRIVATE_KEY }} ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }} ZULIP_USERNAME: ${{ secrets.ZULIP_USERNAME }} CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 2c050da26..a2f145225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,18 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -168,6 +180,20 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -310,6 +336,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -328,13 +360,39 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.10" @@ -505,6 +563,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fnv" version = "1.0.7" @@ -587,9 +651,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1325,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", "zerocopy", ] @@ -1336,7 +1400,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", ] [[package]] @@ -1462,11 +1535,13 @@ dependencies = [ name = "rust_team_data" version = "1.0.0" dependencies = [ + "blake3", "chacha20poly1305", "getrandom 0.2.15", "hex", "indexmap", "serde", + "x25519-dalek", ] [[package]] @@ -1481,6 +1556,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.3" @@ -1605,6 +1689,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.194" @@ -2471,6 +2561,18 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "yoke" version = "0.7.5" @@ -2541,6 +2643,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index 0b575cc7a..e12490e03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,8 @@ ansi_term = "0.12.1" atty = "0.2.14" base64 = "0.22" chacha20poly1305 = "0.9.0" +x25519-dalek = "2.0.1" +blake3 = "1.8.3" clap = "4.5" derive_builder = "0.20.2" dialoguer = "0.10.1" diff --git a/README.md b/README.md index 311bc6ee5..d5e06900e 100644 --- a/README.md +++ b/README.md @@ -187,15 +187,15 @@ it. Encrypted email addresses look like this: encrypted+3eeedb8887004d9a8266e9df1b82a2d52dcce82c4fa1d277c5f14e261e8155acc8a66344edc972fa58b678dc2bcad2e8f7c201a1eede9c16639fe07df8bac5aa1097b2ad9699a700edb32ef192eaa74bf7af0a@rust-lang.invalid ``` -The production key is accessible to select Infrastructure Team members, so if -you need to add an encrypted email address you'll need to reach out to that -team. The key is stored in the following parameter on AWS SSM Parameter Store: +The `cargo run encrypt-email` CLI command can be used to encrypt an email address in a self-service manner. +Decryption is automatically done as part of the sync process executed in the CI. + +The production private key is accessible to select Infrastructure Team members, and +it is stored in the following parameter on AWS SSM Parameter Store: ``` /prod/sync-team/email-encryption-key ``` -The `cargo run encrypt-email` and `cargo run decrypt-email` interactive CLI -commands are available for infra team members to interact with encrypted -emails. The `rust_team_data` (with the `email-encryption` feature enabled) also +The `rust_team_data` (with the `email-encryption` feature enabled) also provides a module to programmatically encrypt and decrypt. diff --git a/people/camelid.toml b/people/camelid.toml index 1bd3ba139..5bdcf0ac0 100644 --- a/people/camelid.toml +++ b/people/camelid.toml @@ -2,4 +2,4 @@ name = 'Noah Lev' github = 'camelid' github-id = 37223377 zulip-id = 307537 -email = "encrypted+ebe6f3cec2ee373b57408f88ad0f14dc86c1f1bfaf7829aa5628073ae74e78ae52bc02b1b3ae221ed92284b923258b53a2572142cd62de3f92244cb7045d9058@rust-lang.invalid" +email = "encrypted+02728fc388dadf662dc77fcec3e7c6d5ed7ae47a92026fc9c75744c45b9427564b4123afcacfdc46a5b0a8a8ebc414adecdd37d617e0a6cea3678488ea95fc84634266bebba24e96d44fd7b7f9fc5c5545eeb8650d5c19582797c70a39c30507@rust-lang.invalid" diff --git a/rust_team_data/Cargo.toml b/rust_team_data/Cargo.toml index f2d255350..a76d08194 100644 --- a/rust_team_data/Cargo.toml +++ b/rust_team_data/Cargo.toml @@ -6,10 +6,12 @@ license.workspace = true [dependencies] chacha20poly1305 = { workspace = true, optional = true } +x25519-dalek = { workspace = true, optional = true, features = ["getrandom", "static_secrets"] } +blake3 = {workspace = true, optional = true} getrandom = { workspace = true, optional = true } hex = { workspace = true, optional = true } indexmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } [features] -email-encryption = ["chacha20poly1305", "getrandom", "hex"] +email-encryption = ["chacha20poly1305", "x25519-dalek", "blake3", "getrandom", "hex"] diff --git a/rust_team_data/src/email_encryption.rs b/rust_team_data/src/email_encryption.rs index af564ef89..4dde0c94b 100644 --- a/rust_team_data/src/email_encryption.rs +++ b/rust_team_data/src/email_encryption.rs @@ -2,43 +2,79 @@ //! the team repository. It generates encrypted content that looks like this: //! //! ```text -//! encrypted+3eeedb8887004d9a8266e9df1b82a2d52dcce82c4fa1d277c5f14e261e8155acc8a66344edc972fa58b678dc2bcad2e8f7c201a1eede9c16639fe07df8bac5aa1097b2ad9699a700edb32ef192eaa74bf7af0a@rust-lang.invalid +//! encrypted+bfab2fae1acf74ed9f0df0d3f296f45a620f33ac249e35595dcdfe57ad96d01e54ff770a95111e6cc4d4c2c7f8b34feb42397e67b11d3136e380f1ca878c9a8e924de216d1253252363bbc8fa858cd3ce02bcc9c8f5142@rust-lang.invalid //! ``` //! -//! The hex-encoded part of the email address is a concatenation of a 24-byte random nonce and the -//! XChaCha20Poly1305-encrypted email address. Utilities are provided to both encrypt and decrypt. +//! The hex-encoded part of the email address is a concatenation of a 32-byte ephemeral x25519 public key, +//! a 24-byte random nonce and the XChaCha20Poly1305-encrypted email address. Utilities are provided +//! to both encrypt and decrypt. use chacha20poly1305::aead::{Aead, NewAead}; use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; +use hex::{FromHex, ToHex}; +use std::convert::TryInto; +use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret}; const PREFIX: &str = "encrypted+"; const SUFFIX: &str = "@rust-lang.invalid"; const KEY_LENGTH: usize = 32; const NONCE_LENGTH: usize = 24; +const PUBLIC_KEY: &str = "a5f80676b1368fe1b2e903058810a6de86ecf2be1225a64e6d79c8d747557b7b"; +// Globally unique context (see here for details: https://docs.rs/blake3/latest/blake3/fn.derive_key.html) +const KDF_CONTEXT: &str = "rust-team 2026-02-14 email-encryption"; + +fn get_public_key(public_key: &str) -> PublicKey { + PublicKey::from(<[u8; KEY_LENGTH]>::from_hex(public_key).expect( + "invalid public key configured, ensure it was generated with the generate-key command", + )) +} + +fn get_private_key(key: &str) -> Result { + Ok(StaticSecret::from( + <[u8; KEY_LENGTH]>::from_hex(key).map_err(Error::Hex)?, + )) +} -/// Encrypt an email address with the provided key. -pub fn encrypt(key: &str, email: &str) -> Result { - // Generate a random nonce every time something is encrypted. +/// Encrypt an email address with x25519-dalek, with blake3 for KDF and ChaCha20Poly1305 for AEAD. +/// The encryption process follows this flow: +/// 1. an ephemeral x25519 key is generated; +/// 2. a shared secret is computed against the public key defined by an infra admin as a constant above; +/// 3. the shared secret is used with a key derivation function (blake3) to generate a uniform symmetric key; +/// 4. the symmetric key is finally used to encrypt the email; +/// 5. the hex-encoded information required for decryption (public key, nonce, encrypted email) is returned as part of a fake email address. +pub fn encrypt_with_public_key(email: &str, public_key: &str) -> Result { + let ephemeral_secret = EphemeralSecret::random(); + let ephemeral_public_key = PublicKey::from(&ephemeral_secret); + let backend_public_key = get_public_key(public_key); + // Generate the shared secret + let shared_secret = ephemeral_secret.diffie_hellman(&backend_public_key); + // Generate random nonce every time something is encrypted let mut nonce = [0u8; NONCE_LENGTH]; getrandom::getrandom(&mut nonce).map_err(Error::GetRandom)?; let nonce = XNonce::from_slice(&nonce); + let shared_key = blake3::derive_key(KDF_CONTEXT, shared_secret.as_bytes()); - let mut encrypted = init_cipher(key)? + let mut encrypted = init_cipher(&shared_key) .encrypt(nonce, email.as_bytes()) .map_err(|_| Error::EncryptionFailed)?; - // Concatenate both the nonce and the payload, as both will be needed for decryption. - let mut payload = nonce.to_vec(); + // Concatenate ephemeral public key, nonce, and payload, as all three will be needed for decryption. + let mut payload = ephemeral_public_key.as_bytes().to_vec(); + payload.append(&mut nonce.to_vec()); payload.append(&mut encrypted); Ok(format!("{}{}{}", PREFIX, hex::encode(payload), SUFFIX)) } -/// Try decrypting an email address encrypted by this module with the provided key. +pub fn encrypt(email: &str) -> Result { + encrypt_with_public_key(email, PUBLIC_KEY) +} + +/// Try decrypting an email address encrypted by this module with the provided x25519 private key. /// /// If the email address was not encrypted by this module it will returned as-is. Because of that /// you can pass all the email addresses you have through this function. -pub fn try_decrypt(key: &str, email: &str) -> Result { +pub fn try_decrypt(private_key: &str, email: &str) -> Result { let combined = match email .strip_prefix(PREFIX) .and_then(|e| e.strip_suffix(SUFFIX)) @@ -46,24 +82,39 @@ pub fn try_decrypt(key: &str, email: &str) -> Result { Some(encrypted) => hex::decode(encrypted).map_err(Error::Hex)?, None => return Ok(email.to_string()), }; + if combined.len() < KEY_LENGTH + NONCE_LENGTH { + return Err(Error::WrongKeyLength); + } - let (nonce, encrypted) = combined.split_at(NONCE_LENGTH); + let (public_key, rest) = combined.split_at(KEY_LENGTH); + let public_key: &[u8; KEY_LENGTH] = public_key.try_into().unwrap(); // Safe unwrap as the length is verified above + let (nonce, encrypted) = rest.split_at(NONCE_LENGTH); let nonce = XNonce::from_slice(nonce); + let private_key = get_private_key(private_key)?; + let shared_secret = private_key.diffie_hellman(&PublicKey::from(public_key.to_owned())); + let shared_key = blake3::derive_key(KDF_CONTEXT, shared_secret.as_bytes()); + String::from_utf8( - init_cipher(key)? + init_cipher(&shared_key) .decrypt(nonce, encrypted) .map_err(|_| Error::EncryptionFailed)?, ) .map_err(|_| Error::InvalidUtf8) } -fn init_cipher(key: &str) -> Result { - if key.len() != KEY_LENGTH { - return Err(Error::WrongKeyLength); - } - let key = Key::from_slice(key.as_bytes()); - Ok(XChaCha20Poly1305::new(key)) +fn init_cipher(key: &[u8; KEY_LENGTH]) -> XChaCha20Poly1305 { + let key = Key::from_slice(key); + XChaCha20Poly1305::new(key) +} + +pub fn generate_x25519_keypair() -> (String, String) { + let ephemeral_secret = StaticSecret::random(); + let ephemeral_public_key = PublicKey::from(&ephemeral_secret); + ( + ephemeral_secret.encode_hex(), + ephemeral_public_key.encode_hex(), + ) } #[derive(Debug)] @@ -97,16 +148,18 @@ mod tests { #[test] fn test_encrypt_decrypt() -> Result<(), Error> { - const KEY: &str = "rxrtZ4uQ7uYJnikmUVxdcxrBmazEiH0k"; + const PRIVATE_KEY: &str = + "73cd73133b310671933f020b957594960bc046410765a1e145f144f88f379408"; + const PUBLIC_KEY: &str = "d1734021de0af5cfeca64482f3c38b3350a38fd4be2e6a88b2c150be4416b261"; const ADDRESS: &str = "foo@example.com"; - let encrypted = encrypt(KEY, ADDRESS)?; + let encrypted = encrypt_with_public_key(ADDRESS, PUBLIC_KEY)?; assert!( !encrypted.contains(ADDRESS), "the encrypted version did contain the plaintext!" ); - assert_eq!(ADDRESS, try_decrypt(KEY, &encrypted)?); + assert_eq!(ADDRESS, try_decrypt(PRIVATE_KEY, &encrypted)?); Ok(()) } diff --git a/src/main.rs b/src/main.rs index b3bd12da6..86a7188ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,6 +92,8 @@ enum Cli { EncryptEmail, /// Decrypt an email address DecryptEmail, + /// Generate a x25519 key for use with the email encryption module + GenerateKey, /// CI scripts #[clap(subcommand)] Ci(CiOpts), @@ -102,7 +104,7 @@ enum Cli { /// Environment variables: /// - GITHUB_TOKEN Authentication token with GitHub /// - MAILGUN_API_TOKEN Authentication token with Mailgun - /// - EMAIL_ENCRYPTION_KEY Key used to decrypt encrypted emails in the team repo + /// - EMAIL_PRIVATE_KEY Key used to decrypt encrypted emails in the team repo /// - ZULIP_USERNAME Username of the Zulip bot /// - ZULIP_API_TOKEN Authentication token of the Zulip bot #[clap(verbatim_doc_comment)] @@ -526,26 +528,24 @@ fn run() -> Result<(), Error> { let plain: String = dialoguer::Input::new() .with_prompt("Plaintext address") .interact_text()?; - let key = dialoguer::Password::new() - .with_prompt("Secret key") - .interact()?; - println!( - "{}", - rust_team_data::email_encryption::encrypt(&key, &plain)? - ); + println!("{}", rust_team_data::email_encryption::encrypt(&plain)?); } Cli::DecryptEmail => { let encrypted: String = dialoguer::Input::new() .with_prompt("Encrypted address") .interact_text()?; let key = dialoguer::Password::new() - .with_prompt("Secret key") + .with_prompt("Private key") .interact()?; println!( "{}", rust_team_data::email_encryption::try_decrypt(&key, &encrypted)? ); } + Cli::GenerateKey => { + let (secret, public) = rust_team_data::email_encryption::generate_x25519_keypair(); + println!("Generated keypair: secret: {} - public: {}", secret, public); + } Cli::Ci(opts) => match opts { CiOpts::GenerateCodeowners => generate_codeowners_file(data)?, CiOpts::CheckCodeowners => check_codeowners(data)?, diff --git a/sync-team/README.md b/sync-team/README.md index 0a2a80d00..ae9d04030 100644 --- a/sync-team/README.md +++ b/sync-team/README.md @@ -6,5 +6,5 @@ team data with some of the services the Rust Team uses. | Service name | Description | Environment variables | |--------------|-------------------------------------------------|---------------------------------------------| | github | Synchronize GitHub teams and repo configuration | `GITHUB_TOKEN` | -| mailgun | Synchronize mailing lists on Mailgun | `MAILGUN_API_TOKEN`, `EMAIL_ENCRYPTION_KEY` | +| mailgun | Synchronize mailing lists on Mailgun | `MAILGUN_API_TOKEN`, `EMAIL_PRIVATE_KEY` | | zulip | Synchronize Zulip user groups | `ZULIP_USERNAME`, `ZULIP_API_TOKEN` | diff --git a/sync-team/src/lib.rs b/sync-team/src/lib.rs index e8c73a733..912f6878c 100644 --- a/sync-team/src/lib.rs +++ b/sync-team/src/lib.rs @@ -54,8 +54,8 @@ pub fn run_sync_team( } "mailgun" => { let token = SecretString::from(get_env("MAILGUN_API_TOKEN")?); - let encryption_key = get_env("EMAIL_ENCRYPTION_KEY")?; - mailgun::run(token, &encryption_key, &team_api, dry_run)?; + let private_key = get_env("EMAIL_PRIVATE_KEY")?; + mailgun::run(token, &private_key, &team_api, dry_run)?; } "zulip" => { let username = get_env("ZULIP_USERNAME")?; diff --git a/sync-team/src/mailgun/mod.rs b/sync-team/src/mailgun/mod.rs index 498fdbb79..f4b9e7bea 100644 --- a/sync-team/src/mailgun/mod.rs +++ b/sync-team/src/mailgun/mod.rs @@ -22,12 +22,12 @@ struct List { priority: i32, } -fn mangle_lists(email_encryption_key: &str, lists: team_data::Lists) -> anyhow::Result> { +fn mangle_lists(email_private_key: &str, lists: team_data::Lists) -> anyhow::Result> { let mut result = Vec::new(); for (_key, mut list) in lists.lists.into_iter() { // Handle encrypted list addresses. - list.address = email_encryption::try_decrypt(email_encryption_key, &list.address)?; + list.address = email_encryption::try_decrypt(email_private_key, &list.address)?; let base_list = List { address: mangle_address(&list.address)?, @@ -51,7 +51,7 @@ fn mangle_lists(email_encryption_key: &str, lists: team_data::Lists) -> anyhow:: let mut partitions_count = 0; for mut member in list.members { // Handle encrypted member email addresses. - member = email_encryption::try_decrypt(email_encryption_key, &member)?; + member = email_encryption::try_decrypt(email_private_key, &member)?; let action = build_route_action(&member); if current_actions_len + action.len() > ACTIONS_SIZE_LIMIT_BYTES { @@ -89,7 +89,7 @@ fn mangle_address(addr: &str) -> anyhow::Result { pub(crate) fn run( token: SecretString, - email_encryption_key: &str, + email_private_key: &str, team_api: &TeamApi, dry_run: bool, ) -> anyhow::Result<()> { @@ -97,7 +97,7 @@ pub(crate) fn run( let mailmap = team_api.get_lists()?; // Mangle all the mailing lists - let lists = mangle_lists(email_encryption_key, mailmap)?; + let lists = mangle_lists(email_private_key, mailmap)?; let mut routes = Vec::new(); let mut response = mailgun.get_routes(None)?; @@ -224,12 +224,16 @@ mod tests { #[test] fn test_mangle_lists() { - const ENCRYPTION_KEY: &str = "mGDTk1eIx8P2gTerzKXwvun67d41iUid"; + const PRIVATE_KEY: &str = + "73cd73133b310671933f020b957594960bc046410765a1e145f144f88f379408"; + const PUBLIC_KEY: &str = "d1734021de0af5cfeca64482f3c38b3350a38fd4be2e6a88b2c150be4416b261"; - let secret_list = email_encryption::encrypt(ENCRYPTION_KEY, "secret-list@example.com") - .expect("failed to encrypt list"); - let secret_member = email_encryption::encrypt(ENCRYPTION_KEY, "secret-member@example.com") - .expect("failed to encrypt member"); + let secret_list = + email_encryption::encrypt_with_public_key("secret-list@example.com", PUBLIC_KEY) + .expect("failed to encrypt list"); + let secret_member = + email_encryption::encrypt_with_public_key("secret-member@example.com", PUBLIC_KEY) + .expect("failed to encrypt member"); let original = rust_team_data::v1::Lists { lists: indexmap::indexmap![ @@ -254,7 +258,7 @@ mod tests { ], }; - let mangled = mangle_lists(ENCRYPTION_KEY, original).unwrap(); + let mangled = mangle_lists(PRIVATE_KEY, original).unwrap(); let expected = vec![ List { address: mangle_address("small@example.com").unwrap(), diff --git a/teams/archive/project-foundation.toml b/teams/archive/project-foundation.toml index bb1da6e7d..73a8a2c26 100644 --- a/teams/archive/project-foundation.toml +++ b/teams/archive/project-foundation.toml @@ -24,16 +24,10 @@ email = "foundation@rust-lang.org" [[lists]] address = "foundation@rust-lang.org" include-team-members = false -extra-emails = [ - "encrypted+f1dde3df4bdeb7f371978faf7a9310b5e247fcb70c8b7fbd46979965a4241f9d82d353116ddf82e755344cbb04038159513b23c7cd3ce11a5a90773bb707bca54c189ae6cd@rust-lang.invalid", -] [[lists]] address = "foundation-moderation@rust-lang.org" include-team-members = false -extra-emails = [ - "encrypted+c973fe12f465d22c7391b4c666f86d98214a224d354a74631b423fadce1c2bd40cd2da1c847cf3c6ff290db7368049b58876e3655ee1db4efb1fa5185f6c07d722c921a802@rust-lang.invalid", -] [[github]] orgs = ["rust-lang"]