diff --git a/tests/test.py b/tests/test.py index 5882154f30..71184e1aa9 100755 --- a/tests/test.py +++ b/tests/test.py @@ -6,7 +6,7 @@ TODO: * ApplySettings workflow, zistit cez Features ci sa zmeny aplikovali * WipeDevice workflow, zistit cez Features ci to prebehlo * LoadDevice workflow, zistit cez Features ci to prebehlo -* ResetDevice workflow +x ResetDevice workflow - zrejme v sucinnosti s inymi testami * ButtonRequest/ButtonAck workflow (vyvolat napr. pomocou GetEntropy, myslim ze ten GetEntropy vyzaduje PIN, ale ja by som to dal na button) @@ -33,4 +33,5 @@ TODO: x Zero signature test * test bip39, utf, passphrase +x Clear session on ChangePin ''' diff --git a/tests/test_debuglink.py b/tests/test_debuglink.py index 36e0484d2a..3ad80448ac 100644 --- a/tests/test_debuglink.py +++ b/tests/test_debuglink.py @@ -23,5 +23,20 @@ class TestDebugLink(common.TrezorTest): node = self.client.debug.read_node() self.assertIsNotNone(node) + def test_pin(self): + self.setup_mnemonic_pin_passphrase() + + # Manually trigger PinMatrixRequest + resp = self.client.call_raw(proto.Ping(message='test', pin_protection=True)) + self.assertIsInstance(resp, proto.PinMatrixRequest) + + pin = self.client.debug.read_pin() + self.assertEqual(pin[0], '1234') + self.assertNotEqual(pin[1], '') + + pin_encoded = self.client.debug.read_pin_encoded() + resp = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded)) + self.assertIsInstance(resp, proto.Success) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_device_reset.py b/tests/test_device_reset.py index d0bfe29e26..9ddc8665d2 100644 --- a/tests/test_device_reset.py +++ b/tests/test_device_reset.py @@ -1,12 +1,175 @@ import unittest import common +import hashlib -from trezorlib import messages_pb2 as messages +from trezorlib import messages_pb2 as proto +from mnemonic import Mnemonic + +def generate_entropy(strength, internal_entropy, external_entropy): + ''' + strength - length of produced seed. One of 128, 192, 256 + random - binary stream of random data from external HRNG + ''' + if strength not in (128, 192, 256): + raise Exception("Invalid strength") + + if not internal_entropy: + raise Exception("Internal entropy is not provided") + + if len(internal_entropy) < 32: + raise Exception("Internal entropy too short") + + if not external_entropy: + raise Exception("External entropy is not provided") + + if len(external_entropy) < 32: + raise Exception("External entropy too short") + + entropy = hashlib.sha256(internal_entropy + external_entropy).digest() + entropy_stripped = entropy[:strength / 8] + + if len(entropy_stripped) * 8 != strength: + raise Exception("Entropy length mismatch") + + return entropy_stripped class TestDeviceReset(common.TrezorTest): def test_reset_device(self): - pass + # No PIN, no passphrase + external_entropy = 'zlutoucky kun upel divoke ody' * 2 + strength = 128 + ret = self.client.call_raw(proto.ResetDevice(display_random=False, + strength=strength, + passphrase_protection=False, + pin_protection=False, + language='english', + label='test')) + + self.assertIsInstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + self.assertIsInstance(ret, proto.EntropyRequest) + ret = self.client.call_raw(proto.EntropyAck(entropy=external_entropy)) + + # Read internal entropy and generate mnemonic locally + internal_entropy = self.client.debug.read_entropy() + entropy = generate_entropy(strength, internal_entropy, external_entropy) + expected_mnemonic = Mnemonic('english').to_mnemonic(entropy) + + mnemonic = [] + for _ in range(12): + self.assertIsInstance(ret, proto.ButtonRequest) + mnemonic.append(self.client.debug.read_word()[0]) + self.client.debug.press_yes() + self.client.call_raw(proto.ButtonAck()) + + mnemonic = ' '.join(mnemonic) + + # Compare that device generated proper mnemonic for given entropies + self.assertEqual(mnemonic, expected_mnemonic) + + mnemonic = [] + for _ in range(12): + self.assertIsInstance(ret, proto.ButtonRequest) + mnemonic.append(self.client.debug.read_word()[0]) + self.client.debug.press_yes() + resp = self.client.call_raw(proto.ButtonAck()) + + self.assertIsInstance(resp, proto.Success) + + mnemonic = ' '.join(mnemonic) + + # Compare that second pass printed out the same mnemonic once again + self.assertEqual(mnemonic, expected_mnemonic) + + # Check if device is properly initialized + resp = self.client.call_raw(proto.Initialize()) + self.assertFalse(resp.pin_protection) + self.assertFalse(resp.passphrase_protection) + + # Do passphrase-protected action, PassphraseRequest should NOT be raised + resp = self.client.call_raw(proto.Ping(passphrase_protection=True)) + self.assertIsInstance(resp, proto.Success) + + # Do PIN-protected action, PinRequest should NOT be raised + resp = self.client.call_raw(proto.Ping(pin_protection=True)) + self.assertIsInstance(resp, proto.Success) + + def test_reset_device_pin(self): + external_entropy = 'zlutoucky kun upel divoke ody' * 2 + strength = 128 + + ret = self.client.call_raw(proto.ResetDevice(display_random=True, + strength=strength, + passphrase_protection=True, + pin_protection=True, + language='english', + label='test')) + + self.assertIsInstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + self.assertIsInstance(ret, proto.EntropyRequest) + ret = self.client.call_raw(proto.EntropyAck(entropy=external_entropy)) + + self.assertIsInstance(ret, proto.PinMatrixRequest) + + # Enter PIN for first time + pin_encoded = self.client.debug.encode_pin('654') + ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded)) + self.assertIsInstance(ret, proto.PinMatrixRequest) + + # Enter PIN for second time + pin_encoded = self.client.debug.encode_pin('654') + ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded)) + + # Read internal entropy and generate mnemonic locally + internal_entropy = self.client.debug.read_entropy() + entropy = generate_entropy(strength, internal_entropy, external_entropy) + expected_mnemonic = Mnemonic('english').to_mnemonic(entropy) + + mnemonic = [] + for _ in range(12): + self.assertIsInstance(ret, proto.ButtonRequest) + mnemonic.append(self.client.debug.read_word()[0]) + self.client.debug.press_yes() + self.client.call_raw(proto.ButtonAck()) + + mnemonic = ' '.join(mnemonic) + + # Compare that device generated proper mnemonic for given entropies + self.assertEqual(mnemonic, expected_mnemonic) + + mnemonic = [] + for _ in range(12): + self.assertIsInstance(ret, proto.ButtonRequest) + mnemonic.append(self.client.debug.read_word()[0]) + self.client.debug.press_yes() + resp = self.client.call_raw(proto.ButtonAck()) + + self.assertIsInstance(resp, proto.Success) + + mnemonic = ' '.join(mnemonic) + + # Compare that second pass printed out the same mnemonic once again + self.assertEqual(mnemonic, expected_mnemonic) + + # Check if device is properly initialized + resp = self.client.call_raw(proto.Initialize()) + self.assertTrue(resp.pin_protection) + self.assertTrue(resp.passphrase_protection) + + # Do passphrase-protected action, PassphraseRequest should be raised + resp = self.client.call_raw(proto.Ping(passphrase_protection=True)) + self.assertIsInstance(resp, proto.PassphraseRequest) + + # Do PIN-protected action, PinRequest should be raised + resp = self.client.call_raw(proto.Ping(pin_protection=True)) + self.assertIsInstance(resp, proto.PinMatrixRequest) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_protect_call.py b/tests/test_protect_call.py index 672ff3e9b8..0a946f7220 100644 --- a/tests/test_protect_call.py +++ b/tests/test_protect_call.py @@ -40,7 +40,7 @@ class TestProtectCall(common.TrezorTest): # Scenario 4 - Received what expected self.client.set_expected_responses([proto.ButtonRequest(), proto.PinMatrixRequest(), - # proto.PassphraseRequest(), # passhrase is already in session + proto.PassphraseRequest(), proto.Success(message='random data')]) self._some_protected_call(True, True, True) diff --git a/trezorlib/debuglink.py b/trezorlib/debuglink.py index c6d9897813..a7e16b404c 100644 --- a/trezorlib/debuglink.py +++ b/trezorlib/debuglink.py @@ -26,7 +26,13 @@ class DebugLink(object): return (obj.pin, obj.matrix) def read_pin_encoded(self): - pin, matrix = self.read_pin() + pin, _ = self.read_pin() + pin_encoded = self.encode_pin(pin) + self.pin_func(pin_encoded) + return pin_encoded + + def encode_pin(self, pin): + _, matrix = self.read_pin() # Now we have real PIN and PIN matrix. # We have to encode that into encoded pin, @@ -35,10 +41,8 @@ class DebugLink(object): pin_encoded = ''.join([ str(matrix.index(p) + 1) for p in pin]) print "Encoded PIN:", pin_encoded - self.pin_func(pin_encoded) - return pin_encoded - + def read_layout(self): self.transport.write(proto.DebugLinkGetState()) obj = self.transport.read_blocking() @@ -54,6 +58,16 @@ class DebugLink(object): obj = self.transport.read_blocking() return obj.node + def read_word(self): + self.transport.write(proto.DebugLinkGetState()) + obj = self.transport.read_blocking() + return (obj.word, obj.word_pos) + + def read_entropy(self): + self.transport.write(proto.DebugLinkGetState()) + obj = self.transport.read_blocking() + return obj.entropy + def read_passphrase_protection(self): self.transport.write(proto.DebugLinkGetState()) obj = self.transport.read_blocking()