-
Notifications
You must be signed in to change notification settings - Fork 0
PIN_UV Authentication
- Introduction
- Architecture Overview
- Core Components
- PIN Protocol Implementation
- Cryptographic Operations
- Authentication Token Derivation
- Security Considerations
- Error Handling
- Integration with Extensions Framework
- Test Examples
- Troubleshooting Guide
The PIN/UV Authentication extension provides secure authentication mechanisms for FIDO2 authenticators through Personal Identification Numbers (PINs) and User Verification (UV) methods. This extension enables clients to establish secure channels with authenticators, authenticate using PINs or biometric verification, and derive authentication tokens with specific permissions for protected operations.
The implementation supports two protocol versions (v1 and v2) with enhanced security features in v2, including HKDF-based key derivation, separate AES and HMAC keys, and improved encryption mechanisms. The extension integrates seamlessly with the broader FIDO2 Extensions framework, allowing for permission-based access control and fine-grained authorization.
The PIN/UV Authentication system follows a layered architecture that separates protocol concerns from cryptographic operations and client interactions:
graph TB
subgraph "Client Layer"
Client[Client Application]
Extensions[Extensions Framework]
end
subgraph "PIN/UV Layer"
ClientPin[ClientPin Class]
PinProtocol[PinProtocol Interface]
PinProtocolV1[PinProtocolV1]
PinProtocolV2[PinProtocolV2]
end
subgraph "Cryptographic Layer"
ECDH[ECDH Key Agreement]
AES[AES Encryption]
HMAC[HMAC-SHA256]
HKDF[HKDF Key Derivation]
end
subgraph "Authenticator Layer"
CTAP2[CTAP2 Commands]
Info[Authenticator Info]
end
Client --> Extensions
Extensions --> ClientPin
ClientPin --> PinProtocol
PinProtocol --> PinProtocolV1
PinProtocol --> PinProtocolV2
PinProtocolV1 --> ECDH
PinProtocolV2 --> ECDH
ECDH --> AES
ECDH --> HMAC
ECDH --> HKDF
ClientPin --> CTAP2
CTAP2 --> Info
Diagram sources
- fido2/ctap2/pin.py
- fido2/ctap2/extensions.py
The ClientPin class serves as the primary interface for PIN/UV authentication operations, providing methods for PIN management, token acquisition, and retry monitoring:
classDiagram
class ClientPin {
+Ctap2 ctap
+PinProtocol protocol
+PROTOCOLS : list
+CMD : IntEnum
+RESULT : IntEnum
+PERMISSION : IntFlag
+is_supported(info) bool
+is_token_supported(info) bool
+get_pin_token(pin, permissions, rpid) bytes
+get_uv_token(permissions, rpid, event, keepalive) bytes
+get_pin_retries() tuple
+get_uv_retries() int
+set_pin(pin) void
+change_pin(old_pin, new_pin) void
-_get_shared_secret() tuple
}
class PinProtocol {
<<abstract>>
+VERSION : ClassVar[int]
+encapsulate(peer_key) tuple
+encrypt(key, plaintext) bytes
+decrypt(key, ciphertext) bytes
+authenticate(key, message) bytes
+validate_token(token) bytes
}
class PinProtocolV1 {
+VERSION : int = 1
+IV : bytes
+kdf(z) bytes
+encapsulate(peer_key) tuple
+encrypt(key, plaintext) bytes
+decrypt(key, ciphertext) bytes
+authenticate(key, message) bytes
+validate_token(token) bytes
}
class PinProtocolV2 {
+VERSION : int = 2
+HKDF_SALT : bytes
+HKDF_INFO_HMAC : bytes
+HKDF_INFO_AES : bytes
+kdf(z) bytes
+encrypt(key, plaintext) bytes
+decrypt(key, ciphertext) bytes
+authenticate(key, message) bytes
+validate_token(token) bytes
}
ClientPin --> PinProtocol
PinProtocol <|-- PinProtocolV1
PinProtocol <|-- PinProtocolV2
Diagram sources
- fido2/ctap2/pin.py
The permission system allows fine-grained access control for authentication tokens:
| Permission | Value | Description |
|---|---|---|
| MAKE_CREDENTIAL | 0x01 | Allows creation of new credentials |
| GET_ASSERTION | 0x02 | Enables authentication assertions |
| CREDENTIAL_MGMT | 0x04 | Permits credential management operations |
| BIO_ENROLL | 0x08 | Allows biometric enrollment |
| LARGE_BLOB_WRITE | 0x10 | Enables large blob storage writes |
| AUTHENTICATOR_CFG | 0x20 | Permits authenticator configuration |
| PERSISTENT_CREDENTIAL_MGMT | 0x40 | Allows persistent credential management |
Section sources
- fido2/ctap2/pin.py
The protocol establishes secure channels through Elliptic Curve Diffie-Hellman (ECDH) key exchange:
sequenceDiagram
participant Client as Client
participant Auth as Authenticator
participant ECDH as ECDH Engine
participant Crypto as Cryptographic Backend
Client->>Auth : GET_KEY_AGREEMENT
Auth->>Client : Public Key (COSE format)
Client->>ECDH : Generate private key
ECDH->>Crypto : secp256r1 key pair
Crypto-->>ECDH : Private/public key
ECDH->>ECDH : Exchange ECDH
ECDH->>Crypto : Compute shared secret
Crypto-->>ECDH : x-coordinate (32 bytes)
ECDH->>Client : Key agreement + shared secret
Client->>Client : Apply KDF/HKDF
Diagram sources
- fido2/ctap2/pin.py
| Feature | PinProtocolV1 | PinProtocolV2 |
|---|---|---|
| Key Derivation | SHA256 | HKDF with separate keys |
| Encryption Mode | CBC with fixed IV | CBC with random IV |
| MAC Algorithm | HMAC-SHA256 (16 bytes) | HMAC-SHA256 (full) |
| Token Size | 16 or 32 bytes | 32 bytes |
| Salt | None | Fixed 32-byte zero salt |
Section sources
- fido2/ctap2/pin.py
The ECDH implementation uses secp256r1 curve with COSE format for public key representation:
flowchart TD
Start([Generate Key Pair]) --> GenPriv["Generate secp256r1 Private Key"]
GenPriv --> GenPub["Derive Public Key"]
GenPub --> SendPub["Send Public Key to Authenticator"]
SendPub --> RecvPub["Receive Authenticator Public Key"]
RecvPub --> Validate["Validate COSE Format"]
Validate --> Exchange["Perform ECDH Exchange"]
Exchange --> Extract["Extract X-coordinate"]
Extract --> KDF["Apply KDF (SHA256 or HKDF)"]
KDF --> SharedSecret["Shared Secret Ready"]
SharedSecret --> End([Ready for Encryption])
Diagram sources
- fido2/ctap2/pin.py
PINs undergo a standardized hashing process before transmission:
flowchart TD
PIN[Input PIN String] --> Validate["Validate PIN Length (≥4 chars)"]
Validate --> Encode["Encode to UTF-8"]
Encode --> Pad["Pad to 64 bytes with zeros"]
Pad --> Align["Align to 16-byte boundary"]
Align --> Truncate["Truncate to 16 bytes SHA256 hash"]
Truncate --> Encrypt["Encrypt with shared secret"]
Encrypt --> Transmit["Transmit to Authenticator"]
Diagram sources
- fido2/ctap2/pin.py
- fido2/ctap2/pin.py
Both protocol versions implement robust encryption and authentication:
| Operation | PinProtocolV1 | PinProtocolV2 |
|---|---|---|
| Encryption | AES-CBC (fixed IV) | AES-CBC (random IV) |
| Authentication | HMAC-SHA256 (16 bytes) | HMAC-SHA256 (full) |
| Key Derivation | SHA256 | HKDF with separate keys |
Section sources
- fido2/ctap2/pin.py
The token derivation process varies based on protocol version and authenticator capabilities:
sequenceDiagram
participant Client as Client
participant CP as ClientPin
participant Proto as PinProtocol
participant Auth as Authenticator
Client->>CP : get_pin_token(pin, permissions)
CP->>CP : _get_shared_secret()
CP->>Proto : encapsulate(peer_key)
Proto->>Proto : ECDH key exchange
Proto-->>CP : key_agreement + shared_secret
CP->>CP : hash_pin(pin)
CP->>Proto : encrypt(shared_secret, pin_hash)
Proto-->>CP : encrypted_pin_hash
CP->>Auth : client_pin(GET_TOKEN_USING_PIN, ...)
Auth->>Auth : Verify PIN + derive token
Auth-->>CP : encrypted_token
CP->>Proto : decrypt(shared_secret, token_enc)
Proto-->>CP : validated_token
CP-->>Client : authentication token
Diagram sources
- fido2/ctap2/pin.py
Each protocol version implements specific token validation rules:
flowchart TD
Receive[Receive Token] --> CheckSize{"Token Size?"}
CheckSize --> |V1| V1Check{"16 or 32 bytes?"}
CheckSize --> |V2| V2Check{"Exactly 32 bytes?"}
V1Check --> |Yes| Valid[Valid Token]
V1Check --> |No| Error[ValueError]
V2Check --> |Yes| Valid
V2Check --> |No| Error
Valid --> Return[Return Token]
Error --> Raise[Raise Exception]
Diagram sources
- fido2/ctap2/pin.py
- fido2/ctap2/pin.py
The authenticator implements comprehensive brute force protection mechanisms:
| Protection Level | Behavior | Recovery |
|---|---|---|
| Normal Attempts | Decrease retry count | Immediate |
| Soft Lock | Block after 3+ failed attempts | Power cycle required |
| Hard Lock | Permanent block | Factory reset required |
The secure channel establishment ensures confidentiality and authenticity:
graph LR
subgraph "Channel Security"
ECDH[ECDH Key Exchange]
AES[AES Encryption]
HMAC[HMAC Authentication]
KDF[Key Derivation]
end
subgraph "Security Guarantees"
Confidentiality[Confidentiality]
Integrity[Integrity]
ReplayAttack[Replay Attack Prevention]
ManInTheMiddle[MITM Protection]
end
ECDH --> KDF
KDF --> AES
KDF --> HMAC
AES --> Confidentiality
HMAC --> Integrity
ReplayAttack --> ReplayAttack
ManInTheMiddle --> ManInTheMiddle
The authenticator maintains cryptographic binding between PIN operations and the authenticator identity through:
- Shared secret derived from ECDH
- HMAC authentication of all messages
- PIN hash verification
- Token validation against authenticator state
Section sources
- tests/device/test_clientpin.py
The system handles various error conditions with specific error codes:
| Error Condition | Error Code | Description |
|---|---|---|
| Incorrect PIN | PIN_INVALID (0x31) | Single incorrect PIN attempt |
| Auth Blocked | PIN_AUTH_BLOCKED (0x34) | Authentication temporarily blocked |
| PIN Blocked | PIN_BLOCKED (0x32) | PIN permanently blocked |
| PIN Not Set | PIN_NOT_SET (0x35) | Attempting PIN operations without PIN |
| Policy Violation | PIN_POLICY_VIOLATION (0x37) | PIN length or complexity violation |
The authenticator tracks retry attempts and implements progressive locking:
stateDiagram-v2
[*] --> Active : Initial state
Active --> Active : Successful PIN
Active --> Active : Incorrect PIN (decrements counter)
Active --> SoftLocked : 3rd incorrect attempt
SoftLocked --> Active : Power cycle
SoftLocked --> HardLocked : Any PIN attempt
HardLocked --> [*] : Factory reset required
Diagram sources
- fido2/ctap.py
Section sources
- tests/device/test_clientpin.py
PIN/UV functionality is signaled through the CTAP2 Info object:
classDiagram
class Info {
+dict options
+list pin_uv_protocols
+bool clientPin
+bool pinUvAuthToken
+int min_pin_length
+get_identifier(pin_token) bytes
}
class ExtensionsFramework {
+list supported_extensions
+dict extension_inputs
+dict extension_outputs
+prepare_inputs() dict
+prepare_outputs() dict
}
Info --> ExtensionsFramework : signals availability
ExtensionsFramework --> ClientPin : enables functionality
Diagram sources
- fido2/ctap2/base.py
- fido2/ctap2/extensions.py
Extensions integrate with PIN/UV permissions for fine-grained access control:
sequenceDiagram
participant Ext as Extension
participant CP as ClientPin
participant Auth as Authenticator
Ext->>CP : Request token with permissions
CP->>CP : Check authenticator support
CP->>Auth : Get token with permissions
Auth->>Auth : Validate permissions
Auth-->>CP : Token with granted permissions
CP-->>Ext : Authentication token
Ext->>Ext : Apply permissions to operations
Diagram sources
- fido2/ctap2/extensions.py
Section sources
- fido2/ctap2/extensions.py
The test suite demonstrates comprehensive PIN validation scenarios:
flowchart TD
Start[Test PIN Validation] --> CheckSupport["Check ClientPin Support"]
CheckSupport --> GetRetries["Get Initial Retries (8)"]
GetRetries --> WrongPin["Attempt Wrong PIN"]
WrongPin --> DecrementRetry["Retry Count Decrements"]
DecrementRetry --> SoftLock{"3+ Attempts?"}
SoftLock --> |Yes| SoftLocked["Soft Locked State"]
SoftLock --> |No| Continue["Continue Testing"]
SoftLocked --> BlockAttempts["Block All Attempts"]
BlockAttempts --> PowerCycle["Power Cycle Required"]
PowerCycle --> ResetRetries["Retries Reset"]
ResetRetries --> Unlock["Unlock with Correct PIN"]
Unlock --> Success["Validation Complete"]
Diagram sources
- tests/device/test_clientpin.py
The PIN change functionality demonstrates atomic operations:
sequenceDiagram
participant Test as Test Suite
participant CP as ClientPin
participant Auth as Authenticator
Test->>CP : get_pin_token(current_pin)
CP->>Auth : Authenticate with current PIN
Auth-->>CP : PIN token
Test->>CP : change_pin(old_pin, new_pin)
CP->>Auth : Verify old PIN + set new PIN
Auth->>Auth : Atomic PIN update
Auth-->>CP : Success
Test->>CP : get_pin_token(new_pin)
CP->>Auth : Authenticate with new PIN
Auth-->>CP : New PIN token
Test->>CP : get_pin_token(old_pin)
CP->>Auth : Attempt with old PIN
Auth-->>CP : PIN_INVALID error
Diagram sources
- tests/device/test_clientpin.py
Section sources
- tests/device/test_clientpin.py
| Issue | Symptoms | Solution |
|---|---|---|
| PIN_AUTH_BLOCKED | All PIN attempts fail immediately | Power cycle authenticator |
| PIN_NOT_SET | PIN operations unavailable | Set PIN first |
| Protocol Mismatch | Unsupported protocol version | Check authenticator capabilities |
| Token Validation Failure | Invalid token size | Verify authenticator version |
Enable debug logging to trace PIN operations:
import logging
logging.getLogger('fido2.ctap2.pin').setLevel(logging.DEBUG)For locked authenticators:
- Soft Lock Recovery: Power cycle the authenticator
- Hard Lock Recovery: Perform factory reset
- PIN Reset: Use authenticator-specific reset procedures
- Key agreement operations are computationally intensive
- Network latency affects retry timing
- Token caching improves performance for repeated operations
Section sources
- fido2/ctap2/pin.py