feat: complete all WZP-S integration tasks (S-4/5/6/7/9)
WZP-S-4: Room access control
- hash_room_name() in wzp-crypto: SHA-256("featherchat-group:"+name)[:16]
- CLI --room flag hashes before SNI, web bridge does the same
- RoomManager gains ACL: with_acl(), allow(), is_authorized()
- join() returns Result, rejects unauthorized fingerprints
WZP-S-5: Crypto handshake wired into all live paths
- CLI: perform_handshake() after connect, before any mode
- Relay: accept_handshake() after auth, before room join
- Web bridge: perform_handshake() after auth, before audio
- Relay generates ephemeral identity at startup
WZP-S-6: Web bridge featherChat auth
- --auth-url flag: browsers send {"type":"auth","token":"..."} as first WS msg
- Validates against featherChat, passes token to relay
- --cert/--key flags for production TLS (replaces self-signed)
WZP-S-7: wzp-proto standalone
- Cargo.toml uses explicit versions (no workspace inheritance)
- FC can use as git dependency
WZP-S-9: All 6 hardcoded assumptions resolved
- Auth, hashed rooms, mandatory handshake, real TLS certs,
profile negotiation, token validation
CLI also gains --room and --token flags.
179 tests passing across all crates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -187,6 +187,22 @@ pub struct PublicIdentity {
|
||||
pub fingerprint: Fingerprint,
|
||||
}
|
||||
|
||||
/// Hash a human-readable room/group name into an opaque hex string.
|
||||
/// Used as QUIC SNI to prevent leaking group names to network observers.
|
||||
///
|
||||
/// `hash_room_name("my-group")` → 32 hex chars (16 bytes of SHA-256).
|
||||
///
|
||||
/// Mirrors the convention in featherChat WZP-FC-5:
|
||||
/// `SHA-256("featherchat-group:" + group_name)[:16]`
|
||||
pub fn hash_room_name(group_name: &str) -> String {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"featherchat-group:");
|
||||
hasher.update(group_name.as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
hex::encode(&hash[..16])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -231,6 +247,20 @@ mod tests {
|
||||
assert_eq!(fp_str.chars().filter(|c| *c == ':').count(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_room_name_deterministic() {
|
||||
let h1 = hash_room_name("my-group");
|
||||
let h2 = hash_room_name("my-group");
|
||||
assert_eq!(h1, h2);
|
||||
assert_eq!(h1.len(), 32); // 16 bytes = 32 hex chars
|
||||
assert!(h1.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_room_name_different_inputs() {
|
||||
assert_ne!(hash_room_name("alpha"), hash_room_name("beta"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_handshake_derivation() {
|
||||
use wzp_proto::KeyExchange;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub mod session;
|
||||
|
||||
pub use anti_replay::AntiReplayWindow;
|
||||
pub use handshake::WarzoneKeyExchange;
|
||||
pub use identity::{Fingerprint, IdentityKeyPair, PublicIdentity, Seed};
|
||||
pub use identity::{hash_room_name, Fingerprint, IdentityKeyPair, PublicIdentity, Seed};
|
||||
pub use nonce::{build_nonce, Direction};
|
||||
pub use rekey::RekeyManager;
|
||||
pub use session::ChaChaSession;
|
||||
|
||||
Reference in New Issue
Block a user