Storage Backend Abstraction for CA Private Keys
Version: v0.2-alpha Status: Phase 2 Complete (Database + Vaultwarden backends) Next: Phase 3 (Migration tools + Configuration UI)
Overview
The PKI system implements a pluggable storage architecture for CA private keys, enabling enterprise organizations to choose their preferred storage backend based on security requirements, compliance needs, and existing infrastructure.
Supported Backends
| Backend | Status | Description | Use Case |
|---|---|---|---|
| Database | β Production | Direct table storage with libsodium encryption | Baseline, self-contained deployments |
| Vaultwarden | β Phase 2 | Self-hosted Password Manager API | On-premise deployments |
| Bitwarden Secrets Manager | π Future | Official PHP SDK integration | Enterprise secret management |
| Shamir Secret Sharing | π Phase 3 | n-of-m threshold cryptography | Distributed trust, high security |
| Hardware Security Module | π Phase 4 | YubiKey/NitroKey PKCS#11 | Maximum security, compliance |
Architecture
Interface Contract
All storage backends implement the SecureKeyStorageInterface:
interface SecureKeyStorageInterface
{
// Store encrypted CA private key
public function storePrivateKey(string $caId, string $encryptedKey, array $metadata = []): string;
// Retrieve encrypted CA private key
public function retrievePrivateKey(string $caId): string;
// Delete CA private key from storage
public function deletePrivateKey(string $caId): bool;
// Check if CA private key exists
public function keyExists(string $caId): bool;
// Migrate key to another backend
public function migrateKey(string $caId, SecureKeyStorageInterface $newBackend): bool;
// Get backend name identifier
public function getBackendName(): string;
// Health check for backend availability
public function healthCheck(): array;
}
Database Schema
Three new columns in certificate_authorities table:
-- Storage backend identifier
key_storage_backend VARCHAR(50) DEFAULT 'database'
COMMENT 'database|bitwarden|vaultwarden|shamir_sss|hsm'
-- External storage reference
key_storage_reference VARCHAR(255) NULL
COMMENT 'Bitwarden secret ID, HSM slot ID, etc.'
-- Backend-specific metadata
key_storage_metadata JSON NULL
COMMENT 'project_id, hsm_config, etc.'
Phase 1: Database Backend
Implementation
The DatabaseKeyStorage class wraps the existing SecureKeyStorageService to provide baseline storage functionality.
Location: app/Services/Storage/DatabaseKeyStorage.php
Key Features:
- Direct storage in certificate_authorities table
- Libsodium secretbox encryption (AEAD)
- Password-protected key support
- Health monitoring (latency, key count)
- Full interface compliance
Security: - Keys encrypted with APP_KEY + unique salt per CA - Password-protected keys use APP_KEY + password hybrid derivation - Argon2id key derivation for resistance against brute force - Automatic corruption detection and flagging
Usage Example
use App\Services\Storage\DatabaseKeyStorage;
use App\Services\SecureKeyStorageService;
$secureKeyStorage = app(SecureKeyStorageService::class);
$storage = new DatabaseKeyStorage($secureKeyStorage);
// Store key
$reference = $storage->storePrivateKey($caId, $encryptedKey, [
'salt' => $salt,
'algorithm' => 'sodium_secretbox',
'password_protected' => false,
]);
// Retrieve key
$encryptedKey = $storage->retrievePrivateKey($caId);
// Health check
$health = $storage->healthCheck();
// ['available' => true, 'latency_ms' => 2, 'keys_stored' => 15]
Phase 2: Vaultwarden/Bitwarden Integration
Implementation Decision
Chosen Approach: Vaultwarden Password Manager API (not Secrets Manager)
Rationale:
- Vaultwarden only implements Password Manager API, not Secrets Manager
- bitwarden/sdk PHP package only supports Secrets Manager (incompatible with Vaultwarden)
- Direct REST API integration provides maximum compatibility
- Supports both personal vaults and organization shared vaults
Vaultwarden Architecture
Components:
- VaultwardenClient: HTTP REST API wrapper
- VaultwardenKeyStorage: SecureKeyStorageInterface implementation
Data Flow:
PKI App β VaultwardenClient β Vaultwarden REST API
β
[Cipher Creation]
β
[Attachment Upload: encrypted_key.enc]
β
[Returns cipher UUID]
Storage Mechanism:
1. Create Secure Note cipher (type 2)
2. Upload encrypted CA key as attachment
3. Store cipher UUID in certificate_authorities.key_storage_reference
VaultwardenClient Features
Location: app/Services/Vaultwarden/VaultwardenClient.php
Capabilities: - Authentication: PBKDF2-SHA256 master password hashing + OAuth2 token - Cipher Management: Create, retrieve, delete secure notes - Attachments: Upload/download encrypted files - Organizations: Support for shared vaults (organizationId + collectionIds) - Health Check: API connectivity and authentication status
Authentication Flow:
// 1. Prelogin to get KDF iterations
POST /accounts/prelogin
{ "email": "user@example.com" }
β { "KdfIterations": 600000 }
// 2. Derive master password hash (PBKDF2)
$masterPasswordHash = hash_pbkdf2('sha256', $password, $email, $iterations, 32);
// 3. Request access token
POST /identity/connect/token
grant_type=password&username=...&password=$masterPasswordHash
β { "access_token": "...", "expires_in": 3600 }
VaultwardenKeyStorage Implementation
Location: app/Services/Storage/VaultwardenKeyStorage.php
Key Methods:
class VaultwardenKeyStorage implements SecureKeyStorageInterface
{
// Store: Create cipher β Upload attachment β Return cipher UUID
public function storePrivateKey(string $caId, string $encryptedKey, array $metadata = []): string;
// Retrieve: Get cipher β Find attachment β Download content
public function retrievePrivateKey(string $caId): string;
// Delete: Delete cipher (removes attachments automatically) + Update CA record
public function deletePrivateKey(string $caId): bool;
// Check: Verify cipher exists with attachments
public function keyExists(string $caId): bool;
// Migrate: Retrieve β Decrypt β Re-encrypt β Store in new backend β Delete old
public function migrateKey(string $caId, SecureKeyStorageInterface $newBackend): bool;
// Identity: Returns 'vaultwarden'
public function getBackendName(): string;
// Monitoring: Delegate to VaultwardenClient::healthCheck()
public function healthCheck(): array;
}
Configuration
Environment Variables (.env):
# Vaultwarden API endpoint
VAULTWARDEN_API_URL=http://your-vaultwarden-server.com
# Master account credentials
VAULTWARDEN_EMAIL=vault-account@example.com
VAULTWARDEN_PASSWORD=your-master-password
# Optional: Organization shared vaults
VAULTWARDEN_ORGANIZATION_ID=organization-uuid
VAULTWARDEN_COLLECTION_IDS=collection-uuid-1,collection-uuid-2
# Health check settings
VAULTWARDEN_HEALTH_CHECK_ENABLED=true
VAULTWARDEN_HEALTH_CHECK_INTERVAL=300
Usage Example
use App\Services\Vaultwarden\VaultwardenClient;
use App\Services\Storage\VaultwardenKeyStorage;
use App\Services\SecureKeyStorageService;
// Initialize client
$client = new VaultwardenClient(
apiUrl: config('vaultwarden.api_url'),
email: config('vaultwarden.email'),
password: config('vaultwarden.password'),
organizationId: config('vaultwarden.organization_id'),
collectionIds: config('vaultwarden.collection_ids')
);
// Create storage backend
$storage = new VaultwardenKeyStorage(
app(SecureKeyStorageService::class),
$client
);
// Store CA private key
$cipherUuid = $storage->storePrivateKey($ca->id, $encryptedKey, [
'salt' => $salt,
'algorithm' => 'sodium_secretbox',
'password_protected' => false,
'ca_name' => 'Root CA - Production',
]);
// Retrieve key
$encryptedKey = $storage->retrievePrivateKey($ca->id);
// Health check
$health = $storage->healthCheck();
// ['available' => true, 'latency_ms' => 150, 'authenticated' => true]
Cipher Metadata Structure
Each Vaultwarden cipher includes metadata in Notes field:
{
"ca_id": "1",
"ca_common_name": "Root CA - Example Corp",
"encryption_algorithm": "sodium_secretbox",
"password_protected": false,
"stored_at": "2025-10-14T12:34:56Z",
"pkiaas_version": "0.2-alpha"
}
Security Features
Multi-Layer Encryption: 1. PKI Layer: Libsodium secretbox (pre-Vaultwarden) 2. Transport Layer: HTTPS for API calls 3. Vaultwarden Layer: Master password encryption (vault encryption)
Organization Support: - Personal Vault: Single-user access - Shared Vault: Multi-user access via collections - Access Control: Vaultwarden handles user permissions
Migration
Database β Vaultwarden:
$databaseStorage = new DatabaseKeyStorage($secureKeyStorage);
$vaultwardenStorage = new VaultwardenKeyStorage($secureKeyStorage, $client);
// Automatic migration (decrypts + re-encrypts + transfers)
$databaseStorage->migrateKey($caId, $vaultwardenStorage);
// Update CA record
$ca->update([
'key_storage_backend' => 'vaultwarden',
'key_storage_reference' => $cipherUuid, // Set by migrateKey()
]);
Migration Command (planned):
# Interactive wizard
php artisan pki:migrate-storage
# Batch migration to Vaultwarden
php artisan pki:migrate-storage --from=database --to=vaultwarden --all
# Specific CA
php artisan pki:migrate-storage --ca=1 --to=vaultwarden
Testing
Location: tests/Unit/VaultwardenKeyStorageTest.php
Coverage: 21 test cases - Interface implementation - Backend name - Store with cipher creation + attachment upload - Retrieve with cipher fetch + attachment download - Delete with cipher removal + CA cleanup - Key existence checks (with API failure handling) - Health monitoring (success + failure scenarios) - Error handling (missing references, failed uploads, cleanup on error) - Migration prerequisites (password-protected, missing keys)
Mocking Strategy: - VaultwardenClient fully mocked (no real API calls) - Mockery for method expectations - Tests run without external dependencies
Phase 3: Shamir Secret Sharing (Planned)
Concept
Split CA private key into N shares, requiring M shares to reconstruct: - Example: 5-of-7 scheme (7 shares, any 5 can reconstruct) - Use case: Distributed trust, no single point of compromise
Storage Options
- Distributed Database: Each share in different DB instance
- Hardware Tokens: YubiKey, NitroKey for physical distribution
- Geographic Distribution: Shares across multiple data centers
Implementation
Library: php-shamir-secret-sharing or custom implementation
Metadata Example:
{
"scheme": "5-of-7",
"shares": [
{"id": 1, "location": "db-us-east"},
{"id": 2, "location": "db-eu-west"},
...
]
}
Phase 4: Hardware Security Module (Planned)
Supported HSMs
- YubiKey (USB-A, USB-C, NFC)
- NitroKey (Open-source alternative)
- PKCS#11 Standard: Generic HSM support
Benefits
- Private key never leaves hardware device
- Tamper-resistant
- FIPS 140-2 compliance
- Suitable for root CAs
Implementation
Library: pkcs11-php extension
Metadata Example:
{
"hsm_type": "yubikey",
"slot_id": 1,
"pin_protected": true,
"serial_number": "12345678"
}
Migration Between Backends
Automated Migration
The migrateKey() interface method enables seamless backend migration:
// Migrate from Database to Bitwarden
$databaseStorage = new DatabaseKeyStorage($secureKeyStorage);
$bitwardenStorage = new BitwardenKeyStorage($bitwardenClient);
$databaseStorage->migrateKey($caId, $bitwardenStorage);
Migration Process:
1. Retrieve encrypted key from source backend
2. Decrypt with password (if required)
3. Re-encrypt for target backend
4. Store in target backend
5. Delete from source backend
6. Update key_storage_backend in database
Manual Migration Command
# Interactive wizard
php artisan pki:migrate-storage
# Batch migration
php artisan pki:migrate-storage --from=database --to=bitwarden --all
# Specific CA
php artisan pki:migrate-storage --ca=1 --to=shamir_sss
Security Considerations
Encryption Layers
- Application Layer: Libsodium secretbox encryption
- Transport Layer: TLS for API calls
- Storage Layer: Backend-specific encryption (Bitwarden, HSM)
Password-Protected Keys
Password-protected CAs require password during migration:
php artisan pki:migrate-storage --ca=1 --to=bitwarden --password
# Prompt: Enter CA private key password: ****
Audit Logging
All storage operations logged to pki-compliance channel:
- Key storage/retrieval/deletion
- Backend migration
- Health check failures
- Access attempts
Health Monitoring
Each backend implements health checks:
$health = $storage->healthCheck();
[
'available' => true, // Backend reachable
'latency_ms' => 15, // Response time
'error' => null, // Error message (if unavailable)
'backend' => 'database', // Backend identifier
'keys_stored' => 42, // Total keys (database only)
]
Monitoring Integration: - Prometheus metrics export - Dashboard widgets - Alert triggers (latency > 1000ms)
Testing
Unit Tests
DatabaseKeyStorage
Location: tests/Unit/DatabaseKeyStorageTest.php
Coverage: 11 test cases - Interface implementation - Backend name - Store/retrieve/delete operations - Key existence checks - Health monitoring - Error handling (missing metadata, nonexistent keys)
VaultwardenKeyStorage
Location: tests/Unit/VaultwardenKeyStorageTest.php
Coverage: 21 test cases - Interface implementation - Backend name - Store with cipher creation + attachment upload - Retrieve with cipher fetch + attachment download - Delete with cipher removal + CA cleanup - Key existence checks (with API failure handling) - Health monitoring (success + failure scenarios) - Error handling (missing references, failed uploads, cleanup on error) - Migration prerequisites (password-protected, missing keys)
Mocking Strategy: VaultwardenClient fully mocked (Mockery)
Integration Tests
Planned for Phase 3: - Cross-backend migration (Database β Vaultwarden) - Password-protected key migration with password prompt - Real Vaultwarden instance integration - Health check reliability under load
Best Practices
Backend Selection Guidelines
| Requirement | Recommended Backend |
|---|---|
| Simple deployment | Database |
| Enterprise secret management | Bitwarden Secrets Manager |
| Self-hosted | Vaultwarden |
| Maximum security | HSM (YubiKey) |
| Distributed trust | Shamir SSS |
| Compliance (FIPS 140-2) | HSM |
Migration Strategy
- Test Environment First: Validate migration with test CAs
- Backup Before Migration: Export CA certificates and metadata
- Gradual Rollout: Migrate non-critical CAs first
- Monitor Health: Check backend availability post-migration
- Document Recovery: Maintain rollback procedures
Roadmap
v0.2-alpha (Current - Completed)
- β Phase 1.1: Interface contract (SecureKeyStorageInterface)
- β Phase 1.2: Database backend (DatabaseKeyStorage)
- β Phase 1.3: Database migration (3 new columns)
- β Phase 1.4: Unit tests (11 test cases)
- β Phase 2.1: Vaultwarden client (REST API wrapper)
- β Phase 2.2: Vaultwarden backend (VaultwardenKeyStorage)
- β Phase 2.3: Configuration (.env + .env.example)
- β Phase 2.4: Unit tests (21 test cases)
- β Phase 2.5: Documentation update
v0.3-alpha (Q1 2026)
- π Phase 2.6: Configuration UI (backend selection in CA creation)
- π Phase 2.7: Migration command (
php artisan pki:migrate-storage) - π Integration tests (cross-backend migration)
- π Bitwarden Secrets Manager support (optional)
v0.4-alpha (Q2 2026)
- π Phase 3: Shamir Secret Sharing (n-of-m)
- π Phase 4: HSM support (YubiKey/NitroKey)
Related Documentation
- Vaultwarden/Bitwarden Study - Comprehensive analysis
- Security Architecture - Overall security design
- API Documentation - CA management APIs
References
Vous n'avez pas envie de la manager ?
DΓ©couvrir notre offre PKI As A Service