Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,62 @@ The 0.x line is treated as a single pre-stable development series — see
1.0.0 stabilisation cut; for 0.x history consult `git log` against the relevant
tags.

## [2.1.0] — 2026-06-23

A backward-compatible minor release. The headline is **crypto-agility and
post-quantum readiness for passkey signature algorithms** (ADR 0019): the
two divergent hardcoded COSE algorithm lists are replaced by a single
operator-configurable source of truth, and there is now a way to see which
algorithm each stored credential uses. No public API is removed and the HTTP
`/auth/**` wire contract is unchanged, so hosts upgrade by bumping the version.

To be precise about scope: **no post-quantum signature algorithm is added** —
PQC for passkeys is gated end-to-end by authenticator hardware, CTAP2/FIDO2,
the WebAuthn/COSE registry, and WebAuthn4J's verifier, none of which yet
standardize one. The goal here is crypto-agility (clean removal of the
migration obstacles) and honest documentation, not new algorithms.

### Added

- **Crypto-agility for passkey signature algorithms** (ADR 0019). A new
framework-neutral `CoseAlgorithm` enum, and `CeremonyConfig` now carries two
ordered lists: `offeredAlgorithms` (advertised in registration create-options)
and `acceptedAlgorithms` (enforced on the WebAuthn4J verify path).
`acceptedAlgorithms` is authoritative and `offeredAlgorithms` must be a subset.
Both the create-options ceremony and the verify path derive their lists from
this single config, replacing the two previously-divergent hardcoded lists, so
operators can narrow either without code changes. A new 5-arg `CeremonyConfig`
convenience constructor applies backward-compatible defaults — accepted =
`ES256, EdDSA, RS256, ES384, RS384` (the union of everything previously
accepted, so no already-registered credential can fail verification); offered =
`ES256, EdDSA, RS256` (the historical create-options subset) — so every
existing call site compiles and behaves identically.
- **Per-credential algorithm visibility.**
`CredentialAlgorithms.coseAlgorithm(record)` decodes the COSE algorithm already
embedded in a stored public key (**no schema change**), and
`AdminService.listCredentialsByAlgorithm(actor, target, coseAlgorithm)` reports
which stored credentials use a given algorithm — the read side a future
"re-enroll off algorithm X" campaign drives off.
- **Offered/accepted algorithm configuration wired through every adapter.** The
Spring Boot starter, Dropwizard, and Micronaut adapters (and the matching demo
`application.yml` files) expose the two algorithm lists as host config, so the
create-options and verify lists can be tuned without touching code.

### Changed

- **Honest JWT crypto framing** (ADR 0019, documentation only — no behavior
change). The HS256-vs-ES256 choice is re-documented as a trust-topology
decision rather than a dev-vs-prod one: HMAC-SHA256 is *not* broken by Shor and
retains ~128-bit security under Grover with the enforced ≥ 256-bit key, making
it the quantum-conservative default for a single-issuer/single-verifier
deployment; ES256 JWTs exist for untrusted third-party verification and carry
Shor exposure bounded by the short token TTL. See `docs/threat-model.md` and
`docs/operator-guide.md`.
- **Build / CI.** The Playwright end-to-end suites under `examples/` are now
wired into each demo's `check` via an opt-in `e2eTest` task (gated on
`PK_RUN_E2E=1` / `-PrunE2e`, so the default gate stays fast and Chrome-free),
and CI runs all three demos in a dedicated `e2e` matrix job.

## [2.0.0] — 2026-06-13

The first 2.x release. The bulk of the work is correctness, hardening, and
Expand Down Expand Up @@ -352,7 +408,9 @@ Security-review follow-ups (hardening; no known exploit in the items below).
First stable release. Captures the surface produced by the 0.x development
series; see `git log` for the full history.

[Unreleased]: https://github.com/codeheadsystems/pk-auth/compare/v1.3.1...HEAD
[Unreleased]: https://github.com/codeheadsystems/pk-auth/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/codeheadsystems/pk-auth/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/codeheadsystems/pk-auth/compare/v1.3.1...v2.0.0
[1.3.1]: https://github.com/codeheadsystems/pk-auth/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/codeheadsystems/pk-auth/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/codeheadsystems/pk-auth/compare/v1.1.0...v1.2.0
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this is

pk-auth is a **passkeys-first authentication library set for the JVM**, published to Maven Central under `com.codeheadsystems`. All modules share one version (`gradle.properties` → currently `2.0.1-SNAPSHOT`). It is *not* an identity provider: it owns passkeys/credentials, never users — the host maps users via the `UserLookup` SPI.
pk-auth is a **passkeys-first authentication library set for the JVM**, published to Maven Central under `com.codeheadsystems`. All modules share one version (`gradle.properties` → currently `2.1.0-SNAPSHOT`). It is *not* an identity provider: it owns passkeys/credentials, never users — the host maps users via the `UserLookup` SPI.

The canonical architecture reference is [`DESIGN.md`](./DESIGN.md); per-decision rationale lives in [`docs/adr/`](./docs/adr/) (Nygard format, numbered). Read the relevant ADR before changing cross-module behavior.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ tests and Playwright end-to-end suites:
## Status

