1.10.0
Chronicle v1.10.0 — Key Rotation, Multi-Key Verification & External Signing
This release makes Chronicle's cryptographic trust survive key rotation and key custody outside the application. Signing keys now live in a key ring: one active key signs new artifacts, while every key — active or retired — remains available to verify the artifacts it produced. Because each checkpoint, export, and compliance report records the algorithm and key_id it was signed with, verification resolves the correct historical key automatically.
This release is backward compatible. Existing apps on the 1.9.x flat signing config continue to work with no changes.
✨ Highlights
- 🔑 Signing-key rotation with a multi-key key ring — rotate keys without invalidating historical checkpoints or exports.
- 🔎 Multi-key verification —
chronicle:verifyandchronicle:verify-exportresolve the signing key from the ring per artifact, so artifacts signed by a now-retired key still verify. - 🧩 External signing providers — sign with keys held outside the app (e.g. AWS KMS) via the new
laravel-chronicle/kms-awscompanion package. Remote signing, local verification. - 🆕 ECDSA P-256 provider in core (
EcdsaSigningProvider), verified locally with OpenSSL — the foundation for KMS/HSM custody. - 🛠️ New
chronicle:key:*commands for generating, listing, and rotating keys. - ↩️ Fully backward compatible — the legacy flat
signingconfig is adapted to a single-key ring automatically.
🔐 Key rotation & multi-key verification
Signing keys are now configured as a ring with one active key:
// config/chronicle.php
'signing' => [
'active' => env('CHRONICLE_ACTIVE_KEY', 'chronicle-key-1'),
'keys' => [
'chronicle-key-1' => [
'provider' => Chronicle\Signing\Ed25519SigningProvider::class,
'algorithm' => 'ed25519',
'private_key' => env('CHRONICLE_PRIVATE_KEY'), // set null once retired
'public_key' => env('CHRONICLE_PUBLIC_KEY'), // keep for verification
],
],
],Verification now distinguishes between a genuinely invalid signature and an unknown key: if an artifact references a key that is no longer in the ring, verification fails with a new unknown_key reason rather than reporting a forged signature.
Rotating a key
php artisan chronicle:key:generate --id=chronicle-key-2 # mint a new keypair
# add the printed entry to signing.keys, then:
php artisan chronicle:key:rotate chronicle-key-2 # anchors a boundary checkpoint
# set CHRONICLE_ACTIVE_KEY=chronicle-key-2 and deploychronicle:key:rotate always creates a boundary checkpoint at the current ledger head before handing over, so the transition between keys is itself verifiable. When you later retire a key, keep its public_key in the ring and drop only the private_key.
🧩 External signing providers (KMS / HSM)
Signing providers are pluggable, so the private key can live entirely outside the application. Providers sign remotely and verify locally against a cached public key, keeping verification offline and fast.
The official AWS KMS adapter ships as a separate package (core stays free of cloud SDK dependencies):
composer require laravel-chronicle/kms-awsTo build your own (GCP KMS, HashiCorp Vault, HSM, …), implement a signing provider on top of the core LocalVerifyProvider base. See Custom Signing Providers.
🆕 New Artisan commands
| Command | Purpose |
|---|---|
chronicle:key:generate {--id=} |
Generate an Ed25519 keypair and print a ready-to-paste signing.keys entry |
chronicle:key:list {--with-counts} |
List the keys in the ring, marking the active and verify-only keys |
chronicle:key:rotate {keyId} |
Create a boundary checkpoint and print activation instructions for a new key |
⬆️ Upgrade guide
1. New extension requirement. ECDSA support adds ext-openssl to the requirements (alongside the existing ext-sodium). Both are bundled with most PHP distributions.
2. Nothing else is required. Your existing flat signing config keeps working unchanged — Chronicle adapts it to a single-key ring at boot. You'll see a one-time deprecation notice in your logs pointing to the new shape.
3. Recommended (optional): migrate to the key-ring config. Move your current key into signing.keys and set signing.active:
'signing' => [
'active' => env('CHRONICLE_ACTIVE_KEY', 'chronicle-dev-key'),
'keys' => [
'chronicle-dev-key' => [
'provider' => Chronicle\Signing\Ed25519SigningProvider::class,
'algorithm' => 'ed25519',
'private_key' => env('CHRONICLE_PRIVATE_KEY'),
'public_key' => env('CHRONICLE_PUBLIC_KEY'),
],
],
],Re-publish the config to see the fully documented block:
php artisan vendor:publish --tag=chronicle-config --forceThere are no database migrations in this release — checkpoints and exports already recorded the algorithm and key_id needed for multi-key verification.
📚 Documentation
- Signing and Keys
- Rotate Signing Keys (guide)
- Custom Signing Providers
- Security Model
- Artisan Commands
Requirements
- PHP
^8.2 - Laravel
^12.0or^13.0 ext-sodium,ext-openssl
New Contributors
- @devc4rlos made their first contribution in #162
Full Changelog: 1.9.1...1.10.0