139 lines
3.8 KiB
Python
Executable File
139 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Minimal host tool for checking CTAP2/FIDO2 connectivity to a ChromeCard.
|
|
|
|
Requires:
|
|
pip install fido2
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from typing import Any
|
|
|
|
try:
|
|
from fido2.hid import CtapHidDevice
|
|
from fido2.ctap2 import Ctap2
|
|
except Exception as exc:
|
|
print("Missing dependency: python-fido2", file=sys.stderr)
|
|
print("Install with: python3 -m pip install fido2", file=sys.stderr)
|
|
print(f"Import error: {exc}", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
|
|
def _json_default(value: Any) -> Any:
|
|
if isinstance(value, bytes):
|
|
return value.hex()
|
|
if isinstance(value, set):
|
|
return sorted(value)
|
|
return str(value)
|
|
|
|
|
|
def list_devices() -> list[CtapHidDevice]:
|
|
return list(CtapHidDevice.list_devices())
|
|
|
|
|
|
def describe_device(dev: CtapHidDevice) -> dict[str, Any]:
|
|
desc = getattr(dev, "descriptor", None)
|
|
out = {
|
|
"product_name": getattr(desc, "product_name", None),
|
|
"manufacturer": getattr(desc, "manufacturer_string", None),
|
|
"vendor_id": getattr(desc, "vid", None),
|
|
"product_id": getattr(desc, "pid", None),
|
|
"path": getattr(desc, "path", None),
|
|
}
|
|
return out
|
|
|
|
|
|
def print_device_table(devs: list[CtapHidDevice]) -> None:
|
|
if not devs:
|
|
print("No CTAP HID devices found.")
|
|
return
|
|
for idx, dev in enumerate(devs):
|
|
info = describe_device(dev)
|
|
print(
|
|
f"[{idx}] "
|
|
f"vid:pid={info['vendor_id']}:{info['product_id']} "
|
|
f"manufacturer={info['manufacturer']} "
|
|
f"product={info['product_name']} "
|
|
f"path={info['path']}"
|
|
)
|
|
|
|
|
|
def get_info(dev: CtapHidDevice) -> dict[str, Any]:
|
|
ctap2 = Ctap2(dev)
|
|
info = ctap2.get_info()
|
|
out = {
|
|
"versions": getattr(info, "versions", None),
|
|
"extensions": getattr(info, "extensions", None),
|
|
"aaguid": getattr(info, "aaguid", None),
|
|
"options": getattr(info, "options", None),
|
|
"max_msg_size": getattr(info, "max_msg_size", None),
|
|
"pin_uv_protocols": getattr(info, "pin_uv_protocols", None),
|
|
"firmware_version": getattr(info, "firmware_version", None),
|
|
"transports": getattr(info, "transports", None),
|
|
"algorithms": getattr(info, "algorithms", None),
|
|
}
|
|
return out
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Probe USB FIDO2/CTAP2 devices")
|
|
parser.add_argument(
|
|
"--index",
|
|
type=int,
|
|
default=0,
|
|
help="Device index from --list output (default: 0)",
|
|
)
|
|
parser.add_argument(
|
|
"--list",
|
|
action="store_true",
|
|
help="List devices only",
|
|
)
|
|
parser.add_argument(
|
|
"--json",
|
|
action="store_true",
|
|
help="Print CTAP2 info as JSON",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
devs = list_devices()
|
|
if args.list:
|
|
print_device_table(devs)
|
|
return 0 if devs else 1
|
|
|
|
if not devs:
|
|
print("No CTAP HID devices found.")
|
|
return 1
|
|
|
|
if args.index < 0 or args.index >= len(devs):
|
|
print(f"Invalid --index {args.index}; found {len(devs)} device(s).", file=sys.stderr)
|
|
print_device_table(devs)
|
|
return 2
|
|
|
|
dev = devs[args.index]
|
|
dev_meta = describe_device(dev)
|
|
info = get_info(dev)
|
|
|
|
if args.json:
|
|
payload = {"device": dev_meta, "ctap2_info": info}
|
|
print(json.dumps(payload, indent=2, default=_json_default))
|
|
return 0
|
|
|
|
print("Selected device:")
|
|
print(
|
|
f" vid:pid={dev_meta['vendor_id']}:{dev_meta['product_id']} "
|
|
f"manufacturer={dev_meta['manufacturer']} "
|
|
f"product={dev_meta['product_name']}"
|
|
)
|
|
print(f" path={dev_meta['path']}")
|
|
print("CTAP2 getInfo:")
|
|
print(json.dumps(info, indent=2, default=_json_default))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|