otp_offline 0.2.0

Library for offline verification of YubiKey OTPs.
Documentation
# YubiKey OTP Verification Library

A Rust library for working with YubiKey One-Time Passwords (OTP), including modhex encoding/decoding functionality.

## Overview

The YubiKey OTP output is provided in the Modhex character set. The Modhex character set uses characters common across the majority of latin alphabet QWERTY keyboard layouts, allowing for functionality regardless of the language set.

## Modhex Character Set

The modhex character set provides a direct substitution for hexadecimal characters:

| Hex | `a` | `b` | `c` | `d` | `e` | `f` | `0` | `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9` |
|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| **Modhex** | `l` | `n` | `r` | `t` | `u` | `v` | `c` | `b` | `d` | `e` | `f` | `g` | `h` | `i` | `j` | `k` |

## Features

- **Hex to Modhex conversion**: Convert hexadecimal strings to modhex format
- **Modhex to Hex conversion**: Convert modhex strings back to hexadecimal
- **Byte array support**: Direct conversion between byte arrays and modhex
- **OTP decryption**: Full YubiKey OTP decryption with AES-128-ECB
- **Data extraction**: Parse all embedded OTP data (counters, timestamp, etc.)
- **CRC validation**: Built-in CRC16 checksum verification
- **Validation functions**: Check if strings contain valid hex or modhex characters
- **Error handling**: Comprehensive error types for invalid characters
- **Minimal dependencies**: Uses only AES crate for cryptographic operations

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
yubikey-otp-verify = "0.1.0"
```

## Usage

### Basic Modhex Operations

```rust
use otp_offline::modhex::{hex_to_modhex, modhex_to_hex, is_valid_modhex};

// Convert hex to modhex
let hex = "0123456789abcdef";
let modhex = hex_to_modhex(hex)?;
assert_eq!(modhex, "cbdefghijklnrtuv");

// Convert modhex back to hex
let hex_result = modhex_to_hex(&modhex)?;
assert_eq!(hex_result, "0123456789abcdef");

// Validate modhex strings
let yubikey_otp = "cccccdcfgvjubvnccblhhktldthvurhdrbvbtnvjtttr";
assert!(is_valid_modhex(yubikey_otp));
```

### Working with Byte Arrays

```rust
use otp_offline::modhex::{bytes_to_modhex, modhex_to_bytes};

// Convert bytes to modhex
let bytes = [0x01, 0x23, 0xab, 0xcd];
let modhex = bytes_to_modhex(&bytes);
assert_eq!(modhex, "cbdelnrt");

// Convert modhex back to bytes
let result_bytes = modhex_to_bytes(&modhex)?;
assert_eq!(result_bytes, vec![0x01, 0x23, 0xab, 0xcd]);
```

### OTP Decryption

```rust
use otp_offline::otp::decrypt_otp;

// Your YubiKey configuration
let aes_key = [0x00, 0x01, 0x02, /* ... your 16-byte AES key ... */];
let expected_public_id = Some("cccccdcfgvju");

// Decrypt a YubiKey OTP
let otp = "cccccdcfgvjubvnccblhhktldthvurhdrbvbtnvjtttr";
match decrypt_otp(otp, &aes_key, expected_public_id, None) {
    Ok(decrypted) => {
        println!("Public ID: {}", decrypted.public_id);
        println!("Private ID: {}", decrypted.private_id);
        println!("Usage Counter: {}", decrypted.usage_counter);
        println!("Timestamp: {}", decrypted.timestamp);
        println!("Session Counter: {}", decrypted.session_counter);
        println!("Valid CRC: {}", decrypted.valid);
    },
    Err(e) => println!("Decryption failed: {}", e),
}
```

### Error Handling

```rust
use otp_offline::modhex::{hex_to_modhex, ModhexError};

