Skip to content

Commit 92ad013

Browse files
committed
Add verbose logging
1 parent 2914bf7 commit 92ad013

File tree

3 files changed

+61
-40
lines changed

3 files changed

+61
-40
lines changed

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This project provides a translation bridge for NFCCTAP authenticators connected via PC/SC to a virtual USB device using CTAPHID. This enables software which only implements support for USB CTAPHID (e.g. Firefox and Chrome on Linux) to use NFC FIDO2 tokens via PC/SC as well. Fragmentation can be set using the `-f` switch.
44

5-
This project has been forked from the *Virtual WebAuthn Authenticator* project at https://github.com/UoS-SCCS/VirtualWebAuthn , which provides a fully virtualized authenticator. This implementation has been removed and replaced by the bridging code, only the HID and CTAP drivers are still used. For more information and documentation on CTAP2, see that repository, this fork has been stripped down to the bare minimum.
5+
This project has been forked from the *Virtual WebAuthn Authenticator* project at https://github.com/UoS-SCCS/VirtualWebAuthn , which provides a fully virtualized authenticator. This implementation has been removed and replaced by the bridging code, only the HID and CTAP layers are still used. For more information and documentation on CTAP2, see that repository, this fork has been stripped down to the bare minimum.
66

77
## Setup
88

@@ -42,3 +42,26 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16c0", ATTRS{idProduct
4242
```
4343

4444
If your distribution uses `plugdev`, add `, GROUP="plugdev"` to both lines.
45+
46+
## Usage
47+
48+
See the help text for command line flags:
49+
50+
```
51+
usage: bridge.py [-h] [-f [{chaining,extended}]] [-e] [-nr] [-it [IDLETIMEOUT]] [-st [SCANTIMEOUT]] [-v]
52+
53+
FIDO2 PC/SC CTAPHID Bridge
54+
55+
options:
56+
-h, --help show this help message and exit
57+
-f [{chaining,extended}], --fragmentation [{chaining,extended}]
58+
APDU fragmentation to use (default: chaining)
59+
-e, --exit-on-error Exit on APDU error responses (for fuzzing)
60+
-nr, --no-simulate-replug
61+
Do not simulate USB re-plugging (for fuzzing)
62+
-it [IDLETIMEOUT], --idle-timeout [IDLETIMEOUT]
63+
Idle timeout after which to disconnect from the card in seconds
64+
-st [SCANTIMEOUT], --scan-timeout [SCANTIMEOUT]
65+
Time to wait for a token to be scanned
66+
-v, --verbose Log verbose APDU data
67+
```

bridge.py

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
#!/usr/bin/env python3
22

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
65
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
146

157
from hid.ctap import CTAPHID
168
from hid.usb import USBHID
@@ -36,7 +28,6 @@
3628

3729
APDU_SELECT = [0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01]
3830
APDU_SELECT_RESP = [0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]
39-
APDU_DESELECT = [0x80, 0x12, 0x01, 0x00]
4031

4132
scripts = Path(__file__).parent.resolve() / "scripts"
4233

@@ -63,13 +54,17 @@ def matches(self, atr, reader=None):
6354
class LoggingCardConnectionObserver(CardConnectionObserver):
6455
def update (self, cardconnection, ccevent):
6556
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())
6760
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())
6964
elif(ccevent.type == "connect"):
70-
log.debug("Event: Card connected")
65+
log.info("Event: Card connected")
7166
elif(ccevent.type == "disconnect"):
72-
log.debug("Event: Card disconnected")
67+
log.info("Event: Card disconnected")
7368
bridge._card = None
7469

7570
class Bridge():
@@ -130,20 +125,12 @@ def timeout_card(self):
130125
if(not self._card is None):
131126
log.info("Card connection was idle too long, disconnecting.")
132127
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'"))
134129
time.sleep(1)
135130

136131
def reset_timeout(self):
137132
self._timeout_last = datetime.datetime.now()
138133

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-
147134
def ensure_card(self):
148135
if (self._card == None):
149136
log.info("Transmit requested, watching for FIDO2 cards ...")
@@ -158,11 +145,11 @@ def ensure_card(self):
158145
self._card.connection.addObserver(LoggingCardConnectionObserver())
159146
self._card.connection.connect()
160147
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'"))
162149
return True
163150
except Exception as e:
164151
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'"))
166153
raise NoCardException(hresult=0, message="No valid card presented in time: " + str(e))
167154
return False
168155

@@ -196,6 +183,14 @@ def requires_up(self, cbor_data):
196183

197184
def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=None)->bytes:
198185
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")
199194

200195
if(self.requires_up(cbor_data)):
201196
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=
247242
log.error("APDU error: sw1=%s, sw2=%s", sw1, sw2)
248243
if(args.holderror):
249244
log.error("Encountered APDU error response, halting")
250-
self.print_failing_cbor(cbor_data)
251245
self.shutdown()
252246
sys.exit(1)
253247
else:
@@ -284,7 +278,6 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
284278
log.error("APDU error: sw1=%s, sw2=%s", sw1, sw2)
285279
if(args.holderror):
286280
log.error("Encountered APDU error response, halting")
287-
self.print_failing_cbor(cbor_data)
288281
self.shutdown()
289282
sys.exit(1)
290283
else:
@@ -312,23 +305,28 @@ def process_cbor(self, cbor_data:bytes, keep_alive: CTAPHIDKeepAlive, cid:bytes=
312305
self._init_msg_last = datetime.datetime.now()
313306

314307
if(not err is None):
315-
self.print_failing_cbor(cbor_data)
316308
raise err
317309
else:
318310
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)
319316
return res
320317
else:
318+
if(args.verbose):
319+
log.debug("No CBOR response payload")
321320
return bytes([])
322321

323322
def process_wink(self, payload:bytes, keep_alive: CTAPHIDKeepAlive)->bytes:
324323
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'")
326325
return bytes([])
327326

328327
def process_initialization(self):
329-
log.info("Initialization request received")
330328
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'"))
332330
self._init_msg_last = datetime.datetime.now()
333331
try:
334332
# New card found, simulate re-plug
@@ -354,13 +352,13 @@ def signal_handler(sig, frame):
354352
parser.add_argument('-e', '--exit-on-error', action='store_true', dest='holderror',
355353
help='Exit on APDU error responses (for fuzzing)')
356354
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)')
358356
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')
361358
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')
364362
args = parser.parse_args()
365363

366364
log.info("FIDO2 PC/SC CTAPHID Bridge running")

scripts/notify.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#!/usr/bin/env bash
22

3-
echo "Sending message: $2"
3+
echo "Sending message: $1"
44

55
USERS=$(users)
66

77
for USER in $USERS; do
88
CUID=$(id -u $USER)
9-
sudo -u $USER DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$CUID/bus notify-send "$1" "$2" -c "$3" -a "CTAP Bridge" -i dialog-information -e
9+
sudo -u $USER DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$CUID/bus notify-send "FIDO NFC Token" "$1" -c "$2" -a "CTAP Bridge" -i dialog-information -e
1010
done

0 commit comments

Comments
 (0)