75 lines
2.1 KiB
Python
75 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Manual CTAPHID INIT probe for a specific hidraw node.
|
|
|
|
This bypasses python-fido2's device bootstrap so we can see whether the raw HID
|
|
transport itself exchanges packets on the expected FIDO interface.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import secrets
|
|
import select
|
|
import struct
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
CTAPHID_INIT = 0x06
|
|
TYPE_INIT = 0x80
|
|
BROADCAST_CID = 0xFFFFFFFF
|
|
|
|
|
|
def build_init_packet(nonce: bytes) -> bytes:
|
|
frame = struct.pack(">IBH", BROADCAST_CID, TYPE_INIT | CTAPHID_INIT, len(nonce)) + nonce
|
|
return b"\0" + frame.ljust(64, b"\0")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Manual CTAPHID INIT probe")
|
|
parser.add_argument("--device-path", default="/dev/hidraw0")
|
|
parser.add_argument("--timeout", type=float, default=3.0)
|
|
args = parser.parse_args()
|
|
|
|
path = Path(args.device_path)
|
|
if not path.exists():
|
|
print(f"missing device: {path}", file=sys.stderr)
|
|
return 2
|
|
|
|
nonce = secrets.token_bytes(8)
|
|
packet = build_init_packet(nonce)
|
|
print(f"device={path}")
|
|
print(f"nonce={nonce.hex()}")
|
|
print(f"write_len={len(packet)}")
|
|
print(f"write_hex={packet.hex()}")
|
|
|
|
fd = os.open(str(path), os.O_RDWR)
|
|
try:
|
|
written = os.write(fd, packet)
|
|
print(f"written={written}")
|
|
poller = select.poll()
|
|
poller.register(fd, select.POLLIN)
|
|
events = poller.poll(int(args.timeout * 1000))
|
|
print(f"events={events}")
|
|
if not events:
|
|
print("timeout_waiting_for_response")
|
|
return 1
|
|
response = os.read(fd, 64)
|
|
print(f"read_len={len(response)}")
|
|
print(f"read_hex={response.hex()}")
|
|
if len(response) >= 24:
|
|
cid, cmd, bc = struct.unpack(">IBH", response[:7])
|
|
print(f"resp_cid=0x{cid:08x}")
|
|
print(f"resp_cmd=0x{cmd:02x}")
|
|
print(f"resp_bc={bc}")
|
|
print(f"resp_payload={response[7:7+bc].hex()}")
|
|
return 0
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|