Skip to content

fix(did-provider-oyd): follow the CMSM session protocol, delegate resolution - #403

Open
fabianekc wants to merge 1 commit into
Sphereon-Opensource:developfrom
fabianekc:fix/did-oyd-cmsm-sessions-and-resolver
Open

fix(did-provider-oyd): follow the CMSM session protocol, delegate resolution#403
fabianekc wants to merge 1 commit into
Sphereon-Opensource:developfrom
fabianekc:fix/did-oyd-cmsm-sessions-and-resolver

Conversation

@fabianekc

Copy link
Copy Markdown

fix(did-provider-oyd): follow the CMSM session protocol, delegate resolution

Repo: Sphereon-Opensource/SSI-SDK, base branch develop (checked against 4f8b21c).

Why

Two independent things were broken in packages/did-provider-oyd.

1. Client-Managed-Secret-Mode could not complete. The did:oyd registrar moved
CMSM to session handles. Creating a DID is a challenge/response flow: every
intermediate answer is HTTP 201 and names the value to sign (sign), the key to
sign it with (with), and a session handle that must be echoed back. This
provider did two calls with one signature and no session, so the registrar
started a fresh flow on the second call and never finished one. That is what the
// FIXME: Enabled when CMSM is working on the second test was about.

To be fair: the server side was broken too. The registrar driver dropped both the
session and the signature, so even a correct client would have failed. That is
fixed and deployed — oydid-registrar.data-container.net now reports version
0.5.1 and the multi-phase flow is verified against it.

2. The resolver was a copy of an old library with
https://oydid-resolver.data-container.net hard-coded inside the function, not
overridable. That host is being replaced by resolver.ownyourdata.eu. The copy
also reported every failure as invalidDid — including a 404 — and treated a
deactivated DID (HTTP 410) as an invalid one.

What changed

CMSM flowcreateIdentifierWithCMSM becomes a loop: post, and while the
answer is 201, sign the returned value with the key named in with and post back
{session, sig}. With a server-managed revocation key that is three calls and
two signatures, both signing with the document key.

The callback interface is unchanged. publicKeyCallback and signCallback keep
their signatures, and the signing itself is untouched: for P-256 it is SHA-256
over the returned ASCII string, ECDSA, base64url r‖s without padding — exactly
what SphereonKeyManager already produces.

log_revoke from the closing answer is kept in identifier.keys[0].meta.oyd.logRevoke.
It is signed with a key the registrar never sees, and a secure element does not
have to sign deterministically, so it cannot be reproduced later; without it the
DID can neither be updated nor revoked. It is a log entry, not a secret. If you
would rather surface it somewhere else, say where and I will move it.

Registrar endpoint is now configurable through OydConstructorOptions.registrarUrl.

Resolution is delegated to oydid-did-resolver@0.3.0,
maintained alongside the method. Configurable endpoint, DID Resolution error
codes, deactivation per DID Core 7.1.3, and no runtime dependencies at all —
did-resolver is a types-only peer dependency there.

Housekeeping — the console.log calls that printed the request, the response
and the signature are gone; the CMSM test no longer carries a hard-coded private
key; the stray top-level console.log(generatePrivateKeyHex()) that ran on
import is removed.

Verification

The signature format was confirmed against the live registrar from plain
JavaScript, with a non-extractable WebCrypto P-256 key:

extractable private key: false
phase 1: 201 phase=1 with=key-doc session=cmsm-b5b0bfe8f087316a
signature bytes: 64
phase 2: 201 phase=2 with=key-doc session=cmsm-b5b0bfe8f087316a
OK - WebCrypto signature accepted, flow resumed

The probe stops after phase 2 on purpose, so it creates no DID; the session
expires after 15 minutes.

Both tests in __tests__ now pass against the live registrar, including the one
that carried the FIXME:

 ✓  @sphereon/did-provider-oyd  __tests__/oyd-did-provider.test.ts (2 tests) 1258ms
   ✓ should create identifier             621ms
   ✓ should create identifier with CMSM   636ms

That also settles the one thing I could not check from the outside:
SphereonKeyManager produces exactly the signature encoding the registrar
expects for Secp256r1, so signCallback needed no change.

pnpm --filter @sphereon/did-provider-oyd... build is clean.

Unrelated, but you may want to know: on Node 24, pnpm install fails while
building canvas@2.11.2 — no prebuilt binary for the ABI and the source build
needs pixman-1. pnpm install --ignore-scripts works around it. Nothing to do
with this change; it just bit me on the way in.

Open

updateIdentifier still throws not supported yet. The registrar now implements
CMSM for update as well (three signatures, the last with the previous document
key, plus the log_revoke_old of the current document). Happy to follow up with
that in a separate PR if it is useful to you.

…olution

The did:oyd registrar moved client-managed-secret-mode to session handles.
Creating a DID is now a challenge/response flow: every intermediate answer
is HTTP 201 and names the value to sign, the key to sign it with, and a
session handle that has to be echoed back. This provider did two calls with
one signature and no session, so the registrar started a new flow on the
second call and never completed one - which is what the FIXME on the CMSM
test was about. Three calls and two signatures are needed with a
server-managed revocation key, and both sign with the document key.

- drive the flow as a loop: post, and while the answer is 201, sign the
  returned value with the key named in `with` and post back session and
  signature. Bounded by MAX_CMSM_PHASES against a misbehaving registrar
- keep the log_revoke from the closing answer in the key metadata. It is
  signed with a key the registrar never sees and a secure element need not
  sign deterministically, so it cannot be reproduced; without it the DID can
  neither be updated nor revoked. It is a log entry, not a secret
- make the registrar endpoint configurable via OydConstructorOptions
- surface registrar errors with their status and message instead of only the
  status text, and drop the console.log calls that printed the request, the
  response and the signature

Resolution now goes through oydid-did-resolver, which is maintained with the
method. The copy in this package had the resolver host hard-coded inside the
function with no way to override it, and that host is being replaced by
resolver.ownyourdata.eu. It also reported every failure as invalidDid,
including a 404, and treated a deactivated DID (HTTP 410) as invalid. The
library reports DID Resolution error codes, reports deactivation as
didDocumentMetadata.deactivated per DID Core 7.1.3, and has no runtime
dependencies - did-resolver is a types-only peer dependency.

The CMSM test no longer carries a hard-coded private key, and the stray
top-level console.log that ran on import is gone.
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