Showing posts with label Rocky Linux 9. Show all posts
Showing posts with label Rocky Linux 9. Show all posts

OpenSSH Hash-Based Authentication System

This post describes a secure SSH authentication system that replaces traditional authorized_keys files with SQLite database lookups using SHA256 hashes of public keys. The system provides enhanced security, performance, and manageability for SSH key authentication.

Table of Contents

Overview

The SSH Hash-Based Authentication System replaces traditional authorized_keys files with SQLite database lookups using SHA256 hashes of public keys. This provides enhanced security, performance, and manageability for SSH key authentication.

System Requirements

  • Operating System: Linux (kernel 5.14.0-570.30.1.el9_6.x86_64)
  • Linux Distribution: Rocky Linux 9.6
  • OpenSSH Server: openssh-server-8.7p1-45.el9.rocky.0.1.x86_64
  • OpenSSH Clients: openssh-clients-8.7p1-45.el9.rocky.0.1.x86_64
  • Additional Dependencies: SQLite3, bash, core utilities

Key Benefits

  • Security: No plain-text key storage, hash-based lookups
  • Performance: SQLite binary lookups (faster than file parsing)
  • Scalability: Per-user databases with indexed lookups
  • Manageability: Comprehensive management tools
  • Auditability: Complete audit trail and logging
  • Flexibility: Supports multiple key types (RSA, ED25519, ECDSA)

System Architecture

Core Components

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   SSH Client    │    │   SSHD Server    │    │  Hash Database  │
│                 │    │                  │    │                 │
│ • SSH Key       │───▶│ • AuthorizedKeys │───▶│ • SQLite DB     │
│ • Key Data      │    │   Command        │    │ • SHA256 Hashes │
│                 │    │ • ssh_hash_auth  │    │ • Per-user      │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Authentication Flow

  1. Client connects with SSH key
  2. SSHD calls ssh_hash_auth.sh with key data
  3. Script extracts key type and generates hash
  4. SQLite lookup in user's database
  5. Returns authorised key or nothing
  6. SSHD validates the returned key

Database Structure

-- Standard Database
CREATE TABLE hashes (
    hash TEXT PRIMARY KEY,
    description TEXT,
    added TEXT
);

-- Enhanced Database (with expiration)
CREATE TABLE hashes (
    hash TEXT PRIMARY KEY,
    description TEXT,
    added TEXT,
    expires TEXT,
    last_used TEXT,
    usage_count INTEGER DEFAULT 0,
    status TEXT DEFAULT 'active'
);

Installation & Setup

Step 1: Install Core System

# Copy scripts to /usr/local/bin
sudo cp ssh_hash_auth.sh /usr/local/bin/
sudo cp ssh_hash_manager.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/ssh_hash_auth.sh
sudo chmod +x /usr/local/bin/ssh_hash_manager.sh

# Install SSHD configuration
sudo cp 99-hash-auth.conf /etc/ssh/sshd_config.d/99-hash-auth.conf
sudo systemctl reload sshd

Step 2: Set Up User Database

# Generate hash from user's public key
./ssh_hash_manager.sh generate <username> ~<username>/.ssh/id_rsa.pub

# Or add hash manually
./ssh_hash_manager.sh add <username> SHA256:hash... "description"

Step 3: Test Authentication

# Test connection
ssh <username>@localhost

# Check debug output if needed
./debug_auth.sh <username> <key_data>

Usage & Management

Basic Operations

# Generate hash from public key file
./ssh_hash_manager.sh generate <user> <key_file>

# Add hash manually
./ssh_hash_manager.sh add <user> <hash> [description]

# List all hashes
./ssh_hash_manager.sh list <user>

# Remove hash
./ssh_hash_manager.sh remove <user> <hash>

# Search hashes
./ssh_hash_manager.sh search <user> <term>

Enhanced Operations (with expiration)

# Generate hash with expiration (90 days)
./enhanced_hash_manager.sh generate <user> <key_file> 90

# Add hash with expiration (30 days)
./enhanced_hash_manager.sh add <user> <hash> "description" 30

# Check for expired hashes
./enhanced_hash_manager.sh check-expired <user>

# Clean up expired hashes (dry run)
./enhanced_hash_manager.sh cleanup <user> --dry-run

# Backup database
./enhanced_hash_manager.sh backup <user> /var/backup/ssh_hashes/user

# Restore database
./enhanced_hash_manager.sh restore <user> <backup_file>

# Migrate from authorized_keys file
./enhanced_hash_manager.sh migrate <user> ~user/.ssh/authorized_keys 60

Migration from Traditional authorized_keys

# Option 1: Manual migration
./enhanced_hash_manager.sh migrate <user> ~<user>/.ssh/authorized_keys 90

# Option 2: Bulk migration
for user in $(cut -d: -f1 /etc/passwd); do
    if [ -f "/home/$user/.ssh/authorized_keys" ]; then
        ./enhanced_hash_manager.sh migrate "$user" "/home/$user/.ssh/authorized_keys" 90
    fi
done

Security Features

Core Security Benefits

  • No plain-text key storage - only SHA256 hashes
  • Hash-based lookups prevent key enumeration
  • Database files have restricted permissions (600)
  • User-specific databases prevent cross-user access
  • No modification of existing authorized_keys files

Enhanced Security Features

  • Key expiration dates for automatic rotation
  • Audit logging of all authentication attempts
  • Rate limiting to prevent brute force attacks
  • Usage tracking and analytics
  • Backup/restore functionality

Audit Logging

Log Format:

timestamp|event|username|key_hash|ip_address|result

Events Logged:

  • AUTH_SUCCESS - Successful authentication
  • AUTH_FAILED - Failed authentication (with reason)
  • RATE_LIMITED - Rate limited attempts
  • DB_NOT_FOUND - Database not found
  • EXPIRED - Expired key attempt

Rate Limiting:

  • Maximum 5 failed attempts per 5 minutes per user/IP
  • Automatic reset on successful authentication
  • Configurable limits in enhanced script

Troubleshooting

Common Issues

1. Authentication Fails:

# Check if database exists
ls -la ~<user>/.ssh/authorized_keys.db

# Check database contents
./ssh_hash_manager.sh list <user>

# Test with debug script
./debug_auth.sh <user> <key_data>

2. SSHD Configuration Issues:

# Check SSHD config
sudo sshd -T | grep AuthorizedKeys

# Reload SSHD
sudo systemctl reload sshd

# Check SSHD logs
sudo journalctl -u sshd -f

3. Permission Issues:

# Fix database permissions
chmod 600 ~<user>/.ssh/authorized_keys.db
chown <user>:<user> ~<user>/.ssh/authorized_keys.db

# Fix .ssh directory permissions
chmod 700 ~<user>/.ssh
chown <user>:<user> ~<user>/.ssh

Debug Commands

# Test hash generation
echo "ssh-rsa AAAAB3NzaC1yc2E..." | ./ssh_hash_manager.sh generate test /dev/stdin

# Test database lookup
sqlite3 ~<user>/.ssh/authorized_keys.db "SELECT * FROM hashes;"

# Check audit logs
sudo tail -f /var/log/ssh_hash_auth.log

Advanced Features

Key Expiration

  • Automatic expiration dates for keys
  • Configurable expiration periods
  • Automatic cleanup of expired keys
  • Notifications for expiring keys

Usage Analytics

  • Track last used timestamp
  • Count usage frequency
  • Identify unused keys
  • Generate usage reports

Backup and Recovery

  • Automated database backups
  • Point-in-time recovery
  • Backup verification
  • Disaster recovery procedures

Best Practices

Security

  • Regular key rotation (use expiration dates)
  • Monitor audit logs for suspicious activity
  • Backup databases regularly
  • Use rate limiting to prevent brute force
  • Restrict database access to authorised users only

Management

  • Document all key additions/removals
  • Regular cleanup of expired keys
  • Monitor database performance
  • Test backup/restore procedures
  • Keep scripts updated and secure

Monitoring

  • Set up log monitoring for failed attempts
  • Monitor database size and performance
  • Track key usage patterns
  • Alert on unusual access patterns
  • Regular security audits

File Reference

