1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 15:38:11 +00:00

Merge pull request #840 from trezor/matejcik/sys-exit

wipe code activation tests
This commit is contained in:
matejcik 2020-02-13 15:11:33 +01:00 committed by GitHub
commit cdb7a7eb68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 322 additions and 254 deletions

View File

@ -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); }

View File

@ -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:

View File

@ -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)

View File

@ -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) {

View File

@ -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")

View File

@ -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):

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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()]

View 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

View File

@ -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")

View File

@ -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",