Skip to content

1.10.0

Choose a tag to compare

@ntoufoudis ntoufoudis released this 06 Jun 11:04
· 49 commits to main since this release
3f93609

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 verificationchronicle:verify and chronicle:verify-export resolve 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-aws companion 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 signing config 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 deploy

chronicle: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-aws

To 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 --force

There are no database migrations in this release — checkpoints and exports already recorded the algorithm and key_id needed for multi-key verification.


📚 Documentation


Requirements

  • PHP ^8.2
  • Laravel ^12.0 or ^13.0
  • ext-sodium, ext-openssl

New Contributors


Full Changelog: 1.9.1...1.10.0