Pass tools/ through black

This commit is contained in:
Nicolas Stalder 2019-01-03 14:27:21 +01:00
parent b2c78ca7c0
commit 6a5449b8cb
8 changed files with 614 additions and 474 deletions

View File

@ -23,7 +23,7 @@ import sys
from sys import argv
if len(argv) != 2:
print("usage: %s <input-log>" % argv[0]);
print("usage: %s <input-log>" % argv[0])
sys.exit(1)
log = open(argv[1]).readlines()
@ -34,13 +34,13 @@ for x in log:
parse = []
for i in x.split(' '):
try:
n = int(i,16)
n = int(i, 16)
parse.append(n)
except:
pass
if len(parse) == 0:
continue
assert(len(parse) == 64)
assert len(parse) == 64
nums.append(parse)
hexlines = []
@ -52,4 +52,4 @@ for l in nums:
hexlines.append(s)
for x in hexlines:
print('"'+x+'"')
print('"' + x + '"')

View File

@ -31,15 +31,15 @@ from fido2.ctap1 import CTAP1
from fido2.ctap2 import *
from fido2.cose import *
from fido2.utils import Timeout
import sys,os,time
import sys, os, time
from random import randint
from binascii import hexlify
import array,struct,socket
import array, struct, socket
# Set up a FIDO 2 client using the origin https://example.com
def ForceU2F(client,device):
def ForceU2F(client, device):
client.ctap = CTAP1(device)
client.pin_protocol = None
client._do_make_credential = client._ctap1_make_credential
@ -47,7 +47,7 @@ def ForceU2F(client,device):
class Packet(object):
def __init__(self,data):
def __init__(self, data):
l = len(data)
self.data = data
@ -55,16 +55,17 @@ class Packet(object):
return self.data
@staticmethod
def FromWireFormat(pkt_size,data):
def FromWireFormat(pkt_size, data):
return Packet(data)
class Tester():
class Tester:
def __init__(self,):
self.origin = 'https://examplo.org'
self.host = 'examplo.org'
def find_device(self,):
print (list(CtapHidDevice.list_devices()))
print(list(CtapHidDevice.list_devices()))
dev = next(CtapHidDevice.list_devices(), None)
if not dev:
raise RuntimeError('No FIDO device found')
@ -73,15 +74,15 @@ class Tester():
self.ctap = self.client.ctap2
# consume timeout error
#cmd,resp = self.recv_raw()
# cmd,resp = self.recv_raw()
def send_data(self, cmd, data):
if type(data) != type(b''):
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)
return self.dev.call(cmd, data, event)
def send_raw(self, data, cid = None):
def send_raw(self, data, cid=None):
if cid is None:
cid = self.dev._dev.cid
elif type(cid) != type(b''):
@ -91,28 +92,28 @@ class Tester():
data = cid + data
l = len(data)
if l != 64:
pad = '\x00' * (64-l)
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)
assert len(data) == 64
self.dev._dev.InternalSendPacket(Packet(data))
def cid(self,):
return self.dev._dev.cid
def set_cid(self,cid):
def set_cid(self, cid):
if type(cid) not in [type(b''), type(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) as t:
cmd,payload = self.dev._dev.InternalRecv()
cmd, payload = self.dev._dev.InternalRecv()
return cmd, payload
def check_error(self,data,err=None):
assert(len(data) == 1)
def check_error(self, data, err=None):
assert len(data) == 1
if err is None:
if data[0] != 0:
raise CtapError(data[0])
@ -127,37 +128,35 @@ class Tester():
r = self.send_data(CTAPHID.PING, pingdata)
t2 = time.time() * 1000
delt = t2 - t1
#if (delt < 140 ):
#raise RuntimeError('Fob is too fast (%d ms)' % delt)
if (delt > 555 * (amt/1000)):
# if (delt < 140 ):
# raise RuntimeError('Fob is too fast (%d ms)' % delt)
if delt > 555 * (amt / 1000):
raise RuntimeError('Fob is too slow (%d ms)' % delt)
if (r != pingdata):
if r != pingdata:
raise ValueError('Ping data not echo\'d')
print('1000 byte ping time: %s ms' % delt)
except CtapError as e:
print('7609 byte Ping failed:', e)
raise RuntimeError('ping failed')
print('PASS: 7609 byte ping')
#sys.flush(sys.sto)
# sys.flush(sys.sto)
sys.stdout.flush()
def test_hid(self,check_timeouts = False):
def test_hid(self, check_timeouts=False):
if check_timeouts:
print('Test idle')
try:
cmd,resp = self.recv_raw()
cmd, resp = self.recv_raw()
except socket.timeout:
print('Pass: Idle')
print('Test init')
r = self.send_data(CTAPHID.INIT, '\x11\x11\x11\x11\x11\x11\x11\x11')
pingdata = os.urandom(100)
try:
r = self.send_data(CTAPHID.PING, pingdata)
if (r != pingdata):
if r != pingdata:
raise ValueError('Ping data not echo\'d')
except CtapError as e:
print('100 byte Ping failed:', e)
@ -169,25 +168,25 @@ class Tester():
try:
r = self.send_data(CTAPHID.WINK, '')
print(hexlify(r))
#assert(len(r) == 0)
# assert(len(r) == 0)
except CtapError as e:
print('wink failed:', e)
raise RuntimeError('wink failed')
print('PASS: wink')
#try:
#r = self.send_data(CTAPHID.WINK, 'we9gofrei8g')
#raise RuntimeError('Wink is not supposed to have payload')
#except CtapError as e:
#assert(e.code == CtapError.ERR.INVALID_LENGTH)
#print('PASS: malformed wink')
# try:
# r = self.send_data(CTAPHID.WINK, 'we9gofrei8g')
# raise RuntimeError('Wink is not supposed to have payload')
# except CtapError as e:
# assert(e.code == CtapError.ERR.INVALID_LENGTH)
# print('PASS: malformed wink')
try:
r = self.send_data(CTAPHID.CBOR, '')
if len(r) > 1 or r[0] == 0:
raise RuntimeError('Cbor is supposed to have payload')
except CtapError as e:
assert(e.code == CtapError.ERR.INVALID_LENGTH)
assert e.code == CtapError.ERR.INVALID_LENGTH
print('PASS: no data cbor')
try:
@ -196,7 +195,7 @@ class Tester():
if len(r) > 2:
raise RuntimeError('MSG is supposed to have payload')
except CtapError as e:
assert(e.code == CtapError.ERR.INVALID_LENGTH)
assert e.code == CtapError.ERR.INVALID_LENGTH
print('PASS: no data msg')
try:
@ -209,28 +208,27 @@ class Tester():
r = self.send_data(0x66, '')
raise RuntimeError('Invalid command did not return error')
except CtapError as e:
assert(e.code == CtapError.ERR.INVALID_COMMAND)
assert e.code == CtapError.ERR.INVALID_COMMAND
print('PASS: invalid HID command')
print('Sending packet with too large of a length.')
self.send_raw('\x81\x1d\xba\x00')
cmd,resp = self.recv_raw()
cmd, resp = self.recv_raw()
self.check_error(resp, CtapError.ERR.INVALID_LENGTH)
print('PASS: invalid length')
r = self.send_data(CTAPHID.PING, '\x44'*200)
r = self.send_data(CTAPHID.PING, '\x44' * 200)
print('Sending packets that skip a sequence number.')
self.send_raw('\x81\x04\x90')
self.send_raw('\x00')
self.send_raw('\x01')
# skip 2
self.send_raw('\x03')
cmd,resp = self.recv_raw()
cmd, resp = self.recv_raw()
self.check_error(resp, CtapError.ERR.INVALID_SEQ)
if check_timeouts:
cmd,resp = self.recv_raw()
assert(cmd == 0xbf) # timeout
cmd, resp = self.recv_raw()
assert cmd == 0xBF # timeout
print('PASS: invalid sequence')
print('Resync and send ping')
@ -238,7 +236,7 @@ class Tester():
r = self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88')
pingdata = os.urandom(100)
r = self.send_data(CTAPHID.PING, pingdata)
if (r != pingdata):
if r != pingdata:
raise ValueError('Ping data not echo\'d')
except CtapError as e:
raise RuntimeError('resync fail: ', e)
@ -261,15 +259,17 @@ class Tester():
self.send_raw('\x00')
self.send_raw('\x01')
self.set_cid(newcid)
self.send_raw('\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88') # init from different cid
self.send_raw(
'\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88'
) # init from different cid
print('wait for init response')
cmd,r = self.recv_raw() # init response
assert(cmd == 0x86)
cmd, r = self.recv_raw() # init response
assert cmd == 0x86
self.set_cid(oldcid)
if check_timeouts:
#print('wait for timeout')
cmd,r = self.recv_raw() # timeout response
assert(cmd == 0xbf)
# print('wait for timeout')
cmd, r = self.recv_raw() # timeout response
assert cmd == 0xBF
print('PASS: resync and timeout')
@ -279,12 +279,12 @@ class Tester():
self.send_raw('\x81\x04\x00')
self.send_raw('\x00')
self.send_raw('\x01')
cmd,r = self.recv_raw() # timeout response
cmd, r = self.recv_raw() # timeout response
t2 = time.time() * 1000
delt = t2 - t1
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.TIMEOUT)
assert(delt < 1000 and delt > 400)
assert cmd == 0xBF
assert r[0] == CtapError.ERR.TIMEOUT
assert delt < 1000 and delt > 400
print('Pass timeout')
print('Test not cont')
@ -292,10 +292,10 @@ class Tester():
self.send_raw('\x81\x04\x00')
self.send_raw('\x00')
self.send_raw('\x01')
self.send_raw('\x81\x10\x00') # init packet
cmd,r = self.recv_raw() # timeout response
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.INVALID_SEQ)
self.send_raw('\x81\x10\x00') # init packet
cmd, r = self.recv_raw() # timeout response
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_SEQ
print('PASS: Test not cont')
if check_timeouts:
@ -303,7 +303,7 @@ class Tester():
self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88')
self.send_raw('\x01\x10\x00')
try:
cmd,r = self.recv_raw() # timeout response
cmd, r = self.recv_raw() # timeout response
except socket.timeout:
pass
print('PASS: random cont')
@ -316,16 +316,16 @@ class Tester():
self.send_raw('\x81\x04\x00')
self.set_cid(newcid)
self.send_raw('\x81\x04\x00')
cmd,r = self.recv_raw() # busy response
cmd, r = self.recv_raw() # busy response
t2 = time.time() * 1000
assert(t2-t1 < 100)
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.CHANNEL_BUSY)
assert t2 - t1 < 100
assert cmd == 0xBF
assert r[0] == CtapError.ERR.CHANNEL_BUSY
self.set_cid(oldcid)
cmd,r = self.recv_raw() # timeout response
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.TIMEOUT)
cmd, r = self.recv_raw() # timeout response
assert cmd == 0xBF
assert r[0] == CtapError.ERR.TIMEOUT
print('PASS: busy')
print('Check busy interleaved')
@ -335,55 +335,59 @@ class Tester():
self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88')
self.set_cid(cid1)
self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88')
self.send_raw('\x81\x00\x63') # echo 99 bytes first channel
self.send_raw('\x81\x00\x63') # echo 99 bytes first channel
self.set_cid(cid2) # send ping on 2nd channel
self.send_raw('\x81\x00\x63')
self.send_raw('\x00')
cmd,r = self.recv_raw() # busy response
cmd, r = self.recv_raw() # busy response
self.set_cid(cid1) # finish 1st channel ping
self.set_cid(cid1) # finish 1st channel ping
self.send_raw('\x00')
self.set_cid(cid2)
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.CHANNEL_BUSY)
assert cmd == 0xBF
assert r[0] == CtapError.ERR.CHANNEL_BUSY
self.set_cid(cid1)
cmd,r = self.recv_raw() # ping response
assert(cmd == 0x81)
assert(len(r) == 0x63)
cmd, r = self.recv_raw() # ping response
assert cmd == 0x81
assert len(r) == 0x63
if check_timeouts:
cmd,r = self.recv_raw() # timeout
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.TIMEOUT)
cmd, r = self.recv_raw() # timeout
assert cmd == 0xBF
assert r[0] == CtapError.ERR.TIMEOUT
print('PASS: busy interleaved')
if check_timeouts:
print('Test idle, wait for timeout')
sys.stdout.flush()
try:
cmd,resp = self.recv_raw()
cmd, resp = self.recv_raw()
except socket.timeout:
print('Pass: Idle')
print('Test cid 0 is invalid')
self.set_cid('\x00\x00\x00\x00')
self.send_raw('\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88', cid = '\x00\x00\x00\x00')
cmd,r = self.recv_raw() # timeout
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.INVALID_CHANNEL)
self.send_raw(
'\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88', cid='\x00\x00\x00\x00'
)
cmd, r = self.recv_raw() # timeout
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_CHANNEL
print('Pass: cid 0')
print('Test invalid broadcast cid use')
self.set_cid('\xff\xff\xff\xff')
self.send_raw('\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88', cid = '\xff\xff\xff\xff')
cmd,r = self.recv_raw() # timeout
assert(cmd == 0xbf)
assert(r[0] == CtapError.ERR.INVALID_CHANNEL)
self.send_raw(
'\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88', cid='\xff\xff\xff\xff'
)
cmd, r = self.recv_raw() # timeout
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_CHANNEL
print('Pass: cid broadcast')
def test_u2f(self,):
@ -392,34 +396,38 @@ class Tester():
def test_fido2_simple(self, pin_token=None):
creds = []
exclude_list = []
rp = {'id': self.host, 'name': 'ExaRP'}
rp = {'id': self.host, 'name': 'ExaRP'}
user = {'id': b'usee_od', 'name': 'AB User'}
challenge = 'Y2hhbGxlbmdl'
PIN = pin_token
fake_id1 = array.array('B',[randint(0,255) for i in range(0,150)]).tobytes()
fake_id2 = array.array('B',[randint(0,255) for i in range(0,73)]).tobytes()
fake_id1 = array.array('B', [randint(0, 255) for i in range(0, 150)]).tobytes()
fake_id2 = array.array('B', [randint(0, 255) for i in range(0, 73)]).tobytes()
exclude_list.append({'id': fake_id1, 'type': 'public-key'})
exclude_list.append({'id': fake_id2, 'type': 'public-key'})
print('MC')
t1 = time.time() * 1000
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = [])
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=[]
)
t2 = time.time() * 1000
attest.verify(data.hash)
print('Register valid (%d ms)' % (t2-t1))
print('Register valid (%d ms)' % (t2 - t1))
cred = attest.auth_data.credential_data
creds.append(cred)
allow_list = [{'id':creds[0].credential_id, 'type': 'public-key'}]
allow_list = [{'id': creds[0].credential_id, 'type': 'public-key'}]
t1 = time.time() * 1000
assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, allow_list, pin=PIN
)
t2 = time.time() * 1000
assertions[0].verify(client_data.hash, creds[0].public_key)
print('Assertion valid (%d ms)' % (t2-t1))
print('Assertion valid (%d ms)' % (t2 - t1))
def test_fido2_brute_force(self):
creds = []
@ -432,46 +440,52 @@ class Tester():
self.ctap.reset()
for i in range(0,2048**2):
for i in range(0, 2048 ** 2):
creds = []
challenge = ''.join([abc[randint(0,len(abc)-1)] for x in range(0,32)])
challenge = ''.join([abc[randint(0, len(abc) - 1)] for x in range(0, 32)])
fake_id1 = array.array('B',[randint(0,255) for i in range(0,150)]).tostring()
fake_id2 = array.array('B',[randint(0,255) for i in range(0,73)]).tostring()
fake_id1 = array.array(
'B', [randint(0, 255) for i in range(0, 150)]
).tostring()
fake_id2 = array.array(
'B', [randint(0, 255) for i in range(0, 73)]
).tostring()
exclude_list.append({'id': fake_id1, 'type': 'public-key'})
exclude_list.append({'id': fake_id2, 'type': 'public-key'})
#for i in range(0,2048**2):
for i in range(0,1):
# for i in range(0,2048**2):
for i in range(0, 1):
t1 = time.time() * 1000
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = [])
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=[]
)
print(attest.auth_data.counter)
t2 = time.time() * 1000
attest.verify(data.hash)
print('Register valid (%d ms)' % (t2-t1))
print('Register valid (%d ms)' % (t2 - t1))
sys.stdout.flush()
cred = attest.auth_data.credential_data
creds.append(cred)
#for i in range(0,2048**2):
for i in range(0,1):
allow_list = [{'id':creds[0].credential_id, 'type': 'public-key'}]
# for i in range(0,2048**2):
for i in range(0, 1):
allow_list = [{'id': creds[0].credential_id, 'type': 'public-key'}]
t1 = time.time() * 1000
assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, allow_list, pin=PIN
)
t2 = time.time() * 1000
assertions[0].verify(client_data.hash, creds[0].public_key)
print(assertions[0].auth_data.counter)
print('Assertion valid (%d ms)' % (t2-t1))
print('Assertion valid (%d ms)' % (t2 - t1))
sys.stdout.flush()
def test_fido2(self):
def test(self,pincode=None):
def test(self, pincode=None):
creds = []
exclude_list = []
rp = {'id': self.host, 'name': 'ExaRP'}
@ -479,16 +493,22 @@ class Tester():
challenge = 'Y2hhbGxlbmdl'
PIN = pincode
fake_id1 = array.array('B',[randint(0,255) for i in range(0,150)]).tostring()
fake_id2 = array.array('B',[randint(0,255) for i in range(0,73)]).tostring()
fake_id1 = array.array(
'B', [randint(0, 255) for i in range(0, 150)]
).tostring()
fake_id2 = array.array(
'B', [randint(0, 255) for i in range(0, 73)]
).tostring()
exclude_list.append({'id': fake_id1, 'type': 'public-key'})
exclude_list.append({'id': fake_id2, 'type': 'public-key'})
# test make credential
print('make 3 credentials')
for i in range(0,3):
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = [])
for i in range(0, 3):
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=[]
)
attest.verify(data.hash)
cred = attest.auth_data.credential_data
creds.append(cred)
@ -498,15 +518,19 @@ class Tester():
if PIN is not None:
print('make credential with wrong pin code')
try:
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN + ' ', exclude_list = [])
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN + ' ', exclude_list=[]
)
except CtapError as e:
assert(e.code == CtapError.ERR.PIN_INVALID)
assert e.code == CtapError.ERR.PIN_INVALID
except ClientError as e:
assert(e.cause.code == CtapError.ERR.PIN_INVALID)
assert e.cause.code == CtapError.ERR.PIN_INVALID
print('PASS')
print('make credential with exclude list')
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = exclude_list)
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=exclude_list
)
attest.verify(data.hash)
cred = attest.auth_data.credential_data
creds.append(cred)
@ -515,37 +539,44 @@ class Tester():
print('make credential with exclude list including real credential')
real_excl = [{'id': cred.credential_id, 'type': 'public-key'}]
try:
attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = exclude_list + real_excl)
attest, data = self.client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=exclude_list + real_excl
)
raise RuntimeError('Exclude list did not return expected error')
except CtapError as e:
assert(e.code == CtapError.ERR.CREDENTIAL_EXCLUDED)
assert e.code == CtapError.ERR.CREDENTIAL_EXCLUDED
except ClientError as e:
assert(e.cause.code == CtapError.ERR.CREDENTIAL_EXCLUDED)
assert e.cause.code == CtapError.ERR.CREDENTIAL_EXCLUDED
print('PASS')
for i, x in enumerate(creds):
print('get assertion %d' % i)
allow_list = [{'id':x.credential_id, 'type': 'public-key'}]
assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN)
allow_list = [{'id': x.credential_id, 'type': 'public-key'}]
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, allow_list, pin=PIN
)
assertions[0].verify(client_data.hash, x.public_key)
print('PASS')
if PIN is not None:
print('get assertion with wrong pin code')
try:
assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN + ' ')
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, allow_list, pin=PIN + ' '
)
except CtapError as e:
assert(e.code == CtapError.ERR.PIN_INVALID)
assert e.code == CtapError.ERR.PIN_INVALID
except ClientError as e:
assert(e.cause.code == CtapError.ERR.PIN_INVALID)
assert e.cause.code == CtapError.ERR.PIN_INVALID
print('PASS')
print('get multiple assertions')
allow_list = [{'id': x.credential_id, 'type': 'public-key'} for x in creds]
assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, allow_list, pin=PIN
)
for ass,cred in zip(assertions, creds):
for ass, cred in zip(assertions, creds):
i += 1
ass.verify(client_data.hash, cred.public_key)
@ -571,34 +602,34 @@ class Tester():
try:
self.client.pin_protocol.set_pin(PIN)
except CtapError as e:
assert(e.code == CtapError.ERR.NOT_ALLOWED)
assert e.code == CtapError.ERR.NOT_ALLOWED
print('PASS')
print('Change pin code')
PIN2 = PIN + '_pin2'
self.client.pin_protocol.change_pin(PIN,PIN2)
self.client.pin_protocol.change_pin(PIN, PIN2)
PIN = PIN2
print('PASS')
print('Change pin code using wrong pin')
try:
self.client.pin_protocol.change_pin(PIN.replace('a','b'),'1234')
self.client.pin_protocol.change_pin(PIN.replace('a', 'b'), '1234')
except CtapError as e:
assert(e.code == CtapError.ERR.PIN_INVALID)
assert e.code == CtapError.ERR.PIN_INVALID
print('PASS')
print('MC using wrong pin')
try:
self.test_fido2_simple('abcd3');
self.test_fido2_simple('abcd3')
except ClientError as e:
assert(e.cause.code == CtapError.ERR.PIN_INVALID)
assert e.cause.code == CtapError.ERR.PIN_INVALID
print('PASS')
print('get info')
inf = self.ctap.get_info()
print('PASS')
self.test_fido2_simple(PIN);
self.test_fido2_simple(PIN)
print('Re-run make_credential and get_assertion tests with pin code')
test(self, PIN)
@ -610,158 +641,171 @@ class Tester():
print('Warning, reset failed: ', e)
print('PASS')
def test_rk(self, ):
def test_rk(self,):
creds = []
rp = {'id': self.host, 'name': 'ExaRP'}
user0 = {'id': b'first one', 'name': 'single User'}
users = [{'id': b'user' + os.urandom(16), 'name': 'AB User'} for i in range(0,2)]
users = [
{'id': b'user' + os.urandom(16), 'name': 'AB User'} for i in range(0, 2)
]
challenge = 'Y2hhbGxlbmdl'
PIN = None
print('reset')
self.ctap.reset()
#if PIN: self.client.pin_protocol.set_pin(PIN)
# if PIN: self.client.pin_protocol.set_pin(PIN)
print('registering 1 user with RK')
t1 = time.time() * 1000
attest, data = self.client.make_credential(rp, user0, challenge, pin = PIN, exclude_list = [], rk = True)
attest, data = self.client.make_credential(
rp, user0, challenge, pin=PIN, exclude_list=[], rk=True
)
t2 = time.time() * 1000
attest.verify(data.hash)
creds.append(attest.auth_data.credential_data)
print('Register valid (%d ms)' % (t2-t1))
print('Register valid (%d ms)' % (t2 - t1))
print('1 assertion')
t1 = time.time() * 1000
assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, pin=PIN
)
t2 = time.time() * 1000
assertions[0].verify(client_data.hash, creds[0].public_key)
print('Assertion valid (%d ms)' % (t2-t1))
print('Assertion valid (%d ms)' % (t2 - t1))
print(assertions[0], client_data)
print('registering %d users with RK' % len(users))
for i in range(0,len(users)):
for i in range(0, len(users)):
t1 = time.time() * 1000
attest, data = self.client.make_credential(rp, users[i], challenge, pin = PIN, exclude_list = [], rk = True)
attest, data = self.client.make_credential(
rp, users[i], challenge, pin=PIN, exclude_list=[], rk=True
)
t2 = time.time() * 1000
attest.verify(data.hash)
print('Register valid (%d ms)' % (t2-t1))
print('Register valid (%d ms)' % (t2 - t1))
creds.append(attest.auth_data.credential_data)
t1 = time.time() * 1000
assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, pin=PIN
)
t2 = time.time() * 1000
for x,y in zip(assertions, creds):
x.verify(client_data.hash,y.public_key)
print('Assertion(s) valid (%d ms)' % (t2-t1))
for x, y in zip(assertions, creds):
x.verify(client_data.hash, y.public_key)
print('Assertion(s) valid (%d ms)' % (t2 - t1))
print('registering a duplicate user ')
t1 = time.time() * 1000
attest, data = self.client.make_credential(rp, users[1], challenge, pin = PIN, exclude_list = [], rk = True)
attest, data = self.client.make_credential(
rp, users[1], challenge, pin=PIN, exclude_list=[], rk=True
)
t2 = time.time() * 1000
attest.verify(data.hash)
creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data]
print('Register valid (%d ms)' % (t2-t1))
creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data]
print('Register valid (%d ms)' % (t2 - t1))
t1 = time.time() * 1000
assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN)
assertions, client_data = self.client.get_assertion(
rp['id'], challenge, pin=PIN
)
t2 = time.time() * 1000
assert(len(assertions) == len(users) +1)
for x,y in zip(assertions, creds):
x.verify(client_data.hash,y.public_key)
print('Assertion(s) valid (%d ms)' % (t2-t1))
assert len(assertions) == len(users) + 1
for x, y in zip(assertions, creds):
x.verify(client_data.hash, y.public_key)
print('Assertion(s) valid (%d ms)' % (t2 - t1))
def test_responses(self,):
PIN = '1234'
RPID = self.host
for dev in (CtapHidDevice.list_devices()):
print('dev',dev)
for dev in CtapHidDevice.list_devices():
print('dev', dev)
client = Fido2Client(dev, RPID)
ctap = client.ctap2
# ctap.reset()
try:
if PIN: client.pin_protocol.set_pin(PIN)
except:pass
if PIN:
client.pin_protocol.set_pin(PIN)
except:
pass
inf = ctap.get_info()
#print (inf)
print('versions: ',inf.versions)
print('aaguid: ',inf.aaguid)
print('rk: ',inf.options['rk'])
print('clientPin: ',inf.options['clientPin'])
print('max_message_size: ',inf.max_msg_size)
# print (inf)
print('versions: ', inf.versions)
print('aaguid: ', inf.aaguid)
print('rk: ', inf.options['rk'])
print('clientPin: ', inf.options['clientPin'])
print('max_message_size: ', inf.max_msg_size)
#rp = {'id': 'SelectDevice', 'name': 'SelectDevice'}
# rp = {'id': 'SelectDevice', 'name': 'SelectDevice'}
rp = {'id': RPID, 'name': 'ExaRP'}
user = {'id': os.urandom(10), 'name': 'SelectDevice'}
user = {'id': b'21first one', 'name': 'single User'}
challenge = 'Y2hhbGxlbmdl'
if 1:
attest, data = client.make_credential(rp,
user, challenge, exclude_list = [], pin = PIN, rk=True)
attest, data = client.make_credential(
rp, user, challenge, exclude_list=[], pin=PIN, rk=True
)
cred = attest.auth_data.credential_data
creds = [cred]
allow_list = [{'id':creds[0].credential_id, 'type': 'public-key'}]
allow_list = [{'id': creds[0].credential_id, 'type': 'public-key'}]
allow_list = []
assertions, client_data = client.get_assertion(rp['id'], challenge, pin = PIN)
assertions, client_data = client.get_assertion(
rp['id'], challenge, pin=PIN
)
assertions[0].verify(client_data.hash, creds[0].public_key)
if 0:
print('registering 1 user with RK')
t1 = time.time() * 1000
attest, data = client.make_credential(rp, user, challenge, pin = PIN, exclude_list = [], rk = True)
attest, data = client.make_credential(
rp, user, challenge, pin=PIN, exclude_list=[], rk=True
)
t2 = time.time() * 1000
attest.verify(data.hash)
creds = [attest.auth_data.credential_data]
print('Register valid (%d ms)' % (t2-t1))
print('Register valid (%d ms)' % (t2 - t1))
print('1 assertion')
t1 = time.time() * 1000
assertions, client_data = client.get_assertion(rp['id'], challenge, pin = PIN)
assertions, client_data = client.get_assertion(
rp['id'], challenge, pin=PIN
)
t2 = time.time() * 1000
assertions[0].verify(client_data.hash, creds[0].public_key)
print('Assertion valid (%d ms)' % (t2-t1))
print('Assertion valid (%d ms)' % (t2 - t1))
#print('fmt:',attest.fmt)
#print('rp_id_hash',attest.auth_data.rp_id_hash)
#print('flags:', hex(attest.auth_data.flags))
#print('count:', hex(attest.auth_data.counter))
print('flags MC:',attest.auth_data)
print('flags GA:',assertions[0].auth_data)
#print('cred_id:',attest.auth_data.credential_data.credential_id)
#print('pubkey:',attest.auth_data.credential_data.public_key)
#print('aaguid:',attest.auth_data.credential_data.aaguid)
# print('fmt:',attest.fmt)
# print('rp_id_hash',attest.auth_data.rp_id_hash)
# print('flags:', hex(attest.auth_data.flags))
# print('count:', hex(attest.auth_data.counter))
print('flags MC:', attest.auth_data)
print('flags GA:', assertions[0].auth_data)
# print('cred_id:',attest.auth_data.credential_data.credential_id)
# print('pubkey:',attest.auth_data.credential_data.public_key)
# print('aaguid:',attest.auth_data.credential_data.aaguid)
# print('cred data:',attest.auth_data.credential_data)
# print('auth_data:',attest.auth_data)
#print('auth_data:',attest.auth_data)
#print('alg:',attest.att_statement['alg'])
#print('sig:',attest.att_statement['sig'])
#print('x5c:',attest.att_statement['x5c'])
#print('data:',data)
# print('auth_data:',attest.auth_data)
# print('alg:',attest.att_statement['alg'])
# print('sig:',attest.att_statement['sig'])
# print('x5c:',attest.att_statement['x5c'])
# print('data:',data)
print('assertion:', assertions[0])
print('clientData:', client_data)
print()
#break
# break
def test_find_brute_force():
@ -771,7 +815,7 @@ def test_find_brute_force():
t = Tester()
t.find_device()
t2 = time.time() * 1000
print('connected %d (%d ms)' % (i, t2-t1))
print('connected %d (%d ms)' % (i, t2 - t1))
i += 1
time.sleep(0.01)
@ -782,8 +826,8 @@ if __name__ == '__main__':
# t.test_hid()
# t.test_long_ping()
t.test_fido2()
#t.test_rk()
#t.test_responses()
# t.test_rk()
# t.test_responses()
# test_find_brute_force()
#t.test_fido2_simple()
#t.test_fido2_brute_force()
# t.test_fido2_simple()
# t.test_fido2_brute_force()

