Skip to content

Commit

Permalink
Add integrity check for Retry packets. (#223)
Browse files Browse the repository at this point in the history
* Add integrity check for Retry packets.
  • Loading branch information
rday committed Nov 13, 2020
1 parent 83b7ee1 commit e18c905
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 0 deletions.
2 changes: 2 additions & 0 deletions quic/s2n-quic-core/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ pub mod label;
pub mod one_rtt;
pub mod packet_protection;
pub mod payload;
pub mod retry;
pub mod tls;
pub mod zero_rtt;

Expand All @@ -148,6 +149,7 @@ pub use key::*;
pub use one_rtt::*;
pub use packet_protection::*;
pub use payload::*;
pub use retry::RetryCrypto;
pub use zero_rtt::*;

/// Trait which aggregates all Crypto types
Expand Down
33 changes: 33 additions & 0 deletions quic/s2n-quic-core/src/crypto/retry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::crypto::CryptoError;
use hex_literal::hex;

pub type IntegrityTag = [u8; 16];

pub trait RetryCrypto {
fn generate_tag(payload: &[u8]) -> IntegrityTag;
fn validate(payload: &[u8], tag: IntegrityTag) -> Result<(), CryptoError>;
}

//= https://tools.ietf.org/id/draft-ietf-quic-tls-32.txt#5.8
//# The Retry Integrity Tag is a 128-bit field that is computed as the
//# output of AEAD_AES_128_GCM ([AEAD]) used with the following inputs:
//#
//# * The secret key, K, is 128 bits equal to
//# 0xccce187ed09a09d05728155a6cb96be1.
//#
pub const SECRET_KEY_BYTES: [u8; 16] = hex!("ccce187ed09a09d05728155a6cb96be1");

//= https://tools.ietf.org/id/draft-ietf-quic-tls-32.txt#5.8
//# * The nonce, N, is 96 bits equal to 0xe54930f97f2136f0530a8c1c.

pub const NONCE_BYTES: [u8; 12] = hex!("e54930f97f2136f0530a8c1c");

//= https://tools.ietf.org/id/draft-ietf-quic-tls-32.txt#A.4
//# This shows a Retry packet that might be sent in response to the
//# Initial packet in Appendix A.2. The integrity check includes the
//# client-chosen connection ID value of 0x8394c8f03e515708, but that
//# value is not included in the final Retry packet:
pub const EXAMPLE_PSEUDO_RETRY_PACKET: [u8; 29] =
hex!("088394c8f03e515708 ffff000020 00 08f067a5502a4262b5 746f6b656e");

pub const EXAMPLE_EXPECTED_TAG: [u8; 16] = hex!("59756519dd6cc85bd90e33a934d2ff85");
1 change: 1 addition & 0 deletions quic/s2n-quic-ring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2018"
license = "Apache-2.0"

[dependencies]
hex-literal = "0.2"
lazy_static = { version = "1.3", default-features = false }
s2n-codec = { version = "0.1.0", path = "../../common/s2n-codec", default-features = false }
s2n-quic-core = { version = "0.1.0", path = "../s2n-quic-core", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions quic/s2n-quic-ring/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct SecretPair {
pub mod handshake;
pub mod initial;
pub mod one_rtt;
pub mod retry;
pub mod zero_rtt;

#[derive(Clone, Copy, Debug, Default)]
Expand Down
53 changes: 53 additions & 0 deletions quic/s2n-quic-ring/src/retry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use core::convert::TryInto;
use ring::aead;
use s2n_quic_core::crypto::{
retry::{IntegrityTag, NONCE_BYTES, SECRET_KEY_BYTES},
CryptoError, RetryCrypto,
};

lazy_static::lazy_static! {
/// Compute the Initial salt once, as the seed is constant
static ref SECRET_KEY: aead::LessSafeKey = aead::LessSafeKey::new(
aead::UnboundKey::new(&aead::AES_128_GCM, &SECRET_KEY_BYTES).unwrap(),
);
}

#[derive(Debug)]
pub struct RingRetryCrypto;

impl RetryCrypto for RingRetryCrypto {
fn generate_tag(pseudo_packet: &[u8]) -> IntegrityTag {
let nonce = aead::Nonce::assume_unique_for_key(NONCE_BYTES);
let tag = SECRET_KEY
.seal_in_place_separate_tag(nonce, aead::Aad::from(pseudo_packet), &mut [])
.expect("in_out len is 0 and should always be less than the nonce max bytes");

tag.as_ref()
.try_into()
.expect("AES_128_GCM tag len should always be 128 bits")
}

fn validate(pseudo_packet: &[u8], tag: IntegrityTag) -> Result<(), CryptoError> {
let expected = RingRetryCrypto::generate_tag(pseudo_packet);

ring::constant_time::verify_slices_are_equal(&expected, &tag)
.map_err(|_| CryptoError::DECRYPT_ERROR)
}
}

#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
use s2n_quic_core::crypto::retry::{EXAMPLE_EXPECTED_TAG, EXAMPLE_PSEUDO_RETRY_PACKET};

#[test]
fn test_valid_tag() {
let invalid_tag: [u8; 16] = hex!("00112233445566778899aabbccddeeff");

assert!(
RingRetryCrypto::validate(&EXAMPLE_PSEUDO_RETRY_PACKET, EXAMPLE_EXPECTED_TAG).is_ok()
);
assert!(RingRetryCrypto::validate(&EXAMPLE_PSEUDO_RETRY_PACKET, invalid_tag).is_err());
}
}

0 comments on commit e18c905

Please sign in to comment.