Remove/fix broken tests.

This commit is contained in:
Dain Nilsson 2020-09-11 17:47:18 +02:00
parent cfebc3b5e5
commit f416734853
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
12 changed files with 25 additions and 959 deletions

View File

@ -3,12 +3,10 @@ repos:
rev: 3.7.9
hooks:
- id: flake8
exclude: '^(fido2|test)/_pyu2f/.*'
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
exclude: '^(fido2|test)/_pyu2f/.*'
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:

View File

@ -32,7 +32,7 @@ See the _COPYING_ file for the full license text.
This project contains source code from pyu2f (https://github.com/google/pyu2f)
which is licensed under the Apache License, version 2.0.
These files are located in `fido2/_pyu2f/` and `test/_pyu2f/`.
These files are located in `fido2/hid/`.
See http://www.apache.org/licenses/LICENSE-2.0,
or the _COPYING.APLv2_ file for the full license text.

View File

@ -1,122 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for _pyu2f.hid.freebsd."""
import base64
import os
import sys
import six
from six.moves import builtins
from mock import patch
if sys.platform.startswith('freebsd'):
from fido2._pyu2f import freebsd
if sys.version_info[:2] < (2, 7):
import unittest2 as unittest # pylint: disable=g-import-not-at-top
else:
import unittest # pylint: disable=g-import-not-at-top
# These are base64 encoded report descriptors of a Yubico token
# and a Logitech keyboard.
YUBICO_RD = 'BtDxCQGhAQkgFQAm/wB1CJVAgQIJIRUAJv8AdQiVQJECwA=='
KEYBOARD_RD = (
'BQEJAqEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVAXUDgQEFAQkwCTEJOBWBJX91CJUDgQbAwA==')
class FakeUhidFreeBSDModule():
def enumerate(self):
return [{'device': 'uhid0',
'path': '/dev/uhid0',
'vendor_id': 0x046d,
'product_id': 0xc31c,
'product_desc': 'Logitech Keyboard'},
{'device': 'uhid1',
'path': '/dev/uhid1',
'vendor_id': 0x1050,
'product_id': 0x0407,
'product_desc': 'Yubico U2F'}]
def get_report_data(self, fd, unused_report_id):
if fd:
return base64.b64decode(YUBICO_RD)
else:
return base64.b64decode(KEYBOARD_RD)
class FakeOsModule():
O_RDONLY = os.O_RDONLY
O_RDWR = os.O_RDWR
path = os.path
data_written = None
data_to_return = None
def open(self, path, unused_opts): # pylint: disable=invalid-name
if path.find('uhid1') >= 0:
return 1 # fd == 1 => magic number to return Yubikey RD below
else:
return 0
def write(self, unused_dev, data): # pylint: disable=invalid-name
self.data_written = data
def read(self, unused_dev, unused_length): # pylint: disable=invalid-name
return self.data_to_return
def close(self, unused_dev): # pylint: disable=invalid-name
pass
@unittest.skipIf(not sys.platform.startswith('freebsd'),
'FreeBSD specific test case')
class FreeBSDTest(unittest.TestCase):
@patch('fido2._pyu2f.freebsd.os', FakeOsModule())
@patch('fido2._pyu2f.freebsd.uhid_freebsd', FakeUhidFreeBSDModule())
def testCallEnumerate(self):
devs = list(freebsd.FreeBSDHidDevice.Enumerate())
devs = sorted(devs, key=lambda k: k['vendor_id'])
self.assertEqual(len(devs), 2)
self.assertEqual(devs[0]['vendor_id'], 0x046d)
self.assertEqual(devs[0]['product_id'], 0xc31c)
self.assertEqual(devs[1]['vendor_id'], 0x1050)
self.assertEqual(devs[1]['product_id'], 0x0407)
self.assertEqual(devs[1]['usage_page'], 0xf1d0)
self.assertEqual(devs[1]['usage'], 1)
@patch('fido2._pyu2f.freebsd.uhid_freebsd', FakeUhidFreeBSDModule())
def testCallOpen(self):
fake_os = FakeOsModule()
with patch('fido2._pyu2f.linux.os', fake_os):
with patch('fido2._pyu2f.freebsd.os', fake_os):
dev = freebsd.FreeBSDHidDevice('/dev/uhid1')
self.assertEqual(dev.GetInReportDataLength(), 64)
self.assertEqual(dev.GetOutReportDataLength(), 64)
dev.Write(list(range(0, 64)))
# The HidDevice implementation prepends one zero-byte-packet
# (64 bytes) representing the report ID + padding
self.assertEqual(list(six.iterbytes(fake_os.data_written)),
[0]*64 + list(range(0, 64)))
fake_os.data_to_return = b'x' * 64
self.assertEqual(dev.Read(), [120] * 64) # chr(120) = 'x'
if __name__ == '__main__':
unittest.main()

View File

@ -1,183 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for _pyu2f.hidtransport."""
from __future__ import absolute_import
import six
import mock
from fido2._pyu2f import hidtransport
from . import util
import unittest
def MakeKeyboard(path, usage):
d = {}
d['vendor_id'] = 1133 # Logitech
d['product_id'] = 49948
d['path'] = path
d['usage'] = usage
d['usage_page'] = 1
return d
def MakeU2F(path):
d = {}
d['vendor_id'] = 4176
d['product_id'] = 1031
d['path'] = path
d['usage'] = 1
d['usage_page'] = 0xf1d0
return d
def RPad(collection, to_size):
while len(collection) < to_size:
collection.append(0)
return collection
class DiscoveryTest(unittest.TestCase):
def testHidUsageSelector(self):
u2f_device = {'usage_page': 0xf1d0, 'usage': 0x01}
other_device = {'usage_page': 0x06, 'usage': 0x01}
self.assertTrue(hidtransport.HidUsageSelector(u2f_device))
self.assertFalse(hidtransport.HidUsageSelector(other_device))
def testDiscoverLocalDevices(self):
def FakeHidDevice(path):
mock_hid_dev = mock.MagicMock()
mock_hid_dev.GetInReportDataLength.return_value = 64
mock_hid_dev.GetOutReportDataLength.return_value = 64
mock_hid_dev.path = path
return mock_hid_dev
with mock.patch.object(hidtransport, 'hid') as hid_mock:
# We mock out init so that it doesn't try to do the whole init
# handshake with the device, which isn't what we're trying to test
# here.
with mock.patch.object(hidtransport.UsbHidTransport, 'InternalInit') as _:
hid_mock.Enumerate.return_value = [
MakeKeyboard('path1', 6), MakeKeyboard('path2', 2),
MakeU2F('path3'), MakeU2F('path4')
]
mock_hid_dev = mock.MagicMock()
mock_hid_dev.GetInReportDataLength.return_value = 64
mock_hid_dev.GetOutReportDataLength.return_value = 64
hid_mock.Open.side_effect = FakeHidDevice
# Force the iterator into a list
devs = list(hidtransport.DiscoverLocalHIDU2FDevices())
self.assertEqual(hid_mock.Enumerate.call_count, 1)
self.assertEqual(hid_mock.Open.call_count, 2)
self.assertEqual(len(devs), 2)
self.assertEqual(devs[0].hid_device.path, 'path3')
self.assertEqual(devs[1].hid_device.path, 'path4')
class TransportTest(unittest.TestCase):
def testInit(self):
fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01]))
t = hidtransport.UsbHidTransport(fake_hid_dev)
self.assertEqual(t.cid, bytearray([0x00, 0x00, 0x00, 0x01]))
self.assertEqual(t.u2fhid_version, 0x01)
def testPing(self):
fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01]))
t = hidtransport.UsbHidTransport(fake_hid_dev)
reply = t.SendPing(b'1234')
self.assertEqual(reply, b'1234')
def testMsg(self):
fake_hid_dev = util.FakeHidDevice(
bytearray([0x00, 0x00, 0x00, 0x01]), bytearray([0x01, 0x90, 0x00]))
t = hidtransport.UsbHidTransport(fake_hid_dev)
reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00])
self.assertEqual(reply, bytearray([0x01, 0x90, 0x00]))
def testMsgBusy(self):
fake_hid_dev = util.FakeHidDevice(
bytearray([0x00, 0x00, 0x00, 0x01]), bytearray([0x01, 0x90, 0x00]))
t = hidtransport.UsbHidTransport(fake_hid_dev)
# Each call will retry twice: the first attempt will fail after 2 retreis,
# the second will succeed on the second retry.
fake_hid_dev.SetChannelBusyCount(3)
with mock.patch.object(hidtransport, 'time') as _:
six.assertRaisesRegex(self, OSError, '^Device Busy', t.SendMsgBytes,
[0x00, 0x01, 0x00, 0x00])
reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00])
self.assertEqual(reply, bytearray([0x01, 0x90, 0x00]))
def testFragmentedResponseMsg(self):
body = bytearray([x % 256 for x in range(0, 1000)])
fake_hid_dev = util.FakeHidDevice(bytearray([0x00, 0x00, 0x00, 0x01]), body)
t = hidtransport.UsbHidTransport(fake_hid_dev)
reply = t.SendMsgBytes([0x00, 0x01, 0x00, 0x00])
# Confirm we properly reassemble the message
self.assertEqual(reply, bytearray(x % 256 for x in range(0, 1000)))
def testFragmentedSendApdu(self):
body = bytearray(x % 256 for x in range(0, 1000))
fake_hid_dev = util.FakeHidDevice(
bytearray([0x00, 0x00, 0x00, 0x01]), [0x90, 0x00])
t = hidtransport.UsbHidTransport(fake_hid_dev)
reply = t.SendMsgBytes(body)
self.assertEqual(reply, bytearray([0x90, 0x00]))
# 1 init packet from creating transport, 18 packets to send
# the fragmented message
self.assertEqual(len(fake_hid_dev.received_packets), 18)
def testInitPacketShape(self):
packet = hidtransport.UsbHidTransport.InitPacket(
64, bytearray(b'\x00\x00\x00\x01'), 0x83, 2, bytearray(b'\x01\x02'))
self.assertEqual(packet.ToWireFormat(), RPad(
[0, 0, 0, 1, 0x83, 0, 2, 1, 2], 64))
copy = hidtransport.UsbHidTransport.InitPacket.FromWireFormat(
64, packet.ToWireFormat())
self.assertEqual(copy.cid, bytearray(b'\x00\x00\x00\x01'))
self.assertEqual(copy.cmd, 0x83)
self.assertEqual(copy.size, 2)
self.assertEqual(copy.payload, bytearray(b'\x01\x02'))
def testContPacketShape(self):
packet = hidtransport.UsbHidTransport.ContPacket(
64, bytearray(b'\x00\x00\x00\x01'), 5, bytearray(b'\x01\x02'))
self.assertEqual(packet.ToWireFormat(), RPad([0, 0, 0, 1, 5, 1, 2], 64))
copy = hidtransport.UsbHidTransport.ContPacket.FromWireFormat(
64, packet.ToWireFormat())
self.assertEqual(copy.cid, bytearray(b'\x00\x00\x00\x01'))
self.assertEqual(copy.seq, 5)
self.assertEqual(copy.payload, RPad(bytearray(b'\x01\x02'), 59))
if __name__ == '__main__':
unittest.main()

