# OTP Identity Challenge **Identity verification for approval workflows in OpenClaw.** This skill enables agents to challenge users for fresh two-factor authentication proof before executing sensitive actions—deployments, financial operations, data access, or any command with change-control concerns. ## Purpose This is **not** chat channel security—it's **identity verification for specific actions**. When your agent needs to execute something sensitive, it challenges the user to prove their identity with a time-based one-time password (TOTP) or YubiKey, providing cryptographic proof they authorized the action. ## Quick Start ```bash # Install via ClawHub clawhub install otp # Or clone manually cd ~/.openclaw/skills git clone https://github.com/ryancnelson/otp-skill.git otp ``` ## Use Cases - **Deploy commands**: Require fresh 2FA before `kubectl apply` or `terraform apply` - **Financial operations**: Verify identity before wire transfers or payment approvals - **Data access**: Challenge before exporting customer data or PII - **Admin operations**: Verify before user account modifications or permission changes - **Change control**: Enforce approval workflows with cryptographic proof of identity ## How It Works 1. Your agent or skill calls `verify.sh` with the user's ID and their code 2. The skill validates the code (TOTP or YubiKey) 3. If valid, verification state is recorded with a timestamp 4. Other scripts can check `check-status.sh` to see if verification is still fresh 5. Verification expires after a configured interval (default: 24 hours) ## Installation ### Via ClawHub (recommended) ```bash clawhub install otp ``` ### Manual ```bash cd ~/.openclaw/skills git clone https://github.com/ryancnelson/otp-skill.git otp ``` ### Check Dependencies After installation, verify required dependencies: ```bash # Check what's available which jq && echo "✅ jq available" || echo "❌ Install: brew install jq" which python3 && echo "✅ python3 available" || echo "❌ Install: brew install python3" which oathtool && echo "✅ oathtool available" || echo "❌ Install: brew install oath-toolkit" which qrencode && echo "✅ qrencode available" || echo "⚠️ Optional: brew install qrencode (for QR codes)" ``` **Note:** `oathtool` is optional - the skill includes a built-in TOTP generator, but `oathtool` provides additional validation. --- ## TOTP Setup ### 1. Generate a TOTP Secret Use the included secret generator: ```bash cd ~/.openclaw/skills/otp ./generate-secret.sh "your-email@example.com" ``` This will display: - A QR code to scan with your authenticator app (if `qrencode` is installed) - The base32 secret for manual entry - Configuration instructions **Note:** QR code display requires the `qrencode` utility. Install with `brew install qrencode` (macOS) or `apt install qrencode` (Ubuntu). Without it, you'll only see the base32 secret for manual entry. **Alternative:** Use any other TOTP secret generator. You need a **base32-encoded secret**. ### 2. Scan QR Code Add the secret to your authenticator app: - Google Authenticator - Authy - 1Password - Bitwarden - Any RFC 6238 compatible app ### 3. Store the Secret **Option A: In your OpenClaw config** ```yaml # ~/.openclaw/config.yaml security: otp: secret: "YOUR_BASE32_SECRET_HERE" accountName: "user@example.com" issuer: "OpenClaw" intervalHours: 24 # Re-verify every 24 hours ``` **Option B: In environment variable** ```bash export OTP_SECRET="YOUR_BASE32_SECRET_HERE" ``` **Option C: In 1Password** (recommended for security) ```yaml security: otp: secret: "op://vault/OpenClaw OTP/totp" ``` ### 4. Test Your Setup ```bash # Get current code from your authenticator app (6 digits) ./verify.sh "testuser" "123456" # Replace with actual code # Should show: ✅ OTP verified for testuser (valid for 24 hours) # Check verification status ./check-status.sh "testuser" # Should show: ✅ Valid for 23 more hours ``` --- ## YubiKey Setup (Alternative to TOTP) If you have a YubiKey, you can use touch-to-verify instead of typing 6-digit codes. ### 1. Get Yubico API Credentials 1. Go to **https://upgrade.yubico.com/getapikey/** 2. Enter your email address 3. Touch your YubiKey to generate an OTP in the form field 4. Submit — you'll receive a **Client ID** and **Secret Key** **Troubleshooting "Invalid OTP" during registration:** If Yubico's site rejects your OTP, your key may not be registered with Yubico's cloud: 1. Install **YubiKey Manager** from https://www.yubico.com/support/download/yubikey-manager/ 2. Open it, go to **Applications → OTP → Configure Slot 1** 3. Select **Yubico OTP** and check **"Upload to Yubico"** 4. This re-registers your key with Yubico's servers 5. Try getting API credentials again ### 2. Configure Credentials **Option A: In your OpenClaw config** ```yaml # ~/.openclaw/config.yaml security: yubikey: clientId: "12345" secretKey: "your-base64-secret-key" ``` **Option B: In environment variables** ```bash export YUBIKEY_CLIENT_ID="12345" export YUBIKEY_SECRET_KEY="your-base64-secret-key" ``` ### 3. Test YubiKey Verification ```bash # Touch your YubiKey when prompted ./verify.sh "testuser" "cccccccccccc..." # paste YubiKey output # Should show: ✅ YubiKey verified for testuser (valid for 24 hours) ``` ### Using Both TOTP and YubiKey You can configure both methods. The script auto-detects which to use based on the code format: - **6 digits** → TOTP validation - **44 characters** → YubiKey validation This lets you use TOTP on your phone and YubiKey at your desk. --- ## Configuration Reference Set these in your OpenClaw config or environment: | Variable | Description | Default | |----------|-------------|---------| | `OTP_SECRET` | Base32 TOTP secret | (required for TOTP) | | `YUBIKEY_CLIENT_ID` | Yubico API client ID | (required for YubiKey) | | `YUBIKEY_SECRET_KEY` | Yubico API secret key (base64) | (required for YubiKey) | | `OTP_INTERVAL_HOURS` | Verification expiry | 24 | | `OTP_GRACE_PERIOD_MINUTES` | Grace period after expiry | 15 | | `OTP_STATE_FILE` | State file path | `memory/otp-state.json` | | `OTP_MAX_FAILURES` | Failed attempts before rate limiting | 3 | | `OTP_FAILURE_HOOK` | Script to run on failures | (none) | | `OTP_AUDIT_LOG` | Audit log file path | (none) | --- ## Usage Examples ### For Skill Authors When your skill needs to verify user identity: ```bash #!/bin/bash # In your sensitive-action.sh # Source the OTP skill source ../otp/verify.sh USER_ID="$1" OTP_CODE="$2" # Challenge the user if ! verify_otp "$USER_ID" "$OTP_CODE"; then echo "❌ OTP verification failed. Run: /otp " exit 1 fi # Proceed with sensitive action echo "✅ Identity verified. Proceeding with deployment..." kubectl apply -f production.yaml ``` ### Checking Verification Status ```bash #!/bin/bash source ../otp/check-status.sh if check_otp_status "$USER_ID"; then echo "✅ User verified within last 24 hours" else echo "⚠️ Verification expired. User must verify again." fi ``` ### For End Users When prompted by a skill: ``` User: deploy to production Agent: 🔒 This action requires identity verification. Please provide your OTP code. User: /otp 123456 Agent: ✅ Identity verified. Deploying to production... ``` ### Deploy Command with OTP ```bash #!/bin/bash # skills/deploy/production.sh source ../otp/verify.sh USER="$1" CODE="$2" SERVICE="$3" # Require OTP for production deploys if ! verify_otp "$USER" "$CODE"; then echo "🔒 Production deployment requires OTP verification" echo "Usage: deploy production --otp " exit 1 fi echo "✅ Identity verified. Deploying $SERVICE to production..." # ... deployment logic ... ``` ### Payment Authorization ```bash #!/bin/bash # skills/finance/transfer.sh source ../otp/check-status.sh USER="$1" AMOUNT="$2" RECIPIENT="$3" # Check if user verified recently if ! check_otp_status "$USER"; then echo "💳 Large transfer requires fresh identity verification" echo "Please verify with: /otp " exit 1 fi echo "✅ Processing transfer of \$$AMOUNT to $RECIPIENT" # ... transfer logic ... ``` ### Failure Hook - Alert on Failed Attempts Configure `OTP_FAILURE_HOOK` to run a script when verification fails: ```bash # Example: Shut down OpenClaw after 3 failures (impersonation defense) export OTP_FAILURE_HOOK="/path/to/shutdown-if-impersonator.sh" ``` The hook receives these environment variables: - `OTP_HOOK_EVENT` - `VERIFY_FAIL` (bad code) or `RATE_LIMIT_HIT` (too many failures) - `OTP_HOOK_USER` - The user ID that failed - `OTP_HOOK_FAILURE_COUNT` - Number of consecutive failures - `OTP_HOOK_TIMESTAMP` - ISO 8601 timestamp ```bash #!/bin/bash # shutdown-if-impersonator.sh if [ "$OTP_HOOK_EVENT" = "RATE_LIMIT_HIT" ]; then echo "🚨 OTP rate limit hit for $OTP_HOOK_USER at $OTP_HOOK_TIMESTAMP" | \ slack-notify "#alerts" pkill -f openclaw fi ``` --- ## Security Considerations ### What This Protects Against - **Session hijacking**: Even if someone steals your chat session, they can't execute protected actions without your physical device - **Replay attacks**: Codes are time-based and expire quickly - **Unauthorized actions**: Cryptographic proof that you authorized the specific action ### What This Doesn't Protect Against - **Compromised agent**: If someone has shell access to your OpenClaw instance, they can read the secret - **Phishing**: Users can still be tricked into providing codes to malicious actors - **Device theft**: If someone has your authenticator device, they can generate codes ### Best Practices 1. **Store secrets securely**: Use 1Password/Bitwarden references, not plaintext in config 2. **Short expiry**: Keep `intervalHours` reasonable (8-24 hours) 3. **Audit logs**: Skills should log verification events 4. **Scope carefully**: Only require OTP for truly sensitive actions 5. **Clear prompts**: Always tell users WHY they're being asked for OTP --- ## Technical Details ### TOTP Implementation - **Standard**: RFC 6238 (Time-Based One-Time Password) - **Algorithm**: HMAC-SHA1 (standard TOTP) - **Time window**: 30 seconds (configurable) - **Code length**: 6 digits - **Clock skew**: ±1 window tolerance (90 seconds total) ### YubiKey OTP Implementation - **API**: Yubico Cloud (api.yubico.com) - **Protocol**: HMAC-SHA1 signed requests - **OTP Format**: 44-character ModHex (alphabet: cbdefghijklnrtuv) - **Public ID**: First 12 characters identify the physical key - **Replay Protection**: Handled by Yubico servers - **Network**: Requires HTTPS to api.yubico.com ### State Management Verification state is stored in `memory/otp-state.json`: ```json { "verifications": { "user@example.com": { "verifiedAt": 1706745600000, "expiresAt": 1706832000000 } } } ``` No secrets are stored in state—only timestamps. ### Dependencies **Required:** - **jq** - for JSON state file manipulation - **python3** - for secure YAML config parsing **Optional:** - **qrencode** - displays QR code during secret generation (without it, you'll only get the base32 secret) - **oathtool** - provides additional TOTP validation (skill has built-in generator) - **Node.js** - only needed for `totp.mjs` standalone CLI - **bats** - for running test suite --- ## Troubleshooting ### TOTP Issues **"OTP verification failed"** - Check your authenticator app has the correct secret - Verify system time is synchronized (NTP) - Try the code from the previous/next 30-second window **"Secret not configured"** - Set `OTP_SECRET` environment variable - Or configure `security.otp.secret` in OpenClaw config **"State file not found"** - First verification creates the file - Check `memory/` directory permissions ### YubiKey Issues **"YUBIKEY_CLIENT_ID not set"** - Get API credentials from https://upgrade.yubico.com/getapikey/ - Set `YUBIKEY_CLIENT_ID` and `YUBIKEY_SECRET_KEY` in environment or config **"Invalid OTP" when getting API key** - Your YubiKey may not be registered with Yubico's cloud - Use YubiKey Manager to reconfigure Slot 1 with "Upload to Yubico" checked **"YubiKey API signature mismatch"** - Check that `YUBIKEY_SECRET_KEY` is correct (should be base64) - Try regenerating API credentials from Yubico **"Failed to contact Yubico API"** - Check internet connectivity - Yubico API requires HTTPS (port 443) - Try: `curl -I https://api.yubico.com/wsapi/2.0/verify` **"YubiKey OTP already used"** - Each YubiKey press generates a unique code - Touch your YubiKey again to generate a fresh code - Don't copy-paste old codes --- ## Philosophy **OTP should be invisible when not needed, obvious when required.** Don't force users to verify for every action—that trains them to treat it as a meaningless ritual. Only challenge when: 1. The action has real-world consequences 2. The risk justifies the friction 3. You need cryptographic proof of intent Think of OTP like `sudo`—you use it before commands that matter, not every command. --- ## Agent Documentation For agent/skill integration details, see **[SKILL.md](./SKILL.md)**. ## Full Source & Tests Complete repository with test suite: **https://github.com/ryancnelson/otp-challenger** ## See Also - [RFC 6238](https://tools.ietf.org/html/rfc6238) - TOTP specification - [Yubico OTP](https://developers.yubico.com/OTP/) - YubiKey OTP documentation ## License MIT ## Author Ryan Nelson ## Contributing Issues and PRs welcome at: https://github.com/ryancnelson/otp-challenger