- Rust 68.2%
- Kotlin 16.2%
- PowerShell 14.6%
- Shell 1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
Build and test / desktop (linux-x64, ubuntu-24.04) (push) Has been cancelled
Build and test / desktop (macos-arm64, macos-15) (push) Has been cancelled
Build and test / desktop (macos-x64, macos-15-intel) (push) Has been cancelled
Build and test / desktop (windows-x64, windows-2025) (push) Has been cancelled
Build and test / android (push) Has been cancelled
|
||
| .github/workflows | ||
| android | ||
| docs | ||
| platform | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| rust-toolchain.toml | ||
Android NFC Bridge
Use an Android phone as a USB NFC reader for a desktop computer. A small Rust client switches the phone into Android Open Accessory (AOA) mode and relays ISO-DEP APDUs into a virtual PC/SC smart-card reader: Windows uses an emulated USB CCID device over loopback USB/IP; Linux/macOS use vsmartcard's PC/SC driver. NFC FIDO/U2F security keys are one intended use; the bridge forwards APDUs rather than implementing or emulating an authenticator.
The Android app is a single status screen, opened by Android's accessory prompt. Keep it visible and hold the key against the NFC antenna. Android's public NFC reader API requires a resumed activity, so a notification alone cannot provide reliable background reading. No accounts, network connection, pairing, ADB forwarding, or root access on the phone are needed.
flowchart LR
App[Desktop PC/SC application] <--> CCID[Windows built-in CCID driver]
CCID <--> USBIP[Signed usbip-win2 virtual USB host]
USBIP <-->|loopback USB/IP| Rust[Rust desktop client]
App <--> Driver[Linux/macOS vsmartcard driver]
Driver <-->|loopback vpcd| Rust
Rust <-->|USB AOA| Android[Android status activity]
Android <-->|ISO-DEP NFC| Key[Security key]
Run
Prebuilt Windows x64 and Linux x64 clients and a debug-signed Android APK are available from releases. macOS currently requires building from source.
- Download/install the Android APK and desktop client, or build them below.
- Complete the one-time platform setup for the virtual reader and USB access. Windows offers to download/install the virtual reader on startup if it is not loaded.
- Start
android-nfc-bridge, connect the phone with a data cable, and accept Android's prompt to open NFC Bridge. Enable NFC and keep the phone unlocked. - Hold the security key against the phone. The client reports when the card is available through PC/SC; use a PC/SC-capable application to communicate with it.
cargo build --locked --release
# Windows: target\release\android-nfc-bridge.exe
./target/release/android-nfc-bridge
Android requires JDK 17 or newer and Android SDK 36; configure ANDROID_HOME
or android/local.properties with sdk.dir.
cd android
# Windows: .\gradlew.bat :app:assembleDebug
bash ./gradlew :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
ADB is only an optional way to install the APK. The actual bridge uses AOA USB, and the APK can also be installed by opening it on the phone. Debug APKs use the local Android debug signing key; distribution builds need your own release key.
Platform support and current limits
| Platform | Desktop client | Virtual reader setup |
|---|---|---|
| Windows x64/ARM64 | Rust + vendored libusb | Signed usbip-win2 virtual host; first-run installation prompt; accessory WinUSB setup prompt using Windows' built-in drivers |
| Linux | Rust + vendored libusb | vsmartcard-vpcd, pcscd, and USB udev permissions |
| macOS Intel/Apple Silicon | Rust + vendored libusb | vsmartcard driver bundle built for the machine and configured for AOA IDs |
Windows uses the signed usbip-win2 release, with no test-signing requirement. The installer verifies a pinned SHA-256 and Authenticode signature, requests UAC, installs the upstream virtual USB host, and checks that it is accessible. The client automatically attaches its CCID device and detaches only its own hub port on exit. Boot security settings and certificate stores are not changed by the helper. A first installation can briefly restart USB devices and require a reboot; the helper never reboots automatically. See Windows setup.
One executable is sufficient to launch the bridge, but system-wide reader emulation still needs OS driver support and first-run administrator approval. It cannot be an entirely installation-free, unprivileged reader on an arbitrary PC. The feasibility investigation explains the choice and what was actually found installed on the development machine.
PC/SC APDU access does not automatically make a key available to every browser's
WebAuthn/passkey UI. The consuming application must support FIDO over PC/SC/NFC;
some browsers and OS authenticators use different transports. The bridge does
not expose a USB HID/CTAPHID device. Phone NFC transfer limits also apply: APDUs
larger than IsoDep.maxTransceiveLength are rejected rather than rewritten.
Only ISO-DEP (ISO 14443 Type A/B) cards are supported. The ATR is synthesized from the card's historical bytes. PC/SC reset reconnects ISO-DEP; power-off is a logical no-op because Android does not expose per-card RF power control. Removal, backgrounding, replacement, and USB disconnect remove the virtual card. Each command is bound to a card generation, and USB replies carry request IDs to prevent stale responses from an earlier desktop session being accepted.
The USB/IP backend listens only on 127.0.0.1; it exports one simulated CCID
reader and never exports physical USB devices. The vpcd client only connects to
loopback. The upstream vpcd driver can listen on all
interfaces: restrict its TCP ports to local use as described in the setup guide.
APDUs are not logged or stored. Other local PC/SC applications can access the
reader according to the OS's usual smart-card access rules.
Diagnostics and development
android-nfc-bridge --doctor
android-nfc-bridge --device 18d1:4ee1 --serial YOUR_PHONE_USB_SERIAL
android-nfc-bridge --help
android-nfc-bridge --backend usbip --no-attach --usbip-port 13240
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
cd android
bash ./gradlew :app:testDebugUnitTest :app:lintDebug
--doctor lists USB devices and checks backend prerequisites without installing
anything. With vpcd it briefly connects to the reader, which can produce a transient
card insertion/removal notification. --backend auto chooses USB/IP on Windows
and vpcd elsewhere; USB/IP can also be selected explicitly on Linux for manual
attachment with its vhci_hcd client. Automatic detection considers MTP/PTP/ADB USB
interfaces; use --device for other initial USB modes. Only one selected phone
is supported at a time.
CI builds/tests Windows, Linux, Intel macOS and Apple
Silicon macOS, and uploads desktop binaries and the debug APK. Local verification
has covered Windows and Linux builds/tests, Android unit tests/lint/APK build,
and signed Windows installer downloads for x64 and ARM64. A Pixel 6 Pro and physical
FIDO key have also completed a real Windows PC/SC → USB/IP → AOA → NFC exchange:
FIDO SELECT returned U2F_V2 90 00. Windows blocks direct FIDO SELECT from
unelevated PC/SC applications; the read-only probe succeeded elevated. This
restriction also affects ordinary physical NFC readers. USB/IP integration tests
exercise enumeration, CCID power/APDUs, pending IN cancellation, and card replacement
against a simulated phone. Browser WebAuthn, native Linux NFC, ARM64 driver loading,
and native macOS still require validation. See the hardware test report.
The USB protocol is documented in docs/protocol.md.
Project code is MIT-licensed. vsmartcard (Linux/macOS) is a separate GPL-3.0-or-later dependency. usbip-win2 has its own upstream license and is downloaded separately; neither driver is bundled into the executable.