diff --git a/pkg/plugin/implementation/vcvalidator/vcvalidator_test.go b/pkg/plugin/implementation/vcvalidator/vcvalidator_test.go index 2b79cd02..af810bec 100644 --- a/pkg/plugin/implementation/vcvalidator/vcvalidator_test.go +++ b/pkg/plugin/implementation/vcvalidator/vcvalidator_test.go @@ -399,6 +399,31 @@ func TestProofPresentButEmpty(t *testing.T) { assertClassCode(t, err, failProof, codeAutSignatureMissing) } +// TestDataIntegrityProofWithoutVerificationMethod asserts that a JSON-LD Data +// Integrity proof carrying no verificationMethod is rejected even when +// RequireProof is false. With requireProof disabled the signature itself is +// deliberately not checked, so the resolvable verificationMethod is the only +// remaining evidence about the credential; absent it there is nothing left to +// verify, which is the same situation as the no-proof and empty-proof cases +// and is classified with the same code. +func TestDataIntegrityProofWithoutVerificationMethod(t *testing.T) { + cfg := DefaultConfig() + cfg.Actions = []string{"confirm"} + cfg.RequireProof = false + + vc := map[string]any{ + "issuer": "did:web:issuer.example.org", + "credentialSubject": map[string]any{"id": "x"}, + "proof": map[string]any{ + "type": "Ed25519Signature2020", + "proofValue": "z58DAdFfa9Skq...", + }, + } + v := testVerifier(cfg) + err := v.verify(context.Background(), vcBytes(t, vc)) + assertClassCode(t, err, failProof, codeAutSignatureMissing) +} + // TestUnsupportedDIDMethodIsKeyNotFound asserts a resolution failure caused by // a disallowed/unsupported DID method (not a network problem) is classified // AUT_KEY_NOT_FOUND rather than a NET_* code. diff --git a/pkg/plugin/implementation/vcvalidator/verify.go b/pkg/plugin/implementation/vcvalidator/verify.go index 34f0f4d8..a4e8a3a9 100644 --- a/pkg/plugin/implementation/vcvalidator/verify.go +++ b/pkg/plugin/implementation/vcvalidator/verify.go @@ -220,12 +220,18 @@ func (v *verifier) verify(ctx context.Context, raw json.RawMessage) error { "set requireProof=false to accept on expiry/revocation only", cred.Proof.Type) } - // Best-effort: confirm the verification method DID resolves. - if vm := cred.Proof.VerificationMethod; vm != "" { - if _, err := resolveDID(ctx, vm, "", v.cfg, v.fetch); err != nil { - if !v.cfg.FailOpen { - return failf(failResolution, resolutionCode(err), "verificationMethod %q did not resolve: %v", vm, err) - } + // Best-effort: confirm the verification method DID resolves. Without a + // verificationMethod there is nothing at all left to check — the same + // defect as the two cases above — so reject rather than accept a + // credential on which no verification was performed. + vm := cred.Proof.VerificationMethod + if vm == "" { + return failf(failProof, codeAutSignatureMissing, + "proof type %q has no verificationMethod to resolve", cred.Proof.Type) + } + if _, err := resolveDID(ctx, vm, "", v.cfg, v.fetch); err != nil { + if !v.cfg.FailOpen { + return failf(failResolution, resolutionCode(err), "verificationMethod %q did not resolve: %v", vm, err) } } } else {