View File

@ -22,15 +22,16 @@
#
from __future__ import print_function
import base64
"""
cbytes.py
Output a c file with the DER certificate.
Read der file as input
"""
import sys,fileinput,binascii
import sys, fileinput, binascii
if len(sys.argv) not in [2,3]:
if len(sys.argv) not in [2, 3]:
print('usage: %s <certificate.der|hex-input> [-s]' % sys.argv[0])
print(' -s: just output c string (for general use)')
sys.exit(1)
@ -39,17 +40,17 @@ buf = None
try:
buf = bytearray(open(sys.argv[1], 'rb').read())
except:
n = sys.argv[1].replace('\n','')
n = sys.argv[1].replace('\r','')
n = sys.argv[1].replace('\n', '')
n = sys.argv[1].replace('\r', '')
buf = bytearray(binascii.unhexlify(n))
c_str = ''
size = len(buf)
a = ''.join(map(lambda c:'\\x%02x'%c, buf))
a = ''.join(map(lambda c: '\\x%02x' % c, buf))
for i in range(0,len(a), 80):
c_str += ("\""+a[i:i+80]+"\"\n")
for i in range(0, len(a), 80):
c_str += "\"" + a[i : i + 80] + "\"\n"
if '-s' in sys.argv:
print(c_str)

