Skip to content

nickr1977/PinWebReset

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

YubiKey PIN Manager

A web-based tool for managing FIDO2 PINs on YubiKey devices — including first-time PIN setup, PIN change, and factory reset — without needing the YubiKey Manager desktop application.


Architecture

┌─────────────────────┐      postMessage      ┌──────────────────────────┐
│   Web UI            │ ─────────────────────▶│  Chrome Extension        │
│   (React / Vite)    │                        │  (MV3, content script /  │
│   localhost:5173    │ ◀─────────────────────│   background service     │
└─────────────────────┘      response          │   worker)                │
                                               └──────────┬───────────────┘
                                                          │ chrome.runtime
                                                          │ sendNativeMessage
                                                          ▼
                                               ┌──────────────────────────┐
                                               │  Native Host             │
                                               │  pinreset_host.py        │
                                               │  (Python, stdio)         │
                                               └──────────┬───────────────┘
                                                          │ fido2 / PC/SC
                                                          ▼
                                               ┌──────────────────────────┐
                                               │  YubiKey                 │
                                               │  (USB HID / NFC)         │
                                               └──────────────────────────┘

Why a browser extension is required: Browsers cannot access USB HID or FIDO devices directly from a web page due to security sandboxing. The Chrome Extension acts as a trusted bridge, using the Native Messaging API to communicate with a locally-installed Python process that has the OS-level privileges to reach the YubiKey.


Prerequisites

Requirement Version Notes
macOS, Linux, or Windows macOS 12+ / Ubuntu 20.04+ / Windows 10+
Python 3.8+ Must be on PATH
pip / pip3 Any recent version Used to install fido2
Chrome or Chromium Any recent version Firefox is not supported
YubiKey Series 5 or later Must support FIDO2
PC/SC reader (NFC) Optional Required for NFC-only operations

Quick Start

1. Install the native host

The native host is a Python script that Chrome's Native Messaging API calls as a subprocess. It must be registered in a specific location so Chrome can find it.

macOS / Linux:

cd /path/to/PinWebReset
chmod +x install/install.sh
./install/install.sh

Windows (run in Command Prompt as your normal user — not Administrator):

cd C:\path\to\PinWebReset
install\install.bat

The installer:

  • Verifies Python 3.8+ is available
  • Installs the fido2 library via pip
  • Writes com.yubico.pinreset.json to the correct native messaging host directory for your OS and browser
  • On Windows, also creates a .bat shim (Chrome cannot invoke Python directly on Windows) and writes the required registry key

If you already know your extension ID (see step 2), pass it to skip the placeholder step:

./install/install.sh --extension-id abcdefghijklmnopqrstuvwxyz123456

2. Load the Chrome extension

  1. Open Chrome and navigate to chrome://extensions
  2. Enable Developer mode using the toggle in the top-right corner
  3. Click Load unpacked
  4. Select the extension/ directory from this repository
  5. The extension appears in the list with a generated ID — copy that ID (32 lowercase letters)

The extension icon may appear in the Chrome toolbar. If it does not, click the puzzle-piece icon and pin it.


3. Update the native messaging manifest with your extension ID

The native messaging manifest restricts which extensions are allowed to call the native host. You must replace the placeholder with your real extension ID.

macOS / Linux:

./install/update-extension-id.sh abcdefghijklmnopqrstuvwxyz123456

Windows — edit the manifest file directly:

  1. Open %APPDATA%\Google\Chrome\NativeMessagingHosts\com.yubico.pinreset.json in a text editor
  2. Replace EXTENSION_ID_PLACEHOLDER with your extension ID
  3. Save the file

Restart Chrome after updating the manifest.


4. Start the web UI

cd web-ui
npm install        # first time only
npm run dev

Open http://localhost:5173 in Chrome. The page communicates with the extension via postMessage. Keep the dev server running while using the tool.


PIN Operations

Operation Description Requirements
Set PIN Configure a PIN for the first time YubiKey with no FIDO2 PIN set
Change PIN Update the existing FIDO2 PIN Current PIN required
Factory Reset Erase all FIDO2 credentials and remove PIN Physical touch held for ~5 seconds

PIN rules:

  • Minimum 4 characters (YubiKey default; some configurations require 6+)
  • Maximum 63 bytes (UTF-8 encoded)
  • Unicode is supported — the PIN is hashed before transmission to the device

Retry counter: The YubiKey tracks failed PIN attempts. After 8 consecutive failures the PIN is blocked and a factory reset is required to recover.


NFC Support

NFC-connected YubiKeys are accessed via PC/SC, not FIDO HID. Requirements:

  • A PC/SC-compatible NFC reader (e.g. ACS ACR1252, HID Omnikey)
  • PC/SC daemon running (pcscd on Linux/macOS)
  • The fido2 Python library must be installed with PC/SC support (installed automatically)

macOS: PC/SC support is built in. No additional configuration needed.

Linux: Install the PC/SC daemon and CCID driver:

sudo apt install pcscd libccid   # Debian / Ubuntu
sudo systemctl enable --now pcscd

Windows: PC/SC is built into Windows. Ensure the "Smart Card" service is running.


Linux: udev Rules for Non-Root HID Access

By default, Linux restricts access to USB HID devices to root. Add a udev rule to allow your user group to access YubiKeys without sudo:

