-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
457 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,5 @@ venv.bak/ | |
|
||
# mypy | ||
.mypy_cache/ | ||
|
||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
fido2 | ||
solo-python | ||
pytest | ||
pytest-ordering |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import tests | ||
import pytest | ||
|
||
|
||
@pytest.mark.run(order=1) | ||
def test_answer(device): | ||
pass |
Oops, something went wrong.