A Windows daemon/service that provides YubiKey OATH-TOTP access to WSL (Windows Subsystem for Linux) and other local applications via REST API and socket interfaces.
WSL 'cannot' directly access USB devices like YubiKeys due to USB passthrough limitations. This daemon bridges that gap by running on Windows (where YubiKey is accessible) and exposing its functionality to WSL applications.
- OATH-TOTP Support: Generate time-based one-time passwords from YubiKey
- Dual Interface:
- REST API (HTTP) for easy integration
- TCP Socket for low-latency requests
- User Notifications: Optional popup windows and sound alerts when YubiKey touch is required
- Background Service: Runs as a Windows background process
- Localhost Only: Secure by default, only accessible from local machine
- WSL Compatible: Seamless integration with Linux applications in WSL
┌─────────────────────────────────────────────────────────┐
│ Windows │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ YubiKey │◄────────┤ yk-daemon (Python) │ │
│ │ (USB) │ │ - REST API (port 5100) │ │
│ └──────────────┘ │ - Socket (port 5101) │ │
│ │ - Notifications │ │
│ └─────────┬───────────────┘ │
│ │ │
└─────────────────────────────────────┼───────────────────┘
│ localhost (127.0.0.1)
│
┌─────────────────────────────────────┼───────────────────┐
│ WSL │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Linux Applications / Scripts │ │
│ │ - curl http://127.0.0.1:5100/api/totp │ │
│ │ - netcat 127.0.0.1 5101 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
- Windows 10/11
- Python 3.12+
- Poetry (Python dependency management)
- YubiKey with OATH-TOTP configured
- WSL 2 (if accessing from Linux)
# Clone the repository
git clone <repository-url>
cd yk-daemon
# Install Poetry (if not already installed)
# See: https://python-poetry.org/docs/#installation
# Install dependencies
poetry install
# Configure (optional)
cp config.example.json config.json
# Edit config.json with your preferences
# Run the daemon
poetry run python yk-daemon.py# Install as service
poetry run python yk-daemon.py --install
# Start service
poetry run python yk-daemon.py --start
# Stop service
poetry run python yk-daemon.py --stop
# Uninstall service
poetry run python yk-daemon.py --removeBase URL: http://127.0.0.1:5100
GET /api/totpResponse:
{
"success": true,
"totp": "123456",
"timestamp": "2025-10-17T12:34:56Z"
}GET /api/accountsResponse:
{
"success": true,
"accounts": [
{"name": "GitHub", "issuer": "github.com"},
{"name": "AWS", "issuer": "aws.amazon.com"}
]
}GET /api/totp/<account_name>Response:
{
"success": true,
"account": "GitHub",
"totp": "123456",
"timestamp": "2025-10-17T12:34:56Z"
}GET /healthResponse:
{
"status": "ok",
"yubikey_connected": true,
"version": "0.1.0"
}Connect to: 127.0.0.1:5101
Request:
GET_TOTP\n
Response:
OK 123456\n
Request:
GET_TOTP GitHub\n
Response:
OK 123456\n
Request:
LIST_ACCOUNTS\n
Response:
OK GitHub,AWS,Google\n
ERROR <error_message>\n
Create a config.json file:
{
"rest_api": {
"enabled": true,
"host": "127.0.0.1",
"port": 5100
},
"socket": {
"enabled": true,
"host": "127.0.0.1",
"port": 5101
},
"notifications": {
"popup": true,
"sound": true,
"sound_file": "notification.wav"
},
"logging": {
"level": "INFO",
"file": "yk-daemon.log"
}
}# Get TOTP code
curl http://127.0.0.1:5100/api/totp
# Get TOTP for specific account
curl http://127.0.0.1:5100/api/totp/GitHub
# List accounts
curl http://127.0.0.1:5100/api/accountsimport requests
response = requests.get('http://127.0.0.1:5100/api/totp')
data = response.json()
print(f"TOTP Code: {data['totp']}")# Using netcat
echo "GET_TOTP" | nc 127.0.0.1 5101import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 5101))
sock.send(b'GET_TOTP\n')
response = sock.recv(1024).decode()
print(response) # OK 123456
sock.close()yk-daemon/
├── README.md
├── CLAUDE.md
├── pyproject.toml # Poetry configuration
├── poetry.lock # Poetry lock file
├── config.json
├── yk-daemon.py # Main daemon entry point
├── src/
│ ├── __init__.py
│ ├── yubikey.py # YubiKey OATH-TOTP interface
│ ├── rest_api.py # REST API server (Flask)
│ ├── socket_server.py # TCP socket server
│ ├── notifications.py # Windows notifications & sounds
│ ├── service.py # Windows service wrapper
│ └── config.py # Configuration management
├── tests/
│ ├── test_yubikey.py
│ ├── test_api.py
│ └── test_socket.py
└── examples/
├── bash_client.sh
└── python_client.py
Managed via Poetry (pyproject.toml):
- yubikey-manager (ykman): Official YubiKey library
- Flask: REST API framework
- pywin32: Windows service support
- plyer: Cross-platform notifications
- pydub: Sound playback (optional)
# Run tests
poetry run pytest tests/
# Run with coverage
poetry run pytest --cov=src tests/
# Run linting
poetry run ruff check .
# Format code
poetry run ruff format .- Project setup and documentation
- Core YubiKey OATH-TOTP integration
- REST API implementation
- Socket server implementation
- Windows notifications (popup + sound)
- Configuration management
- Windows service support
- Error handling and logging
- Unit tests
- Example client scripts
- Multiple YubiKey support
- Authentication/authorization
- FIDO2/U2F support
- PIV support
- GUI configuration tool
- Localhost Only: By default, daemon only listens on 127.0.0.1
- No Authentication: Currently no authentication required (any local process can access)
- Physical Access: YubiKey requires physical touch for TOTP operations
- Audit Logging: All requests are logged for audit purposes
- Optional API key/token authentication
- Request rate limiting
- Process whitelisting
- Encrypted socket communication
# Check YubiKey is connected
ykman list
# Check YubiKey OATH accounts
ykman oath accounts list- Ensure Windows Firewall allows localhost connections
- Verify daemon is running:
curl http://127.0.0.1:5100/healthfrom Windows - Check if ports are in use:
netstat -an | findstr "5100"
- Check logs:
yk-daemon.log - Run in foreground mode:
poetry run python yk-daemon.py --debug - Verify Python path in service configuration
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Follow conventional commit format (see below)
- Submit a pull request
This project uses Conventional Commits for automated versioning and changelog generation. Format your commit messages as:
<type>: <description>
[optional body]
[optional footer]
Types:
feat:- New feature (minor version bump)fix:- Bug fix (patch version bump)feat!:orBREAKING CHANGE:- Breaking change (major version bump)docs:- Documentation onlychore:- Maintenance taskstest:- Test additions or changesrefactor:- Code refactoring
Examples:
feat: add REST API for TOTP generation
fix: handle YubiKey disconnect during operation
docs: update API documentation with examplesThis project uses automated releases via GitHub Actions and release-please:
- Development: Make changes and commit using conventional commit format
- Merge to main: When commits are merged to main, release-please analyzes them
- Release PR: If releasable commits exist, release-please creates/updates a Release PR with:
- Version bump in
pyproject.toml - Updated
CHANGELOG.md - Release notes
- Version bump in
- Release: When the Release PR is merged, GitHub Actions automatically:
- Builds distribution packages with Poetry
- Creates a GitHub release with changelog
- Uploads wheel and sdist artifacts
- Tags the release
feat:commits trigger a minor version bump (0.1.0 → 0.2.0)fix:commits trigger a patch version bump (0.1.0 → 0.1.1)feat!:or commits withBREAKING CHANGE:trigger a major version bump (0.1.0 → 1.0.0)- Other commit types (
docs:,chore:, etc.) are included in changelog but don't bump version
The release workflow uses a GitHub App token to trigger other workflows and create PRs. To set this up:
-
Create a GitHub App (if not already done):
- Go to: Settings → Developer settings → GitHub Apps → New GitHub App
- Name:
Release Please Bot(or similar) - Permissions needed:
- Contents: Read and write
- Pull requests: Read and write
- Metadata: Read-only
- Install the app on your repository
-
Configure Repository Secrets and Variables:
- Variable
GA_RELEASE_PLEASE_APP_ID: Your GitHub App ID - Secret
GA_RELEASE_PLEASE_PRIVATE_KEY: Your GitHub App private key (download from app settings)
- Variable
-
Why GitHub App vs GITHUB_TOKEN?
- GitHub App tokens can trigger other workflows (default GITHUB_TOKEN cannot)
- Provides fine-grained permissions
- Allows better control over automation capabilities
[Specify your license here]
Olivier Berghmans ([email protected])
- Yubico for YubiKey hardware and software libraries
- The WSL team for making Windows/Linux integration possible