Create /etc/udev/rules.d/70-yubikey.rules:

# YubiKey HID access for plugdev group
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", GROUP="plugdev", MODE="0660"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", GROUP="plugdev", MODE="0660"

Apply the rules:

sudo udevadm control --reload-rules
sudo udevadm trigger

Add yourself to the plugdev group (re-login required):

sudo usermod -aG plugdev "$USER"

Verify group membership after re-login:

groups | grep plugdev

Security Considerations

  • The native host is a local binary. Review native-host/pinreset_host.py before running it. It communicates only via stdin/stdout to Chrome; it does not open any network ports.
  • No network transmission. The PIN is sent from the web UI to the extension via postMessage, then from the extension to the native host via the Chrome Native Messaging API (a local IPC mechanism). Nothing leaves the machine.
  • Extension permissions are minimal. The extension requests only the permissions needed to communicate with the native host and the web UI origin.
  • The allowed_origins field in the manifest restricts native messaging access to your specific extension ID. Do not share a manifest file that contains a real extension ID with untrusted parties.
  • Factory reset is irreversible. All FIDO2 credentials stored on the YubiKey will be deleted. Hardware-backed passkeys registered with websites will need to be re-enrolled.

Troubleshooting

Extension not detected / "Extension not found"

  • Make sure the web UI is running at http://localhost:5173 (the extension checks this origin)
  • Confirm the extension is loaded and enabled at chrome://extensions
  • Hard-reload the web UI page with Cmd/Ctrl+Shift+R

Native host not responding

  • Check that install.sh (or install.bat) completed successfully
  • Verify the manifest file exists and contains the correct extension ID:
    • macOS: ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.yubico.pinreset.json
    • Linux: ~/.config/google-chrome/NativeMessagingHosts/com.yubico.pinreset.json
    • Windows: %APPDATA%\Google\Chrome\NativeMessagingHosts\com.yubico.pinreset.json
  • Run the host script manually to check for Python errors:
    python3 /path/to/native-host/pinreset_host.py
    # Then type a newline; you should see a JSON error, not a crash
  • Check Chrome's native messaging log: chrome://extensions → your extension → "Inspect views: service worker" → Console

No device found

  • Unplug and re-insert the YubiKey
  • Check the YubiKey is recognised by the OS:
    • macOS: system_profiler SPUSBDataType | grep -A5 YubiKey
    • Linux: lsusb | grep Yubico
    • Windows: Device Manager → Human Interface Devices
  • On Linux, confirm your user is in the plugdev group and udev rules are applied (see above)

Permission denied on Linux (HID)

Add the udev rules described in the Linux: udev Rules section above.

PIN blocked ("retries remaining: 0")

The YubiKey entered a locked state after too many wrong PIN attempts. A factory reset is the only recovery:

  1. Use the Factory Reset operation in this tool (requires a 5-second touch), or
  2. Use YubiKey Manager CLI: ykman fido reset

All FIDO2 credentials will be erased.

fido2 import error in native host

ModuleNotFoundError: No module named 'fido2'

Install it for the Python interpreter that runs the script:

python3 -m pip install fido2
# or, if pip3 is separate:
pip3 install fido2

On Windows, make sure you're installing into the same Python that install.bat found.

Windows: "Access is denied" writing registry key

Run install.bat as your normal user account, not as Administrator. The key is written to HKCU (current user), which never requires elevation.


Development

Project structure

PinWebReset/
├── extension/              # Chrome MV3 extension
│   ├── manifest.json
│   ├── background.js       # Service worker — native messaging bridge
│   ├── content.js          # Injected into the web UI page
│   └── icons/
├── native-host/
│   └── pinreset_host.py    # Python native messaging host
├── web-ui/                 # React + Vite front-end
│   ├── src/
│   ├── index.html
│   ├── vite.config.js
│   └── package.json
├── install/
│   ├── install.sh          # macOS / Linux installer
│   ├── install.bat         # Windows installer
│   └── update-extension-id.sh
└── README.md

Running locally

# Terminal 1 — web UI dev server
cd web-ui
npm install
npm run dev
# Listening on http://localhost:5173

# Terminal 2 — (no separate process needed for native host or extension;
#               Chrome manages the native host lifecycle)

Load the extension from extension/ as an unpacked extension (see step 2 above). The extension reloads automatically when you click the refresh icon on chrome://extensions.

Testing the native host directly

The native host uses the Chrome native messaging wire format: a 4-byte little-endian length prefix followed by a UTF-8 JSON payload.

A minimal test with Python:

import json, struct, subprocess, sys

HOST = "/path/to/native-host/pinreset_host.py"
proc = subprocess.Popen(
    [sys.executable, HOST],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
)

msg = json.dumps({"action": "list_devices"}).encode()
proc.stdin.write(struct.pack("<I", len(msg)) + msg)
proc.stdin.flush()

length = struct.unpack("<I", proc.stdout.read(4))[0]
response = json.loads(proc.stdout.read(length))
print(response)
proc.terminate()

Making a production build of the web UI

cd web-ui
npm run build
# Output in web-ui/dist/ — serve with any static file server

About

YubiKey FIDO PIN management tool — Chrome extension + Python native host + React UI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors