ece/common.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
/* 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 module implements the parts of ECE that are currently shared by all
//! supported schemes, such as the actual AES-GCM encryption of a single record.
//! It can't be used in isolation; you must instead provide a concrete instantiation
//! of an ECE encryption scheme by implementing the `EncryptionScheme` trait.
use crate::{crypto::Cryptographer, error::*};
use byteorder::{BigEndian, ByteOrder};
pub(crate) const ECE_AES_KEY_LENGTH: usize = 16;
pub(crate) const ECE_NONCE_LENGTH: usize = 12;
pub(crate) const ECE_SALT_LENGTH: usize = 16;
pub(crate) const ECE_TAG_LENGTH: usize = 16;
pub(crate) const ECE_WEBPUSH_PUBLIC_KEY_LENGTH: usize = 65;
pub(crate) const ECE_WEBPUSH_AUTH_SECRET_LENGTH: usize = 16;
pub(crate) const ECE_WEBPUSH_DEFAULT_RS: u32 = 4096;
pub(crate) const ECE_WEBPUSH_DEFAULT_PADDING_BLOCK_SIZE: usize = 128;
/// Parameters that control the details of the encryption process.
///
/// These are the various configuration knobs that could potentially be
/// tweaked when encrypting a given piece of data, packaged together
/// in a struct for convenience.
///
pub(crate) struct WebPushParams {
/// The record size, for chunking the plaintext into multiple records.
pub rs: u32,
/// The total amount of padding to add to the plaintext before encryption.
pub pad_length: usize,
/// The salt to use when deriving keys.
/// The recommended and default value is `None`, which causes a new random
/// salt to be used for every encryption. Specifying a specific salt may
/// be useful for testing purposes.
pub salt: Option<Vec<u8>>,
}
impl WebPushParams {
/// Convenience method for getting an appropriate salt value.
///
/// If we have a pre-configured salt then it is returned, transferring ownership
/// to ensure it is only used once. If we do not have a pre-configured salt then
/// a new random one is generated.
pub fn take_or_generate_salt(&mut self, cryptographer: &dyn Cryptographer) -> Result<Vec<u8>> {
Ok(match self.salt.take() {
Some(salt) => salt,
None => {
let mut salt = [0u8; ECE_SALT_LENGTH];
cryptographer.random_bytes(&mut salt)?;
salt.to_vec()
}
})
}
}
impl Default for WebPushParams {
fn default() -> Self {
// Random salt, no padding, record size = 4096.
Self {
rs: ECE_WEBPUSH_DEFAULT_RS,
pad_length: 0,
salt: None,
}
}
}
impl WebPushParams {
/// Create new parameters suitable for use with the given plaintext.
///
/// This constructor tries to provide some sensible defaults for using
/// ECE to encrypt the given plaintext, including:
///
/// * padding it to a multiple of 128 bytes.
/// * using a random salt
///
pub(crate) fn new_for_plaintext(plaintext: &[u8], min_pad_length: usize) -> Self {
// We want (plaintext.len() + pad_length) % BLOCK_SIZE == 0, but need to
// accomodate the non-zero minimum padding added by the encryption process.
let mut pad_length = ECE_WEBPUSH_DEFAULT_PADDING_BLOCK_SIZE
- (plaintext.len() % ECE_WEBPUSH_DEFAULT_PADDING_BLOCK_SIZE);
if pad_length < min_pad_length {
pad_length += ECE_WEBPUSH_DEFAULT_PADDING_BLOCK_SIZE;
}
WebPushParams {
pad_length,
..Default::default()
}
}
}
/// Flag to indicate whether we're encrypting or decrypting.
/// Used when deriving keys.
///
pub(crate) enum EceMode {
Encrypt,
Decrypt,
}
/// Convenience tuple for "key" and "nonce" pair.
/// These are always derived as a pair.
///
pub(crate) type KeyAndNonce = (Vec<u8>, Vec<u8>);
/// Generates the AES-GCM IV to use for encrypting a single record.
///
/// Each record in ECE is encrypted with a unique IV, that combines a "global" nonce
/// for the whole data with with the record's sequence number.
///
pub(crate) fn generate_iv_for_record(nonce: &[u8], counter: usize) -> [u8; ECE_NONCE_LENGTH] {
let mut iv = [0u8; ECE_NONCE_LENGTH];
let offset = ECE_NONCE_LENGTH - 8;
iv[0..offset].copy_from_slice(&nonce[0..offset]);
// Combine the remaining unsigned 64-bit integer with the record sequence
// number using XOR. See the "nonce derivation" section of the draft.
let mask = BigEndian::read_u64(&nonce[offset..]);
BigEndian::write_u64(&mut iv[offset..], mask ^ (counter as u64));
iv
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_to_block_size() {
const BLOCK_SIZE: usize = ECE_WEBPUSH_DEFAULT_PADDING_BLOCK_SIZE;
assert_eq!(
WebPushParams::new_for_plaintext(&[0; 0], 1).pad_length,
BLOCK_SIZE
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; 1], 1).pad_length,
BLOCK_SIZE - 1
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE - 2], 1).pad_length,
2
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE - 1], 1).pad_length,
1
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE], 1).pad_length,
BLOCK_SIZE
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE + 1], 1).pad_length,
BLOCK_SIZE - 1
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; 0], 2).pad_length,
BLOCK_SIZE
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; 1], 2).pad_length,
BLOCK_SIZE - 1
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE - 2], 2).pad_length,
2
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE - 1], 2).pad_length,
BLOCK_SIZE + 1
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE], 2).pad_length,
BLOCK_SIZE
);
assert_eq!(
WebPushParams::new_for_plaintext(&[0; BLOCK_SIZE + 1], 2).pad_length,
BLOCK_SIZE - 1
);
}
}