35d94dee33
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>
99 lines
3.9 KiB
Rust
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());
|
|
}
|