diff --git a/README.md b/README.md index b1351f1..217df8c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The `--address` option may be specified multiples times and can also be combined In an address file, empty lines and lines starting with the `#` character are ignored. -To generate a private key, it is recommended to use the following command: +The private key's length must be either 128 or 256 bits. To generate a 128 bits key, the following command is recommended: ``` openssl rand -base64 16 @@ -115,6 +115,10 @@ No, this project is based on the filter API used by OpenSMTPD. Yes, internationalized domain names (IDN) are supported. You can specify domain names either using valid UTF-8 or Punycode ([RFC 3492](https://datatracker.ietf.org/doc/html/rfc3492)). +### How long should be my private key? + +Privates keys must have a length of either 128 bits (16 bytes) or 256 bits (32 bytes). Unless you have some very specific needs, you should choose a 128 bits key. + ### What about key rotation? Rotating the key would mean that all previously generated addresses for this local part would suddenly be invalid. Therefore, the key associated with a local part must not change. diff --git a/src/address.rs b/src/address.rs index 7b856a9..48ed1bc 100644 --- a/src/address.rs +++ b/src/address.rs @@ -119,6 +119,10 @@ impl FromStr for KeyedAddress { .transpose()?; let key = BASE64.decode(key_b64.as_bytes())?; ensure!(!key.is_empty(), "{s}: key cannot be empty"); + ensure!( + crate::ALLOWED_KEY_SIZES.contains(&key.len()), + "{s}: key length must be either 128 or 256 bits" + ); Ok(Self { local_part, domain, @@ -169,6 +173,11 @@ mod tests { assert!(KeyedAddress::from_str("derp@").is_err()); } + #[test] + fn parse_keyed_addr_invalid_key_length() { + assert!(KeyedAddress::from_str("test:bAXk1r7mgJY=").is_err()); + } + #[test] fn parse_valid_coded_addr_with_domain() { let addr_str = "a+test+orsxg5a@example.org"; @@ -248,13 +257,19 @@ mod tests { #[test] fn parse_valid_keyed_addr_without_domain() { - let addr_str = "local.part:3d74YQqk"; + let addr_str = "local.part:sbW22xNStWKro4nnMKmG3A=="; let addr = KeyedAddress::from_str(addr_str); assert!(addr.is_ok(), "unable to parse {addr_str}: {addr:?}"); let addr = addr.unwrap(); assert_eq!(addr.local_part, "local.part"); assert_eq!(addr.domain, None); - assert_eq!(addr.key, vec![0xdd, 0xde, 0xf8, 0x61, 0x0a, 0xa4]); + assert_eq!( + addr.key, + vec![ + 0xb1, 0xb5, 0xb6, 0xdb, 0x13, 0x52, 0xb5, 0x62, 0xab, 0xa3, 0x89, 0xe7, 0x30, 0xa9, + 0x86, 0xdc + ] + ); } #[test] @@ -305,77 +320,84 @@ mod tests { #[test] fn cmp_keyed_addr_with_domain_eq() { - let addr_1 = KeyedAddress::from_str("test@example.org:3d74YQqk").unwrap(); + let addr_1 = + KeyedAddress::from_str("test@example.org:gkJfLlKa2OLYItm4JD6p3hEF1kWH8LFtMK0rra8A2SQ=") + .unwrap(); let addr_2 = KeyedAddress::from_str("test@example.org:11voiefK5PgCX5F1TTcuoQ==").unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_keyed_addr_without_domain_eq() { - let addr_1 = KeyedAddress::from_str("test:3d74YQqk").unwrap(); + let addr_1 = + KeyedAddress::from_str("test:gkJfLlKa2OLYItm4JD6p3hEF1kWH8LFtMK0rra8A2SQ=").unwrap(); let addr_2 = KeyedAddress::from_str("test:11voiefK5PgCX5F1TTcuoQ==").unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_keyed_addr_with_domain_ne() { - let addr_1 = KeyedAddress::from_str("test@example.org:3d74YQqk").unwrap(); - let addr_2 = KeyedAddress::from_str("test2@example.org:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test@example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); + let addr_2 = KeyedAddress::from_str("test2@example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); assert_ne!(addr_1, addr_2); } #[test] fn cmp_keyed_addr_without_domain_ne() { - let addr_1 = KeyedAddress::from_str("test:3d74YQqk").unwrap(); - let addr_2 = KeyedAddress::from_str("test2:3d74YQqk").unwrap(); + let addr_1 = + KeyedAddress::from_str("test:gkJfLlKa2OLYItm4JD6p3hEF1kWH8LFtMK0rra8A2SQ=").unwrap(); + let addr_2 = + KeyedAddress::from_str("test2:gkJfLlKa2OLYItm4JD6p3hEF1kWH8LFtMK0rra8A2SQ=").unwrap(); assert_ne!(addr_1, addr_2); } #[test] fn cmp_addr_types_with_domain_eq() { - let addr_1 = KeyedAddress::from_str("test@example.org:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test@example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test+test+orsxg5a@example.org", '+').unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_addr_types_without_domain_eq() { - let addr_1 = KeyedAddress::from_str("test:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test+test+orsxg5a", '+').unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_addr_types_without_sub_addr() { - let addr_1 = KeyedAddress::from_str("test@example.org:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test@example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test@example.org", '+').unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_addr_types_idna_1() { - let addr_1 = KeyedAddress::from_str("test@mél.example.org:3d74YQqk").unwrap(); + let addr_1 = + KeyedAddress::from_str("test@mél.example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test@xn--ml-bja.example.org", '+').unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_addr_types_idna_2() { - let addr_1 = KeyedAddress::from_str("test@xn--ml-bja.example.org:3d74YQqk").unwrap(); + let addr_1 = + KeyedAddress::from_str("test@xn--ml-bja.example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test@mél.example.org", '+').unwrap(); assert_eq!(addr_1, addr_2); } #[test] fn cmp_addr_types_with_domain_ne() { - let addr_1 = KeyedAddress::from_str("test@example.org:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test@example.org:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test+test+orsxg5a@example.com", '+').unwrap(); assert_ne!(addr_1, addr_2); } #[test] fn cmp_addr_types_without_domain_ne() { - let addr_1 = KeyedAddress::from_str("test:3d74YQqk").unwrap(); + let addr_1 = KeyedAddress::from_str("test:sbW22xNStWKro4nnMKmG3A==").unwrap(); let addr_2 = CodedAddress::parse("test2+test+orsxg5a", '+').unwrap(); assert_ne!(addr_1, addr_2); } diff --git a/src/main.rs b/src/main.rs index a8b13f0..db04a45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod config; mod input; mod service; +const ALLOWED_KEY_SIZES: &[usize] = &[16, 32]; const COMMENT_CHAR: char = '#'; const DEFAULT_SEPARATOR: char = '+'; const KEY_SEPARATOR: char = ':';