Skip to content

A proof set is only as strong as its weakest surviving proof #1090

Description

@macterra

Problem

verifyProof accepts a document when any one proof in its proof array verifies. Nothing binds a set together, so a proof can be removed without trace.

The consequence is that adding a strong proof beside a weak one raises nothing. An attacker holding a credential that carries both an archon-ecdsa-jcs-2019 proof and a legacy EcdsaSecp256k1Signature2019 proof can drop the former and keep the latter, and the credential still verifies — under the legacy proof's weaker rules, where created and proofPurpose sit outside the signature and can be altered freely.

So a credential's effective strength is that of its weakest proof, not its strongest.

Why the rule exists

It is deliberate, and the reason is sound: a document may carry a cryptosuite this build does not implement alongside one it does, and rejecting the document for the former would make Archon unable to read a credential that is perfectly valid. Any change has to preserve that.

Current state

Documented in docs/scheme.md under Proof sets, not addressed in code. Credentials issued now carry archon-ecdsa-jcs-2019 (which binds its proof configuration, #1085), so this concerns credentials issued before that and any document carrying a mixture.

Related

The stated attack needs a document nothing emits

Credentials are signed by archonEcdsaJcs2019Proof and eddsaJcs2022Proof
together (packages/keymaster/src/keymaster.ts:1381-1382). The legacy
EcdsaSecp256k1Signature2019 proof is emitted only by addOperationProof
(:1272), which serves DID operations. No credential therefore carries a legacy
proof beside a Data Integrity one, and credentials issued before #1085 carry a
single legacy proof, where there is nothing to drop. Attaching a legacy proof to
a current credential requires the issuer's key.

What is reachable

Resolution keys off the proof's own created — resolveDID(signerDid, { versionTime: proof.created }) (:1632) — which selects the version of the
issuer's document that supplies the verification key.

For a Data Integrity proof created sits inside the signed configuration, so
altering it only makes that proof fail, and the remaining proofs in the set are
still tried. For a legacy proof it sits outside the signature and can be moved
freely while the signature still verifies. Legacy proofs are accepted
indefinitely (supportedProof, :1555), so a holder of an issuer key that has
since been rotated out can sign a legacy-labelled credential and date it before
the rotation, and it resolves to the document version that still lists that key.

This is the same shape as the created-selects-the-authorizing-version problem
recorded on #1087, on the credential path rather than the operation path.

Removal is silent but not a weakening for current credentials

Both emitted proofs bind their own configuration, so stripping the
eddsa-jcs-2022 proof leaves a credential that verifies under
archon-ecdsa-jcs-2019 alone — an unregistered suite no outside implementation
reads. The loss is third-party verifiability, without any signal that it
happened. Herald compares every proof's signer against the issuer
(services/herald/server/src/routes.ts:124-135), which stops a proof being
added but cannot observe one being removed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions