diff --git a/WASM-CLIENT.md b/WASM-CLIENT.md index 71a8295..1638892 100644 --- a/WASM-CLIENT.md +++ b/WASM-CLIENT.md @@ -45,7 +45,7 @@ config.server_certificate_hashes = [ // optional, for certificate pinn ### TLS Certificate Handling -By default — when `server_certificate_hashes` is not set — the browser uses its +By default, when `server_certificate_hashes` is not set, the browser uses its **built-in root certificate store** to verify the server's TLS certificate, just like any other HTTPS/WebSocket connection. This works with publicly-trusted certificate authorities automatically. diff --git a/codec/src/data_value.rs b/codec/src/data_value.rs index 9ad52c0..7ccff8d 100644 --- a/codec/src/data_value.rs +++ b/codec/src/data_value.rs @@ -309,11 +309,7 @@ impl DataValue { * Returns `None` if the value is not a `Container` or signing fails. */ #[cfg(feature = "crypto")] - pub fn sign_container( - &mut self, - algorithm: u8, - signer: &impl SignatureScheme, - ) -> Option<()> { + pub fn sign_container(&mut self, algorithm: u8, signer: &impl SignatureScheme) -> Option<()> { let entries = self.as_container()?; let container_bytes = Self::encode_container(&entries); @@ -334,10 +330,7 @@ impl DataValue { * blob is malformed. */ #[cfg(feature = "crypto")] - pub fn verify_into_container( - &mut self, - verifier: &impl SignatureScheme, - ) -> Option<()> { + pub fn verify_into_container(&mut self, verifier: &impl SignatureScheme) -> Option<()> { let blob = self.as_signed_container()?; if blob.len() < 1 + 64 + 2 { return None; @@ -380,7 +373,7 @@ impl DataValue { /* * Decrypt a `SignedEncryptedContainer` in-place, replacing it with a - * `SignedContainer`. Does NOT verify — call `verify_into_container` next. + * `SignedContainer`. Does NOT verify; call `verify_into_container` next. */ #[cfg(feature = "crypto")] pub fn decrypt_signed_encrypted_container( @@ -1061,9 +1054,7 @@ mod tests { #[test] fn test_null_in_container() { - container_roundtrip(vec![ - (DataTypeId(1), DataValue::Null), - ]); + container_roundtrip(vec![(DataTypeId(1), DataValue::Null)]); } #[test] @@ -1103,21 +1094,23 @@ mod tests { #[test] fn test_container_nested_roundtrip() { container_roundtrip(vec![ - (DataTypeId(1), DataValue::Container(vec![ - (DataTypeId(10), DataValue::BoolTrue), - ])), - (DataTypeId(2), DataValue::Array(vec![ - DataValue::SignedNumber(1), - DataValue::SignedNumber(2), - ])), + ( + DataTypeId(1), + DataValue::Container(vec![(DataTypeId(10), DataValue::BoolTrue)]), + ), + ( + DataTypeId(2), + DataValue::Array(vec![DataValue::SignedNumber(1), DataValue::SignedNumber(2)]), + ), ]); } #[test] fn test_container_base64_roundtrip() { - let dv = DataValue::Container(vec![ - (DataTypeId(7), DataValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])), - ]); + let dv = DataValue::Container(vec![( + DataTypeId(7), + DataValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]), + )]); let b64 = dv.to_base64(); let decoded = DataValue::from_base64(&b64).expect("base64 roundtrip failed"); assert_eq!(dv, decoded); @@ -1128,11 +1121,17 @@ mod tests { assert_eq!(DataValue::BoolTrue.kind(), DataKind::Bool); assert_eq!(DataValue::Bool(false).kind(), DataKind::Bool); assert_eq!(DataValue::SignedNumber(0).kind(), DataKind::SignedNumber); - assert_eq!(DataValue::UnsignedNumber(0).kind(), DataKind::UnsignedNumber); + assert_eq!( + DataValue::UnsignedNumber(0).kind(), + DataKind::UnsignedNumber + ); assert_eq!(DataValue::Float(0, 0).kind(), DataKind::Float); assert_eq!(DataValue::Str(String::new()).kind(), DataKind::Str); assert_eq!(DataValue::Bytes(vec![]).kind(), DataKind::Bytes); - assert_eq!(DataValue::Array(vec![]).kind(), DataKind::Array(Box::new(DataKind::Null))); + assert_eq!( + DataValue::Array(vec![]).kind(), + DataKind::Array(Box::new(DataKind::Null)) + ); assert_eq!(DataValue::Container(vec![]).kind(), DataKind::Container); assert_eq!(DataValue::Null.kind(), DataKind::Null); } @@ -1147,10 +1146,22 @@ mod tests { ]); let map = dv.as_map().expect("should be a container"); - assert_eq!(map.get(&DataTypeId(1)).and_then(|v| v.as_str()), Some("alice")); - assert_eq!(map.get(&DataTypeId(2)).and_then(|v| v.as_signed_number()), Some(42)); - assert_eq!(map.get(&DataTypeId(3)).and_then(|v| v.as_bytes()), Some(vec![0x01, 0x02])); - assert_eq!(map.get(&DataTypeId(4)).and_then(|v| v.as_array()), Some(vec![DataValue::BoolTrue])); + assert_eq!( + map.get(&DataTypeId(1)).and_then(|v| v.as_str()), + Some("alice") + ); + assert_eq!( + map.get(&DataTypeId(2)).and_then(|v| v.as_signed_number()), + Some(42) + ); + assert_eq!( + map.get(&DataTypeId(3)).and_then(|v| v.as_bytes()), + Some(vec![0x01, 0x02]) + ); + assert_eq!( + map.get(&DataTypeId(4)).and_then(|v| v.as_array()), + Some(vec![DataValue::BoolTrue]) + ); } #[test] @@ -1191,9 +1202,7 @@ mod tests { #[test] fn test_truncated_container_rejected() { - let dv = DataValue::Container(vec![ - (DataTypeId(1), DataValue::Str("hello".to_string())), - ]); + let dv = DataValue::Container(vec![(DataTypeId(1), DataValue::Str("hello".to_string()))]); let bytes = dv.to_bytes(); // Truncate to fewer than 2 bytes so neither container nor array can be read assert!(DataValue::from_bytes(&bytes[..1]).is_none()); @@ -1280,9 +1289,8 @@ mod tests { let cipher_a = ChaCha20Poly1305::new([0xAB; 32]); let cipher_b = ChaCha20Poly1305::new([0xCD; 32]); - let mut dv = DataValue::Container(vec![ - (DataTypeId(1), DataValue::Str("secret".to_string())), - ]); + let mut dv = + DataValue::Container(vec![(DataTypeId(1), DataValue::Str("secret".to_string()))]); assert!(dv.encrypt_container(&cipher_a, b"aad").is_some()); assert!(dv.decrypt_into_container(&cipher_b, b"aad").is_none()); @@ -1294,9 +1302,8 @@ mod tests { use mtp_crypto::ChaCha20Poly1305; let cipher = ChaCha20Poly1305::new([0xAB; 32]); - let mut dv = DataValue::Container(vec![ - (DataTypeId(1), DataValue::Str("secret".to_string())), - ]); + let mut dv = + DataValue::Container(vec![(DataTypeId(1), DataValue::Str("secret".to_string()))]); assert!(dv.encrypt_container(&cipher, b"correct-aad").is_some()); assert!(dv.decrypt_into_container(&cipher, b"wrong-aad").is_none()); @@ -1319,16 +1326,21 @@ mod tests { let (signer, sk, _pk) = Ed25519Signer::generate(); let cipher = ChaCha20Poly1305::new([0xAB; 32]); - let mut dv = DataValue::Container(vec![ - (DataTypeId(1), DataValue::Str("signed data".to_string())), - ]); + let mut dv = DataValue::Container(vec![( + DataTypeId(1), + DataValue::Str("signed data".to_string()), + )]); - assert!(dv - .sign_and_encrypt_container(SigAlgorithm::ED25519, &signer, &cipher, b"aad") - .is_some()); + assert!( + dv.sign_and_encrypt_container(SigAlgorithm::ED25519, &signer, &cipher, b"aad") + .is_some() + ); assert!(matches!(dv, DataValue::SignedEncryptedContainer(_))); - assert!(dv.decrypt_signed_encrypted_container(&cipher, b"aad").is_some()); + assert!( + dv.decrypt_signed_encrypted_container(&cipher, b"aad") + .is_some() + ); assert!(matches!(dv, DataValue::SignedContainer(_))); let verifier = Ed25519Signer::new(&sk).unwrap(); @@ -1348,9 +1360,10 @@ mod tests { let (_, sk2, _) = Ed25519Signer::generate(); let wrong_verifier = Ed25519Signer::new(&sk2).unwrap(); - let mut dv = DataValue::Container(vec![ - (DataTypeId(1), DataValue::Str("signed data".to_string())), - ]); + let mut dv = DataValue::Container(vec![( + DataTypeId(1), + DataValue::Str("signed data".to_string()), + )]); assert!(dv.sign_container(SigAlgorithm::ED25519, &signer).is_some()); assert!(dv.verify_into_container(&wrong_verifier).is_none()); diff --git a/example-usage/web-client/src/main.ts b/example-usage/web-client/src/main.ts index fc49670..c5a986e 100644 --- a/example-usage/web-client/src/main.ts +++ b/example-usage/web-client/src/main.ts @@ -6,13 +6,13 @@ import init, { ed25519_generate, keyring_from_ed25519, build_demo_message, -} from 'mtp-wasm'; +} from "mtp-wasm"; -const STATUS = document.getElementById('status')!; -const STORAGE_KEY = 'mtp-web-client-keys'; +const STATUS = document.getElementById("status")!; +const STORAGE_KEY = "mtp-web-client-keys"; -function log(msg: string, cls = '') { - const line = document.createElement('div'); +function log(msg: string, cls = "") { + const line = document.createElement("div"); line.textContent = msg; if (cls) line.className = cls; STATUS.appendChild(line); @@ -37,19 +37,23 @@ function loadKeys(): { clientId: bigint; keyringBytes: Uint8Array } | null { } async function initWasm() { - log('Loading WASM module...'); + log("Loading WASM module..."); await init(); log(`WASM loaded. WebTransport supported: ${WasmClient.is_supported()}`); } function createClient(): WasmClient { return new WasmClient( - (state: number) => log(`[state] ${ConnectionState[state] ?? state}`, 'state'), + (state: number) => + log(`[state] ${ConnectionState[state] ?? state}`, "state"), (data: Uint8Array) => { const decoder = new TextDecoder(); - log(`[message] ${data.length} bytes: ${decoder.decode(data)}`, 'received'); + log( + `[message] ${data.length} bytes: ${decoder.decode(data)}`, + "received", + ); }, - (err: any) => log(`[error] ${err}`, 'error'), + (err: any) => log(`[error] ${err}`, "error"), ); } @@ -65,11 +69,11 @@ async function run() { await initWasm(); if (!WasmClient.is_supported()) { - log('WebTransport is not supported in this browser.', 'error'); + log("WebTransport is not supported in this browser.", "error"); return; } - const serverUrl = 'https://127.0.0.1:8080'; + const serverUrl = "https://127.0.0.1:8080"; const saved = loadKeys(); const client = createClient(); @@ -81,30 +85,35 @@ async function run() { if (saved) { log(`Found saved client keys (ID: ${saved.clientId})`); const hostPk = new Uint8Array(0); - clientId = await client.auth_connect(config, hostPk, saved.keyringBytes, saved.clientId); + clientId = await client.auth_connect( + config, + hostPk, + saved.keyringBytes, + saved.clientId, + ); log(`Authenticated as client ${clientId}`); keyringBytes = saved.keyringBytes; } else { - log('No saved keys — registering new client...'); + log("No saved keys: registering new client..."); const hostPk = new Uint8Array(0); keyringBytes = generateKeyringBytes(); clientId = await client.auth_register(config, hostPk, keyringBytes); log(`Registered with ID: ${clientId}`); saveKeys(clientId, keyringBytes); - log('Saved client keys to localStorage'); + log("Saved client keys to localStorage"); } config.free(); - log('\nSending demo message...'); + log("\nSending demo message..."); const frame = build_demo_message(clientId, keyringBytes); await client.send(frame); log(`Sent ${frame.length} bytes`); - log('\nClient running. Waiting for incoming messages...'); + log("\nClient running. Waiting for incoming messages..."); } run().catch((e) => { - log(`Fatal error: ${e}`, 'error'); + log(`Fatal error: ${e}`, "error"); console.error(e); }); diff --git a/example-usage/web-client/vite.config.ts b/example-usage/web-client/vite.config.ts index e4c1eee..a4c4001 100644 --- a/example-usage/web-client/vite.config.ts +++ b/example-usage/web-client/vite.config.ts @@ -7,4 +7,9 @@ export default defineConfig({ 'mtp-wasm': path.resolve(__dirname, '../../wasm/pkg'), }, }, + server: { + fs: { + allow: ['.', path.resolve(__dirname, '../../wasm/pkg')], + }, + }, });