From c22d5364f8ec6e8adfa44807c5b972dda7303a57 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 21:43:00 -0700 Subject: [PATCH 1/2] fix(vcr/verifier): handle unparseable issuer DID instead of nil deref Closes #4235 Signed-off-by: SAY-5 --- vcr/verifier/verifier.go | 5 ++++- vcr/verifier/verifier_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/vcr/verifier/verifier.go b/vcr/verifier/verifier.go index 59f833327..b41ddf80a 100644 --- a/vcr/verifier/verifier.go +++ b/vcr/verifier/verifier.go @@ -153,7 +153,10 @@ func (v verifier) Verify(credentialToVerify vc.VerifiableCredential, allowUntrus // Check signature if checkSignature { - issuerDID, _ := did.ParseDID(credentialToVerify.Issuer.String()) + issuerDID, err := did.ParseDID(credentialToVerify.Issuer.String()) + if err != nil { + return fmt.Errorf("could not validate issuer: %w", err) + } metadata := resolver.ResolveMetadata{ResolveTime: validAt, AllowDeactivated: false} rawJwt := credentialToVerify.Raw() if rawJwt != "" { diff --git a/vcr/verifier/verifier_test.go b/vcr/verifier/verifier_test.go index 3466d519e..df0a64abd 100644 --- a/vcr/verifier/verifier_test.go +++ b/vcr/verifier/verifier_test.go @@ -120,6 +120,36 @@ func TestVerifier_Verify(t *testing.T) { assert.EqualError(t, validationErr, "could not validate issuer: the DID document has been deactivated") }) + + t.Run("returns error (no panic) when issuer is not a parseable DID", func(t *testing.T) { + // Regression for nuts-foundation/nuts-node#4235: an unparseable issuer + // DID was dereferenced as nil after did.ParseDID returned an error, + // crashing the verifier with a runtime panic. The verifier must now + // surface the parse error instead. + ctx := newMockContext(t) + malformed := vc + issuerURI := ssi.MustParseURI("not a valid did") + malformed.Issuer = issuerURI + // Adjust the credential ID prefix so the default validator accepts it + // and execution reaches the signature-check branch. + malformedID := ssi.MustParseURI(issuerURI.String() + "#test") + malformed.ID = &malformedID + // Strip the NutsOrganizationCredential type so the default validator + // runs (it doesn't itself parse the issuer DID). + malformed.Type = malformed.Type[:0] + for _, tp := range vc.Type { + if tp.String() == "VerifiableCredential" { + malformed.Type = append(malformed.Type, tp) + } + } + ctx.store.EXPECT().GetRevocations(*malformed.ID).Return(nil, ErrNotFound) + + require.NotPanics(t, func() { + validationErr := ctx.verifier.Verify(malformed, true, true, nil) + assert.Error(t, validationErr) + assert.Contains(t, validationErr.Error(), "could not validate issuer") + }) + }) }) t.Run("invalid when revoked", func(t *testing.T) { From 2ce91f9c757a9573c20c0bc7c5bbf95139bdfb20 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Wed, 13 May 2026 13:22:14 -0700 Subject: [PATCH 2/2] fix(vcr/verifier): say 'could not parse issuer' on ParseDID failure --- vcr/verifier/verifier.go | 2 +- vcr/verifier/verifier_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vcr/verifier/verifier.go b/vcr/verifier/verifier.go index b41ddf80a..c6ff01a15 100644 --- a/vcr/verifier/verifier.go +++ b/vcr/verifier/verifier.go @@ -155,7 +155,7 @@ func (v verifier) Verify(credentialToVerify vc.VerifiableCredential, allowUntrus if checkSignature { issuerDID, err := did.ParseDID(credentialToVerify.Issuer.String()) if err != nil { - return fmt.Errorf("could not validate issuer: %w", err) + return fmt.Errorf("could not parse issuer: %w", err) } metadata := resolver.ResolveMetadata{ResolveTime: validAt, AllowDeactivated: false} rawJwt := credentialToVerify.Raw() diff --git a/vcr/verifier/verifier_test.go b/vcr/verifier/verifier_test.go index df0a64abd..ee4fc8d04 100644 --- a/vcr/verifier/verifier_test.go +++ b/vcr/verifier/verifier_test.go @@ -147,7 +147,7 @@ func TestVerifier_Verify(t *testing.T) { require.NotPanics(t, func() { validationErr := ctx.verifier.Verify(malformed, true, true, nil) assert.Error(t, validationErr) - assert.Contains(t, validationErr.Error(), "could not validate issuer") + assert.Contains(t, validationErr.Error(), "could not parse issuer") }) }) })