Skip to content

feat: replace HS256 with EdDSA keystore signing (ROC-161) - #37

Open
Arminek wants to merge 1 commit into
mainfrom
roc-161-keystore-eddsa
Open

feat: replace HS256 with EdDSA keystore signing (ROC-161)#37
Arminek wants to merge 1 commit into
mainfrom
roc-161-keystore-eddsa

Conversation

@Arminek

@Arminek Arminek commented Jul 26, 2026

Copy link
Copy Markdown
Member

Summary

  • Replaces HS256 shared-secret JWTs with EdDSA (Ed25519) signing backed by a DB keystore
  • Private keys sealed with AES-256-GCM under KEY_ENCRYPTION_KEY (base64, 32 bytes); public keys stored in the clear for later JWKS publication (ROC-162)
  • Empty store at boot auto-generates an Ed25519 key; concurrent replicas converge on one active key via a unique-active-key constraint (MySQL stored generated column, SQLite partial unique index)
  • Every JWT header carries kid (RFC 7638 JWK thumbprint); verification selects the key by kid; unknown kid is rejected
  • Signing with a retired key is unrepresentable: the keystore unseals only active records into SigningKey, retired records surface only as VerifyingKey
  • SECRET removed from code, env files, docker-compose, CI workflows, README, and terraform; missing KEY_ENCRYPTION_KEY fails startup, wrong KEK fails on first key access

Scope note

The issue called for squashing migrations to a fresh baseline. Decided during implementation to keep migration history and append signing_keys instead (recorded in a ROC-161 comment).

Testing

  • cargo test in Docker: 178/178 on MySQL, 178/178 on SQLite (16 new tests: keystore unit, envelope crypto, token kid/unknown-kid, keystore init incl. concurrent-boot convergence and wrong-KEK failure)

Tokens were signed with a shared HS256 secret, so consumers could not
verify them without holding it. Keys now live in the database sealed
with AES-256-GCM under KEY_ENCRYPTION_KEY, are auto-generated on first
boot (race-safe across replicas via a unique-active-key constraint),
and every JWT carries an RFC 7638 kid used to select the verification
key. Retired keys surface only as verifying keys, so signing with one
is unrepresentable. SECRET is removed from code, env files, compose,
CI, and terraform.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant