mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +00:00
Merge pull request #840 from trezor/matejcik/sys-exit
wipe code activation tests
This commit is contained in:
commit
cdb7a7eb68
@ -102,9 +102,7 @@ error_shutdown(const char *line1, const char *line2, const char *line3,
|
||||
printf("\nPlease unplug the device.\n");
|
||||
display_backlight(255);
|
||||
hal_delay(5000);
|
||||
__shutdown();
|
||||
for (;;)
|
||||
;
|
||||
exit(4);
|
||||
}
|
||||
|
||||
void hal_delay(uint32_t ms) { usleep(1000 * ms); }
|
||||
|
22
core/emu.py
22
core/emu.py
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import gzip
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
@ -54,7 +55,6 @@ def watch_emulator(emulator):
|
||||
try:
|
||||
for _, type_names, _, _ in watch.event_gen(yield_nones=False):
|
||||
if "IN_CLOSE_WRITE" in type_names:
|
||||
click.echo("Restarting...")
|
||||
emulator.restart()
|
||||
except KeyboardInterrupt:
|
||||
emulator.stop()
|
||||
@ -77,19 +77,23 @@ def run_debugger(emulator):
|
||||
)
|
||||
|
||||
|
||||
def _from_env(name):
|
||||
return os.environ.get(name) == "1"
|
||||
|
||||
|
||||
@click.command(context_settings=dict(ignore_unknown_options=True))
|
||||
# fmt: off
|
||||
@click.option("-a", "--disable-animation", is_flag=True, default=os.environ.get("TREZOR_DISABLE_ANIMATION") == "1", help="Disable animation")
|
||||
@click.option("-a", "--disable-animation/--enable-animation", default=_from_env("TREZOR_DISABLE_ANIMATION"), help="Disable animation")
|
||||
@click.option("-c", "--command", "run_command", is_flag=True, help="Run command while emulator is running")
|
||||
@click.option("-d", "--production", is_flag=True, default=os.environ.get("PYOPT") == "1", help="Production mode (debuglink disabled)")
|
||||
@click.option("-d", "--production/--no-production", default=_from_env("PYOPT"), help="Production mode (debuglink disabled)")
|
||||
@click.option("-D", "--debugger", is_flag=True, help="Run emulator in debugger (gdb/lldb)")
|
||||
@click.option("--executable", type=click.Path(exists=True, dir_okay=False), default=os.environ.get("MICROPYTHON"), help="Alternate emulator executable")
|
||||
@click.option("-g", "--profiling", is_flag=True, default=os.environ.get("TREZOR_PROFILING"), help="Run with profiler wrapper")
|
||||
@click.option("-g", "--profiling/--no-profiling", default=_from_env("TREZOR_PROFILING"), help="Run with profiler wrapper")
|
||||
@click.option("-h", "--headless", is_flag=True, help="Headless mode (no display)")
|
||||
@click.option("--heap-size", metavar="SIZE", default="20M", help="Configure heap size")
|
||||
@click.option("--main", help="Path to python main file")
|
||||
@click.option("--mnemonic", "mnemonics", multiple=True, help="Initialize device with given mnemonic. Specify multiple times for Shamir shares.")
|
||||
@click.option("--log-memory", is_flag=True, default=os.environ.get("TREZOR_LOG_MEMORY") == "1", help="Print memory usage after workflows")
|
||||
@click.option("--log-memory/--no-log-memory", default=_from_env("TREZOR_LOG_MEMORY"), help="Print memory usage after workflows")
|
||||
@click.option("-o", "--output", type=click.File("w"), default="-", help="Redirect emulator output to file")
|
||||
@click.option("-p", "--profile", metavar="NAME", help="Profile name or path")
|
||||
@click.option("-P", "--port", metavar="PORT", type=int, default=int(os.environ.get("TREZOR_UDP_PORT", 0)) or None, help="UDP port number")
|
||||
@ -198,6 +202,10 @@ def cli(
|
||||
if quiet:
|
||||
output = None
|
||||
|
||||
logger = logging.getLogger("trezorlib._internal.emulator")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
|
||||
emulator = CoreEmulator(
|
||||
executable,
|
||||
profile_dir,
|
||||
@ -230,11 +238,7 @@ def cli(
|
||||
run_debugger(emulator)
|
||||
raise RuntimeError("run_debugger should not return")
|
||||
|
||||
click.echo("Waiting for emulator to come up... ", err=True)
|
||||
start = time.monotonic()
|
||||
emulator.start()
|
||||
end = time.monotonic()
|
||||
click.echo(f"Emulator ready after {end - start:.3f} seconds", err=True)
|
||||
|
||||
if mnemonics:
|
||||
if slip0014:
|
||||
|
@ -409,7 +409,7 @@ async def handle_session(iface: WireInterface, session_id: int) -> None:
|
||||
# Unload modules imported by the workflow. Should not raise.
|
||||
utils.unimport_end(modules)
|
||||
|
||||
except BaseException as exc:
|
||||
except Exception as exc:
|
||||
# The session handling should never exit, just log and continue.
|
||||
if __debug__:
|
||||
log.exception(__name__, exc)
|
||||
|
@ -53,7 +53,8 @@ void setup(void) {
|
||||
}
|
||||
|
||||
void __attribute__((noreturn)) shutdown(void) {
|
||||
for (;;) pause();
|
||||
sleep(5);
|
||||
exit(4);
|
||||
}
|
||||
|
||||
void emulatorRandom(void *buffer, size_t size) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
@ -22,6 +23,10 @@ from pathlib import Path
|
||||
from trezorlib.debuglink import TrezorClientDebugLink
|
||||
from trezorlib.transport.udp import UdpTransport
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
EMULATOR_WAIT_TIME = 60
|
||||
|
||||
|
||||
def _rm_f(path):
|
||||
try:
|
||||
@ -84,9 +89,10 @@ class Emulator:
|
||||
def _get_transport(self):
|
||||
return UdpTransport("127.0.0.1:{}".format(self.port))
|
||||
|
||||
def wait_until_ready(self, timeout=60):
|
||||
def wait_until_ready(self, timeout=EMULATOR_WAIT_TIME):
|
||||
transport = self._get_transport()
|
||||
transport.open()
|
||||
LOG.info("Waiting for emulator to come up...")
|
||||
start = time.monotonic()
|
||||
try:
|
||||
while True:
|
||||
@ -103,8 +109,11 @@ class Emulator:
|
||||
finally:
|
||||
transport.close()
|
||||
|
||||
LOG.info("Emulator ready after {:.3f} seconds".format(time.monotonic() - start))
|
||||
|
||||
def wait(self, timeout=None):
|
||||
ret = self.process.wait(timeout=timeout)
|
||||
self.process = None
|
||||
self.stop()
|
||||
return ret
|
||||
|
||||
@ -129,6 +138,7 @@ class Emulator:
|
||||
if self.process:
|
||||
if self.process.poll() is not None:
|
||||
# process has died, stop and start again
|
||||
LOG.info("Starting from a stopped process.")
|
||||
self.stop()
|
||||
else:
|
||||
# process is running, no need to start again
|
||||
@ -139,6 +149,9 @@ class Emulator:
|
||||
self.wait_until_ready()
|
||||
except TimeoutError:
|
||||
# Assuming that after the default 60-second timeout, the process is stuck
|
||||
LOG.warning(
|
||||
"Emulator did not come up after {} seconds".format(EMULATOR_WAIT_TIME)
|
||||
)
|
||||
self.process.kill()
|
||||
raise
|
||||
|
||||
@ -156,10 +169,15 @@ class Emulator:
|
||||
self.client = None
|
||||
|
||||
if self.process:
|
||||
LOG.info("Terminating emulator...")
|
||||
start = time.monotonic()
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(30)
|
||||
self.process.wait(EMULATOR_WAIT_TIME)
|
||||
end = time.monotonic()
|
||||
LOG.info("Emulator shut down after {:.3f} seconds".format(end - start))
|
||||
except subprocess.TimeoutExpired:
|
||||
LOG.info("Emulator seems stuck. Sending kill signal.")
|
||||
self.process.kill()
|
||||
|
||||
_rm_f(self.profile_dir / "trezor.pid")
|
||||
|
@ -184,8 +184,11 @@ class DebugUI:
|
||||
|
||||
def __init__(self, debuglink: DebugLink):
|
||||
self.debuglink = debuglink
|
||||
self.pin = None
|
||||
self.passphrase = "sphinx of black quartz, judge my wov"
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
self.pins = None
|
||||
self.passphrase = ""
|
||||
self.input_flow = None
|
||||
|
||||
def button_request(self, code):
|
||||
@ -221,11 +224,15 @@ class DebugUI:
|
||||
self.input_flow = self.INPUT_FLOW_DONE
|
||||
|
||||
def get_pin(self, code=None):
|
||||
if self.pin:
|
||||
return self.pin
|
||||
else:
|
||||
if self.pins is None:
|
||||
# respond with correct pin
|
||||
return self.debuglink.read_pin_encoded()
|
||||
|
||||
if self.pins == []:
|
||||
raise AssertionError("PIN sequence ended prematurely")
|
||||
else:
|
||||
return self.debuglink.encode_pin(self.pins.pop(0))
|
||||
|
||||
def get_passphrase(self, available_on_device):
|
||||
return self.passphrase
|
||||
|
||||
@ -261,15 +268,10 @@ class TrezorClientDebugLink(TrezorClient):
|
||||
|
||||
self.filters = {}
|
||||
|
||||
# Always press Yes and provide correct pin
|
||||
self.setup_debuglink(True, True)
|
||||
|
||||
# Do not expect any specific response from device
|
||||
self.expected_responses = None
|
||||
self.current_response = None
|
||||
|
||||
# Use blank passphrase
|
||||
self.set_passphrase("")
|
||||
super().__init__(transport, ui=self.ui)
|
||||
|
||||
def open(self):
|
||||
@ -281,6 +283,15 @@ class TrezorClientDebugLink(TrezorClient):
|
||||
super().close()
|
||||
|
||||
def set_filter(self, message_type, callback):
|
||||
"""Configure a filter function for a specified message type.
|
||||
|
||||
The `callback` must be a function that accepts a protobuf message, and returns
|
||||
a (possibly modified) protobuf message of the same type. Whenever a message
|
||||
is sent or received that matches `message_type`, `callback` is invoked on the
|
||||
message and its result is substituted for the original.
|
||||
|
||||
Useful for test scenarios with an active malicious actor on the wire.
|
||||
"""
|
||||
self.filters[message_type] = callback
|
||||
|
||||
def _filter_message(self, msg):
|
||||
@ -292,10 +303,30 @@ class TrezorClientDebugLink(TrezorClient):
|
||||
return msg
|
||||
|
||||
def set_input_flow(self, input_flow):
|
||||
if input_flow is None:
|
||||
self.ui.input_flow = None
|
||||
return
|
||||
"""Configure a sequence of input events for the current with-block.
|
||||
|
||||
The `input_flow` must be a generator function. A `yield` statement in the
|
||||
input flow function waits for a ButtonRequest from the device, and returns
|
||||
its code.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> def input_flow():
|
||||
>>> # wait for first button prompt
|
||||
>>> code = yield
|
||||
>>> assert code == ButtonRequestType.Other
|
||||
>>> # press No
|
||||
>>> client.debug.press_no()
|
||||
>>>
|
||||
>>> # wait for second button prompt
|
||||
>>> yield
|
||||
>>> # press Yes
|
||||
>>> client.debug.press_yes()
|
||||
>>>
|
||||
>>> with client:
|
||||
>>> client.set_input_flow(input_flow)
|
||||
>>> some_call(client)
|
||||
"""
|
||||
if not self.in_with_statement:
|
||||
raise RuntimeError("Must be called inside 'with' statement")
|
||||
|
||||
@ -315,44 +346,59 @@ class TrezorClientDebugLink(TrezorClient):
|
||||
self.in_with_statement -= 1
|
||||
|
||||
# Clear input flow.
|
||||
self.set_input_flow(None)
|
||||
try:
|
||||
if _type is not None:
|
||||
# Another exception raised
|
||||
return False
|
||||
|
||||
if _type is not None:
|
||||
# Another exception raised
|
||||
return False
|
||||
if self.expected_responses is None:
|
||||
# no need to check anything else
|
||||
return False
|
||||
|
||||
if self.expected_responses is None:
|
||||
# no need to check anything else
|
||||
return False
|
||||
# Evaluate missed responses in 'with' statement
|
||||
if self.current_response < len(self.expected_responses):
|
||||
self._raise_unexpected_response(None)
|
||||
|
||||
# return isinstance(value, TypeError)
|
||||
# Evaluate missed responses in 'with' statement
|
||||
if self.current_response < len(self.expected_responses):
|
||||
self._raise_unexpected_response(None)
|
||||
|
||||
# Cleanup
|
||||
self.expected_responses = None
|
||||
self.current_response = None
|
||||
finally:
|
||||
# Cleanup
|
||||
self.expected_responses = None
|
||||
self.current_response = None
|
||||
self.ui.clear()
|
||||
|
||||
return False
|
||||
|
||||
def set_expected_responses(self, expected):
|
||||
"""Set a sequence of expected responses to client calls.
|
||||
|
||||
Within a given with-block, the list of received responses from device must
|
||||
match the list of expected responses, otherwise an AssertionError is raised.
|
||||
|
||||
If an expected response is given a field value other than None, that field value
|
||||
must exactly match the received field value. If a given field is None
|
||||
(or unspecified) in the expected response, the received field value is not
|
||||
checked.
|
||||
"""
|
||||
if not self.in_with_statement:
|
||||
raise RuntimeError("Must be called inside 'with' statement")
|
||||
self.expected_responses = expected
|
||||
self.current_response = 0
|
||||
|
||||
def setup_debuglink(self, button, pin_correct):
|
||||
# self.button = button # True -> YES button, False -> NO button
|
||||
if pin_correct:
|
||||
self.ui.pin = None
|
||||
else:
|
||||
self.ui.pin = "444222"
|
||||
def use_pin_sequence(self, pins):
|
||||
"""Respond to PIN prompts from device with the provided PINs.
|
||||
The sequence must be at least as long as the expected number of PIN prompts.
|
||||
"""
|
||||
# XXX This currently only works on T1 as a response to PinMatrixRequest, but
|
||||
# if we modify trezor-core to introduce PIN prompts predictably (i.e. by
|
||||
# a new ButtonRequestType), it could also be used on TT via debug.input()
|
||||
self.ui.pins = list(pins)
|
||||
|
||||
def set_passphrase(self, passphrase):
|
||||
def use_passphrase(self, passphrase):
|
||||
"""Respond to passphrase prompts from device with the provided passphrase."""
|
||||
self.ui.passphrase = Mnemonic.normalize_string(passphrase)
|
||||
|
||||
def set_mnemonic(self, mnemonic):
|
||||
def use_mnemonic(self, mnemonic):
|
||||
"""Use the provided mnemonic to respond to device.
|
||||
Only applies to T1, where device prompts the host for mnemonic words."""
|
||||
self.mnemonic = Mnemonic.normalize_string(mnemonic).split(" ")
|
||||
|
||||
def _raw_read(self):
|
||||
|
@ -46,7 +46,7 @@ from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
||||
def test_cardano_get_address(client, path, expected_address):
|
||||
# enter passphrase
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
|
||||
address = get_address(client, parse_path(path))
|
||||
assert address == expected_address
|
||||
|
@ -49,7 +49,7 @@ from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
||||
def test_cardano_get_public_key(client, path, public_key, chain_code):
|
||||
# enter passphrase
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
|
||||
key = get_public_key(client, parse_path(path))
|
||||
|
||||
|
@ -137,7 +137,7 @@ def test_cardano_sign_tx(
|
||||
client.debug.swipe_up()
|
||||
client.debug.press_yes()
|
||||
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
with client:
|
||||
client.set_expected_responses(expected_responses)
|
||||
client.set_input_flow(input_flow)
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import messages
|
||||
from trezorlib import device, exceptions, messages
|
||||
|
||||
PinType = messages.PinMatrixRequestType
|
||||
|
||||
PIN4 = "1234"
|
||||
WIPE_CODE4 = "4321"
|
||||
@ -27,77 +29,46 @@ pytestmark = pytest.mark.skip_t2
|
||||
|
||||
def _set_wipe_code(client, wipe_code):
|
||||
# Set/change wipe code.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
with client:
|
||||
if client.features.pin_protection:
|
||||
pin, _ = client.debug.read_pin()
|
||||
pins = [pin, wipe_code, wipe_code]
|
||||
pin_matrices = [
|
||||
messages.PinMatrixRequest(type=PinType.Current),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeFirst),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeSecond),
|
||||
]
|
||||
else:
|
||||
pins = [wipe_code, wipe_code]
|
||||
pin_matrices = [
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeFirst),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeSecond),
|
||||
]
|
||||
|
||||
# Confirm intent to set/change wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
if client.features.pin_protection:
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Now we're done.
|
||||
assert isinstance(ret, messages.Success)
|
||||
client.use_pin_sequence(pins)
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()]
|
||||
+ pin_matrices
|
||||
+ [messages.Success(), messages.Features()]
|
||||
)
|
||||
device.change_wipe_code(client)
|
||||
|
||||
|
||||
def _remove_wipe_code(client):
|
||||
# Remove wipe code
|
||||
ret = client.call_raw(messages.ChangeWipeCode(remove=True))
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to remove wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Now we're done.
|
||||
assert isinstance(ret, messages.Success)
|
||||
def _change_pin(client, old_pin, new_pin):
|
||||
assert client.features.pin_protection is True
|
||||
with client:
|
||||
client.use_pin_sequence([old_pin, new_pin, new_pin])
|
||||
try:
|
||||
return device.change_pin(client)
|
||||
except exceptions.TrezorFailure as f:
|
||||
return f.failure
|
||||
|
||||
|
||||
def _check_wipe_code(client, wipe_code):
|
||||
# Try to change the PIN to the current wipe code value. The operation should fail.
|
||||
ret = client.call_raw(messages.ChangePin())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to change PIN.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Expect failure.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
"""Check that wipe code is set by changing the PIN to it."""
|
||||
old_pin, _ = client.debug.read_pin()
|
||||
f = _change_pin(client, old_pin, wipe_code)
|
||||
assert isinstance(f, messages.Failure)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
@ -122,11 +93,11 @@ def test_set_remove_wipe_code(client):
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
|
||||
# Check that the PIN is correct.
|
||||
# Check that the wipe code is correct.
|
||||
_check_wipe_code(client, WIPE_CODE6)
|
||||
|
||||
# Test remove wipe code.
|
||||
_remove_wipe_code(client)
|
||||
device.change_wipe_code(client, remove=True)
|
||||
|
||||
# Check that there's no wipe code protection now.
|
||||
client.init_device()
|
||||
@ -138,26 +109,18 @@ def test_set_wipe_code_mismatch(client):
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
# Let's set a new wipe code.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time, but different.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(WIPE_CODE6)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# The operation should fail, because the wipe codes are different.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.WipeCodeMismatch
|
||||
with client:
|
||||
client.use_pin_sequence([WIPE_CODE4, WIPE_CODE6])
|
||||
client.set_expected_responses(
|
||||
[
|
||||
messages.ButtonRequest(),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeFirst),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeSecond),
|
||||
messages.Failure(code=messages.FailureType.WipeCodeMismatch),
|
||||
]
|
||||
)
|
||||
with pytest.raises(exceptions.TrezorFailure):
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Check that there is no wipe code protection.
|
||||
client.init_device()
|
||||
@ -170,26 +133,18 @@ def test_set_wipe_code_to_pin(client):
|
||||
assert client.features.wipe_code_protection is None
|
||||
|
||||
# Let's try setting the wipe code to the curent PIN value.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# The operation should fail, because the wipe code must be different from the PIN.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.ProcessError
|
||||
with client:
|
||||
client.use_pin_sequence([PIN4, PIN4])
|
||||
client.set_expected_responses(
|
||||
[
|
||||
messages.ButtonRequest(),
|
||||
messages.PinMatrixRequest(type=PinType.Current),
|
||||
messages.PinMatrixRequest(type=PinType.WipeCodeFirst),
|
||||
messages.Failure(code=messages.FailureType.ProcessError),
|
||||
]
|
||||
)
|
||||
with pytest.raises(exceptions.TrezorFailure):
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Check that there is no wipe code protection.
|
||||
client.init_device()
|
||||
@ -201,26 +156,18 @@ def test_set_pin_to_wipe_code(client):
|
||||
_set_wipe_code(client, WIPE_CODE4)
|
||||
|
||||
# Try to set the PIN to the current wipe code value.
|
||||
ret = client.call_raw(messages.ChangePin())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set PIN.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send the new PIN for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new PIN for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# The operation should fail, because the PIN must be different from the wipe code.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.ProcessError
|
||||
with client:
|
||||
client.use_pin_sequence([WIPE_CODE4, WIPE_CODE4])
|
||||
client.set_expected_responses(
|
||||
[
|
||||
messages.ButtonRequest(),
|
||||
messages.PinMatrixRequest(type=PinType.NewFirst),
|
||||
messages.PinMatrixRequest(type=PinType.NewSecond),
|
||||
messages.Failure(code=messages.FailureType.ProcessError),
|
||||
]
|
||||
)
|
||||
with pytest.raises(exceptions.TrezorFailure):
|
||||
device.change_pin(client)
|
||||
|
||||
# Check that there is no PIN protection.
|
||||
client.init_device()
|
||||
|
@ -219,42 +219,3 @@ def test_set_pin_to_wipe_code(client):
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_pin(client.debug, WIPE_CODE4))
|
||||
device.change_pin(client)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_wipe_code_activate(client):
|
||||
import time
|
||||
|
||||
device_id = client.features.device_id
|
||||
|
||||
# Set wipe code.
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_wipe_code(client.debug, PIN4, WIPE_CODE4))
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Try to change the PIN.
|
||||
ret = client.call_raw(messages.ChangePin(remove=False))
|
||||
|
||||
# Confirm change PIN.
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Enter the wipe code instead of the current PIN
|
||||
assert ret == messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||
client.debug.input(WIPE_CODE4)
|
||||
client._raw_write(messages.ButtonAck())
|
||||
|
||||
# Allow the device to display wipe code popup and restart.
|
||||
time.sleep(7)
|
||||
|
||||
# Check that the device has been wiped.
|
||||
client.init_device()
|
||||
assert client.features.initialized is False
|
||||
assert client.features.pin_protection is False
|
||||
assert client.features.wipe_code_protection is False
|
||||
assert client.features.device_id != device_id
|
||||
|
@ -52,7 +52,7 @@ class TestDeviceLoad:
|
||||
passphrase_protection=True,
|
||||
label="test",
|
||||
)
|
||||
client.set_passphrase("passphrase")
|
||||
client.use_passphrase("passphrase")
|
||||
state = client.debug.state()
|
||||
assert state.mnemonic_secret == MNEMONIC12.encode()
|
||||
|
||||
@ -114,7 +114,7 @@ class TestDeviceLoad:
|
||||
language="en-US",
|
||||
skip_checksum=True,
|
||||
)
|
||||
client.set_passphrase(passphrase_nfkd)
|
||||
client.use_passphrase(passphrase_nfkd)
|
||||
address_nfkd = btc.get_address(client, "Bitcoin", [])
|
||||
|
||||
device.wipe(client)
|
||||
@ -127,7 +127,7 @@ class TestDeviceLoad:
|
||||
language="en-US",
|
||||
skip_checksum=True,
|
||||
)
|
||||
client.set_passphrase(passphrase_nfc)
|
||||
client.use_passphrase(passphrase_nfc)
|
||||
address_nfc = btc.get_address(client, "Bitcoin", [])
|
||||
|
||||
device.wipe(client)
|
||||
@ -140,7 +140,7 @@ class TestDeviceLoad:
|
||||
language="en-US",
|
||||
skip_checksum=True,
|
||||
)
|
||||
client.set_passphrase(passphrase_nfkc)
|
||||
client.use_passphrase(passphrase_nfkc)
|
||||
address_nfkc = btc.get_address(client, "Bitcoin", [])
|
||||
|
||||
device.wipe(client)
|
||||
@ -153,7 +153,7 @@ class TestDeviceLoad:
|
||||
language="en-US",
|
||||
skip_checksum=True,
|
||||
)
|
||||
client.set_passphrase(passphrase_nfd)
|
||||
client.use_passphrase(passphrase_nfd)
|
||||
address_nfd = btc.get_address(client, "Bitcoin", [])
|
||||
|
||||
assert address_nfkd == address_nfc
|
||||
|
@ -30,12 +30,12 @@ def test_128bit_passphrase(client):
|
||||
xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV
|
||||
"""
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
address = btc.get_address(client, "Bitcoin", [])
|
||||
assert address == "1CX5rv2vbSV8YFAZEAdMwRVqbxxswPnSPw"
|
||||
client.state = None
|
||||
client.clear_session()
|
||||
client.set_passphrase("ROZERT")
|
||||
client.use_passphrase("ROZERT")
|
||||
address_compare = btc.get_address(client, "Bitcoin", [])
|
||||
assert address != address_compare
|
||||
|
||||
@ -49,11 +49,11 @@ def test_256bit_passphrase(client):
|
||||
xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c
|
||||
"""
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
address = btc.get_address(client, "Bitcoin", [])
|
||||
assert address == "18oNx6UczHWASBQXc5XQqdSdAAZyhUwdQU"
|
||||
client.state = None
|
||||
client.clear_session()
|
||||
client.set_passphrase("ROZERT")
|
||||
client.use_passphrase("ROZERT")
|
||||
address_compare = btc.get_address(client, "Bitcoin", [])
|
||||
assert address != address_compare
|
||||
|
@ -30,7 +30,7 @@ def test_3of6_passphrase(client):
|
||||
xprv9s21ZrQH143K2pMWi8jrTawHaj16uKk4CSbvo4Zt61tcrmuUDMx2o1Byzcr3saXNGNvHP8zZgXVdJHsXVdzYFPavxvCyaGyGr1WkAYG83ce
|
||||
"""
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
address = btc.get_address(client, "Bitcoin", [])
|
||||
assert address == "18oZEMRWurCZW1FeK8sWYyXuWx2bFqEKyX"
|
||||
|
||||
@ -50,6 +50,6 @@ def test_2of5_passphrase(client):
|
||||
xprv9s21ZrQH143K2o6EXEHpVy8TCYoMmkBnDCCESLdR2ieKwmcNG48ck2XJQY4waS7RUQcXqR9N7HnQbUVEDMWYyREdF1idQqxFHuCfK7fqFni
|
||||
"""
|
||||
assert client.features.passphrase_protection is True
|
||||
client.set_passphrase("TREZOR")
|
||||
client.use_passphrase("TREZOR")
|
||||
address = btc.get_address(client, "Bitcoin", [])
|
||||
assert address == "19Fjs9AvT13Y2Nx8GtoVfADmFWnccsPinQ"
|
||||
|
@ -41,42 +41,28 @@ class TestProtectCall:
|
||||
def test_pin(self, client):
|
||||
with client:
|
||||
assert client.debug.read_pin()[0] == "1234"
|
||||
client.setup_debuglink(button=True, pin_correct=True)
|
||||
client.set_expected_responses([proto.PinMatrixRequest(), proto.Address()])
|
||||
self._some_protected_call(client)
|
||||
|
||||
@pytest.mark.setup_client(pin="1234")
|
||||
def test_incorrect_pin(self, client):
|
||||
client.setup_debuglink(button=True, pin_correct=False)
|
||||
with pytest.raises(PinException):
|
||||
self._some_protected_call(client)
|
||||
|
||||
@pytest.mark.setup_client(pin="1234")
|
||||
def test_cancelled_pin(self, client):
|
||||
client.setup_debuglink(button=True, pin_correct=False) # PIN cancel
|
||||
with pytest.raises(PinException):
|
||||
client.use_pin_sequence(["5678"])
|
||||
self._some_protected_call(client)
|
||||
|
||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
||||
def test_exponential_backoff_with_reboot(self, client):
|
||||
client.setup_debuglink(button=True, pin_correct=False)
|
||||
|
||||
def test_backoff(attempts, start):
|
||||
if attempts <= 1:
|
||||
expected = 0
|
||||
else:
|
||||
expected = (2 ** (attempts - 1)) - 1
|
||||
got = round(time.time() - start, 2)
|
||||
|
||||
msg = "Pin delay expected to be at least %s seconds, got %s" % (
|
||||
expected,
|
||||
got,
|
||||
)
|
||||
print(msg)
|
||||
assert got >= expected
|
||||
|
||||
for attempt in range(1, 4):
|
||||
start = time.time()
|
||||
with pytest.raises(PinException):
|
||||
with client, pytest.raises(PinException):
|
||||
client.use_pin_sequence(["5678"])
|
||||
self._some_protected_call(client)
|
||||
test_backoff(attempt, start)
|
||||
|
@ -125,7 +125,7 @@ class TestProtectionLevels:
|
||||
|
||||
@pytest.mark.setup_client(uninitialized=True)
|
||||
def test_recovery_device(self, client):
|
||||
client.set_mnemonic(MNEMONIC12)
|
||||
client.use_mnemonic(MNEMONIC12)
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[proto.ButtonRequest()]
|
||||
|
108
tests/persistence_tests/test_wipe_code.py
Normal file
108
tests/persistence_tests/test_wipe_code.py
Normal file
@ -0,0 +1,108 @@
|
||||
from trezorlib import debuglink, device, messages
|
||||
|
||||
from ..common import MNEMONIC12
|
||||
from ..emulators import EmulatorWrapper
|
||||
from ..upgrade_tests import core_only, legacy_only
|
||||
|
||||
PIN = "1234"
|
||||
WIPE_CODE = "9876"
|
||||
|
||||
|
||||
def setup_device_legacy(client, pin, wipe_code):
|
||||
device.wipe(client)
|
||||
debuglink.load_device(
|
||||
client, MNEMONIC12, pin, passphrase_protection=False, label="WIPECODE"
|
||||
)
|
||||
|
||||
with client:
|
||||
client.use_pin_sequence([PIN, WIPE_CODE, WIPE_CODE])
|
||||
device.change_wipe_code(client)
|
||||
|
||||
|
||||
def setup_device_core(client, pin, wipe_code):
|
||||
device.wipe(client)
|
||||
debuglink.load_device(
|
||||
client, MNEMONIC12, pin, passphrase_protection=False, label="WIPECODE"
|
||||
)
|
||||
|
||||
def input_flow():
|
||||
yield # do you want to set/change the wipe_code?
|
||||
client.debug.press_yes()
|
||||
if pin is not None:
|
||||
yield # enter current pin
|
||||
client.debug.input(pin)
|
||||
yield # enter new wipe code
|
||||
client.debug.input(wipe_code)
|
||||
yield # enter new wipe code again
|
||||
client.debug.input(wipe_code)
|
||||
yield # success
|
||||
client.debug.press_yes()
|
||||
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(input_flow)
|
||||
device.change_wipe_code(client)
|
||||
|
||||
|
||||
@core_only
|
||||
def test_wipe_code_activate_core():
|
||||
with EmulatorWrapper("core") as emu:
|
||||
# set up device
|
||||
setup_device_core(emu.client, PIN, WIPE_CODE)
|
||||
|
||||
emu.client.init_device()
|
||||
device_id = emu.client.features.device_id
|
||||
|
||||
# Initiate Change pin process
|
||||
ret = emu.client.call_raw(messages.ChangePin(remove=False))
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
emu.client.debug.press_yes()
|
||||
ret = emu.client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Enter the wipe code instead of the current PIN
|
||||
assert ret == messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||
emu.client._raw_write(messages.ButtonAck())
|
||||
emu.client.debug.input(WIPE_CODE)
|
||||
|
||||
# wait 30 seconds for emulator to shut down
|
||||
# this will raise a TimeoutError if the emulator doesn't die.
|
||||
emu.wait(30)
|
||||
|
||||
emu.start()
|
||||
assert emu.client.features.initialized is False
|
||||
assert emu.client.features.pin_protection is False
|
||||
assert emu.client.features.wipe_code_protection is False
|
||||
assert emu.client.features.device_id != device_id
|
||||
|
||||
|
||||
@legacy_only
|
||||
def test_wipe_code_activate_legacy():
|
||||
with EmulatorWrapper("legacy") as emu:
|
||||
# set up device
|
||||
setup_device_legacy(emu.client, PIN, WIPE_CODE)
|
||||
|
||||
emu.client.init_device()
|
||||
device_id = emu.client.features.device_id
|
||||
|
||||
# Initiate Change pin process
|
||||
ret = emu.client.call_raw(messages.ChangePin(remove=False))
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
emu.client.debug.press_yes()
|
||||
ret = emu.client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Enter the wipe code instead of the current PIN
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = emu.client.debug.encode_pin(WIPE_CODE)
|
||||
emu.client._raw_write(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# wait 30 seconds for emulator to shut down
|
||||
# this will raise a TimeoutError if the emulator doesn't die.
|
||||
emu.wait(30)
|
||||
|
||||
emu.start()
|
||||
assert emu.client.features.initialized is False
|
||||
assert emu.client.features.pin_protection is False
|
||||
assert emu.client.features.wipe_code_protection is False
|
||||
assert emu.client.features.device_id != device_id
|
@ -111,4 +111,4 @@ def read_fixtures():
|
||||
|
||||
|
||||
def write_fixtures():
|
||||
HASH_FILE.write_text(json.dumps(HASHES, indent="", sort_keys=True))
|
||||
HASH_FILE.write_text(json.dumps(HASHES, indent="", sort_keys=True) + "\n")
|
||||
|
@ -47,7 +47,6 @@
|
||||
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "82c0d1acbf5ff344189761f808d3cf0e632726341231c20b2c0925ab5549b6af",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "1642d2d15920a3bb2c666b39beca9943ba39adb59289ebc40b97d7088a4d7abf",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "15289574ceb002b5161305b0595dcd20e437d1dd4e7561332e1aba4c1615e9ea",
|
||||
"test_msg_change_wipe_code_t2.py::test_wipe_code_activate": "348ff6811029253d7d520b37a2d3ff219516a7401c8b65ab088c7a7d39bd8b2b",
|
||||
"test_msg_changepin_t2.py::test_change_failed": "370c59da62a84aaefa242562c36a6facac89c7f819e37d1ae8cbe2c44a2de256",
|
||||
"test_msg_changepin_t2.py::test_change_pin": "c42fca9bf8f3b4c330516d90231ae0cfa7419d83370be9cfcf6a81cca3f3b06c",
|
||||
"test_msg_changepin_t2.py::test_remove_pin": "d049eaa6cd11e88b7af193b080cf868b62271266ad6f2973bfd82944b523741d",
|
||||
@ -389,4 +388,4 @@
|
||||
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe",
|
||||
"test_zerosig.py-test_one_zero_signature": "401aeaf7b2f565e2064a3c1a57a8ee3afe1e9bf251fba0874390685e7e0f178f",
|
||||
"test_zerosig.py-test_two_zero_signature": "7a01a057fb5dd3e6e38e7986875c5d07f0700bd80b519660e0b42973a9afd664"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user