(feat): add max message size to wasm
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / wasm build (push) Successful in 1m15s
CI / clippy (push) Successful in 1m30s
CI / test (push) Successful in 1m48s
CI / example (push) Successful in 1m32s
CI / duplicate code (push) Failing after 29s
CI / web client (push) Failing after 30s
CI / cargo-machete (push) Successful in 1m7s
CI / cargo-deny (push) Failing after 2m23s

(feat): add pq key generation to wasm
(qol): update gitignores
This commit is contained in:
Alois 2026-06-28 13:08:37 +02:00
commit d9ad5e5b3d
23 changed files with 341 additions and 1996 deletions

View file

@ -71,21 +71,6 @@ fn route_incoming_frame(
}
}
if let Some(message_type) = message_type.as_ref() {
let matching_id = pending_requests
.borrow()
.iter()
.find_map(|(id, pending)| match pending.response_type.as_ref() {
Some(response_type) if response_type == message_type => Some(*id),
_ => None,
});
if let Some(id) = matching_id {
if let Some(pending) = pending_requests.borrow_mut().remove(&id) {
let _ = pending.sender.send(Ok(frame.clone()));
}
}
}
let _ = on_message.call1(&JsValue::NULL, frame);
let Some(message_type) = message_type else {
@ -154,7 +139,7 @@ fn unexpected_response_type_error(
*/
fn verify_host_challenge(
challenge: &CommunicationValue,
tm: &mtp_codec::TypeMap,
_tm: &mtp_codec::TypeMap,
host_pk: &mtp_crypto::PublicKeyBundle,
id: u64,
server_challenge: u128,
@ -185,7 +170,7 @@ fn verify_host_challenge(
*/
fn verify_host_final(
resp: &CommunicationValue,
tm: &mtp_codec::TypeMap,
_tm: &mtp_codec::TypeMap,
host_pk: &mtp_crypto::PublicKeyBundle,
id: u64,
client_nonce: u128,
@ -230,12 +215,24 @@ fn signed_challenge_response_bytes(
.sign(proof_payload)
.map_err(|e| js_error(&format!("signature failed: {}", e)))?;
CommunicationValue::new(CommunicationType::ChallengeResponse)
let mut proof = CommunicationValue::new(CommunicationType::ChallengeResponse)
.add_typed_default(
DataType::ClientNonce,
DataValue::UnsignedNumber(client_nonce),
)
.add_typed_default(DataType::Signature, DataValue::Bytes(signature))
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
if !keyring.sig_pq_secret_key.as_bytes().is_empty() {
let pq_signer =
mtp_crypto::MlDsaSigner::new(&keyring.sig_pq_secret_key, &keyring.sig_pq_public_key)
.map_err(|e| js_error(&format!("PQ signer creation failed: {}", e)))?;
let pq_signature = pq_signer
.sign(proof_payload)
.map_err(|e| js_error(&format!("PQ signature failed: {}", e)))?;
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
}
proof
.to_bytes()
.map_err(|e| js_error(&format!("encode failed: {}", e)))
}
@ -297,8 +294,12 @@ impl WasmClient {
#[wasm_bindgen]
pub async fn connect(&mut self, config: &ConnectionConfig) -> Result<(), JsValue> {
self.set_state(ConnectionState::Connecting);
let transport =
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
let transport = WasmTransport::connect(
&config.url,
config.server_certificate_hashes.clone(),
config.max_message_size,
)
.await?;
let version_str = format!("{}", PROTOCOL_VERSION);
let ident = CommunicationValue::new(CommunicationType::Identification)
@ -342,8 +343,12 @@ impl WasmClient {
let tm = mtp_codec::TypeMap::latest();
let version_str = format!("{}", PROTOCOL_VERSION);
let transport =
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
let transport = WasmTransport::connect(
&config.url,
config.server_certificate_hashes.clone(),
config.max_message_size,
)
.await?;
// 1. Send the unsigned Identification hello.
let hello = CommunicationValue::new(CommunicationType::Identification)
@ -450,8 +455,12 @@ impl WasmClient {
let version_str = format!("{}", PROTOCOL_VERSION);
let pk_bytes = keyring.public_key_bundle().as_bytes();
let transport =
WasmTransport::connect(&config.url, config.server_certificate_hashes.clone()).await?;
let transport = WasmTransport::connect(
&config.url,
config.server_certificate_hashes.clone(),
config.max_message_size,
)
.await?;
// 1. Send the unsigned Register hello (version + public-key bundle).
let hello = CommunicationValue::new(CommunicationType::Register)

View file

@ -5,6 +5,7 @@ pub struct ConnectionConfig {
pub(crate) url: String,
pub(crate) server_certificate_hashes: Option<Vec<String>>,
pub(crate) client_id: u64,
pub(crate) max_message_size: u32,
}
#[wasm_bindgen]
@ -15,6 +16,7 @@ impl ConnectionConfig {
url,
server_certificate_hashes: None,
client_id: 0,
max_message_size: 1_000_000_000,
}
}
@ -37,4 +39,14 @@ impl ConnectionConfig {
pub fn set_server_certificate_hashes(&mut self, hashes: Vec<String>) {
self.server_certificate_hashes = Some(hashes);
}
#[wasm_bindgen(setter)]
pub fn set_max_message_size(&mut self, max_message_size: u32) {
self.max_message_size = max_message_size;
}
#[wasm_bindgen(getter)]
pub fn max_message_size(&self) -> u32 {
self.max_message_size
}
}

View file

@ -42,6 +42,12 @@ impl WasmKeyring {
}
}
/// Generate a full keyring with KEM, ML-DSA, and Ed25519 keys.
#[wasm_bindgen]
pub fn keyring_generate() -> Vec<u8> {
Keyring::generate().to_bytes()
}
/// Build a [`Keyring`] containing only an Ed25519 keypair (no KEM, no ML-DSA).
///
/// Takes the Ed25519 secret key and public key, each 32 bytes.

View file

@ -253,10 +253,7 @@ pub fn parse_auth_response(response: &[u8]) -> Result<JsValue, JsValue> {
let comm = CommunicationValue::from_bytes(response)
.map_err(|e| js_error(&format!("parse failed: {}", e)))?;
let connected = matches!(
comm.get_data(DataType::Connected),
DataValue::BoolTrue
);
let connected = matches!(comm.get_data(DataType::Connected), DataValue::BoolTrue);
let client_nonce = match comm.get_data(DataType::ClientNonce) {
DataValue::UnsignedNumber(n) => Some(*n),

View file

@ -55,6 +55,7 @@ enum FrameOutcome {
#[derive(Clone)]
pub struct WasmTransport {
inner: JsValue,
max_message_size: u32,
/// Reader over `incoming_unidirectional_streams()` (a singleton stream of streams).
streams_reader: Rc<RefCell<Option<JsValue>>>,
/// Reader over the host's current uni-directional stream, if one is open.
@ -64,7 +65,11 @@ pub struct WasmTransport {
}
impl WasmTransport {
pub async fn connect(url: &str, cert_hashes: Option<Vec<String>>) -> Result<Self, JsValue> {
pub async fn connect(
url: &str,
cert_hashes: Option<Vec<String>>,
max_message_size: u32,
) -> Result<Self, JsValue> {
let ctor = js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("WebTransport"))?
.dyn_into::<js_sys::Function>()
.map_err(|_| js_error("WebTransport not available"))?;
@ -111,6 +116,7 @@ impl WasmTransport {
.map_err(|e| js_error(&format!("WebTransport ready failed: {:?}", e)))?;
Ok(Self {
inner: transport,
max_message_size,
streams_reader: Rc::new(RefCell::new(None)),
stream_reader: Rc::new(RefCell::new(None)),
buffer: Rc::new(RefCell::new(Vec::new())),
@ -122,6 +128,12 @@ impl WasmTransport {
}
pub async fn send_frame(&self, frame: &[u8]) -> Result<(), JsValue> {
if frame.len() as u64 > self.max_message_size as u64
|| frame.len() as u64 >= CLOSE_FRAME_LEN as u64
{
return Err(js_error("message too large"));
}
let create_stream = js_sys::Reflect::get(
&self.inner,
&JsValue::from_str("createUnidirectionalStream"),
@ -273,6 +285,9 @@ impl WasmTransport {
if frame_len == CLOSE_FRAME_LEN {
return Ok(Some(FrameOutcome::Closed));
}
if frame_len > self.max_message_size {
return Err(js_error("message too large"));
}
let frame_len = frame_len as usize;
let Some(frame_end) = 4usize.checked_add(frame_len) else {
return Err(js_error("invalid frame length"));