Core Files

  • ssh_hash_auth.sh - Main authentication script (production)
  • debug_auth.sh - Debug version for troubleshooting
  • ssh_hash_manager.sh - Hash management tool
  • 99-hash-auth.conf - SSHD configuration

Enhanced Files

  • enhanced_hash_auth_with_expiration.sh - Enhanced auth with expiration & audit logging
  • enhanced_hash_manager.sh - Enhanced manager with advanced features

Database Locations

  • User databases: ~<user>/.ssh/authorized_keys.db
  • Audit logs: /var/log/ssh_hash_auth.log
  • Rate limit file: /tmp/ssh_hash_auth_rate_limit

Configuration Files

  • SSHD config: /etc/ssh/sshd_config.d/99-hash-auth.conf
  • Scripts: /usr/local/bin/ssh_hash_auth.sh

Appendixes

ssh_hash_auth.sh (Production Authentication Script)

#!/bin/bash
#
# Fast SSH Hash-Based Authentication Script
# Uses binary database for ultra-fast lookups
#
# Usage: ./ssh_hash_auth.sh <username> <key_data>
# Returns: "ssh-rsa <key_data>" if authorised, nothing if unauthorised

set -e

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Check arguments
if [ $# -ne 2 ]; then
    exit 1
fi

USERNAME="$1"
KEY_DATA="$2"

# SSH passes just the key data without the type prefix
# We need to determine the key type from the data
# For now, we'll assume it's the same type as what we have in the database
# This is a simplified approach - in production you might want to detect the type

# Get user's home directory
USER_HOME=$(eval echo ~$USERNAME)
HASH_DB="$USER_HOME/.ssh/authorized_keys.db"

# Check if hash database exists
if [ ! -f "$HASH_DB" ]; then
    exit 1
fi

# Function to extract key type from base64 data
extract_key_type() {
    local key_data="$1"
    local key_type=""
    
    # Decode base64 and extract the key type
    # The format is: [4-byte length][key-type-string][4-byte length][curve-name][4-byte length][key-data]
    # For RSA: [4-byte length]["ssh-rsa"][4-byte length][exponent][4-byte length][modulus]
    # For ED25519: [4-byte length]["ssh-ed25519"][4-byte length][key-data]
    # For ECDSA: [4-byte length]["ecdsa-sha2-nistp384"][4-byte length][curve-name][4-byte length][key-data]
    
    # Read the first length (4 bytes) and then read the key type string
    local first_length=$(echo "$key_data" | base64 -d | dd bs=1 skip=0 count=4 2>/dev/null | xxd -p | tr -d '\n' | sed 's/000000//')
    local length_dec=$(printf "%d" "0x$first_length")
    
    # Extract the key type string
    key_type=$(echo "$key_data" | base64 -d | dd bs=1 skip=4 count=$length_dec 2>/dev/null | tr -d '\0')
    
    # Handle different key types
    case "$key_type" in
        "ssh-rsa")
            echo "ssh-rsa"
            ;;
        "ssh-ed25519")
            echo "ssh-ed25519"
            ;;
        "ecdsa-sha2-nistp256")
            echo "ecdsa-sha2-nistp256"
            ;;
        "ecdsa-sha2-nistp384")
            echo "ecdsa-sha2-nistp384"
            ;;
        "ecdsa-sha2-nistp521")
            echo "ecdsa-sha2-nistp521"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    local key_type="$2"
    
    # Reconstruct the full public key with a generic comment
    local full_key="${key_type} ${key_data} ssh_hash_auth_key"
    
    # Extract the base64 part and hash with sha256sum
    local key_b64=$(echo "$full_key" | awk '{print $2}')
    local hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    local hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    echo "SHA256:$hash_b64"
}

# Function to check if FTS5 is available
check_fts5() {
    # Check if SQLite3 is available
    if ! command -v sqlite3 >/dev/null 2>&1; then
        return 1
    fi
    
    # Check SQLite version (FTS5 requires SQLite 3.9.0+)
    local version=$(sqlite3 :memory: "SELECT sqlite_version();" 2>/dev/null)
    if [ $? -ne 0 ]; then
        return 1
    fi
    
    # Parse version and check if it's >= 3.9.0
    local major=$(echo "$version" | cut -d. -f1)
    local minor=$(echo "$version" | cut -d. -f2)
    
    if [ "$major" -lt 3 ] || ([ "$major" -eq 3 ] && [ "$minor" -lt 9 ]); then
        return 1
    fi
    
    # Test if FTS5 can be created
    if ! sqlite3 :memory: "CREATE VIRTUAL TABLE test_fts USING fts5(test); DROP TABLE test_fts;" >/dev/null 2>&1; then
        return 1
    fi
    
    return 0
}

# Extract the key type from the base64 data
KEY_TYPE=$(extract_key_type "$KEY_DATA")

# Reconstruct the full key with the extracted type
FULL_KEY="${KEY_TYPE} ${KEY_DATA} ssh_hash_auth_key"

# Generate hash using the correct key type
KEY_HASH=$(generate_hash "$KEY_DATA" "$KEY_TYPE")

# Fast binary lookup using sqlite3 (much faster than grep)
if command -v sqlite3 >/dev/null 2>&1; then
    # Use regular SQLite table lookup (simpler and more reliable)
    if sqlite3 "$HASH_DB" "SELECT 1 FROM hashes WHERE hash='$KEY_HASH' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "$FULL_KEY"
        exit 0
    fi
else
    # Fallback to optimised text search with sort and binary search
    if [ -f "$HASH_DB" ] && sort "$HASH_DB" | grep -q "^$KEY_HASH$"; then
        echo "$FULL_KEY"
        exit 0
    fi
fi

# Key is not authorised, return nothing
exit 1

debug_auth.sh (Debug Authentication Script)

#!/bin/bash

# SSH Hash-Based Authentication Script (DEBUG VERSION)
# This script is called by SSHD via AuthorizedKeysCommand
# Usage: debug_auth.sh <username> <key_data>

set -e

# Configuration
# Get user's home directory
USER_HOME=$(eval echo ~$1)
DB_FILE="$USER_HOME/.ssh/authorized_keys.db"

# Function to extract key type from base64 data
extract_key_type() {
    local key_data="$1"
    local key_type=""
    
    echo "DEBUG: Extracting key type from: $key_data" >&2
    
    # Decode base64 and extract the key type
    # The format is: [4-byte length][key-type-string][4-byte length][curve-name][4-byte length][key-data]
    # For RSA: [4-byte length]["ssh-rsa"][4-byte length][exponent][4-byte length][modulus]
    # For ED25519: [4-byte length]["ssh-ed25519"][4-byte length][key-data]
    # For ECDSA: [4-byte length]["ecdsa-sha2-nistp384"][4-byte length][curve-name][4-byte length][key-data]
    
    # Read the first length (4 bytes) and then read the key type string
    local first_length=$(echo "$key_data" | base64 -d | dd bs=1 skip=0 count=4 2>/dev/null | xxd -p | tr -d '\n' | sed 's/000000//')
    local length_dec=$(printf "%d" "0x$first_length")
    
    echo "DEBUG: First length hex: $first_length, decimal: $length_dec" >&2
    
    # Extract the key type string
    key_type=$(echo "$key_data" | base64 -d | dd bs=1 skip=4 count=$length_dec 2>/dev/null | tr -d '\0')
    
    echo "DEBUG: Extracted key type: '$key_type'" >&2
    
    # Handle different key types
    case "$key_type" in
        "ssh-rsa") echo "ssh-rsa" ;;
        "ssh-ed25519") echo "ssh-ed25519" ;;
        "ecdsa-sha2-nistp256") echo "ecdsa-sha2-nistp256" ;;
        "ecdsa-sha2-nistp384") echo "ecdsa-sha2-nistp384" ;;
        "ecdsa-sha2-nistp521") echo "ecdsa-sha2-nistp521" ;;
        *) echo "unknown" ;;
    esac
}

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    local key_type="$2"
    
    echo "DEBUG: Generating hash for key type: $key_type" >&2
    
    # Reconstruct the full public key with a generic comment
    local full_key="${key_type} ${key_data} ssh_hash_auth_key"
    
    echo "DEBUG: Full key: $full_key" >&2
    
    # Use the same method as ssh_hash_manager.sh
    # Extract the base64 part and hash with sha256sum
    local key_b64=$(echo "$full_key" | awk '{print $2}')
    local hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    local hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    local final_hash="SHA256:$hash_b64"
    echo "DEBUG: Generated hash: $final_hash" >&2
    
    echo "$final_hash"
}

