mirror of https://github.com/telemt/telemt.git
819 lines
19 KiB
Rust
819 lines
19 KiB
Rust
use super::*;
|
|
use crate::protocol::constants::MAX_TLS_PLAINTEXT_SIZE;
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
|
|
#[test]
|
|
fn handshake_record_above_plaintext_limit_must_be_rejected_early() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: (MAX_TLS_PLAINTEXT_SIZE + 1) as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"control-plane handshake record > MAX_TLS_PLAINTEXT_SIZE must fail closed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_record_above_plaintext_limit_must_be_rejected_early() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: TLS_VERSION,
|
|
length: (MAX_TLS_PLAINTEXT_SIZE + 1) as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"TLS alert record > MAX_TLS_PLAINTEXT_SIZE must be rejected"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ccs_record_len_not_equal_one_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_CHANGE_CIPHER,
|
|
version: TLS_VERSION,
|
|
length: 2,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"ChangeCipherSpec length must be exactly 1 byte in compat mode"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_record_len_zero_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: 0,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"zero-length handshake record is structurally invalid"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_record_len_one_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: 1,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"tiny handshake record must be rejected to avoid malformed parser states"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_record_len_four_is_accepted() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: 4,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_ok(),
|
|
"4-byte handshake payload is the minimum carrying handshake header"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_record_at_plaintext_limit_is_accepted() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: MAX_TLS_PLAINTEXT_SIZE as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_ok(),
|
|
"handshake record at plaintext RFC limit must be accepted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_record_at_ciphertext_limit_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: TLS_VERSION,
|
|
length: MAX_TLS_CIPHERTEXT_SIZE as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"control-plane handshake must never use ciphertext upper bound"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_record_len_zero_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: TLS_VERSION,
|
|
length: 0,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"TLS alert must always carry level+description bytes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_record_len_one_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: TLS_VERSION,
|
|
length: 1,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"one-byte TLS alert is malformed and must fail closed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_record_len_two_is_accepted() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: TLS_VERSION,
|
|
length: 2,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_ok(),
|
|
"standard TLS alert shape should be accepted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_record_len_three_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: TLS_VERSION,
|
|
length: 3,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"oversized plaintext alert should be rejected to avoid parser confusion"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ccs_record_len_zero_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_CHANGE_CIPHER,
|
|
version: TLS_VERSION,
|
|
length: 0,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"ChangeCipherSpec with zero length is malformed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ccs_record_len_one_is_accepted() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_CHANGE_CIPHER,
|
|
version: TLS_VERSION,
|
|
length: 1,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_ok(),
|
|
"ChangeCipherSpec compat record length must be accepted only for len=1"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ccs_record_len_at_plaintext_limit_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_CHANGE_CIPHER,
|
|
version: TLS_VERSION,
|
|
length: MAX_TLS_PLAINTEXT_SIZE as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"oversized CCS control frame must fail closed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_record_type_small_len_must_be_rejected_early() {
|
|
let header = TlsRecordHeader {
|
|
record_type: 0x19,
|
|
version: TLS_VERSION,
|
|
length: 8,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"unknown TLS record type should be rejected during header validation"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_record_type_large_len_must_be_rejected_early() {
|
|
let header = TlsRecordHeader {
|
|
record_type: 0x7f,
|
|
version: TLS_VERSION,
|
|
length: MAX_TLS_CIPHERTEXT_SIZE as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"unknown record type with large payload must fail before body allocation"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_tls10_header_with_plaintext_plus_one_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_HANDSHAKE,
|
|
version: [0x03, 0x01],
|
|
length: (MAX_TLS_PLAINTEXT_SIZE + 1) as u16,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"TLS 1.0 compatibility header must not bypass plaintext size cap"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alert_tls10_header_with_invalid_len_must_be_rejected() {
|
|
let header = TlsRecordHeader {
|
|
record_type: TLS_RECORD_ALERT,
|
|
version: [0x03, 0x01],
|
|
length: 3,
|
|
};
|
|
|
|
assert!(
|
|
header.validate().is_err(),
|
|
"TLS 1.0 compatibility header must not bypass strict alert framing"
|
|
);
|
|
}
|
|
|
|
fn validates(record_type: u8, version: [u8; 2], length: u16) -> bool {
|
|
TlsRecordHeader {
|
|
record_type,
|
|
version,
|
|
length,
|
|
}
|
|
.validate()
|
|
.is_ok()
|
|
}
|
|
|
|
macro_rules! expect_reject {
|
|
($name:ident, $record_type:expr, $version:expr, $length:expr) => {
|
|
#[test]
|
|
fn $name() {
|
|
assert!(
|
|
!validates($record_type, $version, $length),
|
|
"expected reject for type=0x{:02x} version={:02x?} len={}",
|
|
$record_type,
|
|
$version,
|
|
$length
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! expect_accept {
|
|
($name:ident, $record_type:expr, $version:expr, $length:expr) => {
|
|
#[test]
|
|
fn $name() {
|
|
assert!(
|
|
validates($record_type, $version, $length),
|
|
"expected accept for type=0x{:02x} version={:02x?} len={}",
|
|
$record_type,
|
|
$version,
|
|
$length
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
expect_reject!(
|
|
appdata_zero_len_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
0
|
|
);
|
|
expect_accept!(
|
|
appdata_one_len_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
1
|
|
);
|
|
expect_accept!(
|
|
appdata_small_len_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
32
|
|
);
|
|
expect_accept!(
|
|
appdata_medium_len_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
1024
|
|
);
|
|
expect_accept!(
|
|
appdata_plaintext_limit_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
MAX_TLS_PLAINTEXT_SIZE as u16
|
|
);
|
|
expect_accept!(
|
|
appdata_ciphertext_limit_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
MAX_TLS_CIPHERTEXT_SIZE as u16
|
|
);
|
|
expect_reject!(
|
|
appdata_ciphertext_plus_one_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
(MAX_TLS_CIPHERTEXT_SIZE as u16) + 1
|
|
);
|
|
|
|
expect_reject!(
|
|
appdata_tls10_header_len_one_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
[0x03, 0x01],
|
|
1
|
|
);
|
|
expect_reject!(
|
|
appdata_tls10_header_medium_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
[0x03, 0x01],
|
|
1024
|
|
);
|
|
expect_reject!(
|
|
appdata_tls10_header_ciphertext_limit_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
[0x03, 0x01],
|
|
MAX_TLS_CIPHERTEXT_SIZE as u16
|
|
);
|
|
|
|
expect_reject!(
|
|
ccs_tls10_header_len_one_must_be_rejected,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
[0x03, 0x01],
|
|
1
|
|
);
|
|
expect_reject!(
|
|
ccs_tls10_header_len_zero_must_be_rejected,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
[0x03, 0x01],
|
|
0
|
|
);
|
|
expect_reject!(
|
|
ccs_tls10_header_len_two_must_be_rejected,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
[0x03, 0x01],
|
|
2
|
|
);
|
|
|
|
expect_reject!(
|
|
alert_tls10_header_len_two_must_be_rejected,
|
|
TLS_RECORD_ALERT,
|
|
[0x03, 0x01],
|
|
2
|
|
);
|
|
expect_reject!(
|
|
alert_tls10_header_len_one_must_be_rejected,
|
|
TLS_RECORD_ALERT,
|
|
[0x03, 0x01],
|
|
1
|
|
);
|
|
expect_reject!(
|
|
alert_tls10_header_len_three_must_be_rejected,
|
|
TLS_RECORD_ALERT,
|
|
[0x03, 0x01],
|
|
3
|
|
);
|
|
|
|
expect_accept!(
|
|
handshake_tls10_header_min_len_is_accepted,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x01],
|
|
4
|
|
);
|
|
expect_accept!(
|
|
handshake_tls10_header_plaintext_limit_is_accepted,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x01],
|
|
MAX_TLS_PLAINTEXT_SIZE as u16
|
|
);
|
|
expect_reject!(
|
|
handshake_tls10_header_too_small_must_be_rejected,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x01],
|
|
3
|
|
);
|
|
expect_reject!(
|
|
handshake_tls10_header_too_large_must_be_rejected,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x01],
|
|
(MAX_TLS_PLAINTEXT_SIZE as u16) + 1
|
|
);
|
|
|
|
expect_reject!(
|
|
unknown_type_tls13_zero_must_be_rejected,
|
|
0x00,
|
|
TLS_VERSION,
|
|
0
|
|
);
|
|
expect_reject!(
|
|
unknown_type_tls13_small_must_be_rejected,
|
|
0x13,
|
|
TLS_VERSION,
|
|
32
|
|
);
|
|
expect_reject!(
|
|
unknown_type_tls13_large_must_be_rejected,
|
|
0xfe,
|
|
TLS_VERSION,
|
|
MAX_TLS_CIPHERTEXT_SIZE as u16
|
|
);
|
|
expect_reject!(
|
|
unknown_type_tls10_small_must_be_rejected,
|
|
0x13,
|
|
[0x03, 0x01],
|
|
32
|
|
);
|
|
|
|
expect_reject!(
|
|
appdata_invalid_version_0302_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
[0x03, 0x02],
|
|
128
|
|
);
|
|
expect_reject!(
|
|
handshake_invalid_version_0302_must_be_rejected,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x02],
|
|
128
|
|
);
|
|
expect_reject!(
|
|
alert_invalid_version_0302_must_be_rejected,
|
|
TLS_RECORD_ALERT,
|
|
[0x03, 0x02],
|
|
2
|
|
);
|
|
expect_reject!(
|
|
ccs_invalid_version_0302_must_be_rejected,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
[0x03, 0x02],
|
|
1
|
|
);
|
|
|
|
expect_reject!(
|
|
appdata_invalid_version_0304_must_be_rejected,
|
|
TLS_RECORD_APPLICATION,
|
|
[0x03, 0x04],
|
|
128
|
|
);
|
|
expect_reject!(
|
|
handshake_invalid_version_0304_must_be_rejected,
|
|
TLS_RECORD_HANDSHAKE,
|
|
[0x03, 0x04],
|
|
128
|
|
);
|
|
expect_reject!(
|
|
alert_invalid_version_0304_must_be_rejected,
|
|
TLS_RECORD_ALERT,
|
|
[0x03, 0x04],
|
|
2
|
|
);
|
|
expect_reject!(
|
|
ccs_invalid_version_0304_must_be_rejected,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
[0x03, 0x04],
|
|
1
|
|
);
|
|
|
|
expect_accept!(
|
|
handshake_tls13_len_5_is_accepted,
|
|
TLS_RECORD_HANDSHAKE,
|
|
TLS_VERSION,
|
|
5
|
|
);
|
|
expect_accept!(
|
|
appdata_tls13_len_16385_is_accepted,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION,
|
|
(MAX_TLS_PLAINTEXT_SIZE as u16) + 1
|
|
);
|
|
|
|
#[test]
|
|
fn matrix_version_policy_is_strict_and_deterministic() {
|
|
let versions = [
|
|
[0x03, 0x01],
|
|
TLS_VERSION,
|
|
[0x03, 0x02],
|
|
[0x03, 0x04],
|
|
[0x00, 0x00],
|
|
];
|
|
let record_types = [
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
TLS_RECORD_ALERT,
|
|
TLS_RECORD_HANDSHAKE,
|
|
];
|
|
|
|
for version in versions {
|
|
for record_type in record_types {
|
|
let len = match record_type {
|
|
TLS_RECORD_APPLICATION => 1,
|
|
TLS_RECORD_CHANGE_CIPHER => 1,
|
|
TLS_RECORD_ALERT => 2,
|
|
TLS_RECORD_HANDSHAKE => 4,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let accepted = validates(record_type, version, len);
|
|
let expected = if version == TLS_VERSION {
|
|
true
|
|
} else {
|
|
version == [0x03, 0x01] && record_type == TLS_RECORD_HANDSHAKE
|
|
};
|
|
|
|
assert_eq!(
|
|
accepted, expected,
|
|
"version policy mismatch for type=0x{:02x} version={:02x?}",
|
|
record_type, version
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn appdata_partition_property_holds_for_all_u16_edges() {
|
|
for len in [
|
|
0u16,
|
|
1,
|
|
2,
|
|
3,
|
|
64,
|
|
255,
|
|
1024,
|
|
4096,
|
|
8192,
|
|
16_384,
|
|
16_385,
|
|
16_640,
|
|
16_641,
|
|
u16::MAX,
|
|
] {
|
|
let accepted = validates(TLS_RECORD_APPLICATION, TLS_VERSION, len);
|
|
let expected = len >= 1 && usize::from(len) <= MAX_TLS_CIPHERTEXT_SIZE;
|
|
assert_eq!(
|
|
accepted, expected,
|
|
"unexpected appdata decision for len={len}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn handshake_partition_property_holds_for_all_u16_edges() {
|
|
for len in [
|
|
0u16,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
64,
|
|
255,
|
|
1024,
|
|
4096,
|
|
8192,
|
|
16_383,
|
|
16_384,
|
|
16_385,
|
|
u16::MAX,
|
|
] {
|
|
let accepted_tls13 = validates(TLS_RECORD_HANDSHAKE, TLS_VERSION, len);
|
|
let accepted_tls10 = validates(TLS_RECORD_HANDSHAKE, [0x03, 0x01], len);
|
|
let expected = (4..=MAX_TLS_PLAINTEXT_SIZE).contains(&usize::from(len));
|
|
|
|
assert_eq!(
|
|
accepted_tls13, expected,
|
|
"TLS1.3 handshake mismatch for len={len}"
|
|
);
|
|
assert_eq!(
|
|
accepted_tls10, expected,
|
|
"TLS1.0 compat handshake mismatch for len={len}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn control_record_exact_lengths_are_enforced_under_fuzzed_lengths() {
|
|
let mut x: u32 = 0xC0FFEE11;
|
|
for _ in 0..5000 {
|
|
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
|
|
let len = (x & 0xFFFF) as u16;
|
|
|
|
let ccs_ok = validates(TLS_RECORD_CHANGE_CIPHER, TLS_VERSION, len);
|
|
let alert_ok = validates(TLS_RECORD_ALERT, TLS_VERSION, len);
|
|
|
|
assert_eq!(ccs_ok, len == 1, "ccs length gate mismatch for len={len}");
|
|
assert_eq!(
|
|
alert_ok,
|
|
len == 2,
|
|
"alert length gate mismatch for len={len}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_record_types_never_validate_under_supported_versions() {
|
|
for record_type in 0u8..=255 {
|
|
if matches!(
|
|
record_type,
|
|
TLS_RECORD_APPLICATION
|
|
| TLS_RECORD_CHANGE_CIPHER
|
|
| TLS_RECORD_ALERT
|
|
| TLS_RECORD_HANDSHAKE
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
assert!(
|
|
!validates(record_type, TLS_VERSION, 1),
|
|
"unknown type must not validate under TLS_VERSION: 0x{record_type:02x}"
|
|
);
|
|
assert!(
|
|
!validates(record_type, [0x03, 0x01], 4),
|
|
"unknown type must not validate under TLS1.0 compat: 0x{record_type:02x}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_rejects_tls10_appdata_header_before_payload_processing() {
|
|
let (mut tx, rx) = tokio::io::duplex(128);
|
|
tx.write_all(&[TLS_RECORD_APPLICATION, 0x03, 0x01, 0x00, 0x01, 0xAB])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 1];
|
|
let err = reader.read(&mut out).await.unwrap_err();
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_rejects_zero_len_appdata_record() {
|
|
let (mut tx, rx) = tokio::io::duplex(128);
|
|
tx.write_all(&[
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION[0],
|
|
TLS_VERSION[1],
|
|
0x00,
|
|
0x00,
|
|
])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 1];
|
|
let err = reader.read(&mut out).await.unwrap_err();
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_accepts_single_byte_tls13_appdata_and_yields_payload() {
|
|
let (mut tx, rx) = tokio::io::duplex(128);
|
|
tx.write_all(&[
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION[0],
|
|
TLS_VERSION[1],
|
|
0x00,
|
|
0x01,
|
|
0x5A,
|
|
])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 1];
|
|
let n = reader.read(&mut out).await.unwrap();
|
|
assert_eq!(n, 1);
|
|
assert_eq!(out[0], 0x5A);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_rejects_tls10_alert_even_with_structural_length() {
|
|
let (mut tx, rx) = tokio::io::duplex(128);
|
|
tx.write_all(&[TLS_RECORD_ALERT, 0x03, 0x01, 0x00, 0x02, 0x02, 0x28])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 8];
|
|
let err = reader.read(&mut out).await.unwrap_err();
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_rejects_unknown_record_type_fast() {
|
|
let (mut tx, rx) = tokio::io::duplex(128);
|
|
tx.write_all(&[0x7f, TLS_VERSION[0], TLS_VERSION[1], 0x00, 0x01, 0x01])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 8];
|
|
let err = reader.read(&mut out).await.unwrap_err();
|
|
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reader_preserves_data_after_valid_ccs_then_valid_appdata() {
|
|
let (mut tx, rx) = tokio::io::duplex(256);
|
|
tx.write_all(&[
|
|
TLS_RECORD_CHANGE_CIPHER,
|
|
TLS_VERSION[0],
|
|
TLS_VERSION[1],
|
|
0x00,
|
|
0x01,
|
|
0x01,
|
|
TLS_RECORD_APPLICATION,
|
|
TLS_VERSION[0],
|
|
TLS_VERSION[1],
|
|
0x00,
|
|
0x03,
|
|
0xDE,
|
|
0xAD,
|
|
0xBE,
|
|
])
|
|
.await
|
|
.unwrap();
|
|
tx.shutdown().await.unwrap();
|
|
|
|
let mut reader = FakeTlsReader::new(rx);
|
|
let mut out = [0u8; 3];
|
|
let n = reader.read(&mut out).await.unwrap();
|
|
assert_eq!(n, 3);
|
|
assert_eq!(out, [0xDE, 0xAD, 0xBE]);
|
|
}
|
|
|
|
#[test]
|
|
fn deterministic_lcg_never_breaks_validation_invariants() {
|
|
let mut x: u64 = 0xD1A5_CE55_0BAD_F00D;
|
|
for _ in 0..20000 {
|
|
x = x.wrapping_mul(6364136223846793005).wrapping_add(1);
|
|
let record_type = (x & 0xFF) as u8;
|
|
let version = match (x >> 8) & 0x3 {
|
|
0 => TLS_VERSION,
|
|
1 => [0x03, 0x01],
|
|
2 => [0x03, 0x02],
|
|
_ => [0x03, 0x04],
|
|
};
|
|
let len = ((x >> 16) & 0xFFFF) as u16;
|
|
|
|
let accepted = validates(record_type, version, len);
|
|
|
|
let expected = match record_type {
|
|
TLS_RECORD_APPLICATION => {
|
|
version == TLS_VERSION && len >= 1 && usize::from(len) <= MAX_TLS_CIPHERTEXT_SIZE
|
|
}
|
|
TLS_RECORD_CHANGE_CIPHER => version == TLS_VERSION && len == 1,
|
|
TLS_RECORD_ALERT => version == TLS_VERSION && len == 2,
|
|
TLS_RECORD_HANDSHAKE => {
|
|
(version == TLS_VERSION || version == [0x03, 0x01])
|
|
&& (4..=MAX_TLS_PLAINTEXT_SIZE).contains(&usize::from(len))
|
|
}
|
|
_ => false,
|
|
};
|
|
|
|
assert_eq!(
|
|
accepted, expected,
|
|
"invariant mismatch: type=0x{record_type:02x} version={version:02x?} len={len}"
|
|
);
|
|
}
|
|
}
|