match hex_to_modhex("xyz123") {
    Ok(modhex) => println!("Converted: {}", modhex),
    Err(ModhexError::InvalidHexCharacter(c)) => {
        println!("Invalid hex character: '{}'", c);
    }
    Err(e) => println!("Error: {}", e),
}
```

## API Reference

### Functions

#### `hex_to_modhex(hex_str: &str) -> Result<String, ModhexError>`
Convert a hexadecimal string to modhex format. Accepts both uppercase and lowercase hex characters.

#### `modhex_to_hex(modhex_str: &str) -> Result<String, ModhexError>`
Convert a modhex string to hexadecimal format (lowercase output).

#### `bytes_to_modhex(bytes: &[u8]) -> String`
Convert a byte array to modhex string representation.

#### `modhex_to_bytes(modhex_str: &str) -> Result<Vec<u8>, ModhexError>`
Convert a modhex string to a byte vector. The modhex string must have even length.

#### `is_valid_modhex(s: &str) -> bool`
Check if a string contains only valid modhex characters.

#### `is_valid_hex(s: &str) -> bool`
Check if a string contains only valid hexadecimal characters (0-9, a-f, A-F).

#### `decrypt_otp(otp: &str, aes_key: &[u8; 16], expected_public_id: Option<&str>, expected_private_id: Option<&[u8; 6]>) -> Result<DecryptedOtp, OtpError>`
Decrypt and validate a YubiKey OTP using AES-128-ECB. Returns a `DecryptedOtp` struct containing all embedded data.

### Error Types

#### `ModhexError`
- `InvalidHexCharacter(char)`: Invalid character found in hex string
- `InvalidModhexCharacter(char)`: Invalid character found in modhex string

#### `OtpError`
- `InvalidOtpFormat`: Invalid OTP format or length
- `ModhexError(ModhexError)`: Modhex conversion error
- `InvalidPrivatePartLength`: Invalid private part length (must be 32 characters)
- `DecryptionError`: AES decryption failed
- `InvalidHex`: Invalid hex string conversion

#### `DecryptedOtp`
Structure containing decrypted OTP data:
- `public_id`: Public ID (first 12 characters)
- `private_id`: Private ID as hex string with colons
- `usage_counter`: Usage counter (increments with each use)
- `timestamp`: Internal timestamp (~8Hz counter)
- `session_counter`: Session counter (increments each power-on)
- `random`: Random value as hex string with colons
- `checksum`: CRC16 checksum as hex string with colons
- `valid`: Whether the CRC checksum is valid
- `public_id_valid`: Whether the public ID matches expected value

## Examples

### YubiKey OTP Processing

```rust
use otp_offline::modhex::{modhex_to_hex, is_valid_modhex};

fn process_yubikey_otp(otp: &str) -> Result<String, Box<dyn std::error::Error>> {
    // First, validate that the OTP is valid modhex
    if !is_valid_modhex(otp) {
        return Err("Invalid OTP: contains non-modhex characters".into());
    }

    // Convert to hex for further processing
    let hex_otp = modhex_to_hex(otp)?;

    // The hex string can now be processed for OTP verification
    Ok(hex_otp)
}

// Example YubiKey OTP
let otp = "cccccdcfgvjubvnccblhhktldthvurhdrbvbtnvjtttr";
let hex_otp = process_yubikey_otp(otp)?;
println!("OTP in hex: {}", hex_otp);
```

### Complete OTP Verification

```rust
use otp_offline::otp::{decrypt_otp, DecryptedOtp, OtpError};
use otp_offline::modhex::is_valid_modhex;

fn verify_yubikey_otp(
    otp: &str,
    aes_key: &[u8; 16],
    expected_public_id: &str,
    expected_private_id: &[u8; 6]
) -> Result<DecryptedOtp, OtpError> {
    // Validate modhex format
    if !is_valid_modhex(otp) {
        return Err(OtpError::InvalidOtpFormat);
    }

    // Decrypt and validate
    let decrypted = decrypt_otp(
        otp,
        aes_key,
        Some(expected_public_id),
        Some(expected_private_id)
    )?;

    // Check validation results
    if !decrypted.valid {
        println!("Warning: CRC checksum validation failed");
    }

    if !decrypted.public_id_valid {
        println!("Warning: Public ID doesn't match expected value");
    }

    Ok(decrypted)
}

// Usage
let aes_key = [/* your 16-byte AES key */];
let public_id = "cccccdcfgvju";
let private_id = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];

let otp = "cccccdcfgvjubvnccblhhktldthvurhdrbvbtnvjtttr";
match verify_yubikey_otp(otp, &aes_key, public_id, &private_id) {
    Ok(decrypted) => println!("OTP verified successfully!"),
    Err(e) => println!("OTP verification failed: {}", e),
}
```

## Key Configuration

To use the OTP decryption functionality, you'll need:

1. **Public ID**: The first 12 characters of your YubiKey OTP
2. **AES Key**: The 16-byte AES key programmed into your YubiKey (for validation)
3. **Private ID**: The 6-byte private identifier (for validation)

These values are set during YubiKey personalization and are unique to each device.

## Testing

Run the test suite:

```bash
cargo test
```

## License

This project is licensed under the MIT License - see the LICENSE file for details.