# Check arguments
if [ $# -ne 2 ]; then
    echo "Usage: $0 <username> <key_data>" >&2
    exit 1
fi

USERNAME="$1"
KEY_DATA="$2"

echo "DEBUG: Username: $USERNAME" >&2
echo "DEBUG: Key data: $KEY_DATA" >&2

# Check if database exists
if [ ! -f "$DB_FILE" ]; then
    echo "DEBUG: Database file not found: $DB_FILE" >&2
    exit 1
fi

echo "DEBUG: Database file found: $DB_FILE" >&2

# Extract the key type from the base64 data
KEY_TYPE=$(extract_key_type "$KEY_DATA")

echo "DEBUG: Extracted key type: $KEY_TYPE" >&2

# Reconstruct the full key with the extracted type
FULL_KEY="${KEY_TYPE} ${KEY_DATA} ssh_hash_auth_key"

echo "DEBUG: Full key: $FULL_KEY" >&2

# Generate hash using the correct key type
KEY_HASH=$(generate_hash "$KEY_DATA" "$KEY_TYPE")

echo "DEBUG: Key hash: $KEY_HASH" >&2

# Fast binary lookup using sqlite3 (much faster than grep)
if command -v sqlite3 >/dev/null 2>&1; then
    echo "DEBUG: Using SQLite lookup" >&2
    # Use regular SQLite table lookup (simpler and more reliable)
    if sqlite3 "$DB_FILE" "SELECT 1 FROM hashes WHERE hash='$KEY_HASH' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "DEBUG: Key found in database!" >&2
        echo "$FULL_KEY"
        exit 0
    else
        echo "DEBUG: Key not found in database" >&2
    fi
else
    echo "DEBUG: Using fallback text search" >&2
    # Fallback to optimised text search with sort and binary search
    if [ -f "$DB_FILE" ] && sort "$DB_FILE" | grep -q "^$KEY_HASH$"; then
        echo "DEBUG: Key found in database!" >&2
        echo "$FULL_KEY"
        exit 0
    else
        echo "DEBUG: Key not found in database" >&2
    fi
fi

# Key is not authorised, return nothing
echo "DEBUG: Authentication failed" >&2
exit 1

99-hash-auth.conf (SSHD Configuration)

# Hash-based authentication configuration (per-user)
# This file is automatically generated by ssh_hash_auth installation
# To disable, rename this file or remove it

AuthorizedKeysCommand /usr/local/bin/ssh_hash_auth.sh %u %k
AuthorizedKeysCommandUser root

ssh_hash_manager.sh (Hash Management Tool)

#!/bin/bash
#
# Fast SSH Hash Manager using SQLite database
# Provides ultra-fast hash lookups and management
#

set -e

# Configuration
HASH_FILE_PERMS=600

# Function to generate hash from public key
generate_hash() {
    local key_data="$1"
    
    # Normalise the key (remove extra whitespace)
    normalised_key=$(echo "$key_data" | tr -s ' ')
    
    # Extract the base64 part (second field)
    key_b64=$(echo "$normalised_key" | awk '{print $2}')
    
    # Decode base64 and hash with sha256sum
    hash=$(echo "$key_b64" | base64 -d | sha256sum | cut -d' ' -f1)
    
    # Convert to base64
    hash_b64=$(echo "$hash" | xxd -r -p | base64)
    
    echo "SHA256:$hash_b64"
}

# Function to initialise SQLite database with FTS5 support check
init_database() {
    local username="$1"
    local db_file="$2"
    
    # Create .ssh directory if it doesn't exist
    local ssh_dir=$(dirname "$db_file")
    if [ ! -d "$ssh_dir" ]; then
        mkdir -p "$ssh_dir"
        chmod 700 "$ssh_dir"
        chown "$username:$username" "$ssh_dir"
    fi
    
    # Create SQLite database if it doesn't exist
    if [ ! -f "$db_file" ]; then
        # Always create the main table
        sqlite3 "$db_file" "CREATE TABLE hashes (hash TEXT PRIMARY KEY, description TEXT, added TEXT);"
        
        # Check if FTS5 is available and create FTS table if supported
        if check_fts5; then
            echo "FTS5 detected - creating optimised full-text search tables"
            sqlite3 "$db_file" "CREATE VIRTUAL TABLE hashes_fts USING fts5(hash, description, content='hashes', content_rowid='rowid');"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_ai AFTER INSERT ON hashes BEGIN INSERT INTO hashes_fts(rowid, hash, description) VALUES (new.rowid, new.hash, new.description); END;"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_ad AFTER DELETE ON hashes BEGIN INSERT INTO hashes_fts(hashes_fts, rowid, hash, description) VALUES('delete', old.rowid, old.hash, old.description); END;"
            sqlite3 "$db_file" "CREATE TRIGGER hashes_au AFTER UPDATE ON hashes BEGIN INSERT INTO hashes_fts(hashes_fts, rowid, hash, description) VALUES('delete', old.rowid, old.hash, old.description); INSERT INTO hashes_fts(rowid, hash, description) VALUES (new.rowid, new.hash, new.description); END;"
        else
            echo "FTS5 not available - using standard SQLite tables (slower but functional)"
        fi
        
        chmod $HASH_FILE_PERMS "$db_file"
        chown "$username:$username" "$db_file"
    fi
}

# Function to add hash to database
add_hash() {
    local username="$1"
    local hash="$2"
    local description="$3"
    
    # Get user's home directory
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    # Initialise database
    init_database "$username" "$db_file"
    
    # Check if hash already exists
    if sqlite3 "$db_file" "SELECT 1 FROM hashes WHERE hash='$hash' LIMIT 1;" 2>/dev/null | grep -q "1"; then
        echo "Hash already exists: $hash"
        return
    fi
    
    # Add hash to database
    sqlite3 "$db_file" "INSERT INTO hashes (hash, description, added) VALUES ('$hash', '$description', '$(date -Iseconds)');"
    
    # Set proper permissions
    chmod $HASH_FILE_PERMS "$db_file"
    chown "$username:$username" "$db_file"
    
    echo "Added hash: $hash"
}

# Function to generate hash from public key file
generate_from_file() {
    local username="$1"
    local key_file="$2"
    
    if [ ! -f "$key_file" ]; then
        echo "Error: Key file not found: $key_file"
        exit 1
    fi
    
    # Read the public key
    key_data=$(cat "$key_file")
    
    # Extract comment from the public key (third field)
    comment=$(echo "$key_data" | awk '{print $3}')
    
    # If no comment found, use a default
    if [ -z "$comment" ]; then
        comment="No comment"
    fi
    
    # Generate hash
    hash=$(generate_hash "$key_data")
    
    # Add to user's database with extracted comment
    add_hash "$username" "$hash" "$comment"
    
    echo "Generated hash: $hash"
    echo "Public key: $key_data"
    echo "Comment: $comment"
}

# Function to list hashes for user
list_hashes() {
    local username="$1"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    echo "Authorised hashes for user '$username':"
    echo "======================================"
    
    # Use FTS5 for faster search if available
    if check_fts5; then
        sqlite3 "$db_file" "SELECT hash, description FROM hashes_fts;" 2>/dev/null || echo "No hashes found"
    else
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes ORDER BY added;" 2>/dev/null || echo "No hashes found"
    fi
}

# Function to remove hash
remove_hash() {
    local username="$1"
    local hash="$2"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    # Remove hash from database
    sqlite3 "$db_file" "DELETE FROM hashes WHERE hash='$hash';"
    
    echo "Removed hash: $hash"
}

# Function to search hashes using FTS
search_hashes() {
    local username="$1"
    local search_term="$2"
    
    user_home=$(eval echo ~$username)
    db_file="$user_home/.ssh/authorized_keys.db"
    
    if [ ! -f "$db_file" ]; then
        echo "No hash database found for user '$username'"
        return
    fi
    
    echo "Searching hashes for user '$username' with term: '$search_term'"
    echo "================================================================"
    
    # Use FTS5 for fast full-text search
    if check_fts5; then
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes_fts WHERE hashes_fts MATCH '$search_term' ORDER BY rank;" 2>/dev/null || echo "No matches found"
    else
        sqlite3 "$db_file" "SELECT hash, description, added FROM hashes WHERE hash LIKE '%$search_term%' OR description LIKE '%$search_term%' ORDER BY added;" 2>/dev/null || echo "No matches found"
    fi
}

# Function to check if sqlite3 is available
check_sqlite() {
    if ! command -v sqlite3 >/dev/null 2>&1; then
        echo "Error: sqlite3 is required but not installed."
        echo "Install it with: sudo dnf install sqlite"
        exit 1
    fi
}

# Function to check if FTS5 is available
check_fts5() {
    # Check if SQLite3 is available
    if ! command -v sqlite3 >/dev/null 2>&1; then
        return 1
    fi
    
    # Check SQLite version (FTS5 requires SQLite 3.9.0+)
    local version=$(sqlite3 :memory: "SELECT sqlite_version();" 2>/dev/null)
    if [ $? -ne 0 ]; then
        return 1
    fi
    
    # Parse version and check if it's >= 3.9.0
    local major=$(echo "$version" | cut -d. -f1)
    local minor=$(echo "$version" | cut -d. -f2)
    
    if [ "$major" -lt 3 ] || ([ "$major" -eq 3 ] && [ "$minor" -lt 9 ]); then
        return 1
    fi
    
    # Test if FTS5 can be created
    if ! sqlite3 :memory: "CREATE VIRTUAL TABLE test_fts USING fts5(test); DROP TABLE test_fts;" >/dev/null 2>&1; then
        return 1
    fi
    
    return 0
}

# Main script logic
case "${1:-}" in
    "add")
        if [ $# -lt 4 ]; then
            echo "Usage: $0 add <username> <hash> [description]"
            exit 1
        fi
        check_sqlite
        add_hash "$2" "$3" "${4:-}"
        ;;
    "generate")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 generate <username> <key_file>"
            exit 1
        fi
        check_sqlite
        generate_from_file "$2" "$3"
        ;;
    "list")
        if [ $# -lt 2 ]; then
            echo "Usage: $0 list <username>"
            exit 1
        fi
        check_sqlite
        list_hashes "$2"
        ;;
    "remove")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 remove <username> <hash>"
            exit 1
        fi
        check_sqlite
        remove_hash "$2" "$3"
        ;;
    "search")
        if [ $# -lt 3 ]; then
            echo "Usage: $0 search <username> <search_term>"
            exit 1
        fi
        check_sqlite
        search_hashes "$2" "$3"
        ;;
    *)
        echo "Usage: $0 {add|generate|list|remove|search} [args...]"
        echo ""
        echo "Examples:"
        echo "  $0 generate john ~john/.ssh/id_rsa.pub"
        echo "  $0 add john SHA256:abc123... 'John's desktop'"
        echo "  $0 list john"
        echo "  $0 remove john SHA256:abc123..."
        echo "  $0 search john 'laptop'"
        echo ""
        echo "Note: This system uses SQLite database for ultra-fast lookups"
        exit 1
        ;;
