зеркало из https://github.com/mozilla/rust-ece.git
Merge pull request #4 from jrconlin/feat/aesgcm
feat: Add aesgcm encrypted content support.
This commit is contained in:
Коммит
26b9707d48
|
@ -5,9 +5,10 @@ authors = ["Edouard Oger <eoger@fastmail.com>"]
|
|||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.2.6"
|
||||
failure = "0.1.2"
|
||||
failure_derive = "0.1.2"
|
||||
byteorder = "1.2.7"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
base64 = "0.10"
|
||||
|
||||
[dependencies.ece-crypto]
|
||||
path = "crypto"
|
||||
|
|
|
@ -5,4 +5,5 @@ authors = ["Edouard Oger <eoger@fastmail.com>"]
|
|||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.2"
|
||||
failure = "0.1.5"
|
||||
base64 = "0.10"
|
||||
|
|
|
@ -96,6 +96,12 @@ impl LocalKeyPair for OpenSSLLocalKeyPair {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<EcKey<Private>> for OpenSSLLocalKeyPair {
|
||||
fn from(key: EcKey<Private>) -> OpenSSLLocalKeyPair {
|
||||
OpenSSLLocalKeyPair { ec_key: key }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenSSLCrypto;
|
||||
impl Crypto for OpenSSLCrypto {
|
||||
type RemotePublicKey = OpenSSLRemotePublicKey;
|
||||
|
|
|
@ -12,7 +12,9 @@ pub trait RemotePublicKey {
|
|||
}
|
||||
|
||||
pub trait LocalKeyPair {
|
||||
fn generate_random() -> Result<Self> where Self: Sized;
|
||||
fn generate_random() -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Export the public key component in the
|
||||
/// binary uncompressed point representation.
|
||||
fn pub_as_raw(&self) -> Result<Vec<u8>>;
|
||||
|
|
|
@ -9,44 +9,17 @@ use error::*;
|
|||
|
||||
const ECE_AES128GCM_MIN_RS: u32 = 18;
|
||||
const ECE_AES128GCM_HEADER_LENGTH: usize = 21;
|
||||
const ECE_AES128GCM_MAX_KEY_ID_LENGTH: usize = 255;
|
||||
// The max AES128GCM Key ID Length is 255 octets. We use far less of that because we use
|
||||
// the "key_id" to store the exchanged public key since we don't cache the key_ids.
|
||||
// Code fails if the key_id is not a public key length field.
|
||||
const ECE_AES128GCM_PAD_SIZE: usize = 1;
|
||||
|
||||
const ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX: &'static str = "WebPush: info\0";
|
||||
const ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX: &str = "WebPush: info\0";
|
||||
const ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH: usize = 144; // 14 (prefix len) + 65 (pub key len) * 2;
|
||||
|
||||
const ECE_WEBPUSH_DEFAULT_RS: u32 = 4096;
|
||||
const ECE_WEBPUSH_IKM_LENGTH: usize = 32;
|
||||
const ECE_AES128GCM_KEY_INFO: &'static str = "Content-Encoding: aes128gcm\0";
|
||||
const ECE_AES128GCM_NONCE_INFO: &'static str = "Content-Encoding: nonce\0";
|
||||
|
||||
// TODO: Make it nicer to use with a builder pattern.
|
||||
pub struct WebPushParams {
|
||||
pub rs: u32,
|
||||
pub pad_length: usize,
|
||||
pub salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl WebPushParams {
|
||||
/// Random salt, record size = 4096 and padding length = 0.
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
rs: ECE_WEBPUSH_DEFAULT_RS,
|
||||
pad_length: 0,
|
||||
salt: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Never use the same salt twice as it will derive the same content encryption
|
||||
/// key for multiple messages if the same sender private key is used!
|
||||
pub fn new(rs: u32, pad_length: usize, salt: Vec<u8>) -> Self {
|
||||
Self {
|
||||
rs,
|
||||
pad_length,
|
||||
salt: Some(salt),
|
||||
}
|
||||
}
|
||||
}
|
||||
const ECE_AES128GCM_KEY_INFO: &str = "Content-Encoding: aes128gcm\0";
|
||||
const ECE_AES128GCM_NONCE_INFO: &str = "Content-Encoding: nonce\0";
|
||||
|
||||
// TODO: When done, remove the aes128gcm prefixes and the EC_ ones.
|
||||
// As for now it makes it easier to Ctrl + F into ecec :)
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This supports the now obsolete HTTP-ECE Draft 02 "aesgcm" content
|
||||
* type. There are a number of providers that still use this format,
|
||||
* and there's no real mechanism to return the client supported crypto
|
||||
* versions.
|
||||
*
|
||||
* */
|
||||
|
||||
use base64;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common::{
|
||||
ece_min_block_pad_length, EceMode, EceWebPush, KeyAndNonce, WebPushParams, ECE_AES_KEY_LENGTH,
|
||||
ECE_NONCE_LENGTH, ECE_SALT_LENGTH, ECE_TAG_LENGTH,
|
||||
};
|
||||
use ece_crypto::{Crypto, LocalKeyPair, RemotePublicKey};
|
||||
use error::{ErrorKind, Result};
|
||||
|
||||
const ECE_AESGCM_PAD_SIZE: usize = 2;
|
||||
|
||||
const ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH: usize = 134; // (2 + Raw Key Length) * 2
|
||||
const ECE_WEBPUSH_AESGCM_AUTHINFO: &str = "Content-Encoding: auth\0";
|
||||
|
||||
// a DER prefixed key is "\04" + ECE_WEBPUSH_RAW_KEY_LENGTH
|
||||
const ECE_WEBPUSH_RAW_KEY_LENGTH: usize = 65;
|
||||
const ECE_WEBPUSH_IKM_LENGTH: usize = 32;
|
||||
|
||||
pub struct AesGcmEncryptedBlock {
|
||||
pub dh: Vec<u8>,
|
||||
pub salt: Vec<u8>,
|
||||
pub rs: u32,
|
||||
pub ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AesGcmEncryptedBlock {
|
||||
pub fn aesgcm_rs(rs: u32) -> u32 {
|
||||
if rs > u32::max_value() - ECE_TAG_LENGTH as u32 {
|
||||
return 0;
|
||||
}
|
||||
rs + ECE_TAG_LENGTH as u32
|
||||
}
|
||||
|
||||
/// Create a new block from the various header strings and body content.
|
||||
pub fn new(
|
||||
dh: &Vec<u8>,
|
||||
salt: &Vec<u8>,
|
||||
rs: u32,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<AesGcmEncryptedBlock> {
|
||||
Ok(AesGcmEncryptedBlock {
|
||||
dh: dh.to_owned(),
|
||||
salt: salt.to_owned(),
|
||||
rs: Self::aesgcm_rs(rs),
|
||||
ciphertext,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the headers Hash, NOTE you may need to merge Crypto-Key if there's
|
||||
/// already a VAPID element present.
|
||||
pub fn headers(self) -> HashMap<String, String> {
|
||||
let mut result: HashMap<String, String> = HashMap::new();
|
||||
let mut rs = "".to_owned();
|
||||
result.insert(
|
||||
"Crypto-Key".to_owned(),
|
||||
format!(
|
||||
"dh={}",
|
||||
base64::encode_config(&self.dh, base64::URL_SAFE_NO_PAD)
|
||||
),
|
||||
);
|
||||
if self.rs > 0 {
|
||||
rs = format!(";rs={}", self.rs);
|
||||
}
|
||||
result.insert(
|
||||
"Encryption".to_owned(),
|
||||
format!(
|
||||
"salt={}{}",
|
||||
base64::encode_config(&self.salt, base64::URL_SAFE_NO_PAD),
|
||||
rs
|
||||
),
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
/// Encode the body as a String.
|
||||
/// If you need the bytes, probably just call .ciphertext directly
|
||||
pub fn body(self) -> String {
|
||||
base64::encode_config(&self.ciphertext, base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AesGcmEceWebPush<L, R, C> {
|
||||
_marker1: ::std::marker::PhantomData<L>,
|
||||
_marker2: ::std::marker::PhantomData<R>,
|
||||
_marker3: ::std::marker::PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<L, R, C> AesGcmEceWebPush<L, R, C>
|
||||
where
|
||||
L: LocalKeyPair,
|
||||
R: RemotePublicKey,
|
||||
C: Crypto<LocalKeyPair = L, RemotePublicKey = R>,
|
||||
{
|
||||
/// Encrypts a Web Push message using the "aesgcm" scheme. This function
|
||||
/// automatically generates an ephemeral ECDH key pair.
|
||||
pub fn encrypt(
|
||||
remote_pub_key: &R,
|
||||
auth_secret: &[u8],
|
||||
plaintext: &[u8],
|
||||
params: WebPushParams,
|
||||
) -> Result<AesGcmEncryptedBlock> {
|
||||
let local_prv_key = C::generate_ephemeral_keypair()?;
|
||||
Self::encrypt_with_keys(
|
||||
&local_prv_key,
|
||||
remote_pub_key,
|
||||
auth_secret,
|
||||
plaintext,
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
/// Encrypts a Web Push message using the "aesgcm" scheme, with an explicit
|
||||
/// sender key. The sender key can be reused.
|
||||
pub fn encrypt_with_keys(
|
||||
local_prv_key: &L,
|
||||
remote_pub_key: &R,
|
||||
auth_secret: &[u8],
|
||||
plaintext: &[u8],
|
||||
params: WebPushParams,
|
||||
) -> Result<AesGcmEncryptedBlock> {
|
||||
let salt = {
|
||||
let mut salt = [0u8; ECE_SALT_LENGTH];
|
||||
C::random(&mut salt)?;
|
||||
salt.to_vec()
|
||||
};
|
||||
let raw_local_pub_key = local_prv_key.pub_as_raw()?;
|
||||
let ciphertext = Self::common_encrypt(
|
||||
local_prv_key,
|
||||
remote_pub_key,
|
||||
auth_secret,
|
||||
&salt,
|
||||
params.rs,
|
||||
params.pad_length,
|
||||
plaintext,
|
||||
)?;
|
||||
Ok(AesGcmEncryptedBlock {
|
||||
salt,
|
||||
dh: raw_local_pub_key,
|
||||
rs: params.rs,
|
||||
ciphertext,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decrypts a Web Push message encrypted using the "aesgcm" scheme.
|
||||
pub fn decrypt(
|
||||
local_prv_key: &L,
|
||||
auth_secret: &[u8],
|
||||
block: &AesGcmEncryptedBlock,
|
||||
) -> Result<Vec<u8>> {
|
||||
let sender_key = C::public_key_from_raw(&block.dh)?;
|
||||
Self::common_decrypt(
|
||||
local_prv_key,
|
||||
&sender_key,
|
||||
auth_secret,
|
||||
&block.salt,
|
||||
block.rs,
|
||||
&block.ciphertext,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, C> EceWebPush for AesGcmEceWebPush<L, R, C>
|
||||
where
|
||||
L: LocalKeyPair,
|
||||
R: RemotePublicKey,
|
||||
C: Crypto<LocalKeyPair = L, RemotePublicKey = R>,
|
||||
{
|
||||
type Crypto = C;
|
||||
type LocalKeyPair = L;
|
||||
type RemotePublicKey = R;
|
||||
|
||||
fn needs_trailer(rs: u32, ciphertextlen: usize) -> bool {
|
||||
ciphertextlen as u32 % rs == 0
|
||||
}
|
||||
|
||||
fn pad_size() -> usize {
|
||||
ECE_AESGCM_PAD_SIZE
|
||||
}
|
||||
|
||||
fn min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize {
|
||||
ece_min_block_pad_length(pad_len, max_block_len)
|
||||
}
|
||||
|
||||
fn pad(plaintext: &[u8], _: usize, _: bool) -> Result<Vec<u8>> {
|
||||
let plen = plaintext.len();
|
||||
let mut block = vec![0; plen + ECE_AESGCM_PAD_SIZE];
|
||||
block[2..].copy_from_slice(plaintext);
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn unpad(block: &[u8], _: bool) -> Result<&[u8]> {
|
||||
Ok(&block[2..])
|
||||
}
|
||||
|
||||
/// Derives the "aesgcm" decryption keyn and nonce given the receiver private
|
||||
/// key, sender public key, authentication secret, and sender salt.
|
||||
fn derive_key_and_nonce(
|
||||
ece_mode: EceMode,
|
||||
local_prv_key: &Self::LocalKeyPair,
|
||||
remote_pub_key: &Self::RemotePublicKey,
|
||||
auth_secret: &[u8],
|
||||
salt: &[u8],
|
||||
) -> Result<KeyAndNonce> {
|
||||
let shared_secret = Self::Crypto::compute_ecdh_secret(remote_pub_key, local_prv_key)?;
|
||||
let raw_remote_pub_key = remote_pub_key.as_raw()?;
|
||||
let raw_local_pub_key = local_prv_key.pub_as_raw()?;
|
||||
|
||||
let keypair = match ece_mode {
|
||||
EceMode::ENCRYPT => encode_keys(&raw_remote_pub_key, &raw_local_pub_key),
|
||||
EceMode::DECRYPT => encode_keys(&raw_local_pub_key, &raw_remote_pub_key),
|
||||
}?;
|
||||
let keyinfo = generate_info("aesgcm", &keypair)?;
|
||||
let nonceinfo = generate_info("nonce", &keypair)?;
|
||||
let ikm = Self::Crypto::hkdf_sha256(
|
||||
auth_secret,
|
||||
&shared_secret,
|
||||
&ECE_WEBPUSH_AESGCM_AUTHINFO.as_bytes(),
|
||||
ECE_WEBPUSH_IKM_LENGTH,
|
||||
)?;
|
||||
let key = Self::Crypto::hkdf_sha256(salt, &ikm, &keyinfo, ECE_AES_KEY_LENGTH)?;
|
||||
let nonce = Self::Crypto::hkdf_sha256(salt, &ikm, &nonceinfo, ECE_NONCE_LENGTH)?;
|
||||
Ok((key, nonce))
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_keys(raw_key1: &[u8], raw_key2: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut combined = vec![0u8; ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH];
|
||||
|
||||
if raw_key1.len() > ECE_WEBPUSH_RAW_KEY_LENGTH || raw_key2.len() > ECE_WEBPUSH_RAW_KEY_LENGTH {
|
||||
return Err(ErrorKind::InvalidKeyLength.into());
|
||||
}
|
||||
// length prefix each key
|
||||
combined[0] = 0;
|
||||
combined[1] = 65;
|
||||
combined[2..67].copy_from_slice(raw_key1);
|
||||
combined[67] = 0;
|
||||
combined[68] = 65;
|
||||
combined[69..].copy_from_slice(raw_key2);
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
// The "aesgcm" IKM info string is "WebPush: info", followed by the
|
||||
// receiver and sender public keys prefixed by their lengths.
|
||||
fn generate_info(encoding: &str, keypair: &[u8]) -> Result<Vec<u8>> {
|
||||
let info_str = format!("Content-Encoding: {}\0P-256\0", encoding);
|
||||
let offset = info_str.len();
|
||||
let mut info = vec![0u8; offset + keypair.len()];
|
||||
info[0..offset].copy_from_slice(info_str.as_bytes());
|
||||
info[offset..offset + ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH].copy_from_slice(keypair);
|
||||
Ok(info)
|
||||
}
|
|
@ -13,13 +13,39 @@ pub const ECE_NONCE_LENGTH: usize = 12;
|
|||
|
||||
// From ece.h:
|
||||
pub const ECE_SALT_LENGTH: usize = 16;
|
||||
const ECE_TAG_LENGTH: usize = 16;
|
||||
const ECE_WEBPUSH_PRIVATE_KEY_LENGTH: usize = 32;
|
||||
pub const ECE_TAG_LENGTH: usize = 16;
|
||||
//const ECE_WEBPUSH_PRIVATE_KEY_LENGTH: usize = 32;
|
||||
pub const ECE_WEBPUSH_PUBLIC_KEY_LENGTH: usize = 65;
|
||||
const ECE_WEBPUSH_AUTH_SECRET_LENGTH: usize = 16;
|
||||
const ECE_WEBPUSH_DEFAULT_RS: u32 = 4096;
|
||||
|
||||
const ECE_AESGCM_MIN_RS: u8 = 3;
|
||||
const ECE_AESGCM_PAD_SIZE: u8 = 2;
|
||||
// TODO: Make it nicer to use with a builder pattern.
|
||||
pub struct WebPushParams {
|
||||
pub rs: u32,
|
||||
pub pad_length: usize,
|
||||
pub salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl WebPushParams {
|
||||
/// Random salt, record size = 4096 and padding length = 0.
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
rs: ECE_WEBPUSH_DEFAULT_RS,
|
||||
pad_length: 2,
|
||||
salt: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Never use the same salt twice as it will derive the same content encryption
|
||||
/// key for multiple messages if the same sender private key is used!
|
||||
pub fn new(rs: u32, pad_length: usize, salt: Vec<u8>) -> Self {
|
||||
Self {
|
||||
rs,
|
||||
pad_length,
|
||||
salt: Some(salt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EceMode {
|
||||
ENCRYPT,
|
||||
|
@ -48,7 +74,7 @@ pub trait EceWebPush {
|
|||
if salt.len() != ECE_SALT_LENGTH {
|
||||
return Err(ErrorKind::InvalidSalt.into());
|
||||
}
|
||||
if plaintext.len() == 0 {
|
||||
if plaintext.is_empty() {
|
||||
return Err(ErrorKind::ZeroPlaintext.into());
|
||||
}
|
||||
let (key, nonce) = Self::derive_key_and_nonce(
|
||||
|
@ -143,7 +169,7 @@ pub trait EceWebPush {
|
|||
if salt.len() != ECE_SALT_LENGTH {
|
||||
return Err(ErrorKind::InvalidSalt.into());
|
||||
}
|
||||
if ciphertext.len() == 0 {
|
||||
if ciphertext.is_empty() {
|
||||
return Err(ErrorKind::ZeroCiphertext.into());
|
||||
}
|
||||
if Self::needs_trailer(rs, ciphertext.len()) {
|
||||
|
@ -170,6 +196,7 @@ pub trait EceWebPush {
|
|||
let block_len = record.len() - ECE_TAG_LENGTH;
|
||||
let data = &record[0..block_len];
|
||||
let tag = &record[block_len..];
|
||||
// when this fails, it's always "OpenSSL error"
|
||||
let plaintext = Self::Crypto::aes_gcm_128_decrypt(&key, &iv, data, tag)?;
|
||||
let last_record = count == records_count - 1;
|
||||
if plaintext.len() < Self::pad_size() {
|
||||
|
@ -208,11 +235,11 @@ pub fn ece_min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize {
|
|||
// the padding first.
|
||||
block_pad_len += 1;
|
||||
}
|
||||
return if block_pad_len > pad_len {
|
||||
if block_pad_len > pad_len {
|
||||
pad_len
|
||||
} else {
|
||||
block_pad_len
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a 96-bit IV, 48 bits of which are populated.
|
||||
|
|
11
src/error.rs
11
src/error.rs
|
@ -2,6 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use base64;
|
||||
use ece_crypto;
|
||||
use failure::{Backtrace, Context, Fail};
|
||||
use std::boxed::Box;
|
||||
|
@ -89,6 +90,9 @@ pub enum ErrorKind {
|
|||
|
||||
#[fail(display = "Crypto error")]
|
||||
CryptoError,
|
||||
|
||||
#[fail(display = "Could not decode base64 entry")]
|
||||
DecodeError,
|
||||
}
|
||||
|
||||
// This is bad design, however handling cross-crates errors
|
||||
|
@ -106,3 +110,10 @@ impl From<ece_crypto::Error> for Error {
|
|||
ErrorKind::from(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for Error {
|
||||
#[inline]
|
||||
fn from(_: base64::DecodeError) -> Error {
|
||||
ErrorKind::DecodeError.into()
|
||||
}
|
||||
}
|
||||
|
|
96
src/lib.rs
96
src/lib.rs
|
@ -2,18 +2,19 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
extern crate base64;
|
||||
extern crate byteorder;
|
||||
extern crate ece_crypto;
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
|
||||
mod aes128gcm;
|
||||
// TODO: mod aesgcm;
|
||||
mod aesgcm;
|
||||
mod common;
|
||||
mod error;
|
||||
|
||||
pub use aes128gcm::WebPushParams;
|
||||
pub use aesgcm::AesGcmEncryptedBlock;
|
||||
pub use common::WebPushParams;
|
||||
pub use ece_crypto::{LocalKeyPair, RemotePublicKey};
|
||||
pub use error::*;
|
||||
|
||||
|
@ -28,6 +29,13 @@ pub type Aes128GcmEceWebPush = aes128gcm::Aes128GcmEceWebPush<
|
|||
ece_crypto_openssl::OpenSSLCrypto,
|
||||
>;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub type AesGcmEceWebPush = aesgcm::AesGcmEceWebPush<
|
||||
OpenSSLLocalKeyPair,
|
||||
OpenSSLRemotePublicKey,
|
||||
ece_crypto_openssl::OpenSSLCrypto,
|
||||
>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod aes128gcm_tests {
|
||||
extern crate ece_crypto_openssl;
|
||||
|
@ -233,3 +241,85 @@ mod aes128gcm_tests {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// =====================
|
||||
#[cfg(test)]
|
||||
mod aesgcm_tests {
|
||||
extern crate base64;
|
||||
extern crate ece_crypto_openssl;
|
||||
extern crate hex;
|
||||
|
||||
use super::*;
|
||||
use aesgcm::AesGcmEncryptedBlock;
|
||||
use ece_crypto::Crypto;
|
||||
use ece_crypto_openssl::OpenSSLCrypto;
|
||||
|
||||
fn try_decrypt(
|
||||
priv_key: &str,
|
||||
auth_secret: &str,
|
||||
block: &AesGcmEncryptedBlock,
|
||||
) -> Result<String> {
|
||||
// The AesGcmEncryptedBlock is composed from the `Crypto-Key` & `Encryption` headers, and post body
|
||||
// The Block will attempt to decode the base64 strings for dh & salt, so no additional action needed.
|
||||
// Since the body is most likely not encoded, it is expected to be a raw buffer of [u8]
|
||||
let priv_key_raw = base64::decode_config(priv_key, base64::URL_SAFE_NO_PAD)?;
|
||||
let priv_key = OpenSSLLocalKeyPair::new(&priv_key_raw)?;
|
||||
let auth_secret = base64::decode_config(auth_secret, base64::URL_SAFE_NO_PAD)?;
|
||||
let plaintext = AesGcmEceWebPush::decrypt(&priv_key, &auth_secret, &block)?;
|
||||
Ok(String::from_utf8(plaintext).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode() {
|
||||
// generated the content using pywebpush, which verified against the client.
|
||||
let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
|
||||
let priv_key_raw = "yerDmA9uNFoaUnSt2TkWWLwPseG1qtzS2zdjUl8Z7tc";
|
||||
//let pub_key_raw = "BLBlTYure2QVhJCiDt4gRL0JNmUBMxtNB5B6Z1hDg5h-Epw6mVFV4whoYGBlWNY-ENR1FObkGFyMf7-6ZMHMAxw";
|
||||
|
||||
// Incoming Crypto-Key: dh=
|
||||
let dh = "BJvcyzf8ocm6F7lbFePebtXU7OHkmylXN9FL2g-yBHwUKqo6cD-FP1h5SHEQQ-xEgJl-F0xEEmSaEx2-qeJHYmk";
|
||||
// Incoming Encryption-Key: salt=
|
||||
let salt = "8qX1ZgkLD50LHgocZdPKZQ";
|
||||
// Incoming Body (this is normally raw bytes. It's encoded here for presentation)
|
||||
let ciphertext = base64::decode_config("8Vyes671P_VDf3G2e6MgY6IaaydgR-vODZZ7L0ZHbpCJNVaf_2omEms2tiPJiU22L3BoECKJixiOxihcsxWMjTgAcplbvfu1g6LWeP4j8dMAzJionWs7OOLif6jBKN6LGm4EUw9e26EBv9hNhi87-HaEGbfBMGcLvm1bql1F",
|
||||
base64::URL_SAFE_NO_PAD).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::decode_config(dh, base64::URL_SAFE_NO_PAD).unwrap(),
|
||||
&base64::decode_config(salt, base64::URL_SAFE_NO_PAD).unwrap(),
|
||||
4096,
|
||||
ciphertext,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = try_decrypt(priv_key_raw, auth_raw, &block).unwrap();
|
||||
|
||||
assert!(result == plaintext)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_e2e() {
|
||||
let (local_key, remote_key) = ece_crypto_openssl::generate_keys().unwrap();
|
||||
let plaintext = "When I grow up, I want to be a watermelon".as_bytes();
|
||||
let mut auth_secret = vec![0u8; 16];
|
||||
OpenSSLCrypto::random(&mut auth_secret).unwrap();
|
||||
let remote_public =
|
||||
OpenSSLCrypto::public_key_from_raw(&remote_key.pub_as_raw().unwrap()).unwrap();
|
||||
let params = WebPushParams::default();
|
||||
let ciphertext = AesGcmEceWebPush::encrypt_with_keys(
|
||||
&local_key,
|
||||
&remote_public,
|
||||
&auth_secret,
|
||||
&plaintext,
|
||||
params,
|
||||
)
|
||||
.unwrap();
|
||||
let decrypted = AesGcmEceWebPush::decrypt(&remote_key, &auth_secret, &ciphertext).unwrap();
|
||||
assert_eq!(decrypted, plaintext);
|
||||
}
|
||||
|
||||
// If decode using externally validated data works, and e2e using the same decoder work, things
|
||||
// should encode/decode.
|
||||
// Other tests to be included if required, but skipping for now because of time constraints.
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче