bb835e4ca7
aura-proto: 5-byte wire header + Frame codec (§6.1/§6.3); transport-agnostic handshake state machine (§6.2) over split tokio AsyncRead/AsyncWrite — hybrid X25519+ML-KEM-768 KEM, SHA-256 transcript, mutual X.509 auth with ECDSA-P256 transcript signatures (ring), constant-time HMAC Finished; Session with sliding-window replay protection. 13 tests green, clippy clean. Handshake message order pinned (resolves spec diagram ambiguity); reader/writer taken by value since Session owns both halves. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
87 lines
3.4 KiB
Rust
87 lines
3.4 KiB
Rust
//! `test_pki_mutual_auth` — the server must reject a client whose certificate was issued by a
|
|
//! different CA, and must reject a client that presents a valid certificate but a forged signature
|
|
//! (one made with a key that does not match the certificate).
|
|
|
|
mod common;
|
|
|
|
use aura_pki::AuraCa;
|
|
use aura_proto::{client_handshake, server_handshake, ClientConfig, ProtoError};
|
|
use tokio::io::split;
|
|
|
|
/// Run a handshake and return both sides' results.
|
|
async fn run(
|
|
client_cfg: ClientConfig,
|
|
server_cfg: aura_proto::ServerConfig,
|
|
) -> (Result<(), ProtoError>, Result<Option<String>, ProtoError>) {
|
|
let (client_end, server_end) = tokio::io::duplex(64 * 1024);
|
|
let (c_read, c_write) = split(client_end);
|
|
let (s_read, s_write) = split(server_end);
|
|
|
|
let client = tokio::spawn(async move {
|
|
client_handshake(c_read, c_write, &client_cfg)
|
|
.await
|
|
.map(|_| ())
|
|
});
|
|
let server = tokio::spawn(async move {
|
|
server_handshake(s_read, s_write, &server_cfg)
|
|
.await
|
|
.map(|s| s.peer_id().map(str::to_string))
|
|
});
|
|
let (c, s) = tokio::join!(client, server);
|
|
(c.expect("client task"), s.expect("server task"))
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn wrong_ca_client_cert_is_rejected() {
|
|
// The legitimate server-side PKI.
|
|
let pki = common::mint_pki("vpn.aura.example", "client-alpha");
|
|
|
|
// An attacker CA issues a client cert with a plausible CN, but it does NOT chain to the
|
|
// server's trusted CA.
|
|
let rogue_ca = AuraCa::generate("Rogue CA").expect("rogue CA");
|
|
let rogue_client = rogue_ca
|
|
.issue_client_cert("client-alpha")
|
|
.expect("rogue client cert");
|
|
|
|
let client_cfg = ClientConfig {
|
|
ca_cert_pem: pki.ca_cert_pem.clone(),
|
|
client_cert_pem: rogue_client.cert_pem,
|
|
client_key_pem: rogue_client.key_pem,
|
|
server_name: pki.server_name.clone(),
|
|
};
|
|
|
|
let (_client_res, server_res) = run(client_cfg, pki.server_config()).await;
|
|
// The server must fail verifying the client chain against its trusted CA.
|
|
assert!(
|
|
matches!(server_res, Err(ProtoError::Pki(_))),
|
|
"expected a PKI verification failure, got {server_res:?}"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn forged_client_signature_is_rejected() {
|
|
let pki = common::mint_pki("vpn.aura.example", "client-alpha");
|
|
|
|
// Mint an unrelated P-256 keypair (via a throwaway issued cert) to use as the WRONG signing
|
|
// key. We pair the legitimate client's certificate with this mismatched private key: the chain
|
|
// verifies fine, but the signature over the transcript is made with a key that does not match
|
|
// the certificate's public key, so signature verification must fail.
|
|
let throwaway_ca = AuraCa::generate("throwaway").expect("throwaway CA");
|
|
let mismatched = throwaway_ca
|
|
.issue_client_cert("mismatched")
|
|
.expect("throwaway cert");
|
|
|
|
let client_cfg = ClientConfig {
|
|
ca_cert_pem: pki.ca_cert_pem.clone(),
|
|
client_cert_pem: pki.client_cert_pem.clone(), // valid cert (chains to trusted CA)
|
|
client_key_pem: mismatched.key_pem, // WRONG key -> forged signature
|
|
server_name: pki.server_name.clone(),
|
|
};
|
|
|
|
let (_client_res, server_res) = run(client_cfg, pki.server_config()).await;
|
|
assert!(
|
|
matches!(server_res, Err(ProtoError::Signature(_))),
|
|
"expected a signature verification failure, got {server_res:?}"
|
|
);
|
|
}
|