1.0.0 cut the stable baseline; the current development line is
**2.0.1-SNAPSHOT** (see `gradle.properties` for the authoritative
**2.1.0-SNAPSHOT** (see `gradle.properties` for the authoritative
version). The 1.1 line added per-audience JWT TTLs, the
`AccessTokenStore` (stateful access tokens), the
`UserDeletionService` fan-out, and the `pk-auth-refresh-tokens`
Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ git tag, because the release is already tagged (`vX.Y.Z`) for Maven Central. Pin
the SDK version to that same number so the two stay in lockstep.

> The committed `version` in `package.json` is the in-development line
> (e.g. `2.0.0-SNAPSHOT`), mirroring `gradle.properties`. `SNAPSHOT` is a valid
> (e.g. `2.1.0-SNAPSHOT`), mirroring `gradle.properties`. `SNAPSHOT` is a valid
> semver prerelease identifier, so it will not be published by accident — step 1
> always pins it to the concrete release version first. After publishing, you may
> leave `package.json` at the published version or restore the `-SNAPSHOT` line on
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Use one of these private channels instead:

Please include, as far as you can:

- The affected module(s) and version (e.g. `pk-auth-core 2.0.0`).
- The affected module(s) and version (e.g. `pk-auth-core 2.1.0`).
- A description of the issue and its impact (what an attacker can do).
- Steps to reproduce — a minimal proof of concept, failing test, or request
sequence is ideal.
Expand Down
2 changes: 1 addition & 1 deletion clients/passkeys-browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pk-auth/passkeys-browser",
"version": "2.0.1-SNAPSHOT",
"version": "2.1.0",
"description": "pk-auth browser SDK: WebAuthn ceremonies + admin operations against the pk-auth wire contract.",
"license": "MIT",
"author": "Ned Wolpert",
Expand Down
2 changes: 1 addition & 1 deletion docs/stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Current status: 2.x

pk-auth is **post-1.0** as of the `v1.0.0` tag. The current development line is
**2.0.1-SNAPSHOT** (`gradle.properties` is authoritative); the 1.1, 1.2, 1.3, and
**2.1.0-SNAPSHOT** (`gradle.properties` is authoritative); the 1.1, 1.2, 1.3, and
2.0 releases have shipped. See [`CHANGELOG.md`](../CHANGELOG.md) for the
per-release delta.

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ org.gradle.jvmargs=-Xmx2g \

# Project identity
group=com.codeheadsystems
version=2.0.1-SNAPSHOT
version=2.1.0-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public interface AdminService {
* Shor-vulnerable algorithm and prompt those users to re-enroll. The algorithm is read from the
* already-stored COSE key — no schema change. See ADR 0019.
*
* @since 2.0.1
* @since 2.1.0
*/
AdminResult<List<CredentialSummary>> listCredentialsByAlgorithm(
UserHandle actor, UserHandle target, int coseAlgorithm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public record CeremonyConfig(
* PublicKeyCredentialParameters}). The historical offered set: ES256, EdDSA, RS256. Deliberately
* a subset of {@link #DEFAULT_ACCEPTED_ALGORITHMS}.
*
* @since 2.0.1
* @since 2.1.0
*/
public static final List<CoseAlgorithm> DEFAULT_OFFERED_ALGORITHMS =
List.of(CoseAlgorithm.ES256, CoseAlgorithm.EdDSA, CoseAlgorithm.RS256);
Expand All @@ -52,7 +52,7 @@ public record CeremonyConfig(
* the <em>union</em> of everything previously accepted so no already-registered credential can
* fail verification.
*
* @since 2.0.1
* @since 2.1.0
*/
public static final List<CoseAlgorithm> DEFAULT_ACCEPTED_ALGORITHMS =
List.of(
Expand Down Expand Up @@ -173,7 +173,7 @@ public static CeremonyConfig from(
* @param offeredAlgorithms algorithms advertised in create-options, or null for the default.
* @param acceptedAlgorithms algorithms accepted on verify, or null for the default.
* @return the resolved ceremony config.
* @since 2.0.1
* @since 2.1.0
*/
public static CeremonyConfig from(
@Nullable Duration challengeTtl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* touching the ceremony or verification code. See {@code docs/threat-model.md} (Post-quantum
* readiness) and ADR 0019.
*
* @since 2.0.1
* @since 2.1.0
*/
public enum CoseAlgorithm {

Expand Down Expand Up @@ -47,7 +47,7 @@ public enum CoseAlgorithm {
* The IANA COSE algorithm identifier for this algorithm (e.g. {@code -7} for {@link #ES256}).
*
* @return the signed COSE algorithm identifier.
* @since 2.0.1
* @since 2.1.0
*/
public int coseValue() {
return coseValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* against the configured algorithm vocabulary directly. See {@code docs/threat-model.md}
* (Post-quantum readiness) and ADR 0019.
*
* @since 2.0.1
* @since 2.1.0
*/
public final class CredentialAlgorithms {

Expand All @@ -35,7 +35,7 @@ private CredentialAlgorithms() {}
* @param record the stored credential.
* @return the IANA COSE algorithm identifier (e.g. {@code -7} for ES256).
* @throws IllegalStateException if the stored COSE key cannot be decoded or carries no algorithm.
* @since 2.0.1
* @since 2.1.0
*/
public static int coseAlgorithm(CredentialRecord record) {
COSEKey coseKey;
Expand All @@ -60,7 +60,7 @@ public static int coseAlgorithm(CredentialRecord record) {
* @param coseAlgorithm the IANA COSE algorithm identifier to test for (e.g. {@code -7} for
* ES256).
* @return {@code true} if the credential uses that algorithm.
* @since 2.0.1
* @since 2.1.0
*/
public static boolean usesAlgorithm(CredentialRecord record, int coseAlgorithm) {
return coseAlgorithm(record) == coseAlgorithm;
Expand Down
20 changes: 10 additions & 10 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@
</svg>
<div class="hero-text">
<div class="hero-badges">
<span class="chip chip--solid">v<span class="pkv">2.0.0</span></span>
<span class="chip chip--solid">v<span class="pkv">2.1.0</span></span>
<span class="chip">JDK 21</span>
<span class="chip">MIT</span>
<span class="chip chip--brass">WebAuthn4J</span>
Expand Down Expand Up @@ -1146,9 +1146,9 @@ <h1>
<div class="code" data-filename="build.gradle.kts" aria-label="Gradle dependency snippet">
<pre><span class="cm">// One adapter, one persistence module, done.</span>
<span class="kw">dependencies</span> <span class="pn">{</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-spring-boot-starter:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-persistence-jdbi:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-admin-api:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-spring-boot-starter:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-persistence-jdbi:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-admin-api:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="pn">}</span></pre>
</div>
<div class="hero-annot">
Expand Down Expand Up @@ -1332,9 +1332,9 @@ <h3>Add the adapter, persistence, and admin API.</h3>
<div class="panel" data-panel="deps-gradle">
<div class="code" data-filename="build.gradle.kts">
<pre><span class="kw">dependencies</span> <span class="pn">{</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-spring-boot-starter:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-persistence-jdbi:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-admin-api:<span class="pkv">2.0.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-spring-boot-starter:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-persistence-jdbi:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="fn">implementation</span><span class="pn">(</span><span class="str">"com.codeheadsystems:pk-auth-admin-api:<span class="pkv">2.1.0</span>"</span><span class="pn">)</span>
<span class="pn">}</span></pre>
</div>
</div>
Expand All @@ -1343,7 +1343,7 @@ <h3>Add the adapter, persistence, and admin API.</h3>
<pre><span class="pn">&lt;</span><span class="ty">dependency</span><span class="pn">&gt;</span>
<span class="pn">&lt;</span><span class="ty">groupId</span><span class="pn">&gt;</span>com.codeheadsystems<span class="pn">&lt;/</span><span class="ty">groupId</span><span class="pn">&gt;</span>
<span class="pn">&lt;</span><span class="ty">artifactId</span><span class="pn">&gt;</span>pk-auth-spring-boot-starter<span class="pn">&lt;/</span><span class="ty">artifactId</span><span class="pn">&gt;</span>
<span class="pn">&lt;</span><span class="ty">version</span><span class="pn">&gt;</span><span class="pkv">2.0.0</span><span class="pn">&lt;/</span><span class="ty">version</span><span class="pn">&gt;</span>
<span class="pn">&lt;</span><span class="ty">version</span><span class="pn">&gt;</span><span class="pkv">2.1.0</span><span class="pn">&lt;/</span><span class="ty">version</span><span class="pn">&gt;</span>
<span class="pn">&lt;/</span><span class="ty">dependency</span><span class="pn">&gt;</span>
<span class="cm">&lt;!-- + pk-auth-persistence-jdbi, pk-auth-admin-api --&gt;</span></pre>
</div>
Expand Down Expand Up @@ -1754,7 +1754,7 @@ <h3>pk-auth</h3>
A passkey credential layer for the JVM. MIT-licensed.
Three adapters, one wire contract, your user table.
</p>
<p class="ver">v<span class="pkv">2.0.0</span> &middot; JDK 21 &middot; WebAuthn4J</p>
<p class="ver">v<span class="pkv">2.1.0</span> &middot; JDK 21 &middot; WebAuthn4J</p>
</div>
<div class="footer-col">
<h4>Read first</h4>
Expand Down Expand Up @@ -1814,7 +1814,7 @@ <h4>Project</h4>

/* ---- Live latest version (Maven Central via shields, CORS-enabled) ----
Keeps the page in sync with releases without editing the site; the
hardcoded "2.0.0" in the markup is only a fallback if the fetch fails. */
hardcoded "2.1.0" in the markup is only a fallback if the fetch fails. */
var pkv = document.querySelectorAll(".pkv");
if (pkv.length && window.fetch) {
fetch("https://img.shields.io/maven-central/v/com.codeheadsystems/pk-auth-core.json")
Expand Down
2 changes: 1 addition & 1 deletion site/og-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading