Use an Android phone as a USB NFC reader through Android Open Accessory and a virtual PC/SC reader.
  • Rust 68.2%
  • Kotlin 16.2%
  • PowerShell 14.6%
  • Shell 1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
GameTec_live 3d111d4131
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
Build Android NFC bridge with AOA and virtual PC/SC readers
2026-10-11 10:25:44 +02:00
.github/workflows Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
android Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
docs Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
platform Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
src Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
tests Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
.gitattributes Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
.gitignore Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
Cargo.lock Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
Cargo.toml Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
LICENSE Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
README.md Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00
rust-toolchain.toml Build Android NFC bridge with AOA and virtual PC/SC readers 2026-10-11 10:25:44 +02:00

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.

  1. Download/install the Android APK and desktop client, or build them below.
  2. 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.
  3. 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.
  4. 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.