pub use crate::aesgcm::AesGcmEncryptedBlock;
use crate::{aesgcm, common::WebPushParams, crypto::EcKeyComponents, error::*};
pub fn encrypt_aesgcm(
remote_pub: &[u8],
remote_auth: &[u8],
data: &[u8],
) -> Result<AesGcmEncryptedBlock> {
let cryptographer = crate::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, aesgcm::ECE_AESGCM_PAD_SIZE);
aesgcm::encrypt(&*local_key_pair, &*remote_key, remote_auth, data, params)
}
pub fn decrypt_aesgcm(
components: &EcKeyComponents,
auth: &[u8],
data: &AesGcmEncryptedBlock,
) -> Result<Vec<u8>> {
let cryptographer = crate::crypto::holder::get_cryptographer();
let priv_key = cryptographer.import_key_pair(components).unwrap();
aesgcm::decrypt(&*priv_key, auth, data)
}
#[cfg(all(test, feature = "backend-openssl"))]
mod aesgcm_tests {
use super::*;
use base64::Engine;
use hex;
#[derive(Debug)]
struct AesGcmTestPayload {
dh: String,
salt: String,
rs: u32,
ciphertext: String,
}
#[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<AesGcmTestPayload> {
let cryptographer = crate::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 encrypted_block = aesgcm::encrypt(
&*local_key_pair,
&*remote_pub_key,
&auth_secret,
plaintext,
params,
)?;
Ok(AesGcmTestPayload {
dh: hex::encode(encrypted_block.dh),
salt: hex::encode(encrypted_block.salt),
rs: encrypted_block.rs,
ciphertext: hex::encode(encrypted_block.ciphertext),
})
}
fn try_decrypt(
private_key: &str,
public_key: &str,
auth_secret: &str,
payload: &AesGcmTestPayload,
) -> 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_aesgcm(
&ec_key,
&hex::decode(auth_secret).unwrap(),
&AesGcmEncryptedBlock::new(
&hex::decode(&payload.dh).unwrap(),
&hex::decode(&payload.salt).unwrap(),
payload.rs,
hex::decode(&payload.ciphertext).unwrap(),
)?,
)?;
Ok(String::from_utf8(plaintext).unwrap())
}
#[test]
fn test_e2e() {
let (local_key, remote_key) = crate::generate_keys().unwrap();
let plaintext = b"There was a green mouse, running in the grass";
let mut auth_secret = vec![0u8; 16];
let cryptographer = crate::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 params = WebPushParams::default();
let encrypted_block = aesgcm::encrypt(
&*local_key,
&*remote_public,
&auth_secret,
plaintext,
params,
)
.unwrap();
let decrypted = aesgcm::decrypt(&*remote_key, &auth_secret, &encrypted_block).unwrap();
assert_eq!(decrypted, plaintext.to_vec());
}
#[test]
fn test_conv_fn() -> Result<()> {
let (local_key, auth) = crate::generate_keypair_and_auth_secret()?;
let plaintext = b"There was a little ship that had never sailed";
let encoded = encrypt_aesgcm(&local_key.pub_as_raw()?, &auth, plaintext).unwrap();
let decoded = decrypt_aesgcm(&local_key.raw_components()?, &auth, &encoded)?;
assert_eq!(decoded, plaintext.to_vec());
Ok(())
}
#[test]
fn try_encrypt_ietf_rfc() {
let encrypted_block = try_encrypt(
"9c249c7a4f90a448e638e953fab437f27673bdd3e5a9ad34672d22ea6d8e26f6",
"04da110db6fce091a6f20e59e42171bab4aab17589d7522d7d71166152c4f3963b0989038d7b0811ce1aab161a4351bc06a917089e833e90eb5ad7568ff9ae8075",
"042124063ccbf19dc2fa88b643ba04e6dd8da7ea7ba2c8c62e0f77a943f4c2fa914f6d44116c9fd1c40341c6a440cab3e2140a60e4378a5da735972de078005105",
"476f6f20676f6f206727206a6f6f6221",
"96781aadbc8a7cca22f59ef9c585e692",
0,
4096,
"I am the walrus",
).unwrap();
assert_eq!(
encrypted_block.ciphertext,
"ea7a80414304f2136ac39277925f1ca55549ca55ca62a64e7ac7991bc52e78aa40"
);
}
#[test]
fn test_decrypt_ietf_rfc() {
let plaintext = try_decrypt(
"f455a5d79fd05100160da0f7937979d19059409e1abb6ec5d55e05d2e2d20ff3",
"042124063ccbf19dc2fa88b643ba04e6dd8da7ea7ba2c8c62e0f77a943f4c2fa914f6d44116c9fd1c40341c6a440cab3e2140a60e4378a5da735972de078005105",
"476f6f20676f6f206727206a6f6f6221",
&AesGcmTestPayload {
ciphertext : "ea7a80414304f2136ac39277925f1ca55549ca55ca62a64e7ac7991bc52e78aa40".to_owned(),
salt : "96781aadbc8a7cca22f59ef9c585e692".to_owned(),
dh : "04da110db6fce091a6f20e59e42171bab4aab17589d7522d7d71166152c4f3963b0989038d7b0811ce1aab161a4351bc06a917089e833e90eb5ad7568ff9ae8075".to_owned(),
rs : 4096,
}
).unwrap();
assert_eq!(plaintext, "I am the walrus");
}
fn try_decrypt_b64(
priv_key: &str,
pub_key: &str,
auth_secret: &str,
block: &AesGcmEncryptedBlock,
) -> Result<String> {
let priv_key_raw = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(priv_key)?;
let pub_key_raw = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(pub_key)?;
let ec_key = EcKeyComponents::new(priv_key_raw, pub_key_raw);
let auth_secret = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(auth_secret)?;
let plaintext = decrypt_aesgcm(&ec_key, &auth_secret, block)?;
Ok(String::from_utf8(plaintext).unwrap())
}
#[test]
fn test_decode() {
use base64::Engine;
let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
let priv_key_raw = "yerDmA9uNFoaUnSt2TkWWLwPseG1qtzS2zdjUl8Z7tc";
let pub_key_raw = "BLBlTYure2QVhJCiDt4gRL0JNmUBMxtNB5B6Z1hDg5h-Epw6mVFV4whoYGBlWNY-ENR1FObkGFyMf7-6ZMHMAxw";
let dh = "BJvcyzf8ocm6F7lbFePebtXU7OHkmylXN9FL2g-yBHwUKqo6cD-FP1h5SHEQQ-xEgJl-F0xEEmSaEx2-qeJHYmk";
let salt = "8qX1ZgkLD50LHgocZdPKZQ";
let ciphertext = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("8Vyes671P_VDf3G2e6MgY6IaaydgR-vODZZ7L0ZHbpCJNVaf_2omEms2tiPJiU22L3BoECKJixiOxihcsxWMjTgAcplbvfu1g6LWeP4j8dMAzJionWs7OOLif6jBKN6LGm4EUw9e26EBv9hNhi87-HaEGbfBMGcLvm1bql1F").unwrap();
let plaintext = "Amidst the mists and coldest frosts I thrust my fists against the\nposts and still demand to see the ghosts.\n";
let block = AesGcmEncryptedBlock::new(
&base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(dh)
.unwrap(),
&base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(salt)
.unwrap(),
4096,
ciphertext,
)
.unwrap();
let result = try_decrypt_b64(priv_key_raw, pub_key_raw, auth_raw, &block).unwrap();
assert!(result == plaintext)
}
#[test]
fn test_decode_padding() {
use base64::Engine;
let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
let priv_key_raw = "yerDmA9uNFoaUnSt2TkWWLwPseG1qtzS2zdjUl8Z7tc";
let pub_key_raw = "BLBlTYure2QVhJCiDt4gRL0JNmUBMxtNB5B6Z1hDg5h-Epw6mVFV4whoYGBlWNY-ENR1FObkGFyMf7-6ZMHMAxw";
let dh = "BCX7KJ_1Em-LjeB56E2KDoMjKDhTaDhjv8c6dwbvZQZ_Gsfp3AT54x2zYUPcBwd1GVyGsk55ProJ98cFrVxrPz4";
let salt = "x2I2OZpSCoe-Cc5UW36Nng";
let ciphertext = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("Ua3-WW5kTbt11dBTiXBP6_hLBYhBNOtDFfue5QHMTd2DicL0wutDnt5z9pjRJ76w562egPq5qro95YLnsX0NWGmDQbsQ0Azds6jcBGsxHPt0p5GELAtR4AJj2OsB_LV7dTuGHN2SqsyXLARjTFN2wsF3xWhmuw").unwrap();
let plaintext = "Tabs are the real indent";
let block = AesGcmEncryptedBlock::new(
&base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(dh)
.unwrap(),
&base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(salt)
.unwrap(),
4096,
ciphertext,
)
.unwrap();
let result = try_decrypt_b64(priv_key_raw, pub_key_raw, auth_raw, &block).unwrap();
assert!(result == plaintext)
}
}