First commit

This commit is contained in:
Conor Patrick
2019-08-01 17:44:30 -04:00
parent ffe2d5ecae
commit 59d44c1e7a
8 changed files with 457 additions and 0 deletions
+2
View File
@@ -102,3 +102,5 @@ venv.bak/
# mypy
.mypy_cache/
*.swp
+4
View File
@@ -0,0 +1,4 @@
fido2
solo-python
pytest
pytest-ordering
+142
View File
@@ -0,0 +1,142 @@
import struct, time
import pytest
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client
from fido2.utils import Timeout
from solo.fido2 import force_udp_backend
__device = None
origin = "https://solokeys.com"
class Packet(object):
def __init__(self, data):
self.data = data
def ToWireFormat(self,):
return self.data
@staticmethod
def FromWireFormat(pkt_size, data):
return Packet(data)
def cid():
return __device._dev.cid
def set_cid(cid):
if not isinstance(cid, (bytes, bytearray)):
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
__device._dev.cid = cid
def recv_raw():
with Timeout(1.0):
cmd, payload = __device._dev.InternalRecv()
return cmd, payload
def send_data(cmd, data):
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
with Timeout(1.0) as event:
return __device.call(cmd, data, event)
def send_raw(data, cid=None):
if cid is None:
cid = __device._dev.cid
elif not isinstance(cid, bytes):
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
data = cid + data
l = len(data)
if l != 64:
pad = "\x00" * (64 - l)
pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad])
data = data + pad
data = list(data)
assert len(data) == 64
__device._dev.InternalSendPacket(Packet(data))
def send_magic_reboot():
"""
For use in simulation and testing. Random bytes that authentictor should detect
and then restart itself.
"""
magic_cmd = (
b"\xac\x10\x52\xca\x95\xe5\x69\xde\x69\xe0\x2e\xbf"
+ b"\xf3\x33\x48\x5f\x13\xf9\xb2\xda\x34\xc5\xa8\xa3"
+ b"\x40\x52\x66\x97\xa9\xab\x2e\x0b\x39\x4d\x8d\x04"
+ b"\x97\x3c\x13\x40\x05\xbe\x1a\x01\x40\xbf\xf6\x04"
+ b"\x5b\xb2\x6e\xb7\x7a\x73\xea\xa4\x78\x13\xf6\xb4"
+ b"\x9a\x72\x50\xdc"
)
__device.dev._dev.InternalSendPacket(Packet(magic_cmd))
def reboot():
if is_simulation:
print("Sending restart command...")
self.send_magic_reboot()
Tester.delay(0.25)
else:
print("Please reboot authentictor and hit enter")
input()
self.find_device(self.nfc_interface_only)
def find_device(nfcInterfaceOnly=False):
print(is_simulation)
if is_simulation:
print("FORCE UDP")
force_udp_backend()
dev = None
nfcInterfaceOnly
if not nfcInterfaceOnly:
print("--- HID ---")
print(list(CtapHidDevice.list_devices()))
dev = next(CtapHidDevice.list_devices(), None)
if not dev:
from fido2.pcsc import CtapPcscDevice
print("--- NFC ---")
print(list(CtapPcscDevice.list_devices()))
dev = next(CtapPcscDevice.list_devices(), None)
if not dev:
raise RuntimeError("No FIDO device found")
return Fido2Client(dev, origin)
def set_device(dev):
__device = dev
def _get_device(refresh=False):
print(0)
dev = find_device()
print(1, dev)
yield dev
while True:
if refresh:
print("REFRESH")
dev = find_device()
print(2, dev)
yield dev
def get_device(*args):
time.sleep(0.5)
return next(_get_device(*args))
+231
View File
@@ -0,0 +1,231 @@
import pytest
import time
import struct
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client
from fido2.attestation import Attestation
from fido2.ctap1 import CTAP1
from fido2.utils import Timeout
from fido2.ctap import CtapError
from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData
from fido2.utils import sha256, hmac_sha256
from solo.fido2 import force_udp_backend
def pytest_addoption(parser):
parser.addoption("--sim", action="store_true")
parser.addoption("--nfc", action="store_true")
@pytest.fixture()
def is_simulation(pytestconfig):
return pytestconfig.getoption("sim")
@pytest.fixture()
def is_nfc(pytestconfig):
return pytestconfig.getoption("nfc")
@pytest.fixture()
def MCParams():
rp = {"id": "examplo.org", "name": "ExaRP"}
rp2 = {"id": "solokeys.com", "name": "ExaRP"}
user = {"id": b"usee_od", "name": "AB User"}
user1 = {"id": b"1234567890", "name": "Conor Patrick"}
user2 = {"id": b"oiewhfoi", "name": "Han Solo"}
user3 = {"id": b"23ohfpjwo@@", "name": "John Smith"}
challenge = "Y2hhbGxlbmdl"
pin_protocol = 1
key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}]
cdh = b"123456789abcdef0123456789abcdef0"
return [cdh, rp, user, key_params]
def GAParams():
rp = {"id": "examplo.org", "name": "ExaRP"}
rp2 = {"id": "solokeys.com", "name": "ExaRP"}
user = {"id": b"usee_od", "name": "AB User"}
user1 = {"id": b"1234567890", "name": "Conor Patrick"}
user2 = {"id": b"oiewhfoi", "name": "Han Solo"}
user3 = {"id": b"23ohfpjwo@@", "name": "John Smith"}
challenge = "Y2hhbGxlbmdl"
pin_protocol = 1
key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}]
cdh = b"123456789abcdef0123456789abcdef0"
pass
@pytest.fixture(scope="session")
def device(pytestconfig):
if pytestconfig.getoption("sim"):
print("FORCE UDP")
force_udp_backend()
dev = TestDevice()
dev.set_sim(pytestconfig.getoption("sim"))
dev.find_device(pytestconfig.getoption("nfc"))
return dev
class TestDevice:
def __init__(self, tester=None):
self.origin = "https://examplo.org"
self.host = "examplo.org"
self.user_count = 10
self.is_sim = False
self.nfc_interface_only = False
if tester:
self.initFromTester(tester)
def initFromTester(self, tester):
self.user_count = tester.user_count
self.is_sim = tester.is_sim
self.dev = tester.dev
self.ctap2 = tester.ctap2
self.ctap1 = tester.ctap1
self.client = tester.client
self.nfc_interface_only = tester.nfc_interface_only
def find_device(self, nfcInterfaceOnly=False):
dev = None
self.nfc_interface_only = nfcInterfaceOnly
if not nfcInterfaceOnly:
print("--- HID ---")
print(list(CtapHidDevice.list_devices()))
dev = next(CtapHidDevice.list_devices(), None)
if not dev:
from fido2.pcsc import CtapPcscDevice
print("--- NFC ---")
print(list(CtapPcscDevice.list_devices()))
dev = next(CtapPcscDevice.list_devices(), None)
if not dev:
raise RuntimeError("No FIDO device found")
self.dev = dev
self.client = Fido2Client(dev, self.origin)
self.ctap2 = self.client.ctap2
self.ctap1 = CTAP1(dev)
# consume timeout error
# cmd,resp = self.recv_raw()
def set_user_count(self, count):
self.user_count = count
def set_sim(self, b):
self.is_sim = b
def reboot(self,):
if self.is_sim:
print("Sending restart command...")
self.send_magic_reboot()
Tester.delay(0.25)
else:
print("Please reboot authentictor and hit enter")
input()
self.find_device(self.nfc_interface_only)
def send_data(self, cmd, data):
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
with Timeout(1.0) as event:
return self.dev.call(cmd, data, event)
def send_raw(self, data, cid=None):
if cid is None:
cid = self.dev._dev.cid
elif not isinstance(cid, bytes):
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
data = cid + data
l = len(data)
if l != 64:
pad = "\x00" * (64 - l)
pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad])
data = data + pad
data = list(data)
assert len(data) == 64
self.dev._dev.InternalSendPacket(Packet(data))
def send_magic_reboot(self,):
"""
For use in simulation and testing. Random bytes that authentictor should detect
and then restart itself.
"""
magic_cmd = (
b"\xac\x10\x52\xca\x95\xe5\x69\xde\x69\xe0\x2e\xbf"
+ b"\xf3\x33\x48\x5f\x13\xf9\xb2\xda\x34\xc5\xa8\xa3"
+ b"\x40\x52\x66\x97\xa9\xab\x2e\x0b\x39\x4d\x8d\x04"
+ b"\x97\x3c\x13\x40\x05\xbe\x1a\x01\x40\xbf\xf6\x04"
+ b"\x5b\xb2\x6e\xb7\x7a\x73\xea\xa4\x78\x13\xf6\xb4"
+ b"\x9a\x72\x50\xdc"
)
self.dev._dev.InternalSendPacket(Packet(magic_cmd))
def cid(self,):
return self.dev._dev.cid
def set_cid(self, cid):
if not isinstance(cid, (bytes, bytearray)):
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
self.dev._dev.cid = cid
def recv_raw(self,):
with Timeout(1.0):
cmd, payload = self.dev._dev.InternalRecv()
return cmd, payload
def check_error(data, err=None):
assert len(data) == 1
if err is None:
if data[0] != 0:
raise CtapError(data[0])
elif data[0] != err:
raise ValueError("Unexpected error: %02x" % data[0])
def reset(self,):
print("Resetting Authenticator...")
try:
self.ctap2.reset()
except CtapError:
# Some authenticators need a power cycle
print("You must power cycle authentictor. Hit enter when done.")
input()
time.sleep(0.2)
self.find_device(self.nfc_interface_only)
self.ctap2.reset()
def sendMC(self, *args, **kwargs):
attestation_object = self.ctap2.make_credential(
*args, **kwargs
)
if attestation_object:
verifier = Attestation.for_type(attestation_object.fmt)
client_data = args[0]
verifier().verify(
attestation_object.att_statement,
attestation_object.auth_data,
client_data,
)
return attestation_object
def sendGA(self, *args, **kwargs):
return self.ctap2.get_assertion(*args, **kwargs)
def sendCP(self, *args, **kwargs):
return self.ctap2.client_pin(*args, **kwargs)
def sendPP(self, *args, **kwargs):
return self.client.pin_protocol.get_pin_token(test, *args, **kwargs)
def delay(secs):
time.sleep(secs)
+7
View File
@@ -0,0 +1,7 @@
import tests
import pytest
@pytest.mark.run(order=1)
def test_answer(device):
pass
+17
View File
@@ -0,0 +1,17 @@
import time
import tests
def reset():
print("Resetting Authenticator...")
dev = tests.get_device()
try:
dev.ctap2.reset()
except CtapError:
# Some authenticators need a power cycle
print("You must power cycle authentictor. Hit enter when done.")
input()
time.sleep(0.2)
dev = tests.get_device(refresh=True)
dev.ctap2.reset()
+47
View File
@@ -0,0 +1,47 @@
import pytest
from fido2 import cbor
from fido2.ctap import CtapError
@pytest.fixture(scope="session")
def info(device):
info = device.ctap2.get_info()
print("data:", bytes(info))
print("decoded:", cbor.decode_from(bytes(info)))
return info
def test_get_info(info):
pass
def test_get_info_version(info):
assert "FIDO_2_0" in info.versions
def test_Check_pin_protocols_field(info):
if len(info.pin_protocols):
assert sum(info.pin_protocols) > 0
def test_Check_options_field(info):
for x in info.options:
assert info.options[x] in [True, False]
def test_Check_uv_option(device, info, MCParams):
if "uv" in info.options:
if info.options["uv"]:
device.sendMC(
*MCParams,
options = {"uv": True},
)
def test_Check_up_option(device,info,MCParams):
if "up" in info.options:
if info.options["up"]:
with pytest.raises(CtapError) as e:
device.sendMC(
*MCParams,
options = {"up": True},
)
assert e.value.code == CtapError.ERR.INVALID_OPTION
+7
View File
@@ -0,0 +1,7 @@
import tests
from fido2.ctap import CtapError
def test_reset(device):
device.reset()