1
1
#!/usr/bin/env python3
2
2
3
- import sys , signal , os
4
- import argparse , time , datetime
5
- import logging
3
+ import sys , signal , os , logging , threading , argparse , time , datetime
4
+ import cbor2 , json , usb .core , usb .util
6
5
from pathlib import Path
7
- import threading
8
- from random import randrange
9
-
10
- import cbor2 , json
11
-
12
- import usb .core
13
- import usb .util
14
6
15
7
from hid .ctap import CTAPHID
16
8
from hid .usb import USBHID
36
28
37
29
APDU_SELECT = [0x00 , 0xA4 , 0x04 , 0x00 , 0x08 , 0xA0 , 0x00 , 0x00 , 0x06 , 0x47 , 0x2F , 0x00 , 0x01 ]
38
30
APDU_SELECT_RESP = [0x46 , 0x49 , 0x44 , 0x4F , 0x5F , 0x32 , 0x5F , 0x30 ]
39
- APDU_DESELECT = [0x80 , 0x12 , 0x01 , 0x00 ]
40
31
41
32
scripts = Path (__file__ ).parent .resolve () / "scripts"
42
33
@@ -63,13 +54,17 @@ def matches(self, atr, reader=None):
63
54
class LoggingCardConnectionObserver (CardConnectionObserver ):
64
55
def update (self , cardconnection , ccevent ):
65
56
if (ccevent .type == "command" ):
66
- log .debug ("APDU CMD: DATA=%s" , bytes (ccevent .args [0 ]).hex ())
57
+ log .info ("APDU command: %s bytes data" , len (ccevent .args [0 ]))
58
+ if (args .verbose ):
59
+ log .debug ("APDU command: DATA=%s" , bytes (ccevent .args [0 ]).hex ())
67
60
elif (ccevent .type == "response" ):
68
- log .debug ("APDU RES: SW1=%s, SW2=%s, DATA=%s" , hex (ccevent .args [1 ]), hex (ccevent .args [2 ]), bytes (ccevent .args [0 ]).hex ())
61
+ log .info ("APDU response: SW1=%s, SW2=%s, %s bytes data" , hex (ccevent .args [1 ]), hex (ccevent .args [2 ]), len (ccevent .args [0 ]))
62
+ if (args .verbose ):
63
+ log .debug ("APDU response: DATA=%s" , bytes (ccevent .args [0 ]).hex ())
69
64
elif (ccevent .type == "connect" ):
70
- log .debug ("Event: Card connected" )
65
+ log .info ("Event: Card connected" )
71
66
elif (ccevent .type == "disconnect" ):
72
- log .debug ("Event: Card disconnected" )
67
+ log .info ("Event: Card disconnected" )
73
68
bridge ._card = None
74
69
75
70
class Bridge ():
@@ -130,20 +125,12 @@ def timeout_card(self):
130
125
if (not self ._card is None ):
131
126
log .info ("Card connection was idle too long, disconnecting." )
132
127
self .disconnect_card ()
133
- os .system (scripts / ("notify.sh 'FIDO2 NFC Token' ' The token was disconnected due to being unused.' 'device.removed'" ))
128
+ os .system (scripts / ("notify.sh 'The token was disconnected due to being unused.' 'device.removed'" ))
134
129
time .sleep (1 )
135
130
136
131
def reset_timeout (self ):
137
132
self ._timeout_last = datetime .datetime .now ()
138
133
139
- def print_failing_cbor (self , cbor ):
140
- if (len (cbor ) > 1 ):
141
- log .debug ("Failing CBOR command: %s, payload:" , AUTHN_CMD (cbor [:1 ]).name )
142
- try :
143
- log .debug (json .dumps (cbor2 .loads (cbor [1 :]), indent = 2 , cls = BytesEncoder ))
144
- except :
145
- log .debug ("Decoding failed" )
146
-
147
134
def ensure_card (self ):
148
135
if (self ._card == None ):
149
136
log .info ("Transmit requested, watching for FIDO2 cards ..." )
@@ -158,11 +145,11 @@ def ensure_card(self):
158
145
self ._card .connection .addObserver (LoggingCardConnectionObserver ())
159
146
self ._card .connection .connect ()
160
147
self ._card .connection .transmit (APDU_SELECT )
161
- os .system (scripts / ("notify.sh 'FIDO2 NFC Token' ' Found token on " + str (self ._card .connection .getReader ()) + ".' 'device.added'" ))
148
+ os .system (scripts / ("notify.sh 'Found token on " + str (self ._card .connection .getReader ()) + ".' 'device.added'" ))
162
149
return True
163
150
except Exception as e :
164
151
self ._timeout_paused = False
165
- os .system (scripts / ("notify.sh 'FIDO2 NFC Token' ' No valid token was found in time.' 'device.removed'" ))
152
+ os .system (scripts / ("notify.sh 'No valid token was found in time.' 'device.removed'" ))
166
153
raise NoCardException (hresult = 0 , message = "No valid card presented in time: " + str (e ))
167
154
return False
168
155
@@ -196,6 +183,14 @@ def requires_up(self, cbor_data):
196
183
197
184
def process_cbor (self , cbor_data :bytes , keep_alive : CTAPHIDKeepAlive , cid :bytes = None )-> bytes :
198
185
log .info ("Transmitting CTAP command: %s" , AUTHN_CMD (cbor_data [:1 ]).name )
186
+ if (args .verbose ):
187
+ if (len (cbor_data ) > 1 ):
188
+ try :
189
+ log .debug ("CBOR command payload: " + json .dumps (cbor2 .loads (cbor_data [1 :]), indent = 2 , cls = BytesEncoder ))
190
+ except Exception as e :
191
+ log .debug ("CBOR command payload decoding failed: %s" , e )
192
+ else :
193
+ log .debug ("No CBOR command payload" )
199
194
200
195
if (self .requires_up (cbor_data )):
201
196
log .info ("CTAP command requires user presence, sending prompt response" )
@@ -247,7 +242,6 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
247
242
log .error ("APDU error: sw1=%s, sw2=%s" , sw1 , sw2 )
248
243
if (args .holderror ):
249
244
log .error ("Encountered APDU error response, halting" )
250
- self .print_failing_cbor (cbor_data )
251
245
self .shutdown ()
252
246
sys .exit (1 )
253
247
else :
@@ -284,7 +278,6 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
284
278
log .error ("APDU error: sw1=%s, sw2=%s" , sw1 , sw2 )
285
279
if (args .holderror ):
286
280
log .error ("Encountered APDU error response, halting" )
287
- self .print_failing_cbor (cbor_data )
288
281
self .shutdown ()
289
282
sys .exit (1 )
290
283
else :
@@ -312,23 +305,28 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
312
305
self ._init_msg_last = datetime .datetime .now ()
313
306
314
307
if (not err is None ):
315
- self .print_failing_cbor (cbor_data )
316
308
raise err
317
309
else :
318
310
if (not res is None and len (res ) > 0 ):
311
+ if (args .verbose ):
312
+ try :
313
+ log .debug ("CBOR response payload: " + json .dumps (cbor2 .loads (res ), indent = 2 , cls = BytesEncoder ))
314
+ except Exception as e :
315
+ log .debug ("CBOR response payload decoding failed: %s" , e )
319
316
return res
320
317
else :
318
+ if (args .verbose ):
319
+ log .debug ("No CBOR response payload" )
321
320
return bytes ([])
322
321
323
322
def process_wink (self , payload :bytes , keep_alive : CTAPHIDKeepAlive )-> bytes :
324
323
log .info ("Wink request received" )
325
- os .system (scripts / "notify.sh 'FIDO2 NFC Token' ' A service requests your attention.' 'device'" )
324
+ os .system (scripts / "notify.sh 'A service requests your attention.' 'device'" )
326
325
return bytes ([])
327
326
328
327
def process_initialization (self ):
329
- log .info ("Initialization request received" )
330
328
if ((datetime .datetime .now () - self ._init_msg_last ).total_seconds () > 5 ):
331
- os .system (scripts / ("notify.sh 'FIDO2 NFC Token' ' A service requests a connection to your token. Place your token on a reader.' 'device'" ))
329
+ os .system (scripts / ("notify.sh 'A service requests a connection to your token. Place your token on a reader.' 'device'" ))
332
330
self ._init_msg_last = datetime .datetime .now ()
333
331
try :
334
332
# New card found, simulate re-plug
@@ -354,13 +352,13 @@ def signal_handler(sig, frame):
354
352
parser .add_argument ('-e' , '--exit-on-error' , action = 'store_true' , dest = 'holderror' ,
355
353
help = 'Exit on APDU error responses (for fuzzing)' )
356
354
parser .add_argument ('-nr' , '--no-simulate-replug' , action = 'store_false' , dest = 'simreplug' ,
357
- help = 'Simulate USB re-plugging (for fuzzing)' )
355
+ help = 'Do not simulate USB re-plugging (for fuzzing)' )
358
356
parser .add_argument ('-it' , '--idle-timeout' , nargs = '?' , dest = 'idletimeout' , type = int ,
359
- const = 20 , default = 20 ,
360
- help = 'Idle timeout after which to disconnect from the card in seconds' )
357
+ const = 20 , default = 20 , help = 'Idle timeout after which to disconnect from the card in seconds' )
361
358
parser .add_argument ('-st' , '--scan-timeout' , nargs = '?' , dest = 'scantimeout' , type = int ,
362
- const = 30 , default = 30 ,
363
- help = 'Time to wait for a token to be scanned' )
359
+ const = 30 , default = 30 , help = 'Time to wait for a token to be scanned' )
360
+ parser .add_argument ('-v' , '--verbose' , action = 'store_true' , dest = 'verbose' ,
361
+ help = 'Log verbose APDU data' )
364
362
args = parser .parse_args ()
365
363
366
364
log .info ("FIDO2 PC/SC CTAPHID Bridge running" )
0 commit comments