1
1
#!/usr/bin/env python3
2
2
3
- import sys , signal , os , logging , threading , argparse , time , datetime
4
- import cbor2 , json , usb .core , usb .util
3
+ import sys , signal , os , logging , threading , argparse , time , datetime , select
4
+ import cbor2 , json , usb .core , usb .util , setproctitle
5
5
from pathlib import Path
6
6
7
7
from hid .ctap import CTAPHID
25
25
26
26
bridge = None
27
27
args = None
28
+ presence = threading .Lock ()
28
29
29
30
APDU_SELECT = [0x00 , 0xA4 , 0x04 , 0x00 , 0x08 , 0xA0 , 0x00 , 0x00 , 0x06 , 0x47 , 0x2F , 0x00 , 0x01 ]
30
31
APDU_SELECT_RESP = [0x46 , 0x49 , 0x44 , 0x4F , 0x5F , 0x32 , 0x5F , 0x30 ]
31
32
32
33
scripts = Path (__file__ ).parent .resolve () / "scripts"
34
+ cpipe = Path (__file__ ).parent .resolve () / "cpipe"
33
35
34
36
class BytesEncoder (json .JSONEncoder ):
35
37
def default (self , obj ):
@@ -193,8 +195,20 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
193
195
log .debug ("No CBOR command payload" )
194
196
195
197
if (self .requires_up (cbor_data )):
196
- log .info ("CTAP command requires user presence, sending prompt response" )
197
- keep_alive .update_status (CTAPHID_KEEPALIVE_STATUS .STATUS_UPNEEDED )
198
+ present = False
199
+ if (not args .simpresence ) :
200
+ log .info ("Waiting for control pipe to signal user presence" )
201
+ try :
202
+ if (not presence .acquire (timeout = 5 )):
203
+ raise Exception ("Timeout" )
204
+ preset = True
205
+ except Exception as e :
206
+ raise BridgeException (CTAP_STATUS_CODE .CTAP2_ERR_USER_ACTION_TIMEOUT , "Cannot acquire presence lock: " + str (e ))
207
+ else :
208
+ present = True
209
+ if (present ):
210
+ log .info ("CTAP command requires user presence, sending 'waiting for acknowledgment' status" )
211
+ keep_alive .update_status (CTAPHID_KEEPALIVE_STATUS .STATUS_UPNEEDED )
198
212
199
213
res = bytes ([])
200
214
err = None
@@ -339,12 +353,29 @@ def get_version(self)->AuthenticatorVersion:
339
353
return AuthenticatorVersion (1 , 0 , 0 , 0 )
340
354
341
355
342
- def signal_handler (sig , frame ):
356
+ def shutdown_handler (sig , frame ):
343
357
log .info ("Shutting down" )
344
358
bridge .shutdown ()
345
359
sys .exit (0 )
346
360
361
+ def presence_thread ():
362
+ with open (cpipe ) as fifo :
363
+ while True :
364
+ select .select ([fifo ],[],[fifo ])
365
+ data = fifo .read ()
366
+ if (data .strip () == "p" ):
367
+ log .info ("Simulating user presence for pending response" )
368
+ try :
369
+ presence .release ()
370
+ except Exception as e :
371
+ log .error ("Cannot release presence lock: %s" , e )
372
+ elif (data .strip () == "r" ):
373
+ log .info ("Signaling external card reset" )
374
+ bridge .disconnect_card ()
375
+
347
376
if __name__ == "__main__" :
377
+ setproctitle .setproctitle ('ctap-bridge' )
378
+
348
379
parser = argparse .ArgumentParser (description = 'FIDO2 PC/SC CTAPHID Bridge' )
349
380
parser .add_argument ('-f' , '--fragmentation' , nargs = '?' , dest = 'frag' , type = str ,
350
381
const = 'chaining' , default = 'chaining' , choices = ['chaining' , 'extended' ],
@@ -353,6 +384,8 @@ def signal_handler(sig, frame):
353
384
help = 'Exit on APDU error responses (for fuzzing)' )
354
385
parser .add_argument ('-nr' , '--no-simulate-replug' , action = 'store_false' , dest = 'simreplug' ,
355
386
help = 'Do not simulate USB re-plugging (for fuzzing)' )
387
+ parser .add_argument ('-np' , '--no-simulate-presence' , action = 'store_false' , dest = 'simpresence' ,
388
+ help = 'Do not simulate user presence, instead wait for control pipe (for fuzzing)' )
356
389
parser .add_argument ('-it' , '--idle-timeout' , nargs = '?' , dest = 'idletimeout' , type = int ,
357
390
const = 20 , default = 20 , help = 'Idle timeout after which to disconnect from the card in seconds' )
358
391
parser .add_argument ('-st' , '--scan-timeout' , nargs = '?' , dest = 'scantimeout' , type = int ,
@@ -361,12 +394,23 @@ def signal_handler(sig, frame):
361
394
help = 'Log verbose APDU data' )
362
395
args = parser .parse_args ()
363
396
397
+ if (not args .simpresence ):
398
+ try :
399
+ os .mkfifo (cpipe )
400
+ except Exception as e :
401
+ log .error ("Cannot create user presence control pipe: %s" , e )
402
+ pr = threading .Thread (target = presence_thread )
403
+ pr .daemon = True
404
+ pr .start ()
405
+
364
406
log .info ("FIDO2 PC/SC CTAPHID Bridge running" )
365
407
log .info ("Press Ctrl+C to stop" )
366
408
367
409
bridge = Bridge ()
368
410
bridge .start ()
369
411
370
- signal .signal (signal .SIGINT , signal_handler )
371
- signal .signal (signal .SIGTERM , signal_handler )
412
+ presence .acquire ()
413
+
414
+ signal .signal (signal .SIGINT , shutdown_handler )
415
+ signal .signal (signal .SIGTERM , shutdown_handler )
372
416
signal .pause ()
0 commit comments