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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog

## 2.5.0 - Unreleased
## 2.5.1 - Unreleased

- Restore ES256 DPoP key generation on OpenSSL 3.6 while asserting that the
generated named curve remains P-256 (`prime256v1`).
- Run PHPStan with an explicit bounded memory limit for reproducible local and
CI verification on smaller development machines.

## 2.5.0 - 2026-08-01

- Add durable, opaque, exact-once login intents for state, nonce, PKCE verifier,
allowlisted return paths, browser binding and correlation IDs.
Expand Down
16 changes: 16 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Compatibility

| SDK line | PHP | Identity contract | Laravel adapter | Status |
|---|---|---|---|---|
| 2.5.x | 8.2–8.4 | `^2.0` | `identity-laravel ^2.5` | Current |
| 2.0.x | 8.2–8.4 | `^2.0` | `identity-laravel ^2.0` | Security fixes only |
| 1.2.x | 8.2–8.4 | `^1.1` | Application integration | Migration only |

The high-assurance profile requires server metadata and client registration for
PAR, JARM, RFC 9207 issuer binding, DPoP and `private_key_jwt`. Package versions
do not enable those capabilities by themselves. Consumers must validate their
exact issuer, callback, tenant binding and negative/replay cases before rollout.

SDK 2.5 Laravel consumers must use shared atomic cache or database storage for
login intents. In-memory storage is supported only for tests and local,
single-process experiments.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ Never infer high-assurance support from a successful login. Discover metadata,
run `EnterpriseProfileValidator`, store the authorization transaction
server-side, and process callbacks through `AuthorizationResponseProcessor`.
See [the Laravel integration guide](docs/INTEGRATION_LARAVEL.md).
The first-party `novvor/identity-laravel` adapter is published for SDK 2.0. Its
2.5 release must adopt durable login intents before consumers can claim the
2.5 integration contract. Until then Laravel applications must keep protocol
orchestration in one tested application adapter, never in controllers. The
The first-party `novvor/identity-laravel` adapter publishes the 2.5 integration
boundary with shared-cache, encrypted, exact-once login intents. Applications
must keep protocol orchestration in that tested adapter, never in controllers.
Adopting the package still requires an application-specific runtime validation;
a compatible dependency constraint alone is not production evidence. The
cross-package adoption audit and 2.5 release gates are in
[`docs/SDK_2_ADOPTION_AUDIT.md`](docs/SDK_2_ADOPTION_AUDIT.md) and
[`docs/RELEASE_2_5_PLAN.md`](docs/RELEASE_2_5_PLAN.md).
Expand Down
23 changes: 23 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Upgrading

## From 2.0 to 2.5

Use `LoginIntentManager` with a shared, atomic `LoginIntentStore`. Laravel
applications should use `novvor/identity-laravel ^2.5`, whose browser session
contains only an opaque handle while PKCE, nonce, state, return destination and
DPoP private material remain encrypted server-side and are consumed once.

Do not deploy a dependency-only upgrade. Validate exact issuer and redirect URI,
callback replay rejection, state and nonce mismatch, token signature and key
rotation, tenant binding, logout, and the selected standard or high-assurance
profile against the target Identity environment.

## From 1.x to 2.x

Treat the major upgrade as an authentication-boundary migration. The 2.x
high-assurance profile fails closed unless Discovery and client registration
support PAR, JARM, RFC 9207, DPoP and `private_key_jwt`. Keep the standard
profile unless all of those capabilities have been provisioned and tested.