View File

@ -21,7 +21,8 @@
# Please contact SoloKeys for more information.
#
from __future__ import print_function
import sys,fileinput,binascii
import sys, fileinput, binascii
try:
import ecdsa
except:
@ -45,7 +46,6 @@ cstr = ''
it = iter(hstr)
for d1 in it:
d2 = next(it)
cstr += '\\x'+d1+d2
cstr += '\\x' + d1 + d2
print('"%s"' % cstr)

View File

@ -29,9 +29,7 @@ print('Private key in various formats:')
print()
print([c for c in sk.to_string()])
print()
print(''.join(['%02x'%c for c in sk.to_string()]))
print(''.join(['%02x' % c for c in sk.to_string()]))
print()
print('"\\x' + '\\x'.join(['%02x'%c for c in sk.to_string()]) + '"')
print('"\\x' + '\\x'.join(['%02x' % c for c in sk.to_string()]) + '"')
print()

View File

@ -21,7 +21,7 @@
# Please contact SoloKeys for more information.
#
from __future__ import print_function, absolute_import, unicode_literals
from http.server import BaseHTTPRequestHandler,HTTPServer
from http.server import BaseHTTPRequestHandler, HTTPServer
from fido2.hid import CtapHidDevice, CTAPHID
from fido2.client import Fido2Client, ClientError
@ -35,7 +35,7 @@ from intelhex import IntelHex
from ecdsa import SigningKey, NIST256p
import socket,json,base64,ssl,array,binascii
import socket, json, base64, ssl, array, binascii
from sign_firmware import *
@ -44,7 +44,8 @@ udpport = 8111
HEX_FILE = '../efm32/GNU ARM v7.2.1 - Debug/EFM32.hex'
def ForceU2F(client,device):
def ForceU2F(client, device):
client.ctap = CTAP1(device)
client.pin_protocol = None
client._do_make_credential = client._ctap1_make_credential
@ -64,39 +65,39 @@ if __name__ == '__main__':
print(e)
def write(data):
msg = from_websafe(data)
msg = base64.b64decode(msg)
chal = b'A'*32
appid = b'A'*32
#print (msg)
#print (msg.decode())
#print (str(msg))
#msg = msg.decode('ascii')
#print('ascii:',repr(msg))
#print('ascii:',(type(msg)))
#print(msg + chal)
chal = b'A' * 32
appid = b'A' * 32
# print (msg)
# print (msg.decode())
# print (str(msg))
# msg = msg.decode('ascii')
# print('ascii:',repr(msg))
# print('ascii:',(type(msg)))
# print(msg + chal)
#data = client_param + app_param + struct.pack('>B', len(key_handle)) + key_handle
#msg = str(msg.decode())
#print(msg.decode())
s = ctap.authenticate(chal,appid,msg,)
# data = client_param + app_param + struct.pack('>B', len(key_handle)) + key_handle
# msg = str(msg.decode())
# print(msg.decode())
s = ctap.authenticate(chal, appid, msg)
print(s)
#sock.sendto(msg, ('127.0.0.1', udpport))
# sock.sendto(msg, ('127.0.0.1', udpport))
def read():
#msg = [0]*64
# msg = [0]*64
pkt, _ = sock.recvfrom(1000)
#for i,v in enumerate(pkt):
#msg[i] = ord(v)
# for i,v in enumerate(pkt):
# msg[i] = ord(v)
msg = base64.b64encode(pkt)
msg = to_websafe(pkt)
return msg
class UDPBridge(BaseHTTPRequestHandler):
def end_headers (self):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
BaseHTTPRequestHandler.end_headers(self)
@ -109,43 +110,50 @@ class UDPBridge(BaseHTTPRequestHandler):
msg = from_websafe(data)
msg = base64.b64decode(msg)
chal = b"\xf6\xa2\x3c\xa4\x0a\xf9\xda\xd4\x5f\xdc\xba\x7d\xc9\xde\xcb\xed\xb5\x84\x64\x3a\x4c\x9f\x44\xc2\x04\xb0\x17\xd7\xf4\x3e\xe0\x3f"
appid = b'A'*32
appid = b'A' * 32
s = ctap.authenticate(chal,appid,msg,)
s = ctap.authenticate(chal, appid, msg)
data = struct.pack('B',s.user_presence) + struct.pack('>L',s.counter) + s.signature
data = (
struct.pack('B', s.user_presence)
+ struct.pack('>L', s.counter)
+ s.signature
)
data = base64.b64encode(data).decode('ascii')
data = to_websafe(data)
data = json.dumps({'data':data})
data = json.dumps({'data': data})
data = data.encode('ascii')
self.send_response(200)
self.send_header('Content-type','text/json')
self.send_header('Content-type', 'text/json')
self.end_headers()
self.wfile.write(data)
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/json')
self.send_header('Content-type', 'text/json')
msg = get_firmware_object("signing_key.pem",HEX_FILE)
msg = get_firmware_object("signing_key.pem", HEX_FILE)
self.end_headers()
self.wfile.write(json.dumps(msg).encode())
try:
server = HTTPServer(('', httpport), UDPBridge)
print('Started httpserver on port ' , httpport)
print('Started httpserver on port ', httpport)
server.socket = ssl.wrap_socket (server.socket,
keyfile="../web/localhost.key",
certfile='../web/localhost.crt', server_side=True)
server.socket = ssl.wrap_socket(
server.socket,
keyfile="../web/localhost.key",
certfile='../web/localhost.crt',
server_side=True,
)
print('Saving signed firmware to firmware.json')
msg = get_firmware_object("signing_key.pem",HEX_FILE)
wfile = open('firmware.json','wb+')
msg = get_firmware_object("signing_key.pem", HEX_FILE)
wfile = open('firmware.json', 'wb+')
wfile.write(json.dumps(msg).encode())
wfile.close()
@ -153,4 +161,3 @@ try:
except KeyboardInterrupt:
server.socket.close()