esac

Conclusion

This SSH hash-based authentication system provides a practical, secure, and well-designed solution for SSH key management that can scale from small deployments to enterprise environments.

Key Advantages

  • Secure: No plain-text key storage
  • Fast: SQLite binary lookups
  • Scalable: Per-user databases
  • Manageable: Comprehensive management tools
  • Auditable: Complete audit trail
  • Flexible: Supports multiple key types
  • Reliable: Proven in production use

Production Readiness

The system is production-ready and provides significant security and performance benefits over traditional authorized_keys files. The enhanced version adds enterprise-grade features like expiration, audit logging, and rate limiting while maintaining the simplicity and reliability of the original system.

💡 Disclaimer:
This system is designed to be practical, secure, and maintainable - focus on incremental improvements rather than major changes.

Encrypting files using AES keys on SmartCard HSM or Nitrokey HSM2 without key derivation

Table of Contents

  1. Overview
  2. Prerequisites
  3. Installation
  4. Configuration
  5. AES Key Generation
  6. Testing AES Keys
  7. Encryption/Decryption Scripts
  8. Usage Examples
  9. Important Notes
  10. Troubleshooting
  11. Security Considerations
  12. Conclusion

Overview

This guide explains how to use AES keys with your SmartCard HSM (Identiv uTrust 3512) for encryption and decryption operations.

What This Guide Covers

Primary Use Cases:

  • File Encryption/Decryption: Encrypt and decrypt files using AES keys stored on the SmartCard HSM. Files are transferred to the HSM token where encryption/decryption operations are performed, ensuring no sensitive data remains on the host system.
  • Data Protection: Secure sensitive data with hardware-backed AES encryption
  • Key Management: Generate, store, and manage AES keys on the HSM token
  • Multi-Token Deployment: Export and import AES keys across multiple SmartCard HSM tokens

Supported Operations:

  • AES-CBC encryption and decryption
  • AES-CMAC message authentication
  • Key wrapping and unwrapping
  • Key derivation (SP800-56C)

What This Guide Does NOT Cover:

  • RSA or ECC key operations (different guide)
  • Key derivation for file encryption
  • SSH key management (different use case)
  • Certificate operations (separate topic)

Target Applications:

  • Data at Rest Protection: Secure storage of sensitive files and databases with hardware-backed encryption
  • Secure file storage and transmission
  • Database encryption
  • Application-level data protection
  • Compliance requirements (FIPS, Common Criteria)

[WARNING] CRITICAL SECURITY WARNING: Before using AES keys on your SmartCard HSM, you must verify your firmware version. SmartCard HSM devices with firmware versions 3.1 and 3.2 have a critical bug that generates weak AES keys with little to no entropy. These keys must be considered broken and should not be used for any security operations. Read the advisory.

Required Action: SmartCard HSM and Nitrokey HSM2 - Update to firmware version 3.3 or later via the PKI-as-a-Service Portal before generating or using AES keys.

Note: RSA and ECC keys are not affected by this bug. Only AES key generation is impacted.

Prerequisites

Hardware Requirements

  • SmartCard HSM (Identiv uTrust 3512) with firmware version 3.3 or later
  • CCID-compatible card reader
  • USB connection

Software Requirements

  • Linux with PC/SC support
  • SmartCard HSM PKCS#11 library (MANDATORY) - OpenSC PKCS#11 library cannot detect or use AES keys on the token
  • OpenSC tools (pkcs11-tool)
  • SmartCard Shell3 (for AES key generation)

Firmware Verification

Before proceeding, verify your SmartCard HSM firmware version:

pkcs11-tool --module "/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so" \
    --show-info

Look for the firmware version in the output. If it shows version 3.1 or 3.2, you must update before using AES keys.

Installation

1. Install Development Tools

# Install development tools
sudo dnf groupinstall "Development Tools"
sudo dnf install autoconf automake libtool
sudo dnf install pcsc-lite-devel

2. Download and Compile SmartCard HSM PKCS#11 Library

[WARNING] CRITICAL: The SmartCard HSM PKCS#11 library is MANDATORY for AES key operations. The standard OpenSC PKCS#11 library cannot detect or use AES keys stored on the SmartCard HSM token.

# Download from GitHub releases
cd /tmp
wget https://github.com/CardContact/sc-hsm-embedded/releases/download/v2.12/\
    sc-hsm-embedded-2.12.tar.gz
tar -xzf sc-hsm-embedded-2.12.tar.gz
cd sc-hsm-embedded-2.12

# Configure and compile
autoreconf -fi
./configure --prefix=/home/user/bin/sc-hsm-embedded-2.12
make
make check
make install

Configuration

Environment Variables

# Set the module path
export SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
export HSM_SLOT=1

Token Detection

Verify your SmartCard HSM is detected using the SmartCard HSM PKCS#11 library (not OpenSC):

pkcs11-tool --module "/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so" \
    --list-token-slots

Expected output:

Available slots:
Slot 0 (0x1): Identiv uTrust 3512 SAM slot Token [CCID Interface] (55512030605)
  token label        : [REDACTED]-HISEC-1
  token manufacturer : CardContact (www.cardcontact.de)
  token model        : SmartCard-HSM
  token flags        : login required, rng, token initialized, PIN initialized

