#![warn(rust_2018_idioms)]
mod aes128gcm;
mod aesgcm;
mod common;
pub mod crypto;
mod error;
pub mod legacy;
pub use crate::{
crypto::{Cryptographer, EcKeyComponents, LocalKeyPair, RemotePublicKey},
error::*,
};
use crate::{
aes128gcm::ECE_AES128GCM_PAD_SIZE,
common::{WebPushParams, ECE_WEBPUSH_AUTH_SECRET_LENGTH},
};
pub fn generate_keypair_and_auth_secret(
) -> Result<(Box<dyn LocalKeyPair>, [u8; ECE_WEBPUSH_AUTH_SECRET_LENGTH])> {
let cryptographer = crypto::holder::get_cryptographer();
let local_key_pair = cryptographer.generate_ephemeral_keypair()?;
let mut auth_secret = [0u8; ECE_WEBPUSH_AUTH_SECRET_LENGTH];
cryptographer.random_bytes(&mut auth_secret)?;
Ok((local_key_pair, auth_secret))
}
pub fn encrypt(remote_pub: &[u8], remote_auth: &[u8], data: &[u8]) -> Result<Vec<u8>> {
let cryptographer = crypto::holder::get_cryptographer();
let remote_key = cryptographer.import_public_key(remote_pub)?;
let local_key_pair = cryptographer.generate_ephemeral_keypair()?;
let params = WebPushParams::new_for_plaintext(data, ECE_AES128GCM_PAD_SIZE);
aes128gcm::encrypt(&*local_key_pair, &*remote_key, remote_auth, data, params)
}
pub fn decrypt(components: &EcKeyComponents, auth: &[u8], data: &[u8]) -> Result<Vec<u8>> {
let cryptographer = crypto::holder::get_cryptographer();
let priv_key = cryptographer.import_key_pair(components).unwrap();
aes128gcm::decrypt(&*priv_key, auth, data)
}
#[cfg(all(test, feature = "backend-openssl"))]
fn generate_keys() -> Result<(Box<dyn LocalKeyPair>, Box<dyn LocalKeyPair>)> {
let cryptographer = crypto::holder::get_cryptographer();
let local_key = cryptographer.generate_ephemeral_keypair()?;
let remote_key = cryptographer.generate_ephemeral_keypair()?;
Ok((local_key, remote_key))
}
#[cfg(all(test, feature = "backend-openssl"))]
mod aes128gcm_tests {
use super::common::ECE_TAG_LENGTH;
use super::*;
#[allow(clippy::too_many_arguments)]
fn try_encrypt(
private_key: &str,
public_key: &str,
remote_pub_key: &str,
auth_secret: &str,
salt: &str,
pad_length: usize,
rs: u32,
plaintext: &str,
) -> Result<String> {
let cryptographer = crypto::holder::get_cryptographer();
let private_key = hex::decode(private_key).unwrap();
let public_key = hex::decode(public_key).unwrap();
let ec_key = EcKeyComponents::new(private_key, public_key);
let local_key_pair = cryptographer.import_key_pair(&ec_key)?;
let remote_pub_key = hex::decode(remote_pub_key).unwrap();
let remote_pub_key = cryptographer.import_public_key(&remote_pub_key).unwrap();
let auth_secret = hex::decode(auth_secret).unwrap();
let salt = Some(hex::decode(salt).unwrap());
let plaintext = plaintext.as_bytes();
let params = WebPushParams {
rs,
pad_length,
salt,
};
let ciphertext = aes128gcm::encrypt(
&*local_key_pair,
&*remote_pub_key,
&auth_secret,
plaintext,
params,
)?;
Ok(hex::encode(ciphertext))
}
fn try_decrypt(
private_key: &str,
public_key: &str,
auth_secret: &str,
payload: &str,
) -> Result<String> {
let private_key = hex::decode(private_key).unwrap();
let public_key = hex::decode(public_key).unwrap();
let ec_key = EcKeyComponents::new(private_key, public_key);
let plaintext = decrypt(
&ec_key,
&hex::decode(auth_secret).unwrap(),
&hex::decode(payload).unwrap(),
)?;
Ok(String::from_utf8(plaintext).unwrap())
}
#[test]
fn test_keygen() {
let cryptographer = crypto::holder::get_cryptographer();
cryptographer.generate_ephemeral_keypair().unwrap();
}
#[test]
fn test_e2e_through_public_api() {
let (remote_key, auth_secret) = generate_keypair_and_auth_secret().unwrap();
let plaintext = b"When I grow up, I want to be a watermelon";
let ciphertext =
encrypt(&remote_key.pub_as_raw().unwrap(), &auth_secret, plaintext).unwrap();
let decrypted = decrypt(
&remote_key.raw_components().unwrap(),
&auth_secret,
&ciphertext,
)
.unwrap();
assert_eq!(decrypted, plaintext.to_vec());
}
#[test]
fn test_e2e_large_plaintext() {
let (remote_key, auth_secret) = generate_keypair_and_auth_secret().unwrap();
let plaintext = [0; 5000];
let ciphertext =
encrypt(&remote_key.pub_as_raw().unwrap(), &auth_secret, &plaintext).unwrap();
let decrypted = decrypt(
&remote_key.raw_components().unwrap(),
&auth_secret,
&ciphertext,
)
.unwrap();
assert_eq!(decrypted, plaintext.to_vec());
}
#[test]
fn test_e2e_with_different_record_sizes_and_padding() {
let (local_key, remote_key) = generate_keys().unwrap();
let plaintext = b"When I grow up, I want to be a watermelon";
let mut auth_secret = vec![0u8; 16];
let cryptographer = crypto::holder::get_cryptographer();
cryptographer.random_bytes(&mut auth_secret).unwrap();
let remote_public = cryptographer
.import_public_key(&remote_key.pub_as_raw().unwrap())
.unwrap();
let plen = plaintext.len();
for plaintext_rs in &[2, 3, 7, 8, plen - 1, plen, plen + 1, 1024, 8192] {
let rs = (*plaintext_rs + ECE_TAG_LENGTH) as u32;
for pad_length in &[0, 1, 2, 8, 37, 127, 128] {
let pad_length = *pad_length;
let params = WebPushParams {
rs,
pad_length,
..WebPushParams::default()
};
let ciphertext = aes128gcm::encrypt(
&*local_key,
&*remote_public,
&auth_secret,
plaintext,
params,
)
.unwrap();
let decrypted =
aes128gcm::decrypt(&*remote_key, &auth_secret, &ciphertext).unwrap();
assert_eq!(decrypted, plaintext.to_vec());
}
}
}
#[test]
fn test_conv_fn() -> Result<()> {
let (local_key, auth) = generate_keypair_and_auth_secret()?;
let plaintext = b"Mary had a little lamb, with some nice mint jelly";
let encoded = encrypt(&local_key.pub_as_raw()?, &auth, plaintext).unwrap();
let decoded = decrypt(&local_key.raw_components()?, &auth, &encoded)?;
assert_eq!(decoded, plaintext.to_vec());
Ok(())
}
#[test]
fn try_encrypt_ietf_rfc() {
let ciphertext = try_encrypt(
"c9f58f89813e9f8e872e71f42aa64e1757c9254dcc62b72ddc010bb4043ea11c",
"04fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0f",
"042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e",
"05305932a1c7eabe13b6cec9fda48882",
"0c6bfaadad67958803092d454676f397",
0,
4096,
"When I grow up, I want to be a watermelon",
).unwrap();
assert_eq!(ciphertext, "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd");
}
#[test]
fn test_decrypt_ietf_rfc() {
let plaintext = try_decrypt(
"ab5757a70dd4a53e553a6bbf71ffefea2874ec07a6b379e3c48f895a02dc33de",
"042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e",
"05305932a1c7eabe13b6cec9fda48882",
"0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd"
).unwrap();
assert_eq!(plaintext, "When I grow up, I want to be a watermelon");
}
#[test]
fn test_decrypt_rs_18_pad_0() {
let plaintext = try_decrypt(
"27433fab8970b3cb5284b61183efb46286562cd2a7330d8cae960911a5571d0c",
"04515d4326355652399da24b2be9241e633b5cf14faf0cf3a6fd60317b954c0a2f4848548004b27b0cf7480bc810c6bec03a8fb79c8ea00fc8b05e00f8834563ef",
"d65a04df95f2db5e604839f717dcde79",
"7caebdbc20938ee340a946f1bd4f68f100000012410437cfdb5223d9f95eaa02f6ed940ff22eaf05b3622e949dc3ce9f335e6ef9b26aeaacca0f74080a8b364592f2ccc6d5eddd43004b70b91887d144d9fa93f16c3bc7ea68f4fd547a94eca84b16e138a6080177"
).unwrap();
assert_eq!(plaintext, "1");
}
#[test]
fn test_decrypt_missing_header_block() {
let err = try_decrypt(
"1be83f38332ef09681faf3f307b1ff2e10cab78cc7cdab683ac0ee92ac3f6ee1",
"04dba991ca215343f36bdd3e857cafde3d18bf57f1835b2833bad414f0884162051ac96a0b24490037d07cf528e4e18e100a1a64eb744748544bf1e220dabacf2c",
"3471bb98481e02533bf39542bcf3dba4",
"45b74d2b69be9b074de3b35aa87e7c15611d",
)
.unwrap_err();
match err {
Error::HeaderTooShort => {}
_ => panic!("Unexpected error {:?}", err),
};
}
#[test]
fn test_decrypt_truncated_sender_key() {
let err = try_decrypt(
"ce88e8e0b3057a4752eb4c8fa931eb621c302da5ad03b81af459cf6735560cae",
"04a325d99084c40de0ce722a042c448d94a32691721ca79e3cf745e78c69886194b02cea19224176795a9d4dbbb2073af2ccd6fa6f0a4c7c4968556be502a3ba81",
"5c31e0d96d9a139899ac0969d359f740",
"de5b696b87f1a15cb6adebdd79d6f99e000000120100b6bc1826c37c9f73dd6b4859c2b505181952",
)
.unwrap_err();
match err {
Error::InvalidKeyLength => {}
_ => panic!("Unexpected error {:?}", err),
};
}
#[test]
fn test_decrypt_truncated_auth_secret() {
let err = try_decrypt(
"60c7636a517de7039a0ac2d0e3064400794c78e7e049398129a227cee0f9a801",
"04fdd04128a85c05896d7f81fe118bdcb887b9f3c1ff4183adc4c824d128607300e986b2dfb5a610e5af43e408a00730584f93e3dfddfc44737d5f08fb2d6f8916",
"355a38cd6d9bef15990e2d3308dbd600",
"8115f4988b8c392a7bacb43c8f1ac5650000001241041994483c541e9bc39a6af03ff713aa7745c284e138a42a2435b797b20c4b698cf5118b4f8555317c190eabebfab749c164d3f6bdebe0d441719131a357d8890a13c4dbd4b16ff3dd5a83f7c91ad6e040ac42730a7f0b3cd3245e9f8d6ff31c751d410cfd"
).unwrap_err();
match err {
Error::OpenSSLError(_) => {}
_ => panic!("Unexpected error {:?}", err),
};
}
#[test]
fn test_decrypt_early_final_record() {
let err = try_decrypt(
"5dda1d918bc407ba3cda12cb8014d49aa7e0269002820304466bc80034ca9240",
"04c95c6520dad11e8f6a1bf8031a40c2a4ee1045c1903be06a1dfa7f829cceb2de02481ae6bd0476121b12c5532d0b231788077efa0683a5bfe0d62339b251cb35",
"40c241fde4269ee1e6d725592d982718",
"dbe215507d1ad3d2eaeabeae6e874d8f0000001241047bc4343f34a8348cdc4e462ffc7c40aa6a8c61a739c4c41d45125505f70e9fc5f9efa86852dd488dcf8e8ea2cafb75e07abd5ee7c9d5c038bafef079571b0bda294411ce98c76dd031c0e580577a4980a375e45ed30429be0e2ee9da7e6df8696d01b8ec"
).unwrap_err();
match err {
Error::DecryptPadding => {}
_ => panic!("Unexpected error {:?}", err),
};
}
}