From 253d27d6120fbc9bff8402db1c5fb0fc8d9abc75 Mon Sep 17 00:00:00 2001 From: Liam Murphy Date: Mon, 1 Aug 2022 17:13:14 +1000 Subject: [PATCH] Add tests to demonstrate broken user presence timeouts (#520) * Add tests to demonstrate broken user presence timeouts * fix up some formatting * apply auto-formatting --- tools/vendor_hid_test.py | 90 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/tools/vendor_hid_test.py b/tools/vendor_hid_test.py index 616a4e6..849bfa8 100644 --- a/tools/vendor_hid_test.py +++ b/tools/vendor_hid_test.py @@ -1,7 +1,12 @@ """These tests verify the functionality of the VendorHID interface.""" -from typing import Dict, Iterable +from fido2 import ctap +from fido2.hid import CtapHidDevice +from fido2.hid.base import CtapHidConnection +from fido2.client import Fido2Client, UserInteraction, ClientError +from fido2.server import Fido2Server import hid import time +from typing import Dict, Iterable import unittest _OPENSK_VID = 0x1915 @@ -12,6 +17,7 @@ _PACKETS = 4 _PACKET_SIZE = 64 _SEND_DATA_SIZE = _PACKET_SIZE + 1 _BROADCAST_CID = bytes([0xFF, 0xFF, 0xFF, 0xFF]) +_TEST_USER = {'id': b'user_id', 'name': 'Foo User'} def sleep(): @@ -265,5 +271,87 @@ class HidInterfaces(unittest.TestCase): self.fido_hid) +def get_fido_device() -> CtapHidDevice: + for d in CtapHidDevice.list_devices(): + if d.descriptor.vid == _OPENSK_VID and d.descriptor.pid == _OPENSK_PID: + return d + raise Exception('Unable to find Fido device') + + +class CliInteraction(UserInteraction): + """Sends cancel messages while prompting user.""" + + def __init__(self, cid=None, connection: CtapHidConnection = None): + super(CliInteraction).__init__() + self.cid = cid + self.connection = connection + + def prompt_up(self) -> None: + print('\n Don\'t touch your authenticator device now...\n') + # Send cancel messages to the specified device. + if self.connection and self.cid: + cancel_packet = ( + self.cid.to_bytes(4, byteorder='big') + b'\x91' + + b''.join([b'\x00'] * 59)) + assert len(cancel_packet) == _PACKET_SIZE, ( + f'Expected packet to be {_PACKET_SIZE} ' + 'but was {len(cancel_packet)}') + + self.connection.write_packet(cancel_packet) + + +class CancelTests(unittest.TestCase): + """Tests for the canceling while waiting for user touch.""" + + @classmethod + def setUpClass(cls): + cls.fido = get_fido_device() + + def setUp(self) -> None: + super().setUp() + server = Fido2Server({ + 'id': 'example.com', + 'name': 'Example RP' + }, + attestation='direct') + self.create_options, _ = server.register_begin( + _TEST_USER, + user_verification='foo', + authenticator_attachment='cross-platform') + + def test_cancel_works(self): + cid = self.fido._channel_id # pylint: disable=protected-access + connection = self.fido._connection # pylint: disable=protected-access + client = Fido2Client( + self.fido, + 'https://example.com', + user_interaction=CliInteraction(cid, connection)) + + with self.assertRaises(ClientError) as context: + client.make_credential(self.create_options['publicKey']) + + self.assertEqual(context.exception.code, ClientError.ERR.TIMEOUT) + self.assertEqual(context.exception.cause.code, + ctap.CtapError.ERR.KEEPALIVE_CANCEL) + + def test_cancel_ignores_wrong_cid(self): + cid = self.fido._channel_id # pylint: disable=protected-access + connection = self.fido._connection # pylint: disable=protected-access + client = Fido2Client( + self.fido, + 'https://example.com', + user_interaction=CliInteraction(cid + 1, connection)) + # TODO(https://github.com/google/OpenSK/issues/519): This should be an + # exception + client.make_credential(self.create_options['publicKey']) + + def test_timeout(self): + client = Fido2Client( + self.fido, 'https://example.com', user_interaction=CliInteraction()) + # TODO(https://github.com/google/OpenSK/issues/519): This should be an + # exception + client.make_credential(self.create_options['publicKey']) + + if __name__ == '__main__': unittest.main()