Files
AuraVPN/crates/aura-proto/tests/control_frame.rs
T
xah30 35d94dee33 feat(proto,pki,cli): in-band CRL push (closes last v2 limitation)
Server now pushes its signed CRL to each connecting client right after the
handshake; the client verifies the signature against the CA and applies the
revocation list to its verifier (and caches it on disk for restarts).
Removes the v1 "CRL distributed out-of-band" honest limitation.

Wire (multiplexed over existing PacketConnection, no trait change):
control envelope = MAGIC[4]=[0xAA,0xAA,0xC0,0x01] || kind(u8) || u32_be(len)
  || payload. IPv4/IPv6 start with 0x4X/0x6X, so 0xAA cannot collide; an old
peer just drops it as a junk packet in the TUN — back-compat preserved.

- aura-proto: ControlKind { CrlPush, CrlAck, Unknown }, encode/decode_control_
  envelope, CONTROL_ENVELOPE_MAGIC; 7 frame tests.
- aura-pki: CrlStore::{encode_signed, save_signed, decode_signed_verified,
  load_signed_verified} — ECDSA-P256/SHA-256 from the CA private key against
  a textual "CRL-Aura-v1" body + --SIGNATURE--; 7 signing tests. ring 0.17
  added crate-local (already in lockfile via rustls-webpki).
- aura-cli: crl_push module — server pushes via conn.send_packet on accept;
  client wraps the Arc<dyn PacketConnection> in AcceptPushedCrlConn which
  sniffs the magic in recv_packet, verifies the signature, updates the
  AuraCertVerifier, caches to disk. PkiSection gets ca_key, crl_push (default
  true), accept_pushed_crl (default true).
- 5 in_band_crl integration tests via mock PacketConnection.

Workspace: 235 tests passed (+28), clippy -D warnings clean, fmt clean. v2
COMPLETE — all 9 honest v1 limitations resolved (except sing-box, per user).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:35:16 +03:00

99 lines
3.9 KiB
Rust

//! Integration tests for the v2 in-band control envelope used by
//! [`aura_proto::PacketConnection::send_packet`] to multiplex CRL pushes alongside normal IP
//! packets without changing the [`aura_proto::Frame`] wire schema or any [`Frame`] `match` already
//! present in the transport layer.
use aura_proto::{
decode_control_envelope, encode_control_envelope, ControlKind, CONTROL_ENVELOPE_MAGIC,
};
/// Small payload round-trips through the envelope encoder + decoder.
#[test]
fn control_envelope_small_roundtrip() {
let env = encode_control_envelope(ControlKind::CrlPush, b"CRL-Aura-v1\nalice\n");
// Magic + kind + 4-byte length + 18-byte body.
assert_eq!(&env[..4], &CONTROL_ENVELOPE_MAGIC);
assert_eq!(env[4], 0x01); // kind=CrlPush
let (kind, payload) = decode_control_envelope(&env).unwrap().unwrap();
assert_eq!(kind, ControlKind::CrlPush);
assert_eq!(payload, b"CRL-Aura-v1\nalice\n");
}
/// A multi-megabyte payload (well below the 4-GiB u32 cap) round-trips.
#[test]
fn control_envelope_large_payload_roundtrip() {
let big = vec![0x5Au8; 1 << 20]; // 1 MiB
let env = encode_control_envelope(ControlKind::CrlPush, &big);
let (kind, payload) = decode_control_envelope(&env).unwrap().unwrap();
assert_eq!(kind, ControlKind::CrlPush);
assert_eq!(payload.len(), big.len());
assert!(payload.iter().all(|&b| b == 0x5A));
}
/// Unknown control kinds decode as [`ControlKind::Unknown`] so a peer running an older build
/// gracefully ignores future control messages instead of erroring.
#[test]
fn control_envelope_unknown_kind_decodes_as_unknown() {
let mut wire = Vec::new();
wire.extend_from_slice(&CONTROL_ENVELOPE_MAGIC);
wire.push(0x99); // unknown kind
wire.extend_from_slice(&4u32.to_be_bytes());
wire.extend_from_slice(b"data");
let (kind, payload) = decode_control_envelope(&wire).unwrap().unwrap();
assert_eq!(kind, ControlKind::Unknown(0x99));
assert_eq!(payload, b"data");
}
/// The magic prefix cannot collide with a real IPv4/IPv6 packet — IPv4 starts with `0x4X`, IPv6
/// with `0x6X`, and the magic starts with `0xAA`.
#[test]
fn control_envelope_magic_does_not_collide_with_ip() {
assert_eq!(CONTROL_ENVELOPE_MAGIC[0], 0xAA);
for first in [0x40u8, 0x45, 0x60, 0x6F] {
assert_ne!(first, CONTROL_ENVELOPE_MAGIC[0]);
}
}
/// `decode_control_envelope` returns `Ok(None)` for any buffer that does not start with the magic
/// (i.e. a normal IP packet), so the receive path can fall through to the TUN write unchanged.
#[test]
fn control_envelope_pass_through_for_non_control_packets() {
let ipv4 = vec![0x45u8, 0x00, 0x00, 0x14, 0xab, 0xcd];
assert!(decode_control_envelope(&ipv4).unwrap().is_none());
let ipv6 = vec![0x60u8, 0x00, 0x00, 0x00];
assert!(decode_control_envelope(&ipv6).unwrap().is_none());
assert!(decode_control_envelope(&[]).unwrap().is_none());
}
/// Round-trip every supported and one Unknown kind, with a variety of payload sizes.
#[test]
fn control_envelope_round_trip_all_kinds() {
let kinds: &[ControlKind] = &[
ControlKind::CrlPush,
ControlKind::CrlAck,
ControlKind::Unknown(0x42),
];
let payloads: &[&[u8]] = &[
b"",
b"x",
b"longer payload with bytes \xff\x00\x01",
&vec![0xAB; 64 * 1024],
];
for k in kinds {
for p in payloads {
let env = encode_control_envelope(*k, p);
let (got_kind, got_payload) = decode_control_envelope(&env).unwrap().unwrap();
assert_eq!(got_kind, *k);
assert_eq!(got_payload.as_slice(), *p);
}
}
}
/// Truncating the payload bytes (claimed length > available bytes) is a hard error.
#[test]
fn control_envelope_rejects_truncated_payload() {
let mut env = encode_control_envelope(ControlKind::CrlPush, b"payload-bytes");
env.truncate(env.len() - 3);
assert!(decode_control_envelope(&env).is_err());
}