View File

@ -1,130 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for _pyu2f.hid.linux."""
import base64
import os
import sys
import mock
import six
from six.moves import builtins
from fido2._pyu2f import linux
try:
from pyfakefs import fake_filesystem # pylint: disable=g-import-not-at-top
except ImportError:
try:
from fakefs import fake_filesystem # pylint: disable=g-import-not-at-top
except ImportError:
if sys.platform.startswith('linux'):
raise
if sys.version_info[:2] < (2, 7):
import unittest2 as unittest # pylint: disable=g-import-not-at-top
else:
import unittest # pylint: disable=g-import-not-at-top
# These are base64 encoded report descriptors of a Yubico token
# and a Logitech keyboard.
YUBICO_RD = 'BtDxCQGhAQkgFQAm/wB1CJVAgQIJIRUAJv8AdQiVQJECwA=='
KEYBOARD_RD = (
'BQEJAqEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVAXUDgQEFAQkwCTEJOBWBJX91CJUDgQbAwA==')
def AddDevice(fs, dev_name, product_name,
vendor_id, product_id, report_descriptor_b64):
uevent = fs.create_file('/sys/class/hidraw/%s/device/uevent' % dev_name)
rd = fs.create_file('/sys/class/hidraw/%s/device/report_descriptor' % dev_name)
report_descriptor = base64.b64decode(report_descriptor_b64)
rd.set_contents(report_descriptor)
buf = 'HID_NAME=%s\n' % product_name.encode('utf8')
buf += 'HID_ID=0001:%08X:%08X\n' % (vendor_id, product_id)
uevent.set_contents(buf)
class FakeDeviceOsModule(object):
O_RDWR = os.O_RDWR
path = os.path
data_written = None
data_to_return = None
def open(self, unused_path, unused_opts): # pylint: disable=invalid-name
return 0
def write(self, unused_dev, data): # pylint: disable=invalid-name
self.data_written = data
def read(self, unused_dev, unused_length): # pylint: disable=invalid-name
return self.data_to_return
@unittest.skipIf(not sys.platform.startswith('linux'),
'Linux specific test case')
class LinuxTest(unittest.TestCase):
def setUp(self):
self.fs = fake_filesystem.FakeFilesystem()
self.fs.create_dir('/sys/class/hidraw')
def tearDown(self):
self.fs.remove_object('/sys/class/hidraw')
def testCallEnumerate(self):
AddDevice(self.fs, 'hidraw1', 'Logitech USB Keyboard',
0x046d, 0xc31c, KEYBOARD_RD)
AddDevice(self.fs, 'hidraw2', 'Yubico U2F', 0x1050, 0x0407, YUBICO_RD)
with mock.patch.object(linux, 'os', fake_filesystem.FakeOsModule(self.fs)):
fake_open = fake_filesystem.FakeFileOpen(self.fs)
with mock.patch.object(builtins, 'open', fake_open):
devs = list(linux.LinuxHidDevice.Enumerate())
devs = sorted(devs, key=lambda k: k['vendor_id'])
self.assertEqual(len(devs), 2)
self.assertEqual(devs[0]['vendor_id'], 0x046d)
self.assertEqual(devs[0]['product_id'], 0x0c31c)
self.assertEqual(devs[1]['vendor_id'], 0x1050)
self.assertEqual(devs[1]['product_id'], 0x0407)
self.assertEqual(devs[1]['usage_page'], 0xf1d0)
self.assertEqual(devs[1]['usage'], 1)
def testCallOpen(self):
AddDevice(self.fs, 'hidraw1', 'Yubico U2F', 0x1050, 0x0407, YUBICO_RD)
fake_open = fake_filesystem.FakeFileOpen(self.fs)
# The open() builtin is used to read from the fake sysfs
with mock.patch.object(builtins, 'open', fake_open):
# The os.open function is used to interact with the low
# level device. We don't want to use fakefs for that.
fake_dev_os = FakeDeviceOsModule()
with mock.patch.object(linux, 'os', fake_dev_os):
dev = linux.LinuxHidDevice('/dev/hidraw1')
self.assertEqual(dev.GetInReportDataLength(), 64)
self.assertEqual(dev.GetOutReportDataLength(), 64)
dev.Write(list(range(0, 64)))
# The HidDevice implementation prepends a zero-byte representing the
# report ID
self.assertEqual(list(six.iterbytes(fake_dev_os.data_written)),
[0] + list(range(0, 64)))
fake_dev_os.data_to_return = b'x' * 64
self.assertEqual(dev.Read(), [120] * 64) # chr(120) = 'x'
if __name__ == '__main__':
unittest.main()

View File

@ -1,144 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for _pyu2f.hid.macos."""
import ctypes
import sys
import mock
from fido2._pyu2f import macos
if sys.version_info[:2] < (2, 7):
import unittest2 as unittest # pylint: disable=g-import-not-at-top
else:
import unittest # pylint: disable=g-import-not-at-top
def init_mock_iokit(mock_iokit):
# Device open should always return 0 (success)
mock_iokit.IOHIDDeviceOpen = mock.Mock(return_value=0)
mock_iokit.IOHIDDeviceSetReport = mock.Mock(return_value=0)
mock_iokit.IOHIDDeviceCreate = mock.Mock(return_value='handle')
def init_mock_cf(mock_cf):
mock_cf.CFGetTypeID = mock.Mock(return_value=123)
mock_cf.CFNumberGetTypeID = mock.Mock(return_value=123)
mock_cf.CFStringGetTypeID = mock.Mock(return_value=123)
def init_mock_get_int_property(mock_get_int_property):
mock_get_int_property.return_value = 64
@unittest.skipIf(not sys.platform.startswith('darwin'),
'MacOS specific test case')
class MacOsTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
macos.MacOsHidDevice.__del__ = lambda x: None
@mock.patch.object(macos.threading, 'Thread')
@mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT,
GetDeviceIntProperty=mock.DEFAULT)
def testInitHidDevice(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name
init_mock_iokit(iokit)
init_mock_cf(cf)
init_mock_get_int_property(GetDeviceIntProperty)
device = macos.MacOsHidDevice('1')
self.assertEqual(64, device.GetInReportDataLength())
self.assertEqual(64, device.GetOutReportDataLength())
@mock.patch.object(macos.threading, 'Thread')
@mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT,
GetDeviceIntProperty=mock.DEFAULT)
def testCallWriteSuccess(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name
init_mock_iokit(iokit)
init_mock_cf(cf)
init_mock_get_int_property(GetDeviceIntProperty)
device = macos.MacOsHidDevice('1')
# Write 64 bytes to device
data = bytearray(range(64))
# Write to device
device.Write(data)
# Validate that write calls into IOKit
set_report_call_args = iokit.IOHIDDeviceSetReport.call_args
self.assertIsNotNone(set_report_call_args)
set_report_call_pos_args = iokit.IOHIDDeviceSetReport.call_args[0]
self.assertEqual(len(set_report_call_pos_args), 5)
self.assertEqual(set_report_call_pos_args[0], 'handle')
self.assertEqual(set_report_call_pos_args[1], 1)
self.assertEqual(set_report_call_pos_args[2], 0)
self.assertEqual(set_report_call_pos_args[4], 64)
report_buffer = set_report_call_pos_args[3]
self.assertEqual(len(report_buffer), 64)
self.assertEqual(bytearray(report_buffer), data, 'Data sent to '
'IOHIDDeviceSetReport should match data sent to the '
'device')
@mock.patch.object(macos.threading, 'Thread')
@mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT,
GetDeviceIntProperty=mock.DEFAULT)
def testCallWriteFailure(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name
init_mock_iokit(iokit)
init_mock_cf(cf)
init_mock_get_int_property(GetDeviceIntProperty)
# Make set report call return failure (non-zero)
iokit.IOHIDDeviceSetReport.return_value = -1
device = macos.MacOsHidDevice('1')
# Write 64 bytes to device
data = bytearray(range(64))
# Write should throw an OsHidError exception
with self.assertRaises(OSError):
device.Write(data)
@mock.patch.object(macos.threading, 'Thread')
@mock.patch.multiple(macos, iokit=mock.DEFAULT, cf=mock.DEFAULT,
GetDeviceIntProperty=mock.DEFAULT)
def testCallReadSuccess(self, thread, iokit, cf, GetDeviceIntProperty): # pylint: disable=invalid-name
init_mock_iokit(iokit)
init_mock_cf(cf)
init_mock_get_int_property(GetDeviceIntProperty)
device = macos.MacOsHidDevice('1')
# Call callback for IN report
report = (ctypes.c_uint8 * 64)()
report[:] = range(64)[:]
q = device.read_queue
macos.HidReadCallback(q, None, None, None, 0, report, 64)
# Device read should return the callback data
read_result = device.Read()
self.assertEqual(read_result, list(range(64)), 'Read data should match data '
'passed into the callback')
if __name__ == '__main__':
unittest.main()

View File

@ -1,115 +0,0 @@
# Copyright 2019 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for OpenBSD hid interface"""
import ctypes
import os.path
import sys
import mock
if sys.platform.startswith("openbsd"):
from fido2._pyu2f import openbsd
if sys.version_info[:2] < (2, 7):
import unittest2 as unittest # pylint: disable=g-import-not-at-top
else:
import unittest # pylint: disable=g-import-not-at-top
class FakeOsModule:
data = None
dirs = None
path = os.path
O_RDONLY = 0
O_RDWR = 0
def open(self, path, opts):
return 0
def write(self, fd, data):
self.data = data
def read(self, fd, len_):
return self.data
def close(self, fd):
pass
def listdir(self, dir_):
return self.dirs
class FakeLibUsbHid:
hiddata = None
rdesc = None
hiditem_ret = 0
hiditem = None
def hid_get_report_desc(self, fd):
return self.rdesc
def hid_start_parse(self, rdesc, kindset, id_):
return self.hiddata
def hid_report_size(self, rdesc, endpoint, report_id):
return 64
def hid_get_item(self, hiddata, hiditem):
if self.hiditem is not None:
for (fld, _) in self.hiditem._fields_:
setattr(hiditem, fld, getattr(self.hiditem, fld, 0))
return self.hiditem_ret
def hid_dispose_report_desc(self, rdesc):
pass
@unittest.skipIf(not sys.platform.startswith("openbsd"), "OpenBSD specific test cases")
class OpenBSDTest(unittest.TestCase):
def testInvalidEnumerate(self):
fake_os = FakeOsModule()
fake_os.dirs = ["uhid20", "sd0a"]
with mock.patch("fido2._pyu2f.openbsd.os", fake_os), mock.patch(
"fido2._pyu2f.openbsd.fcntl.ioctl", side_effect=OSError
):
devs = list(openbsd.OpenBSDHidDevice.Enumerate())
self.assertEqual(len(devs), 0)
def testEnumerate(self):
fake_os = FakeOsModule()
def usb_get_device(fd, _, dev_info):
dev_info.udi_vendorNo = ctypes.c_uint16(0x1050)
dev_info.udi_vendor = b"Yubico"
dev_info.udi_productNo = ctypes.c_uint16(0x0407)
dev_info.udi_product = b"YubiKey OTP+FIDO+CCID"
fake_os.dirs = ["0"]
with mock.patch("fido2._pyu2f.openbsd.os", fake_os), mock.patch(
"fido2._pyu2f.openbsd.fcntl.ioctl", side_effect=usb_get_device
):
devs = list(openbsd.OpenBSDHidDevice.Enumerate())
self.assertEqual(len(devs), 1)
dev = devs[0]
self.assertEqual(dev["vendor_id"], 0x1050)
self.assertEqual(dev["product_id"], 0x0407)
self.assertEqual(dev["vendor_string"], "Yubico")
self.assertEqual(dev["product_string"], "YubiKey OTP+FIDO+CCID")
if __name__ == "__main__":
unittest.main()

View File

@ -1,65 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for _pyu2f.tests.lib.util."""
from __future__ import absolute_import
import sys
from . import util
if sys.version_info[:2] < (2, 7):
import unittest2 as unittest # pylint: disable=g-import-not-at-top
else:
import unittest # pylint: disable=g-import-not-at-top
class UtilTest(unittest.TestCase):
def testSimplePing(self):
dev = util.FakeHidDevice(cid_to_allocate=None)
dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3])
self.assertEqual(
dev.Read(), [0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3] + [0
for _ in range(54)])
def testErrorBusy(self):
dev = util.FakeHidDevice(cid_to_allocate=None)
dev.SetChannelBusyCount(2)
dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3])
self.assertEqual(
dev.Read(), [0, 0, 0, 1, 0xbf, 0, 1, 6] + [0 for _ in range(56)])
dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3])
self.assertEqual(
dev.Read(), [0, 0, 0, 1, 0xbf, 0, 1, 6] + [0 for _ in range(56)])
dev.Write([0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3])
self.assertEqual(
dev.Read(), [0, 0, 0, 1, 0x81, 0, 3, 1, 2, 3] + [0
for _ in range(54)])
def testFragmentedApdu(self):
dev = util.FakeHidDevice(cid_to_allocate=None, msg_reply=range(85, 0, -1))
dev.Write([0, 0, 0, 1, 0x83, 0, 100] + [x for x in range(57)])
dev.Write([0, 0, 0, 1, 0] + [x for x in range(57, 100)])
self.assertEqual(
dev.Read(), [0, 0, 0, 1, 0x83, 0, 85] + [x for x in range(85, 28, -1)])
self.assertEqual(
dev.Read(),
[0, 0, 0, 1, 0] + [x for x in range(28, 0, -1)] + [0
for _ in range(31)])
if __name__ == '__main__':
unittest.main()

View File

@ -1,149 +0,0 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Testing utilties for _pyu2f.
Testing utilities such as a fake implementation of the pyhidapi device object
that implements parts of the U2FHID frame protocol. This makes it easy to tests
of higher level abstractions without having to use mock to mock out low level
framing details.
"""
from fido2._pyu2f import base, hidtransport
class UnsupportedCommandError(Exception):
pass
class FakeHidDevice(base.HidDevice):
"""Implements a fake hiddevice per the pyhidapi interface.
This class implemetns a fake hiddevice that can be patched into
code that uses pyhidapi to communicate with a hiddevice. This device
impelents part of U2FHID protocol and can be used to test interactions
with a security key. It supports arbitrary MSG replies as well as
channel allocation, and ping.
"""
def __init__(self, cid_to_allocate, msg_reply=None):
self.cid_to_allocate = cid_to_allocate
self.msg_reply = msg_reply
self.transaction_active = False
self.full_packet_received = False
self.init_packet = None
self.packet_body = None
self.reply = None
self.seq = 0
self.received_packets = []
self.busy_count = 0
def GetInReportDataLength(self):
return 64
def GetOutReportDataLength(self):
return 64
def Write(self, data):
"""Write to the device.
Writes to the fake hid device. This function is stateful: if a transaction
is currently open with the client, it will continue to accumulate data
for the client->device messages until the expected size is reached.
Args:
data: A list of integers to accept as data payload. It should be 64 bytes
in length: just the report data--NO report ID.
"""
if len(data) < 64:
data = bytearray(data) + bytearray(0 for i in range(0, 64 - len(data)))
if not self.transaction_active:
self.transaction_active = True
self.init_packet = hidtransport.UsbHidTransport.InitPacket.FromWireFormat(
64, data)
self.packet_body = self.init_packet.payload
self.full_packet_received = False
self.received_packets.append(self.init_packet)
else:
cont_packet = hidtransport.UsbHidTransport.ContPacket.FromWireFormat(
64, data)
self.packet_body += cont_packet.payload
self.received_packets.append(cont_packet)
if len(self.packet_body) >= self.init_packet.size:
self.packet_body = self.packet_body[0:self.init_packet.size]
self.full_packet_received = True
def Read(self):
"""Read from the device.
Reads from the fake hid device. This function only works if a transaction
is open and a complete write has taken place. If so, it will return the
next reply packet. It should be called repeatedly until all expected
data has been received. It always reads one report.
Returns:
A list of ints containing the next packet.
Raises:
UnsupportedCommandError: if the requested amount is not 64.
"""
if not self.transaction_active or not self.full_packet_received:
return None
ret = None
if self.busy_count > 0:
ret = hidtransport.UsbHidTransport.InitPacket(
64, self.init_packet.cid, hidtransport.UsbHidTransport.U2FHID_ERROR,
1, hidtransport.UsbHidTransport.ERR_CHANNEL_BUSY)
self.busy_count -= 1
elif self.reply: # reply in progress
next_frame = self.reply[0:59]
self.reply = self.reply[59:]
ret = hidtransport.UsbHidTransport.ContPacket(64, self.init_packet.cid,
self.seq, next_frame)
self.seq += 1
else:
self.InternalGenerateReply()
first_frame = self.reply[0:57]
ret = hidtransport.UsbHidTransport.InitPacket(
64, self.init_packet.cid, self.init_packet.cmd, len(self.reply),
first_frame)
self.reply = self.reply[57:]
if not self.reply: # done after this packet
self.reply = None
self.transaction_active = False
self.seq = 0
return ret.ToWireFormat()
def SetChannelBusyCount(self, busy_count): # pylint: disable=invalid-name
"""Mark the channel busy for next busy_count read calls."""
self.busy_count = busy_count
def InternalGenerateReply(self): # pylint: disable=invalid-name
if self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_INIT:
nonce = self.init_packet.payload[0:8]
self.reply = nonce + self.cid_to_allocate + bytearray(
b'\x01\x00\x00\x00\x00')
elif self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_PING:
self.reply = self.init_packet.payload
elif self.init_packet.cmd == hidtransport.UsbHidTransport.U2FHID_MSG:
self.reply = self.msg_reply
else:
raise UnsupportedCommandError()

24
test/hid/test_base.py Normal file
View File

@ -0,0 +1,24 @@
import unittest
from binascii import a2b_hex
from fido2.hid.base import parse_report_descriptor
class TestBase(unittest.TestCase):
def test_parse_report_descriptor_1(self):
max_in_size, max_out_size = parse_report_descriptor(
a2b_hex(
"06d0f10901a1010920150026ff007508954081020921150026ff00750895409102c0"
)
)
self.assertEqual(max_in_size, 64)
self.assertEqual(max_out_size, 64)
def test_parse_report_descriptor_2(self):
with self.assertRaises(ValueError):
parse_report_descriptor(
a2b_hex(
"05010902a1010901a10005091901290515002501950575018102950175038101"
"05010930093109381581257f750895038106c0c0"
)
)

View File

@ -27,10 +27,8 @@
from __future__ import absolute_import, unicode_literals
from fido2.ctap import CtapError
from fido2.hid import CtapHidDevice
import unittest
import mock
class HidTest(unittest.TestCase):
@ -50,49 +48,3 @@ class HidTest(unittest.TestCase):
self.assertEqual(dev.ping(msg1), msg1)
self.assertEqual(dev.ping(msg2), msg2)
self.assertEqual(dev.ping(msg3), msg3)
def test_call_error(self):
dev = mock.Mock()
hid_dev = CtapHidDevice(None, dev)
dev.InternalRecv = mock.Mock(return_value=(0xBF, bytearray([7])))
try:
hid_dev.call(0x01)
self.fail("call did not raise exception")
except CtapError as e:
self.assertEqual(e.code, 7)
def test_call_keepalive(self):
dev = mock.Mock()
hid_dev = CtapHidDevice(None, dev)
on_keepalive = mock.MagicMock()
dev.InternalRecv = mock.Mock(
side_effect=[
(0xBB, bytearray([0])),
(0xBB, bytearray([0])),
(0xBB, bytearray([0])),
(0xBB, bytearray([0])),
(0x81, bytearray(b"done")),
]
)
self.assertEqual(hid_dev.call(0x01, on_keepalive=on_keepalive), b"done")
on_keepalive.assert_called_once_with(0)
dev.InternalRecv.side_effect = [
(0xBB, bytearray([1])),
(0xBB, bytearray([0])),
(0xBB, bytearray([0])),
(0xBB, bytearray([1])),
(0xBB, bytearray([1])),
(0xBB, bytearray([1])),
(0xBB, bytearray([1])),
(0xBB, bytearray([0])),
(0x81, bytearray(b"done")),
]
on_keepalive.reset_mock()
self.assertEqual(hid_dev.call(0x01, on_keepalive=on_keepalive), b"done")
self.assertEqual(
on_keepalive.call_args_list,
[mock.call(1), mock.call(0), mock.call(1), mock.call(0)],
)