AES Key Generation

Important Note: AES Key Generation Method

AES keys on SmartCard HSM are generated and stored using SmartCard Shell3 software, not through direct PKCS#11 operations. The SmartCard Shell3 provides the interface for creating AES keys on the HSM token.

SmartCard Shell3 Requirements

  • GUI Version: SmartCard Shell3 GUI (scsh3gui) is the primary method for AES key generation
  • CLI Version: SmartCard Shell3 CLI (scsh3) has known card detection issues (see separate documentation)
  • Key Management: Keys are created through the SmartCard Shell3 Key Manager interface

Key Generation Process

  1. Launch SmartCard Shell3 GUI:
    cd /home/user/CardContact/scsh/scsh-3.18.55/
    ./scsh3gui
    
  2. Authenticate to the HSM:
    • Select your SmartCard HSM token
    • Enter your User PIN when prompted
  3. Access Key Manager:
    • Navigate to the Key Manager in the SmartCard Shell3 interface
    • Right-click on the appropriate key domain (e.g., DKEK share)
  4. Generate AES Key (presumably on the DKEK share):
    • Select "Generate Key" → "AES"
    • Choose key size (128, 192, or 256 bits)
    • Provide a label for the key
    • Select algorithms (AES-CBC, AES-CMAC, etc.)

Exporting AES Keys for Multiple Tokens

If you need to use the same AES key across multiple SmartCard HSM tokens that share the same DKEK, you can export the key:

  1. In the SmartCard Shell3 GUI, click on the AES key object in the list (PIN required)
  2. Right-click on the key and select "Wrap Key (and Certificate)"
  3. Save the key as a *.wky file
  4. Import the *.wky file on other tokens with the same DKEK share

Verifying Generated Keys

After generating AES keys via SmartCard Shell3, you can verify them using PKCS#11 tools, as shown below.

Testing AES Keys

List All AES Keys

Use the following script to list all AES keys on your token:

[WARNING] IMPORTANT: You may need to modify the following variables in the script to match your setup:

  • HSM_SLOT: The slot number where your SmartCard HSM is detected
  • SC_HSM_MODULE: Path to your SmartCard HSM PKCS#11 library

Save this script as list_aes_keys.sh:

#!/bin/bash
# list_aes_keys.sh

set -e

# Configuration
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1

echo "=== AES Keys on SmartCard HSM ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo

echo "Please enter your SmartCard HSM PIN when prompted:"

# List all secret key objects
echo "=== All Secret Key Objects ==="
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --list-objects \
    --type secrkey

echo
echo "=== Summary ==="
echo "The above shows all secret key objects on your SmartCard HSM token."
echo "Look for entries starting with 'Secret Key Object; AES' to find AES keys."
echo "Each AES key will show:"
echo "- Label (name)"
echo "- ID (unique identifier)"
echo "- Usage (encrypt, decrypt)"
echo "- Access (security attributes)"
echo

Usage:

chmod +x list_aes_keys.sh
./list_aes_keys.sh

Expected Output:

Secret Key Object; AES length 32
  label:      AES-1
  ID:         b20ba7e28e29c90f
  Usage:      encrypt, decrypt
  Access:     sensitive, always sensitive, never extractable, local
Secret Key Object; AES length 32
  label:      AES-2
  ID:         4eaa8024a063e8b6
  Usage:      encrypt, decrypt
  Access:     sensitive, always sensitive, never extractable, local

Encryption/Decryption Scripts

Simple AES Encryption Test

Save this script as aes_encrypt_simple.sh:

#!/bin/bash
# aes_encrypt_simple.sh

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Simple AES-1 Encryption Test ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key ID: $AES_KEY_ID"
echo

# Create a test file with exactly 16 bytes (AES block size)
echo "Creating test data..."
echo -n "1234567890123456" > test_block.txt
echo "[INFO] Created test file with 16 bytes (AES block size)"

# Generate IV
echo "Generating IV..."
openssl rand -hex 16 > iv.txt
IV=$(cat iv.txt)
echo "[INFO] IV: $IV"

echo
echo "=== Attempting AES-CBC Encryption ==="
echo "Please enter your SmartCard HSM PIN when prompted:"

# Try encryption with the exact block size
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --encrypt \
    --mechanism AES-CBC \
    --iv "$IV" \
    --input-file test_block.txt \
    --output-file test_block.enc \
    --id "$AES_KEY_ID"

if [ $? -eq 0 ]; then
    echo "[SUCCESS] Encryption completed!"
    echo "[INFO] Encrypted file: test_block.enc"
    echo "[INFO] IV saved to: iv.txt"
else
    echo "[ERROR] Encryption failed"
fi

echo
echo "=== Test Complete ==="

Simple AES Decryption Test

Save this script as aes_decrypt_simple.sh:

#!/bin/bash
# aes_decrypt_simple.sh

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Simple AES-1 Decryption Test ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key ID: $AES_KEY_ID"
echo

# Check if encrypted file exists
if [ ! -f test_block.enc ]; then
    echo "[ERROR] Encrypted file not found: test_block.enc"
    echo "Please run aes_encrypt_simple.sh first"
    exit 1
fi

# Check if IV file exists
if [ ! -f iv.txt ]; then
    echo "[ERROR] IV file not found: iv.txt"
    echo "Please run aes_encrypt_simple.sh first"
    exit 1
fi

IV=$(cat iv.txt)
echo "[INFO] Using IV: $IV"

echo
echo "=== Attempting AES-CBC Decryption ==="
echo "Please enter your SmartCard HSM PIN when prompted:"

# Try decryption
pkcs11-tool --module "$SC_HSM_MODULE" \
    --slot $HSM_SLOT \
    --login \
    --decrypt \
    --mechanism AES-CBC \
    --iv "$IV" \
    --input-file test_block.enc \
    --output-file test_block.dec \
    --id "$AES_KEY_ID"

if [ $? -eq 0 ]; then
    echo "[SUCCESS] Decryption completed!"
    echo "[INFO] Decrypted file: test_block.dec"
    
    # Verify decryption
    if diff test_block.txt test_block.dec > /dev/null; then
        echo "[SUCCESS] Decryption verified - files match!"
    else
        echo "[ERROR] Decryption failed - files don't match"
    fi
else
    echo "[ERROR] Decryption failed"
fi

echo
echo "=== Test Complete ==="

Complete AES Key Usage Script

Save this script as aes_encrypt_decrypt.sh:

#!/bin/bash

# Use the AES-1 key on SmartCard HSM for encryption/decryption
# The key was found with the new SmartCard HSM PKCS#11 library

set -e

# Configuration
# [WARNING] IMPORTANT: Modify these variables to match your setup
SC_HSM_MODULE="/home/user/bin/sc-hsm-embedded-2.12/lib/libsc-hsm-pkcs11.so"
HSM_SLOT=1
AES_KEY_LABEL="AES-1"  # Use the label from your AES key
AES_KEY_ID="b20ba7e28e29c90f"  # Use the ID from your AES key

echo "=== Using AES-1 Key on SmartCard HSM ==="
echo "Library: $SC_HSM_MODULE"
echo "Slot: $HSM_SLOT"
echo "Key Label: $AES_KEY_LABEL"
echo "Key ID: $AES_KEY_ID"
echo

# Function to encrypt a file using AES-1 key
encrypt_file() {
    local input_file="$1"
    local output_file="$2"
    
    echo "=== Encrypting with AES-1 Key ==="
    echo "Input: $input_file"
    echo "Output: $output_file"
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    # Generate a random IV (16 bytes for AES)
    echo "[INFO] Generating random IV..."
    openssl rand -hex 16 > iv.txt
    IV=$(cat iv.txt)
    echo "[INFO] IV: $IV"
    
    # Use AES-CBC encryption with the AES-1 key and IV
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --encrypt \
        --mechanism AES-CBC \
        --iv "$IV" \
        --input-file "$input_file" \
        --output-file "$output_file" \
        --id "$AES_KEY_ID"
    
    if [ $? -eq 0 ]; then
        echo "[SUCCESS] File encrypted successfully"
        echo "[INFO] IV saved to iv.txt for decryption"
    else
        echo "[ERROR] Encryption failed"
        rm -f iv.txt
        return 1
    fi
}

