huskies: merge 728_story_cryptographic_peer_handshake_with_trusted_keys_gating
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::slog_warn;
|
||||
|
||||
use super::auth;
|
||||
use super::dispatch::{handle_incoming_binary, handle_incoming_text};
|
||||
use super::wire::{AuthMessage, ChallengeMessage, SyncMessage};
|
||||
use super::wire::{AuthMessage, ChallengeMessage, HelloMessage, ServerAuthMessage, SyncMessage};
|
||||
use super::{AUTH_TIMEOUT_SECS, PING_INTERVAL_SECS, PONG_TIMEOUT_SECS};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
@@ -86,11 +86,79 @@ pub(crate) async fn connect_and_sync(url: &str, token: Option<&str>) -> Result<(
|
||||
|
||||
let (mut sink, mut stream) = ws_stream.split();
|
||||
|
||||
slog!("[crdt-sync] Connected to rendezvous peer, awaiting challenge");
|
||||
slog!("[crdt-sync] Connected to rendezvous peer, starting mutual-auth handshake");
|
||||
|
||||
// ── Step 1: Receive challenge from listener ───────────────────
|
||||
use tokio_tungstenite::tungstenite::Message as TungsteniteMsg;
|
||||
|
||||
// ── Step 1: Send hello with a fresh client nonce ──────────────
|
||||
let client_nonce = crate::node_identity::generate_challenge();
|
||||
let hello = HelloMessage {
|
||||
r#type: "hello".to_string(),
|
||||
nonce: client_nonce.clone(),
|
||||
};
|
||||
let hello_json = serde_json::to_string(&hello).map_err(|e| format!("Serialize hello: {e}"))?;
|
||||
sink.send(TungsteniteMsg::Text(hello_json.into()))
|
||||
.await
|
||||
.map_err(|e| format!("Send hello failed: {e}"))?;
|
||||
|
||||
slog!("[crdt-sync] Hello sent, awaiting server_auth");
|
||||
|
||||
// ── Step 2: Receive server_auth from the responding node ──────
|
||||
let server_auth_frame = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(AUTH_TIMEOUT_SECS),
|
||||
stream.next(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "Auth timeout waiting for server_auth".to_string())?
|
||||
.ok_or_else(|| "Connection closed before server_auth".to_string())?
|
||||
.map_err(|e| format!("WebSocket read error: {e}"))?;
|
||||
|
||||
let server_auth_text = match server_auth_frame {
|
||||
TungsteniteMsg::Text(t) => t.to_string(),
|
||||
_ => return Err("Expected text frame for server_auth".to_string()),
|
||||
};
|
||||
|
||||
let server_auth: ServerAuthMessage = serde_json::from_str(&server_auth_text)
|
||||
.map_err(|e| format!("Invalid server_auth message: {e}"))?;
|
||||
|
||||
if server_auth.r#type != "server_auth" {
|
||||
return Err(format!(
|
||||
"Expected server_auth message, got type={}",
|
||||
server_auth.r#type
|
||||
));
|
||||
}
|
||||
|
||||
// ── Step 3: Verify server's signature and check trusted-key list ─
|
||||
let versioned_challenge = format!("huskies-v1:{client_nonce}");
|
||||
let server_sig_valid = crate::node_identity::verify_message_strict(
|
||||
&server_auth.pubkey_hex,
|
||||
versioned_challenge.as_bytes(),
|
||||
&server_auth.signature_hex,
|
||||
);
|
||||
let server_key_trusted = auth::trusted_keys()
|
||||
.iter()
|
||||
.any(|k| k == &server_auth.pubkey_hex);
|
||||
|
||||
if !server_sig_valid || !server_key_trusted {
|
||||
slog!(
|
||||
"[crdt-sync] Server auth failed \
|
||||
(sig_valid={server_sig_valid}, key_trusted={server_key_trusted}, \
|
||||
server_pubkey={})",
|
||||
server_auth.pubkey_hex
|
||||
);
|
||||
return Err(format!(
|
||||
"Server auth rejected: sig_valid={server_sig_valid}, \
|
||||
key_trusted={server_key_trusted}, server_pubkey={}",
|
||||
server_auth.pubkey_hex
|
||||
));
|
||||
}
|
||||
|
||||
slog!(
|
||||
"[crdt-sync] Server authenticated: {:.12}…",
|
||||
&server_auth.pubkey_hex
|
||||
);
|
||||
|
||||
// ── Step 4: Receive challenge from the responding node ────────
|
||||
let challenge_frame = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(AUTH_TIMEOUT_SECS),
|
||||
stream.next(),
|
||||
@@ -115,7 +183,7 @@ pub(crate) async fn connect_and_sync(url: &str, token: Option<&str>) -> Result<(
|
||||
));
|
||||
}
|
||||
|
||||
// ── Step 2: Sign challenge and send auth reply ────────────────
|
||||
// ── Step 5: Sign challenge and send auth reply ────────────────
|
||||
let (pubkey_hex, signature_hex) = crdt_state::sign_challenge(&challenge_msg.nonce)
|
||||
.ok_or_else(|| "CRDT not initialised — cannot sign challenge".to_string())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user