See `COMPATIBILITY.md`, `docs/INTEGRATION_LARAVEL.md` and
`docs/SECURITY_PROFILE.md` for the supported boundaries.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"minimum-stability": "stable",
"prefer-stable": true,
"scripts": {
"analyse": "phpstan analyse --no-progress",
"analyse": "php -d memory_limit=512M vendor/bin/phpstan analyse --no-progress",
"test": "phpunit",
"verify": [
"@composer validate --strict",
Expand Down
16 changes: 8 additions & 8 deletions docs/RELEASE_2_5_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ Date: 2026-08-01
|---|---|
| `novvor/identity-contracts` v2.0.0 | Published baseline |
| `novvor/identity-sdk-php` v2.0.0 | Published baseline |
| SDK 2.5 core branch | Candidate; not tagged |
| First-party Laravel adapter | `v2.0.1` published; 2.5 durable-intent upgrade pending |
| SDK 2.5 core | Immutable `v2.5.0` tag exists; GitHub release record pending |
| First-party Laravel adapter | `v2.5.1` tagged with durable encrypted login intents |
| Platform and FilaSign runtime upgrade | Not yet validated against 2.5 |
| Console v1-to-v2 migration | Not started |

The published adapter is an SDK 2.0 integration boundary. Its missing 2.5
durable-intent capability is a consumer rollout blocker, not a reason to weaken
the core release gate or duplicate protocol logic in controllers.
The adapter now implements the SDK 2.5 durable-intent boundary. Consumer runtime
validation remains a rollout blocker and is not replaced by package-level tests.

## Core release gate

Expand All @@ -37,9 +36,10 @@ consumer-rollout approval.

## Consumer order

1. Publish the core SDK `v2.5.0` only after the core gate is green.
2. Publish a Laravel integration package that uses durable login intents and
makes the transaction lifecycle a single supported boundary.
1. Publish a GitHub release record for the existing immutable SDK `v2.5.0` tag
after attaching the successful core-gate evidence; never retag it.
2. Retain the Laravel adapter's durable-login-intent transaction lifecycle as
the single supported Laravel boundary.
3. Upgrade Enix Platform and FilaSign in independent branches; run their
browser and negative callback flows against the new package.
4. Migrate Enix Console from `^1.1` to `^2.5` in a separate review because it
Expand Down
30 changes: 13 additions & 17 deletions docs/SDK_2_ADOPTION_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ framework-neutral backend consumers. It now includes durable, opaque login
intents so applications can keep a return destination, PKCE verifier, nonce and
state on the server under exact-once consumption semantics.

The first-party Laravel adapter exists and is released as `v2.0.1`. It uses an
encrypted Laravel session transaction and has not adopted the SDK 2.5 durable,
opaque login-intent contract. Therefore a consumer-wide Laravel upgrade to 2.5
is not releasable until the adapter is updated, tested and released.
The first-party Laravel adapter is tagged as `v2.5.1`. It adopts the SDK 2.5
durable, opaque login-intent contract with encrypted shared-cache storage and
exact-once consumption. Consumer-wide rollout still requires per-application
runtime evidence; package compatibility is not deployment approval.

This is `PASS_LOCAL_INTEGRATION`, not OpenID certification or production proof.

Expand All @@ -22,7 +22,7 @@ This is `PASS_LOCAL_INTEGRATION`, not OpenID certification or production proof.
|---|---|---|
| `identity-contracts` | claim names and security profiles | transport, secrets |
| `identity-sdk-php` | framework-neutral OAuth/OIDC protocol | Laravel session, admin APIs |
| `identity-laravel` | Laravel config, DI and transaction lifecycle; published `v2.0.1`, 2.5 durable-intent upgrade pending | tenant authorization policy |
| `identity-laravel` | Laravel config, DI and durable transaction lifecycle; `v2.5.1` tagged | tenant authorization policy |
| `identity-admin-sdk-php` | privileged control-plane transport | user login/session logic |
| `identity-sdk-testing` | truthful fakes and negative fixtures | real keys, tokens, customer data |

Expand All @@ -42,10 +42,8 @@ This is `PASS_LOCAL_INTEGRATION`, not OpenID certification or production proof.
11. Bind UserInfo `sub` to the ID Token `sub`.
12. Map tenant and permissions in the application, then regenerate its session.

Laravel consumers must use the published adapter for SDK 2.0 or one tested
application integration service. The adapter requires a 2.5 update and release
against `LoginIntentManager` before it can be treated as the consumer-wide 2.5
contract; controllers must not reconstruct this flow.
Laravel consumers on SDK 2.5 must use the tagged adapter and its
`LoginIntentManager` boundary; controllers must not reconstruct this flow.

## Capability truth

Expand All @@ -68,13 +66,11 @@ contract; controllers must not reconstruct this flow.

## Remaining release blockers

1. Complete the 2.5 core release gate and publish an immutable `v2.5.0` tag.
2. Update, release and test the first-party Laravel adapter against durable
`LoginIntentManager` storage; retain its current 2.0 contract until then.
3. Run a clean Composer install using tags only.
4. Validate Platform and FilaSign as reference consumers end to end.
5. Migrate Console from the v1 line as a separate, explicitly reviewed change.
6. Run negative issuer, callback replay, tenant mismatch and key-rotation tests.
7. Validate staging runtime and an external OpenID conformance profile.
1. Publish a GitHub release record for the immutable `v2.5.0` tag with gate evidence.
2. Run a clean Composer install using tags only.
3. Validate Platform and FilaSign as reference consumers end to end.
4. Migrate Console from the v1 line as a separate, explicitly reviewed change.
5. Run negative issuer, callback replay, tenant mismatch and key-rotation tests.
6. Validate staging runtime and an external OpenID conformance profile.

No package should claim `PASS_RUNTIME` until those external gates have evidence.
4 changes: 4 additions & 0 deletions src/Oidc/DpopKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public static function generateEs256(): self

$resource = openssl_pkey_new([
'private_key_type' => OPENSSL_KEYTYPE_EC,
// OpenSSL 3.6 requires an explicit minimum private-key size even
// when the selected named curve determines the actual EC size.
// The generated key remains prime256v1/P-256.
'private_key_bits' => 384,
'curve_name' => 'prime256v1',
]);
if ($resource === false || ! openssl_pkey_export($resource, $privateKey)) {
Expand Down
3 changes: 3 additions & 0 deletions tests/DpopKeyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public function test_generates_a_fresh_es256_key_without_private_jwk_material():
self::assertSame('ES256', $first->algorithm);
self::assertSame('EC', $first->publicJwk['kty']);
self::assertSame('P-256', $first->publicJwk['crv']);
$privateKey = openssl_pkey_get_private($first->privateKey);
self::assertInstanceOf(\OpenSSLAsymmetricKey::class, $privateKey);
self::assertSame('prime256v1', openssl_pkey_get_details($privateKey)['ec']['curve_name'] ?? null);
self::assertArrayNotHasKey('d', $first->publicJwk);
self::assertNotSame($first->privateKey, $second->privateKey);
self::assertNotSame($first->publicThumbprint(), $second->publicThumbprint());
Expand Down
Loading