# Function to decrypt a file using AES-1 key
decrypt_file() {
    local input_file="$1"
    local output_file="$2"
    local iv_file="${3:-iv.txt}"
    
    echo "=== Decrypting with AES-1 Key ==="
    echo "Input: $input_file"
    echo "Output: $output_file"
    echo "IV file: $iv_file"
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    if [ ! -f "$iv_file" ]; then
        echo "[ERROR] IV file not found: $iv_file"
        echo "Please provide the IV used for encryption"
        return 1
    fi
    
    IV=$(cat "$iv_file")
    echo "[INFO] Using IV: $IV"
    
    # Use AES-CBC decryption with the AES-1 key and IV
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --decrypt \
        --mechanism AES-CBC \
        --iv "$IV" \
        --input-file "$input_file" \
        --output-file "$output_file" \
        --id "$AES_KEY_ID"
    
    if [ $? -eq 0 ]; then
        echo "[SUCCESS] File decrypted successfully"
    else
        echo "[ERROR] Decryption failed"
        return 1
    fi
}

# Function to test the AES-1 key with a simple operation
test_aes1_key() {
    echo "=== Testing AES-1 Key ==="
    echo "Please enter your SmartCard HSM PIN when prompted:"
    
    # Try to get key info
    pkcs11-tool --module "$SC_HSM_MODULE" \
        --slot $HSM_SLOT \
        --login \
        --list-objects \
        --type secrkey \
        --id "$AES_KEY_ID"
}

# Function to show usage
show_usage() {
    echo "Usage: $0 {encrypt|decrypt|test} [ ]"
    echo
    echo "Examples:"
    echo "  $0 test                           # Test AES-1 key"
    echo "  $0 encrypt test.txt test.txt.enc  # Encrypt file"
    echo "  $0 decrypt test.txt.enc test.txt.dec [iv.txt]  # Decrypt file"
    echo
    echo "The script uses the AES-1 key stored on your SmartCard HSM"
    echo "for AES-CBC encryption/decryption operations."
}

# Main script logic
case "${1:-}" in
    encrypt)
        if [ $# -ne 3 ]; then
            echo "[ERROR] Usage: $0 encrypt  "
            exit 1
        fi
        encrypt_file "$2" "$3"
        ;;
    decrypt)
        if [ $# -lt 3 ] || [ $# -gt 4 ]; then
            echo "[ERROR] Usage: $0 decrypt   [iv_file]"
            exit 1
        fi
        decrypt_file "$2" "$3" "${4:-}"
        ;;
    test)
        test_aes1_key
        ;;
    *)
        show_usage
        exit 1
        ;;
esac

echo
echo "=== Operation Complete ==="

Usage Examples

1. List AES Keys

./list_aes_keys.sh

2. Test Simple Encryption/Decryption

# Encrypt
./aes_encrypt_simple.sh

# Decrypt
./aes_decrypt_simple.sh

3. Use Complete Script

# Test key
./aes_encrypt_decrypt.sh test

# Encrypt file
./aes_encrypt_decrypt.sh encrypt myfile.txt myfile.txt.enc

# Decrypt file
./aes_encrypt_decrypt.sh decrypt myfile.txt.enc myfile.txt.dec

Important Notes

Data Requirements

  • Block Size: Data must be padded to AES block size (16 bytes)
  • IV Required: CBC mode requires an Initialization Vector
  • PIN Authentication: All operations require SmartCard HSM PIN

Security Features

  • Hardware Protection: Keys never leave the HSM in plaintext
  • PIN Protection: All operations require authentication
  • Key Export: Keys can be exported only between tokens sharing the same DKEK share

Supported Operations

  • AES-CBC Encryption/Decryption
  • AES-CMAC Signing
  • AES Key Generation (via SmartCard Shell3 GUI only)

Firmware Security Considerations

[WARNING] Critical HSM Firmware Bug (Versions 3.1-3.2):

  • SmartCard HSM and Nitrokey HSM2 devices with firmware versions 3.1 and 3.2 generate weak AES keys. Read the advisory.
  • These keys have little to no entropy and must be considered broken
  • Impact: Only affects AES key generation, not RSA or ECC keys
  • Solution: Be sure the tokens are running the latest firmware version. Check if newer version is available at PKI-as-a-Service-Portal, and install it before generating or using AES keys.

Update Process:

  1. Register an account at CardContact Developer Network (CDN)
  2. Create a firmware update request
  3. Select "Current token in reader" and submit
  4. Follow the portal instructions for the update process
  5. Important: All keys must be removed before firmware update
  6. After update, reinitialize the device and restore keys

Troubleshooting

Common Issues

  1. "CKR_FUNCTION_NOT_SUPPORTED"
    • Ensure data is padded to 16-byte blocks
    • Use exact block size for testing
  2. "CKR_USER_NOT_LOGGED_IN"
    • Enter PIN when prompted
    • Don't redirect output during PIN prompts
  3. "CKR_SLOT_ID_INVALID"
    • Use slot 1 instead of slot 0
    • Verify token is present

Security Considerations

Direct AES Storage vs Key Derivation

Aspect Direct AES Storage HSM Key Derivation
Key Storage AES key stored on HSM Salt stored on disk, key derived on-demand
Intermediate Data None Salt file, derived key file
Cold Boot Vulnerability Low (no temp files) Medium (temp files on disk)
Performance Fast (direct operations) Slower (derivation + operation)
Flexibility Fixed key Variable key from passphrase

Best Practices

  1. Authentication: Always authenticate before operations
  2. Data Padding: Ensure data fits AES block requirements
  3. IV Management: Use random IVs and store them securely
  4. Error Handling: Check return codes and handle failures
  5. Cleanup: Remove temporary files securely

Conclusion

This guide provides a complete workflow for using AES keys with your SmartCard HSM. The approach offers hardware-level security with direct AES operations, making it suitable for high-security applications where key protection is critical.

Technical Summary

  1. AES Key Generation: SmartCard Shell3 GUI required for key generation; PKCS#11 direct operations not supported
  2. Firmware Security: Version 3.3+ mandatory due to weak key generation vulnerability in versions 3.1-3.2
  3. Security Architecture: Hardware-protected AES keys with mandatory PIN authentication for all operations
  4. CLI Implementation: SmartCard Shell3 CLI exhibits card detection failures due to initialization sequence differences
  5. Firmware Update Protocol: Requires complete key removal and device reinitialization post-update

How to fix a problem with running the latest AppImage version of YubiKey Manager on Rocky Linux 9.4

Yubico provides an easy to run YubiKey Manager (Ykman), packed as an AppImage. One can download that image by following the download URL provided at:

https://www.yubico.com/support/download/yubikey-manager/

To obtain and use Ykman as an AppImage, open a terminal window, use wget to download the AppImage file, and then set the right permissions (chmod) for executing it:

wget https://developers.yubico.com/yubikey-manager-qt/Releases/yubikey-manager-qt-latest-linux.AppImage
chmod 755 yubikey-manager-qt-latest-linux.AppImage
./yubikey-manager-qt-latest-linux.AppImage

It may sound easy and convenient. However, if you try running Ykman as an AppImage on Rocky Linux 9.4, the app won't start. This is due to the latest implemented SELinux policy.

To prevent that failure from happening, create a new file called ykman.avc and store inside the following content (as a single line):

type=AVC msg=audit(1717919286.134:498): avc:  denied  { execmod } for  pid=17549 comm="ykman-gui" path=2F6D656D66643A4A4954436F64653A5174516D6C202864656C6574656429 dev="tmpfs" ino=161080 scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tcontext=unconfined_u:object_r:user_tmp_t:s0 tclass=file permissive=0

Afterwards, compile a new module:

sudo audit2allow -M ykman < ykman.avc

and install it:

semodule -i ykman.pp

That should solve the issue.

What to do if Smart Card Shell (scsh) cannot detect HSM tokens connected to RHEL9 or Rocky 9 installation

The HSM management through scsh on RHEL9 (incl Rocky 9) may pose a challenge due to an issue with the manner in which that Linux distribution installs PCSC Lite components. Consequently, neither the scsh command line tool nor scsh3gui are capable of recognizing the HSM tokens that have already been connected to the system.

