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:
Alessandro Ghedini 2018-12-29 13:16:53 +00:00 committed by Brian Smith
parent 9d874613ba
commit 351ed16251
6 changed files with 221 additions and 0 deletions

View File

@ -323,4 +323,5 @@ pub mod chacha20_poly1305_openssh;
mod gcm;
mod nonce;
mod poly1305;
pub mod quic;
mod shift;

View File

@ -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
View 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)
}

View File

@ -0,0 +1,3 @@
KEY = e8904ecc2e37a6e4cc02271e319c804b
SAMPLE = 13484ec85dc4d36349697c7d4ea1a159
MASK = 67387ebf3a

View File

@ -0,0 +1,3 @@
KEY = 85af7213814aec7b92ace6284a906643912ec8853d00d158a927b8697c7ff585
SAMPLE = 82a0db90f4cee12fa4afeddb74396cf6
MASK = 670897adf5

79
tests/quic_tests.rs Normal file
View 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());
}