View File

@ -19,15 +19,17 @@
# This code is available under licenses for commercial use.
# Please contact SoloKeys for more information.
#
import datetime,sys
import datetime, sys
from binascii import hexlify
import Chameleon
def verboseLog(text):
formatString = "[{}] {}"
timeString = datetime.datetime.utcnow()
print(formatString.format(timeString, text), )
print(formatString.format(timeString, text))
chameleon = Chameleon.Device(verboseLog)
@ -43,7 +45,7 @@ else:
chameleon.execCmd('LOGMODE=LIVE')
while 1:
b = chameleon.read(1,20)
b = chameleon.read(1, 20)
h = hexlify(b)
h = h.decode()
sys.stdout.write(h)

View File

@ -24,10 +24,10 @@
# Programs solo using the Solo bootloader
# Requires python-fido2, intelhex
import sys,os,time,struct,argparse
import array,struct,socket,json,base64,binascii
import sys, os, time, struct, argparse
import array, struct, socket, json, base64, binascii
import tempfile
from binascii import hexlify,unhexlify
from binascii import hexlify, unhexlify
from hashlib import sha256
from fido2.hid import CtapHidDevice, CTAPHID
@ -45,20 +45,23 @@ import serial
def to_websafe(data):
data = data.replace('+','-')
data = data.replace('/','_')
data = data.replace('=','')
data = data.replace('+', '-')
data = data.replace('/', '_')
data = data.replace('=', '')
return data
def from_websafe(data):
data = data.replace('-','+')
data = data.replace('_','/')
return data + '=='[:(3*len(data)) % 4]
data = data.replace('-', '+')
data = data.replace('_', '/')
return data + '=='[: (3 * len(data)) % 4]
def get_firmware_object(sk_name, hex_file):
from ecdsa import SigningKey, NIST256p
sk = SigningKey.from_pem(open(sk_name).read())
fw = open(hex_file,'r').read()
fw = open(hex_file, 'r').read()
fw = base64.b64encode(fw.encode())
fw = to_websafe(fw.decode())
ih = IntelHex()
@ -66,18 +69,18 @@ def get_firmware_object(sk_name, hex_file):
# start of firmware and the size of the flash region allocated for it.
# TODO put this somewhere else.
START = ih.segments()[0][0]
END = ((0x08000000 + ((128-19)*2048))-8)
END = (0x08000000 + ((128 - 19) * 2048)) - 8
ih = IntelHex(hex_file)
segs = ih.segments()
arr = ih.tobinarray(start = START, size = END-START)
arr = ih.tobinarray(start=START, size=END - START)
im_size = END-START
im_size = END - START
print('im_size: ', im_size)
print('firmware_size: ', len(arr))
byts = (arr).tobytes() if hasattr(arr,'tobytes') else (arr).tostring()
byts = (arr).tobytes() if hasattr(arr, 'tobytes') else (arr).tostring()
h = sha256()
h.update(byts)
sig = binascii.unhexlify(h.hexdigest())
@ -89,10 +92,11 @@ def get_firmware_object(sk_name, hex_file):
sig = base64.b64encode(sig)
sig = to_websafe(sig.decode())
#msg = {'data': read()}
msg = {'firmware': fw, 'signature':sig}
# msg = {'data': read()}
msg = {'firmware': fw, 'signature': sig}
return msg
class SoloBootloader:
write = 0x40
done = 0x41
@ -110,8 +114,8 @@ class SoloBootloader:
TAG = b'\x8C\x27\x90\xf6'
class SoloClient():
class SoloClient:
def __init__(self,):
self.origin = 'https://example.org'
self.exchange = self.exchange_hid
@ -123,7 +127,7 @@ class SoloClient():
def use_hid(self,):
self.exchange = self.exchange_hid
def set_reboot(self,val):
def set_reboot(self, val):
""" option to reboot after programming """
self.do_reboot = val
@ -145,8 +149,8 @@ class SoloClient():
self.send_data_hid(CTAPHID.INIT, '\x11\x11\x11\x11\x11\x11\x11\x11')
@staticmethod
def format_request(cmd,addr = 0,data = b'A'*16):
arr = b'\x00'*9
def format_request(cmd, addr=0, data=b'A' * 16):
arr = b'\x00' * 9
addr = struct.pack('<L', addr)
cmd = struct.pack('B', cmd)
length = struct.pack('>H', len(data))
@ -162,10 +166,10 @@ class SoloClient():
if type(data) != type(b''):
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)
return self.dev.call(cmd, data, event)
def exchange_hid(self,cmd,addr=0,data=b'A'*16):
req = SoloClient.format_request(cmd,addr,data)
def exchange_hid(self, cmd, addr=0, data=b'A' * 16):
req = SoloClient.format_request(cmd, addr, data)
data = self.send_data_hid(SoloBootloader.HIDCommandBoot, req)
@ -174,24 +178,24 @@ class SoloClient():
str = ''
if ret == CtapError.ERR.NOT_ALLOWED:
str = 'Out of bounds write'
raise RuntimeError('Device returned non-success code %02x: %s' % (ret,str))
raise RuntimeError('Device returned non-success code %02x: %s' % (ret, str))
return data[1:]
def exchange_u2f(self,cmd,addr=0,data=b'A'*16):
appid = b'A'*32
chal = b'B'*32
def exchange_u2f(self, cmd, addr=0, data=b'A' * 16):
appid = b'A' * 32
chal = b'B' * 32
req = SoloClient.format_request(cmd,addr,data)
req = SoloClient.format_request(cmd, addr, data)
res = self.ctap1.authenticate(chal,appid, req)
res = self.ctap1.authenticate(chal, appid, req)
ret = res.signature[0]
if ret != CtapError.ERR.SUCCESS:
str = ''
if ret == CtapError.ERR.NOT_ALLOWED:
str = 'Out of bounds write'
raise RuntimeError('Device returned non-success code %02x: %s' % (ret,str))
raise RuntimeError('Device returned non-success code %02x: %s' % (ret, str))
return res.signature[1:]
@ -199,23 +203,23 @@ class SoloClient():
data = self.exchange(SoloBootloader.version)
return data[0]
def write_flash(self,addr,data):
self.exchange(SoloBootloader.write,addr,data)
def write_flash(self, addr, data):
self.exchange(SoloBootloader.write, addr, data)
def get_rng(self,num=0):
ret = self.send_data_hid(SoloBootloader.HIDCommandRNG,struct.pack('B', num))
def get_rng(self, num=0):
ret = self.send_data_hid(SoloBootloader.HIDCommandRNG, struct.pack('B', num))
return ret
def verify_flash(self,sig):
def verify_flash(self, sig):
"""
Tells device to check signature against application. If it passes,
the application will boot.
Exception raises if signature fails.
"""
self.exchange(SoloBootloader.done,0,sig)
self.exchange(SoloBootloader.done, 0, sig)
def wink(self,):
self.send_data_hid(CTAPHID.WINK,b'')
self.send_data_hid(CTAPHID.WINK, b'')
def enter_solo_bootloader(self,):
"""
@ -258,7 +262,9 @@ class SoloClient():
of any updates.
If you've started from a solo hacker, make you you've programmed a final/production build!
"""
ret = self.exchange(SoloBootloader.disable, 0, b'\xcd\xde\xba\xaa') # magic number
ret = self.exchange(
SoloBootloader.disable, 0, b'\xcd\xde\xba\xaa'
) # magic number
if ret[0] != CtapError.ERR.SUCCESS:
print('Failed to disable bootloader')
return False
@ -266,11 +272,10 @@ class SoloClient():
self.exchange(SoloBootloader.do_reboot)
return True
def program_file(self,name):
def program_file(self, name):
if name.lower().endswith('.json'):
data = json.loads(open(name,'r').read())
data = json.loads(open(name, 'r').read())
fw = base64.b64decode(from_websafe(data['firmware']).encode())
sig = base64.b64decode(from_websafe(data['signature']).encode())
ih = IntelHex()
@ -294,44 +299,45 @@ class SoloClient():
seg = ih.segments()[0]
size = seg[1] - seg[0]
total = 0
t1 = time.time()*1000
t1 = time.time() * 1000
print('erasing...')
for i in range(seg[0], seg[1], chunk):
s = i
e = min(i+chunk,seg[1])
data = ih.tobinarray(start=i,size = e-s)
self.write_flash(i,data)
e = min(i + chunk, seg[1])
data = ih.tobinarray(start=i, size=e - s)
self.write_flash(i, data)
total += chunk
progress = total/float(size)*100
progress = total / float(size) * 100
sys.stdout.write('downloading %.2f%%...\r' % progress)
sys.stdout.write('downloaded 100% \r\n')
t2 = time.time()*1000
print('time: %.2f s' % ((t2-t1)/1000.0))
t2 = time.time() * 1000
print('time: %.2f s' % ((t2 - t1) / 1000.0))
print('Verifying...')
if self.do_reboot:
if sig is not None:
self.verify_flash(sig)
else:
self.verify_flash(b'A'*64)
self.verify_flash(b'A' * 64)
class DFU:
class type:
SEND = 0x21
RECEIVE = 0xa1
RECEIVE = 0xA1
class bmReq:
DETACH = 0x00
DNLOAD = 0x01
UPLOAD = 0x02
DETACH = 0x00
DNLOAD = 0x01
UPLOAD = 0x02
GETSTATUS = 0x03
CLRSTATUS = 0x04
GETSTATE = 0x05
ABORT = 0x06
GETSTATE = 0x05
ABORT = 0x06
class state:
APP_IDLE = 0x00
APP_DETACH = 0x01
APP_DETACH = 0x01
IDLE = 0x02
DOWNLOAD_SYNC = 0x03
DOWNLOAD_BUSY = 0x04
@ -340,60 +346,64 @@ class DFU:
MANIFEST = 0x07
MANIFEST_WAIT_RESET = 0x08
UPLOAD_IDLE = 0x09
ERROR = 0x0a
ERROR = 0x0A
class status:
def __init__(self,s):
def __init__(self, s):
self.status = s[0]
self.timeout = s[1] + (s[2] << 8) + (s[3] << 16)
self.state = s[4]
self.istring = s[5]
# hot patch for windows libusb backend
olddel = usb._objfinalizer._AutoFinalizedObjectBase.__del__
def newdel(self):
try:
olddel(self)
except OSError:
pass
usb._objfinalizer._AutoFinalizedObjectBase.__del__ = newdel
class DFUDevice:
def __init__(self,):
pass
@staticmethod
def addr2list(a):
return [ a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff, (a >> 24) & 0xff ]
return [a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, (a >> 24) & 0xFF]
@staticmethod
def addr2block(addr,size):
def addr2block(addr, size):
addr -= 0x08000000
addr //= size
addr += 2
return addr
@staticmethod
def block2addr(addr,size):
def block2addr(addr, size):
addr -= 2
addr *= size
addr += 0x08000000
return addr
def find(self, altsetting = 0, ser=None):
def find(self, altsetting=0, ser=None):
self.dev = None
if ser:
devs = usb.core.find(idVendor=0x0483, idProduct=0xDF11,find_all=1)
devs = usb.core.find(idVendor=0x0483, idProduct=0xDF11, find_all=1)
for x in devs:
if ser == (usb.util.get_string(x,x.iSerialNumber)):
print('connecting to ',ser)
if ser == (usb.util.get_string(x, x.iSerialNumber)):
print('connecting to ', ser)
self.dev = x
break
else:
self.dev = usb.core.find(idVendor=0x0483, idProduct=0xDF11,)
self.dev = usb.core.find(idVendor=0x0483, idProduct=0xDF11)
if self.dev is None:
raise RuntimeError('No ST DFU devices found.')
@ -418,7 +428,9 @@ class DFUDevice:
def get_status(self,):
# bmReqType, bmReq, wValue, wIndex, data/size
s = self.dev.ctrl_transfer(DFU.type.RECEIVE, DFU.bmReq.GETSTATUS,0, self.intNum, 6)
s = self.dev.ctrl_transfer(
DFU.type.RECEIVE, DFU.bmReq.GETSTATUS, 0, self.intNum, 6
)
return DFU.status(s)
def state(self,):
@ -426,14 +438,18 @@ class DFUDevice:
def clear_status(self,):
# bmReqType, bmReq, wValue, wIndex, data/size
s = self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.CLRSTATUS, 0, self.intNum, None)
s = self.dev.ctrl_transfer(
DFU.type.SEND, DFU.bmReq.CLRSTATUS, 0, self.intNum, None
)
def upload(self,block,size):
def upload(self, block, size):
"""
address is ((block 2) × size) + 0x08000000
"""
# bmReqType, bmReq, wValue, wIndex, data/size
return self.dev.ctrl_transfer(DFU.type.RECEIVE, DFU.bmReq.UPLOAD, block, self.intNum, size)
return self.dev.ctrl_transfer(
DFU.type.RECEIVE, DFU.bmReq.UPLOAD, block, self.intNum, size
)
def set_addr(self, addr):
# must get_status after to take effect
@ -441,19 +457,21 @@ class DFUDevice:
def dnload(self, block, data):
# bmReqType, bmReq, wValue, wIndex, data/size
return self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.DNLOAD, block, self.intNum, data)
return self.dev.ctrl_transfer(
DFU.type.SEND, DFU.bmReq.DNLOAD, block, self.intNum, data
)
def erase(self, a):
d = [0x41, a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff, (a >> 24) & 0xff]
d = [0x41, a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, (a >> 24) & 0xFF]
return self.dnload(0x0, d)
def mass_erase(self):
# self.set_addr(0x08000000)
# self.block_on_state(DFU.state.DOWNLOAD_BUSY)
# assert(DFU.state.DOWNLOAD_IDLE == self.state())
self.dnload(0x0, [0x41,])
self.dnload(0x0, [0x41])
self.block_on_state(DFU.state.DOWNLOAD_BUSY)
assert(DFU.state.DOWNLOAD_IDLE == self.state())
assert DFU.state.DOWNLOAD_IDLE == self.state()
def write_page(self, addr, data):
if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE):
@ -468,10 +486,10 @@ class DFUDevice:
self.dnload(addr, data)
self.block_on_state(DFU.state.DOWNLOAD_BUSY)
assert(DFU.state.DOWNLOAD_IDLE == self.state())
assert DFU.state.DOWNLOAD_IDLE == self.state()
def read_mem(self, addr, size):
addr = DFUDevice.addr2block(addr,size)
addr = DFUDevice.addr2block(addr, size)
if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE):
self.clear_status()
@ -479,12 +497,12 @@ class DFUDevice:
if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE):
raise RuntimeError('DFU device not in correct state for reading memory.')
return self.upload(addr,size)
return self.upload(addr, size)
def block_on_state(self,state):
def block_on_state(self, state):
s = self.get_status()
while s.state == state:
time.sleep(s.timeout/1000.0)
time.sleep(s.timeout / 1000.0)
s = self.get_status()
def detach(self,):
@ -503,7 +521,7 @@ class DFUDevice:
def attempt_to_find_device(p):
found = False
for i in range(0,5):
for i in range(0, 5):
try:
p.find_device()
found = True
@ -512,6 +530,7 @@ def attempt_to_find_device(p):
time.sleep(0.2)
return found
def attempt_to_boot_bootloader(p):
try:
@ -520,20 +539,26 @@ def attempt_to_boot_bootloader(p):
pass
except CtapError as e:
if e.code == CtapError.ERR.INVALID_COMMAND:
print('Solo appears to not be a solo hacker. Try holding down the button for 2 while you plug token in.')
print(
'Solo appears to not be a solo hacker. Try holding down the button for 2 while you plug token in.'
)
sys.exit(1)
else:
raise(e)
raise (e)
print('Solo rebooted. Reconnecting...')
time.sleep(.500)
time.sleep(0.500)
if not attempt_to_find_device(p):
raise RuntimeError('Failed to reconnect!')
def solo_main():
parser = argparse.ArgumentParser()
parser.add_argument("--rng", action="store_true", help = 'Continuously dump random numbers generated from Solo.')
parser.add_argument("--wink", action="store_true", help = 'HID Wink command.')
parser.add_argument(
"--rng",
action="store_true",
help='Continuously dump random numbers generated from Solo.',
)
parser.add_argument("--wink", action="store_true", help='HID Wink command.')
args = parser.parse_args()
p = SoloClient()
@ -549,35 +574,40 @@ def solo_main():
p.wink()
sys.exit(0)
def asked_for_help():
for i,v in enumerate(sys.argv):
for i, v in enumerate(sys.argv):
if v == '-h' or v == '--help':
return True
return False
def monitor_main():
if asked_for_help() or len(sys.argv) != 2:
print(
"""
"""
Reads serial output from USB serial port on Solo hacker. Automatically reconnects.
usage: %s <serial-port> [-h]
* <serial-port> will look like COM10 or /dev/ttyACM0 or something.
* baud is 115200.
""" % sys.argv[0])
"""
% sys.argv[0]
)
sys.exit(1)
port = sys.argv[1]
ser = serial.Serial(port,115200,timeout=.05)
ser = serial.Serial(port, 115200, timeout=0.05)
def reconnect():
while(1):
while 1:
time.sleep(0.02)
try:
ser = serial.Serial(port,115200,timeout=.05)
ser = serial.Serial(port, 115200, timeout=0.05)
return ser
except serial.SerialException:
pass
while 1:
try:
d = ser.read(1)
@ -588,34 +618,37 @@ def monitor_main():
sys.stdout.buffer.write(d)
sys.stdout.flush()
def genkey_main():
from ecdsa import SigningKey, NIST256p
from ecdsa.util import randrange_from_seed__trytryagain
if asked_for_help() or len(sys.argv) not in (2,3):
if asked_for_help() or len(sys.argv) not in (2, 3):
print(
"""
"""
Generates key pair that can be used for Solo's signed firmware updates.
usage: %s <output-pem-file> [input-seed-file] [-h]
* Generates NIST P256 keypair.
* Public key must be copied into correct source location in solo bootloader
* The private key can be used for signing updates.
* You may optionally supply a file to seed the RNG for key generating.
""" % sys.argv[0])
"""
% sys.argv[0]
)
sys.exit(1)
if len(sys.argv) > 2:
seed = sys.argv[2]
print('using input seed file ', seed)
rng = open(seed,'rb').read()
rng = open(seed, 'rb').read()
secexp = randrange_from_seed__trytryagain(rng, NIST256p.order)
sk = SigningKey.from_secret_exponent(secexp,curve = NIST256p)
sk = SigningKey.from_secret_exponent(secexp, curve=NIST256p)
else:
sk = SigningKey.generate(curve = NIST256p)
sk = SigningKey.generate(curve=NIST256p)
sk_name = sys.argv[1]
print('Signing key for signing device firmware: '+sk_name)
open(sk_name,'wb+').write(sk.to_pem())
print('Signing key for signing device firmware: ' + sk_name)
open(sk_name, 'wb+').write(sk.to_pem())
vk = sk.get_verifying_key()
@ -623,31 +656,35 @@ def genkey_main():
print()
print([c for c in vk.to_string()])
print()
print(''.join(['%02x'%c for c in vk.to_string()]))
print(''.join(['%02x' % c for c in vk.to_string()]))
print()
print('"\\x' + '\\x'.join(['%02x'%c for c in vk.to_string()]) + '"')
print('"\\x' + '\\x'.join(['%02x' % c for c in vk.to_string()]) + '"')
print()
def sign_main():
if asked_for_help() or len(sys.argv) != 4:
print('Signs a firmware hex file, outputs a .json file that can be used for signed update.')
print(
'Signs a firmware hex file, outputs a .json file that can be used for signed update.'
)
print('usage: %s <signing-key.pem> <app.hex> <output.json> [-h]' % sys.argv[0])
print()
sys.exit(1)
msg = get_firmware_object(sys.argv[1],sys.argv[2])
msg = get_firmware_object(sys.argv[1], sys.argv[2])
print('Saving signed firmware to', sys.argv[3])
wfile = open(sys.argv[3],'wb+')
wfile = open(sys.argv[3], 'wb+')
wfile.write(json.dumps(msg).encode())
wfile.close()
def use_dfu(args):
fw = args.__dict__['[firmware]']
for i in range(0,8):
for i in range(0, 8):
dfu = DFUDevice()
try:
dfu.find(ser = args.dfu_serial)
dfu.find(ser=args.dfu_serial)
except RuntimeError:
time.sleep(0.25)
dfu = None
@ -665,76 +702,124 @@ def use_dfu(args):
chunk = 2048
seg = ih.segments()[0]
size = sum([max(x[1] - x[0],chunk) for x in ih.segments()])
size = sum([max(x[1] - x[0], chunk) for x in ih.segments()])
total = 0
t1 = time.time()*1000
t1 = time.time() * 1000
print('erasing...')
try:
dfu.mass_erase()
except usb.core.USBError:
dfu.write_page(0x08000000 + 2048*10,'ZZFF'*(2048//4))
dfu.write_page(0x08000000 + 2048 * 10, 'ZZFF' * (2048 // 4))
dfu.mass_erase()
page = 0
for start,end in ih.segments():
for start, end in ih.segments():
for i in range(start, end, chunk):
page += 1
s = i
data = ih.tobinarray(start=i,size = chunk)
dfu.write_page(i,data)
data = ih.tobinarray(start=i, size=chunk)
dfu.write_page(i, data)
total += chunk
progress = total/float(size)*100
progress = total / float(size) * 100
sys.stdout.write('downloading %.2f%% %08x - %08x ... \r' % (progress,i,i+page))
sys.stdout.write(
'downloading %.2f%% %08x - %08x ... \r'
% (progress, i, i + page)
)
# time.sleep(0.100)
# print('done')
# print(dfu.read_mem(i,16))
t2 = time.time()*1000
t2 = time.time() * 1000
print()
print('time: %d ms' %(t2 - t1))
print('time: %d ms' % (t2 - t1))
print('verifying...')
progress = 0
for start,end in ih.segments():
for start, end in ih.segments():
for i in range(start, end, chunk):
data1 = (dfu.read_mem(i,2048))
data2 = ih.tobinarray(start=i,size = chunk)
data1 = dfu.read_mem(i, 2048)
data2 = ih.tobinarray(start=i, size=chunk)
total += chunk
progress = total/float(size)*100
sys.stdout.write('reading %.2f%% %08x - %08x ... \r' % (progress,i,i+page))
if (end-start) == chunk:
assert(data1 == data2)
progress = total / float(size) * 100
sys.stdout.write(
'reading %.2f%% %08x - %08x ... \r'
% (progress, i, i + page)
)
if (end - start) == chunk:
assert data1 == data2
print()
print('firmware readback verified.')
if args.detach:
dfu.detach()
def programmer_main():
parser = argparse.ArgumentParser()
parser.add_argument("[firmware]", nargs='?', default='', help = 'firmware file. Either a JSON or hex file. JSON file contains signature while hex does not.')
parser.add_argument("--use-hid", action="store_true", help = 'Programs using custom HID command (default). Quicker than using U2F authenticate which is what a browser has to use.')
parser.add_argument("--use-u2f", action="store_true", help = 'Programs using U2F authenticate. This is what a web application will use.')
parser.add_argument("--no-reset", action="store_true", help = 'Don\'t reset after writing firmware. Stay in bootloader mode.')
parser.add_argument("--reset-only", action="store_true", help = 'Don\'t write anything, try to boot without a signature.')
parser.add_argument("--reboot", action="store_true", help = 'Tell bootloader to reboot.')
parser.add_argument("--enter-bootloader", action="store_true", help = 'Don\'t write anything, try to enter bootloader. Typically only supported by Solo Hacker builds.')
parser.add_argument("--st-dfu", action="store_true", help = 'Don\'t write anything, try to enter ST DFU. Warning, you could brick your Solo if you overwrite everything. You should reprogram the option bytes just to be safe (boot to Solo bootloader first, then run this command).')
parser.add_argument("--disable", action="store_true", help = 'Disable the Solo bootloader. Cannot be undone. No future updates can be applied.')
parser.add_argument("--detach", action="store_true", help = 'Detach from ST DFU and boot from main flash. Must be in DFU mode.')
parser.add_argument("--dfu-serial", default='', help = 'Specify a serial number for a specific DFU device to connect to.')
parser.add_argument("--use-dfu", action="store_true", help = 'Boot to ST-DFU before continuing.')
parser.add_argument(
"[firmware]",
nargs='?',
default='',
help='firmware file. Either a JSON or hex file. JSON file contains signature while hex does not.',
)
parser.add_argument(
"--use-hid",
action="store_true",
help='Programs using custom HID command (default). Quicker than using U2F authenticate which is what a browser has to use.',
)
parser.add_argument(
"--use-u2f",
action="store_true",
help='Programs using U2F authenticate. This is what a web application will use.',
)
parser.add_argument(
"--no-reset",
action="store_true",
help='Don\'t reset after writing firmware. Stay in bootloader mode.',
)
parser.add_argument(
"--reset-only",
action="store_true",
help='Don\'t write anything, try to boot without a signature.',
)
parser.add_argument(
"--reboot", action="store_true", help='Tell bootloader to reboot.'
)
parser.add_argument(
"--enter-bootloader",
action="store_true",
help='Don\'t write anything, try to enter bootloader. Typically only supported by Solo Hacker builds.',
)
parser.add_argument(
"--st-dfu",
action="store_true",
help='Don\'t write anything, try to enter ST DFU. Warning, you could brick your Solo if you overwrite everything. You should reprogram the option bytes just to be safe (boot to Solo bootloader first, then run this command).',
)
parser.add_argument(
"--disable",
action="store_true",
help='Disable the Solo bootloader. Cannot be undone. No future updates can be applied.',
)
parser.add_argument(
"--detach",
action="store_true",
help='Detach from ST DFU and boot from main flash. Must be in DFU mode.',
)
parser.add_argument(
"--dfu-serial",
default='',
help='Specify a serial number for a specific DFU device to connect to.',
)
parser.add_argument(
"--use-dfu", action="store_true", help='Boot to ST-DFU before continuing.'
)
args = parser.parse_args()
fw = args.__dict__['[firmware]']
p = SoloClient()
try:
p.find_device()
if args.use_dfu:
@ -781,7 +866,6 @@ def programmer_main():
p.disable_solo_bootloader()
sys.exit(0)
if fw == '' and not args.reset_only:
print('Need to supply firmware filename, or see help for more options.')
parser.print_help()
@ -794,20 +878,22 @@ def programmer_main():
print('Bootloader not active. Attempting to boot into bootloader mode...')
attempt_to_boot_bootloader(p)
else:
raise(e)
raise (e)
except ApduError:
print('Bootloader not active. Attempting to boot into bootloader mode...')
attempt_to_boot_bootloader(p)
if args.reset_only:
p.exchange(SoloBootloader.done,0,b'A'*64)
p.exchange(SoloBootloader.done, 0, b'A' * 64)
else:
p.program_file(fw)
def main_mergehex():
if len(sys.argv) < 3:
print('usage: %s <file1.hex> <file2.hex> [...] [-s <secret_attestation_key>] <output.hex>')
print(
'usage: %s <file1.hex> <file2.hex> [...] [-s <secret_attestation_key>] <output.hex>'
)
sys.exit(1)
def flash_addr(num):
@ -816,54 +902,54 @@ def main_mergehex():
args = sys.argv[:]
# generic / hacker attestation key
secret_attestation_key = "1b2626ecc8f69b0f69e34fb236d76466ba12ac16c3ab5750ba064e8b90e02448"
secret_attestation_key = (
"1b2626ecc8f69b0f69e34fb236d76466ba12ac16c3ab5750ba064e8b90e02448"
)
# user supplied, optional
for i,x in enumerate(args):
for i, x in enumerate(args):
if x == '-s':
secret_attestation_key = args[i+1]
args = args[:i] + args[i+2:]
secret_attestation_key = args[i + 1]
args = args[:i] + args[i + 2 :]
break
# TODO put definitions somewhere else
PAGES = 128
APPLICATION_END_PAGE = PAGES - 19
AUTH_WORD_ADDR = (flash_addr(APPLICATION_END_PAGE)-8)
ATTEST_ADDR = (flash_addr(PAGES - 15))
AUTH_WORD_ADDR = flash_addr(APPLICATION_END_PAGE) - 8
ATTEST_ADDR = flash_addr(PAGES - 15)
first = IntelHex(args[1])
for i in range(2, len(args)-1):
for i in range(2, len(args) - 1):
print('merging %s with ' % (args[1]), args[i])
first.merge(IntelHex( args[i] ), overlap = 'replace')
first.merge(IntelHex(args[i]), overlap='replace')
first [ flash_addr(APPLICATION_END_PAGE-1) ] = 0x41
first [ flash_addr(APPLICATION_END_PAGE-1)+1 ] = 0x41
first[flash_addr(APPLICATION_END_PAGE - 1)] = 0x41
first[flash_addr(APPLICATION_END_PAGE - 1) + 1] = 0x41
first[AUTH_WORD_ADDR-4] = 0
first[AUTH_WORD_ADDR-1] = 0
first[AUTH_WORD_ADDR-2] = 0
first[AUTH_WORD_ADDR-3] = 0
first[AUTH_WORD_ADDR - 4] = 0
first[AUTH_WORD_ADDR - 1] = 0
first[AUTH_WORD_ADDR - 2] = 0
first[AUTH_WORD_ADDR - 3] = 0
first[AUTH_WORD_ADDR] = 0
first[AUTH_WORD_ADDR+1] = 0
first[AUTH_WORD_ADDR+2] = 0
first[AUTH_WORD_ADDR+3] = 0
first[AUTH_WORD_ADDR+4] = 0xff
first[AUTH_WORD_ADDR+5] = 0xff
first[AUTH_WORD_ADDR+6] = 0xff
first[AUTH_WORD_ADDR+7] = 0xff
first[AUTH_WORD_ADDR] = 0
first[AUTH_WORD_ADDR + 1] = 0
first[AUTH_WORD_ADDR + 2] = 0
first[AUTH_WORD_ADDR + 3] = 0
first[AUTH_WORD_ADDR + 4] = 0xFF
first[AUTH_WORD_ADDR + 5] = 0xFF
first[AUTH_WORD_ADDR + 6] = 0xFF
first[AUTH_WORD_ADDR + 7] = 0xFF
if secret_attestation_key is not None:
key = unhexlify(secret_attestation_key)
for i,x in enumerate(key):
for i, x in enumerate(key):
first[ATTEST_ADDR + i] = x
first.tofile(args[len(args)-1], format='hex')
first.tofile(args[len(args) - 1], format='hex')
if __name__ == '__main__':
@ -872,7 +958,7 @@ if __name__ == '__main__':
print('usage: %s <command> [options] [-h]' % sys.argv[0])
print('commands: program, solo, monitor, sign, genkey, mergehex')
print(
"""
"""
Examples:
{0} program <filename.hex|filename.json>
{0} program <all.hex> --use-dfu
@ -885,10 +971,12 @@ Examples:
{0} sign <key.pem> <firmware.hex> <output.json>
{0} genkey <output-pem-file> [rng-seed-file]
{0} mergehex bootloader.hex solo.hex combined.hex
""".format(sys.argv[0]))
""".format(
sys.argv[0]
)
)
sys.exit(1)
c = sys.argv[1]
sys.argv = sys.argv[:1] + sys.argv[2:]
sys.argv[0] = sys.argv[0] + ' ' + c