The problem is caused by the set of libraries that are installed by default when smart card support is enabled on the distribution through the package system. That support implies the installation of libpcsclite.so.1 instead of libpcsclite.so. To make scsh recognize the connected HSM tokens, one should find a way to bring libpcsclite.so into the system, which is required by Java OpenJDK for successful execution of scsh classes.

Now, in greater details. By enabling smart card support, the package management system installs those two packages:

pcsc-lite
pcsc-lite-libs

as well as their dependencies. Here pcsc-lite-libs only provides:

/usr/lib64/libpcsclite.so.1

None of the above provides:

/usr/lib64/libpcsclite.so

At this juncture, it is advised to refrain from creating manually libpcsclite.so as a symlink to libpcsclite.so.1, as it may result in interference with the package system. There is a much more proper way to bring libpcsclite.so to the system:

dnf install pcsc-lite-devel

Installing pcsc-lite-devel just creates the necessary symlink and registers it in the RPM database.

How to enable the screen/window sharing (screen casting) on Rocky Linux 9 (GNOME)

By default (fresh installation of the distribution and the apps) the desktop applications available on Rocky Linux 9 (GNOME desktop) cannot share the entire screen, a window, or a browser tab.

That problem appears due to a combination of a missing package (xdg-desktop-portal-gnome), which might not be installed by default, and systemd services (xdg-desktop-portal and/or pipewire), which are either missing or not enabled.

To solve the issue you need to install xdg-desktop-portal-gnome

sudo dnf -y install xdg-desktop-portal-gnome

and enable/start/restart the required services (as user):

systemctl --user enable --now pipewire
systemctl --user restart xdg-desktop-portal.service

SmartCard-HSM USB token: Using Smart Card Shell 3 for initializing and configuring the token, generating key pairs, and importing keys and X.509 certificates from external PKCS#12 containers

Content:

  1. Introduction
  2. Prerequisites
  3. Downloading and installing Smart Card Shell 3
  4. Running Smart Card Shell 3 GUI
  5. Loading the key manager in Smart Card Shell 3 GUI
  6. Initializing the token and configuring DKEK to enable the import of keys and X.509 certificates from PKCS#12 files
  7. Generating ECC key pair (by means of DKEK shares)
  8. Importing key pair and the corresponding X.509 certificate from PKCS#12 file into the token (by means of DKEK shares)

 

1. Introduction

The SmardCard-HSM (Standard-A USB) token (you can order it online):

is a reliable, fast, secure, and OpenSC compatible HSM device, for generating, storing, and importing RSA, AES, and Elliptic Curve (EC) keys and X.509 certificates. Maybe, the best feature of the device is its enhanced support for EC (up to 521-bit keys), and the ability to import key pairs and certificates from PKCS#12 containers. Later allows to clone a key pair into several token devices, as a hardware backup scenario.

Unfortunately, the vendor does not provide (yet) a comprehensive documentation for end users, describing in details the specific process of importing key pairs and X.509 certificates from PKCS#12 containers (files) into the token (which is something very much in demand). Therefore, the goal of this document is to fix (at least partially) that gap in the documentation.

Note, that the procedures, described above, are not part of the everyday practice. They are required only for initializing the token device, generating EC keys for curves that are not currently listed as supported in the token's firmware (for instance, secp384r1 curve is supported by the token's processor, but not listed as supported in the firmware and the OpenSC based tools cannot request secp384r1 key generation), and to import key pairs and X.509 certificates from PKCS#12 files.

 

2. Prerequisites

To be able to follow the steps given below, you need to have installed and updated Linux distribution, running a Graphical Desktop environment (GNOME, KDE). Recent OpenJDK (17 is recommended if available) must be installed and kept updated. Do not install OpenJDK manually, since it is an essential software package! Always use the package manager, provided by the vendor of your Linux distribution to install or update OpenJDK:

  • RHEL7, CentOS 7, Scientific Linux 7:

    # yum install java-11-openjdk.x86_64
  • Fedora (current), RHEL8/9, CentOS 8, Scientific Linux 8, Rocky Linux 8/9, Alma Linux 9:

    # dnf install java-17-openjdk.x86_64
  • Ubuntu:

    # apt-get install openjdk-17-jdk-headless

It is not a good idea to configure the HSM token and manage its content on a system, that is used for social networking, software testing, gaming, or any other activity, that might be considered risky in this case. Always use a dedicated desktop system (or dedicated Linux virtual machine) for managing your PKI infrastructure.

You might have more than one version of OpenJDK installed on your system. So the first step is to check that and set the latest OpenJDK as a default Java provider. Execute the following command line (be super user or root):

# alternatives --config java

to check how many Java packages (provides) are installed and available locally, and which one of them is set as current default. For example, the following result:

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
*+ 1           java-11-openjdk.x86_64 (/usr/lib/jvm/java-11-openjdk-11.0.19.0.7-1.el9_1.x86_64/bin/java)
   2           java-17-openjdk.x86_64 (/usr/lib/jvm/java-17-openjdk-17.0.7.0.7-1.el9_1.x86_64/bin/java)


Enter to keep the current selection[+], or type selection number:

means there are two OpenJDK packages installed, and the first one is set a default Java provider (see which is the entry marked with "+" in the first column). To set OpenJDK 17 default Java provider, type the ID number assigned to the package in the list (in the "Selection" column) and press "Enter" afterwards (in the above example, the ID used is 2):

Enter to keep the current selection[+], or type selection number: 2

It is always a good idea to check if the symlinks created by the alternatives tool points to the correct target. The simplest way to do so for OpenJDK 11 is to follow the symlink /etc/alternatives/java:

$ ls -al /etc/alternatives/java

and verify that the target is the OpenJDK 11 java executable:

lrwxrwxrwx. 1 root root 63 Apr 29 13:58 /etc/alternatives/java -> /usr/lib/jvm/java-17-openjdk-17.0.7.0.7-1.el9_1.x86_64/bin/java

Also check if the Java major version of the target:

$ java --version

is 17:

openjdk 17.0.7 2023-04-18 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.7.0.7-1.el9_1) (build 17.0.7+7-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.7.0.7-1.el9_1) (build 17.0.7+7-LTS, mixed mode, sharing)

Also, have pcscd running.

 

3. Downloading and installing Smart Card Shell 3

Be sure you have OpenJDK 17 installed, as specified above. Then visit the web page:

https://www.openscdp.org/scsh3/download.html

click on "IzPack Installer" link, and save locally the provided JAR archive of the installer.

Decide what kind of installation of Smart Card Shell 3 do you really need - to allow all users of the system to run the program code (run the installer as super user), or to limit that ability to a certain unprivileged user (perform the installation using that particular user ID):

  • run the installer as super user (root):

    You should install the program into a system folder, where the users can only read and execute the Java code (no write access should be given by default). That kind of restrictions will protect the executable code from deletion of modification.

  • run the installer as a non-privileged user:

    In this case, the simplest solution is to install the program into the home folder of the user. That type of installation is recommended only for a user, who really understands how to keep the executable code safe.

If followed, the steps given bellow will install the executable code of the program in the home folder of those user, who is executing the installer.

Open a terminal and type:

$ java -jar /path/to/scsh3.XX.YYY.jar

(here XX and YYY are numbers, unique for the current version). The following window will appear (press there the button "Next" to continue):

Select the installation folder (use the button "Browse" to change it, if you do not like the one suggested by the installer), and press "Next":

Now you will be able to see the progress of the installation process (press the button "Next" to continue, when it is done):

Next, you need to decide whether or not to create a shortcut to the program in the GNOME "Applications" menu (it is recommended to create such a shortcut), and who will be able to invoke the installed program (the last is useful only if you install the software as super user or root into a system folder). Press the button "Next":

and in the last window of the installer, press "Done" to exit:

Important note for those who are running Smart Card Shell 3 on RHEL9 (Rocky Linux 9, Alma Linux 9)!

Smart Card Shell 3 needs the library libpcsclite.so, but no package provides libpcsclite.so on RHEL9. To overcome that issue, install the package pcsc-lite-libs (if it is not already installed) and create the symlink /usr/lib64/libpcsclite.so that points to /usr/lib64/libpcsclite.so.1:

cd /usr/lib64
ln -s libpcsclite.so.1 libpcsclite.so

 

