Skip to content

Bilateral BLE prepare: op_id not bound to decoded operation_data (sibling of #446) #453

Description

@cryptskii

Summary

Follow-up to #446 (the online inbox
signed-byte binding gap, fixed for the storage.sync path). The bilateral BLE prepare path has the same
shape of gap: the operation is decoded from prepare_request.operation_data, but the session is keyed
by the wire-supplied op.op_id, with no check that op_id equals the hash of the decoded operation.
The wire op_id and the decoded operation_data can therefore diverge.

Severity: Medium (not Critical, unlike #446). On the bilateral path the proposer signs their own debit,
so a divergence here is self-harm / a malformed-session bug rather than an attacker-credits-themselves
vector. But it is still an unclosed binding and should be hardened for symmetry with the inbox fix.

Evidence (dsm_client/deterministic_state_machine/dsm_sdk/src/bluetooth/bilateral_ble_handler.rs)

  • Operation decoded from the wire bytes: bilateral_ble_handler.rs:1957
    (Operation::from_bytes(&prepare_request.operation_data)).
  • origin_commitment_hash / session key taken from the wire op.op_id with only a 32-byte length check —
    no hash binding: bilateral_ble_handler.rs:2002-2007
    (op.op_id.as_ref().map(|h| h.v.clone()) ... .try_into() ... "op_id must be 32 bytes").
  • Sender σ_A is signed over the commitment hash, not over a binding of op_id to the decoded operation
    (see Critical: signed-byte binding not closed — inbox applies field-reconstructed op, not signed bytes #446's analysis of the bilateral path).
  • A binding helper already exists: generate_bilateral_hash(...) — the prepare handler should require
    op_id == generate_bilateral_hash(local, remote, Operation::from_bytes(operation_data)) before keying
    the session.

Fix

In the prepare handler, after decoding operation at :1957, recompute the expected op id from the
decoded operation and the local/remote identities and reject the prepare if it does not match the wire
op_id:

let operation = Operation::from_bytes(&prepare_request.operation_data)
    .map_err(|_| DsmError::invalid_operation("invalid operation payload"))?;
let expected_op_id = generate_bilateral_hash(/* local, remote, */ &operation);
if wire_op_id != expected_op_id {
    return Err(DsmError::invalid_operation("op_id does not bind operation_data"));
}

This mirrors the canonical re-serialization binding added for the inbox path in #446
(Operation::decode_and_bind_signed), making the decoded operation the single source of truth for the
session key.

Related

Metadata

Metadata

Assignees

Labels

priority:mediumMedium priorityrustPull requests that update rust codesdkDSM SDK and native integration layerssecuritySecurity-related issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions