1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
import struct
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from fido2.attestation import Attestation
from fido2.ctap2 import Ctap2, CredentialManagement
from fido2.ctap2.pin import ClientPin
from fido2.hid import CTAPHID
from fido2.utils import hmac_sha256
from fido2.webauthn import PublicKeyCredentialCreationOptions
from .. import helpers
# Base class
# Currently some methods are implemented here since they are the same in both devices.
class SoloClient:
def __init__(
self,
):
self.origin = "https://example.org"
self.host = "example.org"
self.user_id = b"they"
self.do_reboot = True
def set_reboot(self, val):
"""option to reboot after programming"""
self.do_reboot = val
def reboot(
self,
):
pass
def find_device(self, dev=None, solo_serial=None):
pass
def get_current_hid_device(
self,
):
"""Return current device class for CTAPHID interface if available."""
pass
def get_current_fido_client(
self,
):
"""Return current fido2 client if available."""
pass
def send_data_hid(self, cmd, data):
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
with helpers.Timeout(1.0) as event:
return self.get_current_hid_device().call(cmd, data, event)
def bootloader_version(
self,
):
pass
def solo_version(
self,
):
pass
def get_rng(self, num=0):
pass
def wink(
self,
):
self.send_data_hid(CTAPHID.WINK, b"")
def ping(self, data="pong"):
return self.send_data_hid(CTAPHID.PING, data)
def reset(
self,
):
Ctap2(self.get_current_hid_device()).reset()
def change_pin(self, old_pin, new_pin):
client = ClientPin(self.ctap2)
client.change_pin(old_pin, new_pin)
def set_pin(self, new_pin):
client = ClientPin(self.ctap2)
client.set_pin(new_pin)
def make_credential(self):
client = self.get_current_fido_client()
rp = {"id": self.host, "name": "example site"}
user = {"id": self.user_id, "name": "example user"}
challenge = b"Y2hhbGxlbmdl"
options = PublicKeyCredentialCreationOptions(
rp,
user,
challenge,
[{"type": "public-key", "alg": -8}, {"type": "public-key", "alg": -7}],
)
result = client.make_credential(options)
attest = result.attestation_object
data = result.client_data
try:
attest.verify(data.hash)
except AttributeError:
verifier = Attestation.for_type(attest.fmt)
verifier().verify(attest.att_stmt, attest.auth_data, data.hash)
print("Register valid")
x5c = attest.att_stmt["x5c"][0]
cert = x509.load_der_x509_certificate(x5c, default_backend())
return cert
def cred_mgmt(self, pin):
client = ClientPin(self.ctap2)
token = client.get_pin_token(pin)
return CredentialManagement(self.ctap2, client.protocol, token)
def enter_solo_bootloader(
self,
):
"""
If solo is configured as solo hacker or something similar,
this command will tell the token to boot directly to the bootloader
so it can be reprogrammed
"""
pass
def enter_bootloader_or_die(self):
pass
def is_solo_bootloader(
self,
):
"""For now, solo bootloader could be the NXP bootrom on Solo v2."""
pass
def program_kbd(self, cmd):
ctap2 = Ctap2(self.get_current_hid_device())
return ctap2.send_cbor(0x51, cmd)
def sign_hash(self, credential_id, dgst, pin):
ctap2 = Ctap2(self.get_current_hid_device())
client = ClientPin(ctap2)
if pin:
pin_token = client.get_pin_token(pin)
pin_auth = hmac_sha256(pin_token, dgst)[:16]
return ctap2.send_cbor(
0x50,
{1: dgst, 2: {"id": credential_id, "type": "public-key"}, 3: pin_auth},
)
else:
return ctap2.send_cbor(
0x50, {1: dgst, 2: {"id": credential_id, "type": "public-key"}}
)
def program_file(self, name):
pass
|