4. Running Smart Card Shell 3 GUI

Be sure the Smart Card Shell 3 GUI is installed. Expand the "Applications" menu (1), go to "Programming" (2), press there "Smart Card Shell 3" (3):

and wait for the appearance of the main window of the program:

During the first run, a new window might appear, asking for configuring the path to a working directory, where the output files will be stored by default. Click the "Browse" button:

select the folder (1) and press "Open" (2) to go back:

Thus path to the folder will appear in the text field (next to the "Browse" button). In addition, mark at least "Use this as the default and do not ask again", to complete the configuration and pressing "OK" to exit:

 

5. Loading the key manager in Smart Card Shell 3 GUI

Run Smart Card Shell 3 GUI. The key manager is a loadable script, dedicated to manage the objects in the token.

To load it, either expand "File" menu and select there "Key Manager":

or press "Ctrl+M". Once loaded, the key manager will check if the token is connected and will create in the main window a tree of those objects, it discovered in the token. Details about all important events will be reported in the "Shell" tab:

 

6. Initializing the token and configuring DKEK to enable the import of keys and X.509 certificates from PKCS#12 files

The goal of the initialization process, is to enable the import (export too) of keys and X.509 certificates, stored in files (most often PKCS#12 files), into the token, , based on "device-key-encryption-key" (DKEK) type of store. Note that DKEK is not enabled by default.

WARNING! DURING THE INITIALIZATION, ALL DATA, STORED IN THE TOKEN, WILL BE LOST!

To start with the initialization, run the Smart Card Shell 3, load the key manager script, click once with the right button of the mouse upon "SmartCard-HSM" (that is the root of the key manager tree), and select "Initialize Device" in the menu:

Supply the following information (or press "Cancel" to terminate the initialization):

  • The actual SO-PIN for configuring the token. The default SO-PIN code is 3537363231383830, unless it has been changed (if you forget the SO-PIN, consider the token lost). Press "OK" to continue:

  • The label of the token (that is the token's friendly name, displayed in the PKI applications). Press "OK" to continue:

  • The authentication mechanism for restricting the access to the objects and processor of the token. In most cases, you might need to select "User PIN" (you may set another authentication mechanism, but this one is the most poplar one). Press "OK" to continue:

  • The way to restore the access to the keys and X.509 certificates, if the PIN is lost, forgotten, or locked (if a wrong PIN is entered more than 3 times, consecutively). Select "Resetting PIN with SO-PIN allowed" and press "OK" (select "Resetting PIN with SO-PIN not allowed" only in specific cases, where the implementation of such policy is necessary):

  • The new PIN code (do not use the number shown in the picture bellow). Press "OK" to continue:

  • The new PIN code (again, for confirmation). Press "OK" to continue:

  • Request for using "DKEK Shares". Press "OK" to continue:

  • The number of DKEK shares (use 1, unless you are expert). Press "OK" to continue:

  • Press "Cancel" here (if you press "OK" both SO-PIN and PIN codes will be stored locally in an unencrypted file):

After the success of the initialization, you will see only three objects displayed in the key manager tree: User PIN, SO-PIN, and DKEK entry. The message "Initializing complete" (an indication that the requested initialization has been successfully completed) will be seen in the "Shell" tab:

Note, that at this point, the requested DKEK shares are not yet initialized or/and imported to the token! The appearance of "DKEK set-up in progress with 1 of 1 shares missing" in the key manager tree indicates that. You need to request manually the creation of DKEK shares file and import its content to the token, by following strictly the instructions given bellow:

  • Request the creation of DKEK share, by clicking once with the right button of the mouse on the root of the key manager tree (on "SmartCard-HSM) and picking "Create DKEK share" in the menu:

  • Enter the name of DKEK file to create, and press "OK" (store the file in the working directory of the program, configured during the first run):

  • Set the password for protecting the DKEK share file content and press "OK":

  • Confirm the password for protecting the DKEK share file content and press "OK":

  • With the left button of the mouse, click once upon the object named "DKEK set-up in progress with 1 of 1 shares missing", displayed in the key manager section of the main window of the program:

  • Use the button "Browse" to find and choose the created DKEK file (file extension is *.pbe), and press "OK":

  • Enter the password set (before) for protecting the content of the DKEK file:

It will take up to 10 seconds to derive the keys and import the DKEK into the token (watch the related messages, appearing in the "Shell" tab). At the end, you will see that the object "DKEK set-up in progress with 1 of 1 shares missing" (in the key manager tree) will be renamed (the new name will include the ID of the DKEK object):

IMPORTANT! At this point, you need to store a copy of the DKEK share file, generated during the initialization, in a safe place!

In the examples above, that file is /home/vesso/CardContact/2019_02.pdb, but in you case the file will be with different name and location.

 

7. Generating ECC key pair (by means of DKEK shares)

IMPORTANT! Be absolutely sure that no application, other than Smart Card Shell 3, is communicating with the token. Stop all running processes of PKCS#11 compatible software (like Mozilla Firefox, Mozilla Thunderbird, Google Chrome, XCA), that might take over the token.

Start the Smart Card Shell 3, plug the token into the USB port, and load the key manager script. Be sure that the token is initialized properly, to support DKEK shares.

Click once on the token name, in the key manager section, with the right button of the mouse and select "Generate ECC Key" in the menu:

Select the elliptic curve type, and press "OK":

Provide a friendly name (alias) for the key pair (it is an internal name for naming the key pair object in the token) and press "OK":

Type (separated by comma) the list of the hex codes of the signature algorithms, that will be allowed for the signing, when using the encryption key (the most commonly used ones, 73,74,75, are given in the example bellow), then press "OK":

Wait until the token is finishing with the generation of the requested key pair. Once ready, you will see the new key object under the tree of the DKEK share:

 

7. Importing key pair and the corresponding X.509 certificate from PKCS#12 file into the token (by means of DKEK shares)

IMPORTANT! Be absolutely sure that no application, other than Smart Card Shell 3, is communicating with the token. Stop all running processes of PKCS#11 compatible software (like Mozilla Firefox, Mozilla Thunderbird, Google Chrome, XCA), that might take over the token.

Otherwise the PKCS#12 import might fail, by rising (in the "Shell" tab) the following error:

GPError: Card (CARD_INVALID_SW/27010) - "Unexpected SW1/SW2=6982 (Checking error: Security condition not satisfied) received" in ...

Start the Smart Card Shell 3, plug the token into the USB port, and load the key manager script. Be sure that the token is initialized properly, to support DKEK shares. Using DKEK share in this case is mandatory for this operation.

  • Click once on the token name in the key manager section, with the right button of the mouse, and select "Import from PKCS#12" in the menu:

  • Specify the number of DKEK shares to use (use 1, if you follow the recipes provided in this document), and click "OK":

  • Select the file, containing the DKEK shares (use the file name created during the initialization), and click "OK":

  • Enter the password for decrypting the DKEK file (that password is set during the creation of the file), click "OK", and wait up to 10 seconds for generating the shared keys:

  • Select the PKCS#12 file and click "OK":

  • Provide the password, set for protecting the content of the PKCS#12 file, and click "OK":

  • Select the key pair and X.509 certificate to import from the PKCS#12 file, by choosing their internal PKCS#12 name, and click "OK":

  • Enter a name to assign to the imported key pair and X.509 certificate, and click "OK":

  • Click "OK" if you wanna import more key pairs and X.509 certificates, stored in the same PKCS#12 file, or click "Cancel" to finish:

If the import is successful, you will see the key pair imported into the DKEK share (in the key manager section), and information about the process, in the "Shell" section (as shown in the red frames bellow):

IMPORTANT! You cannot import X.509 certificate chain from a PKCS#12 container into the token, by using the procedure proposed above.

But you might do that later, by using pkcs11-tool, it that is really necessary. Notice, that the X.509 certificates in the chain are public information and they might be used in out-of-the-box manner (installed in the software certificate repository of the browser, which will be using with the token). Their presence in the token storage is not mandatory.


Creative Commons - Attribution 2.5 Generic. Powered by Blogger.

Implementing LUKS Encryption on Software RAID Arrays with LVM2 Management

A comprehensive guide to partition-level encryption for maximum security ...

Search This Blog

Translate