hww: support host passphrase entry - #2115
Conversation
50b0682 to
baebc25
Compare
Require Python 3.7 so the host-passphrase configuration can use the standard-library dataclass. This keeps BitBoxConfig's optional callbacks and defaults declarative, without a custom constructor or an additional dataclasses backport dependency for Python 3.6. Update the package requirement, classifier and runtime version check together so installers and direct imports agree on the minimum version. Keep typing_extensions: Python 3.7 still needs its TypedDict and Protocol backports, which only became part of typing in Python 3.8.
An HWW workflow can send an intermediate response and wait in next_request while keeping its future and UI alive. The C transport releases its request lock after the response has been read, so that lock does not protect the UI throughout a workflow spanning multiple requests. The existing U2F guard prevents overlapping U2F workflows but does not check for an active HWW task. A U2F unlock or confirmation could therefore start between HWW requests and compete with the HWW workflow for the screen and unlock state. Reject U2F workflow startup whenever async_usb is not idle, before acquiring the U2F workflow guard. This preserves HWW ownership while processing a request, delivering an intermediate response or waiting for the next request. U2F workflows can start again once the HWW task has finished or been cancelled. The check runs in the existing single-threaded scheduling context and requires no additional lock. Host passphrase entry makes this gap particularly visible: the device can wait for user input on the host indefinitely. Apply the guard to all active HWW workflows so the protection also covers other uses of next_request and is independent of the new unlock messages. Extend the workflow guard regression to cover an HWW task before and after its intermediate response is consumed, and to allow U2F startup again after cancellation.
Allow TestingUi string-entry callbacks to return futures. Tests need to keep device input pending while processing host requests, then complete or cancel it at a chosen point. This enables coverage of host passphrase entry and races with device input completion, which an immediately returning callback cannot represent. Store only the async callback and always await it in enter_string. Preserve existing synchronous callers by making set_enter_string wrap its callback's result in core::future::ready and delegate to set_enter_string_async. Both setters replace the same callback, and remove_enter_string clears it regardless of how it was installed. One callback slot avoids precedence rules and prevents removal from leaving a separate async callback active. Update the mnemonic input helper to wrap the same async callback. Mnemonic words return ready futures, while other prompts forward the saved callback's future. Existing password and mnemonic test callers keep their synchronous interface.
next_request decrypts each Noise continuation into a temporary Vec before decoding the protobuf request. Dropping that Vec normally frees its allocation without erasing the plaintext. Continuations can carry secrets such as a host-supplied BIP39 passphrase, so the temporary message buffer must be wiped independently of the decoded value. Wrap the decrypted buffer in Zeroizing so its contents are erased when decoding finishes, including when decoding returns an error. The guard covers the raw plaintext buffer; decoded protobuf fields own separate data and still require their own cleanup.
Display strings can contain BIP39 passphrases, mnemonic words or password presets. Freeing the Rust-side C-string buffers without wiping them leaves that text in reusable heap memory. Appending the terminating NUL to a buffer sized only for the text can also reallocate it, leaving a second copy behind even if the final allocation is wiped. Make display_str_to_cstr_vec return Zeroizing<Vec<c_char>> using the existing zeroizing C-string conversion. It allocates the full buffer, including the NUL, before copying the text. Apply this at the common UI boundary so confirm and the other display callers share the same cleanup, including when an async screen is cancelled. Keep printable-ASCII validation, truncation and the C character type unchanged, and retain the menu string owners for the full menu lifetime. This change protects the Rust conversion buffers; copies owned by C components still require their own cleanup.
5905299 to
843a043
Compare
cedwies
left a comment
There was a problem hiding this comment.
Concept ACK.
There is one issue I would like to discuss (also mentioned in #2000:
Capital i and lowercase l are pixel identical. This issue predates this PR but IMO becomes a larger problem for host passphrase entry, since a malicious host can swap out letters while the user has no chance to detect it. We should fix this issue before merging this PR.
Tried here, but after discussing, we don't continue with this: #2117 The search space to recover the right passphrase is very small, which makes an effective ransom attack here very unlikely. |
Allow host entry when the optional BIP39 passphrase is enabled, keeping device entry as the default. Requesting host entry requires device approval, then the user confirms the submitted value on the device before unlocking, including an empty passphrase. Rejection, cancellation or invalid input restarts device entry. Run the new unlock flow inside the paired Noise channel to protect host input, retaining legacy unlock for older clients. Poll progress within the existing request/response model and use the existing reconnect reset to discard abandoned workflows. Limit host passphrases to the device keyboard's character set and length so users can still access the same wallet through device entry. Withdraw the host-entry option before consent or passphrase confirmation starts, and relock after interrupted unlocks. Repeated unlock calls leave an already unlocked wallet unchanged. Expose Python callbacks for host input and optional entry controls, with polling handled by the library. Support both a host-entry button and requesting host entry automatically. Extend send_message.py to demonstrate host entry, cancellation and withdrawal of the host-entry option.
843a043 to
853a27c
Compare
Allow host entry of the optional BIP39 passphrase while keeping device entry as the default. The user approves switching to host entry on the device, then confirms the submitted passphrase before unlocking. Rejection, cancellation or invalid input restarts device entry.
Run the new unlock flow inside the paired Noise channel, preserving legacy unlock compatibility. Keep the existing request/response transport with polling for device entry, and reuse session reset on reconnect to abandon interrupted workflows safely.
Restrict host input to passphrases that can also be entered on the device. Withdraw the host-entry option before consent or confirmation starts, and prevent repeated unlock calls from changing an unlocked wallet.
Extend the Python API with callbacks for host input and optional entry controls, with polling owned by the library. Demonstrate entry, cancellation and withdrawal in
send_message.py.Keep the Python 3.7 requirement and supporting UI, request-buffer cleanup, U2F and test-helper changes in separate prerequisite commits.
Related PRs: