Implement QUIC header protection API
This adds a new API to the AEAD module for generating QUIC Header Protection masks, as described in draft-ietf-quic-tls-17. Only AES support is currently implemented, but ChaCha20 can be added later. I agree to license my contributions to each file under the terms given at the top of each file I changed.
This commit is contained in:
parent
9d874613ba
commit
351ed16251
@ -323,4 +323,5 @@ pub mod chacha20_poly1305_openssh;
|
||||
mod gcm;
|
||||
mod nonce;
|
||||
mod poly1305;
|
||||
pub mod quic;
|
||||
mod shift;
|
||||
|
@ -186,6 +186,15 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mask(&self, sample: Block) -> [u8; 5] {
|
||||
let block = self.encrypt_block(sample);
|
||||
|
||||
let mut out: [u8; 5] = [0; 5];
|
||||
out.copy_from_slice(&block.as_ref()[..5]);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[must_use]
|
||||
pub fn is_aes_hw(&self) -> bool {
|
||||
|
126
src/aead/quic.rs
Normal file
126
src/aead/quic.rs
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2018 Brian Smith.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
//! QUIC Header Protection.
|
||||
//!
|
||||
//! See draft-ietf-quic-tls.
|
||||
|
||||
use crate::{
|
||||
aead::{aes, block::Block},
|
||||
cpu, error,
|
||||
polyfill::convert::*,
|
||||
};
|
||||
|
||||
/// A key for generating QUIC Header Protection masks.
|
||||
pub struct HeaderProtectionKey {
|
||||
inner: KeyInner,
|
||||
algorithm: &'static Algorithm,
|
||||
}
|
||||
|
||||
#[allow(variant_size_differences)]
|
||||
enum KeyInner {
|
||||
Aes(aes::Key),
|
||||
}
|
||||
|
||||
impl HeaderProtectionKey {
|
||||
/// Create a new header protection key.
|
||||
///
|
||||
/// `key_bytes` must be exactly `algorithm.key_len` bytes long.
|
||||
pub fn new(
|
||||
algorithm: &'static Algorithm, key_bytes: &[u8],
|
||||
) -> Result<Self, error::Unspecified> {
|
||||
cpu::cache_detected_features();
|
||||
Ok(HeaderProtectionKey {
|
||||
inner: (algorithm.init)(key_bytes)?,
|
||||
algorithm,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a new QUIC Header Protection mask.
|
||||
///
|
||||
/// `sample` must be exactly 16 bytes long.
|
||||
pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], error::Unspecified> {
|
||||
let sample = <&[u8; SAMPLE_LEN]>::try_from_(sample)?;
|
||||
let sample = Block::from(sample);
|
||||
|
||||
let out = (self.algorithm.new_mask)(&self.inner, sample);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
const SAMPLE_LEN: usize = super::TAG_LEN;
|
||||
|
||||
/// A QUIC Header Protection Algorithm.
|
||||
pub struct Algorithm {
|
||||
init: fn(key: &[u8]) -> Result<KeyInner, error::Unspecified>,
|
||||
|
||||
new_mask: fn(key: &KeyInner, sample: Block) -> [u8; 5],
|
||||
|
||||
key_len: usize,
|
||||
id: AlgorithmID,
|
||||
}
|
||||
|
||||
impl Algorithm {
|
||||
/// The length of the key.
|
||||
#[inline(always)]
|
||||
pub fn key_len(&self) -> usize { self.key_len }
|
||||
}
|
||||
|
||||
derive_debug_via_self!(Algorithm, self.id);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum AlgorithmID {
|
||||
AES_128,
|
||||
AES_256,
|
||||
}
|
||||
|
||||
impl PartialEq for Algorithm {
|
||||
fn eq(&self, other: &Self) -> bool { self.id == other.id }
|
||||
}
|
||||
|
||||
impl Eq for Algorithm {}
|
||||
|
||||
/// AES-128.
|
||||
pub static AES_128: Algorithm = Algorithm {
|
||||
key_len: 16,
|
||||
init: aes_init_128,
|
||||
new_mask: aes_new_mask,
|
||||
id: AlgorithmID::AES_128,
|
||||
};
|
||||
|
||||
/// AES-256.
|
||||
pub static AES_256: Algorithm = Algorithm {
|
||||
key_len: 32,
|
||||
init: aes_init_256,
|
||||
new_mask: aes_new_mask,
|
||||
id: AlgorithmID::AES_256,
|
||||
};
|
||||
|
||||
fn aes_init_128(key: &[u8]) -> Result<KeyInner, error::Unspecified> {
|
||||
let aes_key = aes::Key::new(key, aes::Variant::AES_128)?;
|
||||
Ok(KeyInner::Aes(aes_key))
|
||||
}
|
||||
|
||||
fn aes_init_256(key: &[u8]) -> Result<KeyInner, error::Unspecified> {
|
||||
let aes_key = aes::Key::new(key, aes::Variant::AES_256)?;
|
||||
Ok(KeyInner::Aes(aes_key))
|
||||
}
|
||||
|
||||
fn aes_new_mask(key: &KeyInner, sample: Block) -> [u8; 5] {
|
||||
let aes_key = match key {
|
||||
KeyInner::Aes(key) => key,
|
||||
};
|
||||
|
||||
aes_key.new_mask(sample)
|
||||
}
|
3
tests/quic_aes_128_tests.txt
Normal file
3
tests/quic_aes_128_tests.txt
Normal file
@ -0,0 +1,3 @@
|
||||
KEY = e8904ecc2e37a6e4cc02271e319c804b
|
||||
SAMPLE = 13484ec85dc4d36349697c7d4ea1a159
|
||||
MASK = 67387ebf3a
|
3
tests/quic_aes_256_tests.txt
Normal file
3
tests/quic_aes_256_tests.txt
Normal file
@ -0,0 +1,3 @@
|
||||
KEY = 85af7213814aec7b92ace6284a906643912ec8853d00d158a927b8697c7ff585
|
||||
SAMPLE = 82a0db90f4cee12fa4afeddb74396cf6
|
||||
MASK = 670897adf5
|
79
tests/quic_tests.rs
Normal file
79
tests/quic_tests.rs
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Brian Smith.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#![forbid(
|
||||
anonymous_parameters,
|
||||
box_pointers,
|
||||
legacy_directory_ownership,
|
||||
missing_copy_implementations,
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_extern_crates,
|
||||
unused_import_braces,
|
||||
unused_qualifications,
|
||||
unused_results,
|
||||
variant_size_differences,
|
||||
warnings
|
||||
)]
|
||||
|
||||
use ring::{aead::quic, test};
|
||||
|
||||
#[test]
|
||||
fn quic_aes_128() { test_quic(&quic::AES_128, "tests/quic_aes_128_tests.txt"); }
|
||||
|
||||
#[test]
|
||||
fn quic_aes_256() { test_quic(&quic::AES_256, "tests/quic_aes_256_tests.txt"); }
|
||||
|
||||
fn test_quic(alg: &'static quic::Algorithm, file_path: &str) {
|
||||
test_sample_len(alg);
|
||||
|
||||
test::from_file(file_path, |section, test_case| {
|
||||
assert_eq!(section, "");
|
||||
let key_bytes = test_case.consume_bytes("KEY");
|
||||
let sample = test_case.consume_bytes("SAMPLE");
|
||||
let mask = test_case.consume_bytes("MASK");
|
||||
|
||||
let key = quic::HeaderProtectionKey::new(alg, &key_bytes)?;
|
||||
|
||||
assert_eq!(mask.as_ref(), key.new_mask(&sample)?);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn test_sample_len(alg: &'static quic::Algorithm) {
|
||||
let key_len = alg.key_len();
|
||||
let key_data = vec![0u8; key_len];
|
||||
|
||||
let key = quic::HeaderProtectionKey::new(alg, &key_data).unwrap();
|
||||
|
||||
let sample_len = 16;
|
||||
let sample_data = vec![0u8; sample_len + 2];
|
||||
|
||||
// Sample is the right size.
|
||||
assert!(key.new_mask(&sample_data[..sample_len]).is_ok());
|
||||
|
||||
// Sample is one byte too small.
|
||||
assert!(key.new_mask(&sample_data[..(sample_len - 1)]).is_err());
|
||||
|
||||
// Sample is one byte too big.
|
||||
assert!(key.new_mask(&sample_data[..(sample_len + 1)]).is_err());
|
||||
|
||||
// Sample is empty.
|
||||
assert!(key.new_mask(&[]).is_err());
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user