diff --git a/go.sum b/go.sum
index 3b46f6cb1e..9ff15d6820 100644
--- a/go.sum
+++ b/go.sum
@@ -199,6 +199,7 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
+github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
diff --git a/jsonld/scalar.go b/jsonld/scalar.go
index fca2d7453c..e13ec6aa7b 100644
--- a/jsonld/scalar.go
+++ b/jsonld/scalar.go
@@ -29,8 +29,6 @@ type Scalar interface {
fmt.Stringer
// Value returns the underlying value (string, float, true or false)
Value() interface{}
- // Equal compares the value of both Scalar
- Equal(o Scalar) bool
}
// StringScalar is the string version of a Scalar
@@ -44,11 +42,6 @@ func (ss StringScalar) Value() interface{} {
return string(ss)
}
-func (ss StringScalar) Equal(o Scalar) bool {
- v, ok := o.(StringScalar)
- return ok && v == ss
-}
-
// BoolScalar is the boolean version of a Scalar
type BoolScalar bool
@@ -63,11 +56,6 @@ func (bs BoolScalar) Value() interface{} {
return bool(bs)
}
-func (bs BoolScalar) Equal(o Scalar) bool {
- v, ok := o.(BoolScalar)
- return ok && v == bs
-}
-
// Float64Scalar is the float64 version of a Scalar
type Float64Scalar float64
@@ -79,11 +67,6 @@ func (fs Float64Scalar) Value() interface{} {
return float64(fs)
}
-func (fs Float64Scalar) Equal(o Scalar) bool {
- v, ok := o.(Float64Scalar)
- return ok && v == fs
-}
-
// ErrInvalidValue is returned when an invalid value is parsed
var ErrInvalidValue = errors.New("invalid value")
diff --git a/jsonld/scalar_test.go b/jsonld/scalar_test.go
index 4b478d91b4..38d88228e5 100644
--- a/jsonld/scalar_test.go
+++ b/jsonld/scalar_test.go
@@ -93,28 +93,3 @@ func TestScalar_String(t *testing.T) {
assert.Equal(t, "false", s.String())
})
}
-
-func TestBoolScalar_Equal(t *testing.T) {
- type entry struct {
- a Scalar
- b Scalar
- equal bool
- }
-
- tt := []entry{
- {a: StringScalar("string"), b: StringScalar("string"), equal: true},
- {a: StringScalar("string"), b: StringScalar("STRING"), equal: false},
- {a: StringScalar("1"), b: Float64Scalar(1), equal: false},
-
- {a: BoolScalar(true), b: BoolScalar(true), equal: true},
- {a: BoolScalar(true), b: BoolScalar(false), equal: false},
- {a: BoolScalar(true), b: StringScalar("true"), equal: false},
-
- {a: Float64Scalar(1.5), b: Float64Scalar(1.5), equal: true},
- {a: Float64Scalar(1.5), b: Float64Scalar(1), equal: false},
- {a: Float64Scalar(1.5), b: StringScalar("1.5"), equal: false},
- }
- for i, test := range tt {
- assert.Equal(t, test.a.Equal(test.b), test.equal, "test %d failed: a=%s, b=%s, expected=%t", i, test.a, test.b, test.equal)
- }
-}
diff --git a/vcr/api/oidc4vci/v0/holder_test.go b/vcr/api/oidc4vci/v0/holder_test.go
index 0fb515d4a5..9e094841fc 100644
--- a/vcr/api/oidc4vci/v0/holder_test.go
+++ b/vcr/api/oidc4vci/v0/holder_test.go
@@ -21,6 +21,7 @@ package v0
import (
"context"
"encoding/json"
+ ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/nuts-node/vcr"
"github.com/nuts-foundation/nuts-node/vcr/holder"
@@ -81,12 +82,12 @@ func TestWrapper_HandleCredentialOffer(t *testing.T) {
credentialOffer := oidc4vci.CredentialOffer{
CredentialIssuer: issuerDID.String(),
- Credentials: []map[string]interface{}{
+ Credentials: []oidc4vci.OfferedCredential{
{
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": []string{"a", "b"},
- "type": []string{"VerifiableCredential", "HumanCredential"},
+ Format: oidc4vci.VerifiableCredentialJSONLDFormat,
+ CredentialDefinition: &oidc4vci.CredentialDefinition{
+ Context: []ssi.URI{ssi.MustParseURI("a"), ssi.MustParseURI("b")},
+ Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential"), ssi.MustParseURI("HumanCredential")},
},
},
},
diff --git a/vcr/api/oidc4vci/v0/issuer_test.go b/vcr/api/oidc4vci/v0/issuer_test.go
index 192b4b13ef..5e44e44fd9 100644
--- a/vcr/api/oidc4vci/v0/issuer_test.go
+++ b/vcr/api/oidc4vci/v0/issuer_test.go
@@ -173,7 +173,7 @@ func TestWrapper_RequestCredential(t *testing.T) {
},
Body: &RequestCredentialJSONRequestBody{
Format: "ldp_vc",
- CredentialDefinition: &map[string]interface{}{},
+ CredentialDefinition: &oidc4vci.CredentialDefinition{},
Proof: nil,
},
})
diff --git a/vcr/holder/openid.go b/vcr/holder/openid.go
index ac616a6ad4..86dc4053e2 100644
--- a/vcr/holder/openid.go
+++ b/vcr/holder/openid.go
@@ -50,7 +50,7 @@ var nowFunc = time.Now
var _ OpenIDHandler = (*openidHandler)(nil)
// NewOpenIDHandler creates an OpenIDHandler that tries to retrieve offered credentials, to store it in the given credential store.
-func NewOpenIDHandler(config oidc4vci.ClientConfig, did did.DID, identifier string, credentialStore vcrTypes.Writer, signer crypto.JWTSigner, resolver vdr.KeyResolver, jsonldReader jsonld.Reader) OpenIDHandler {
+func NewOpenIDHandler(config oidc4vci.ClientConfig, did did.DID, identifier string, credentialStore vcrTypes.Writer, signer crypto.JWTSigner, resolver vdr.KeyResolver) OpenIDHandler {
return &openidHandler{
did: did,
identifier: identifier,
@@ -59,7 +59,6 @@ func NewOpenIDHandler(config oidc4vci.ClientConfig, did did.DID, identifier stri
resolver: resolver,
config: config,
issuerClientCreator: oidc4vci.NewIssuerAPIClient,
- jsonldReader: jsonldReader,
}
}
@@ -94,6 +93,21 @@ func (h openidHandler) HandleCredentialOffer(ctx context.Context, offer oidc4vci
StatusCode: http.StatusBadRequest,
}
}
+ offeredCredential := offer.Credentials[0]
+ if offeredCredential.Format != oidc4vci.VerifiableCredentialJSONLDFormat {
+ return oidc4vci.Error{
+ Err: fmt.Errorf("credential offer: unsupported format '%s'", offeredCredential.Format),
+ Code: oidc4vci.UnsupportedCredentialType,
+ StatusCode: http.StatusBadRequest,
+ }
+ }
+ if err := offeredCredential.CredentialDefinition.Validate(true); err != nil {
+ return oidc4vci.Error{
+ Err: fmt.Errorf("credential offer: %w", err),
+ Code: oidc4vci.InvalidRequest,
+ StatusCode: http.StatusBadRequest,
+ }
+ }
preAuthorizedCode := getPreAuthorizedCodeFromOffer(offer)
if preAuthorizedCode == "" {
@@ -149,7 +163,7 @@ func (h openidHandler) HandleCredentialOffer(ctx context.Context, offer oidc4vci
retrieveCtx := audit.Context(ctx, "app-openid4vci", "VCR/OpenID4VCI", "RetrieveCredential")
retrieveCtx, cancel := context.WithTimeout(retrieveCtx, h.config.Timeout)
defer cancel()
- credential, err := h.retrieveCredential(retrieveCtx, issuerClient, offer, accessTokenResponse)
+ credential, err := h.retrieveCredential(retrieveCtx, issuerClient, offeredCredential.CredentialDefinition, accessTokenResponse)
if err != nil {
return oidc4vci.Error{
Err: fmt.Errorf("unable to retrieve credential: %w", err),
@@ -157,10 +171,10 @@ func (h openidHandler) HandleCredentialOffer(ctx context.Context, offer oidc4vci
StatusCode: http.StatusInternalServerError,
}
}
- if err = credentialTypesMatchOffer(h.jsonldReader, *credential, offer.Credentials[0]); err != nil {
+ if err = oidc4vci.ValidateDefinitionWithCredential(*credential, *offeredCredential.CredentialDefinition); err != nil {
return oidc4vci.Error{
Err: fmt.Errorf("received credential does not match offer: %w", err),
- Code: oidc4vci.UnsupportedCredentialType,
+ Code: oidc4vci.InvalidRequest,
StatusCode: http.StatusInternalServerError,
}
}
@@ -174,48 +188,6 @@ func (h openidHandler) HandleCredentialOffer(ctx context.Context, offer oidc4vci
return nil
}
-// credentialTypesMatchOffer performs format specific validation.
-func credentialTypesMatchOffer(reader jsonld.Reader, credential vc.VerifiableCredential, offeredCredential map[string]interface{}) error {
- switch offeredCredential["format"] {
- case oidc4vci.VerifiableCredentialJSONLDFormat:
- // In json-LD format the types need to be compared in expanded format
- document, err := reader.Read(offeredCredential["credential_definition"])
- if err != nil {
- return fmt.Errorf("invalid credential_definition in offer: %w", err)
- }
- // TODO: can credentialDefinition contain invalid values that makes this panic?
- expectedTypes := document.ValueAt(jsonld.NewPath("@type"))
-
- document, err = reader.Read(credential)
- if err != nil {
- return fmt.Errorf("invalid credential: %w", err)
- }
- receivedTypes := document.ValueAt(jsonld.NewPath("@type"))
-
- if !equal(expectedTypes, receivedTypes) {
- return errors.New("credential Type do not match")
- }
-
- return nil
- default:
- return errors.New("unsupported credential format")
- }
-}
-
-// equal returns true if both slices have the same values in the same order.
-// Note: JSON arrays are ordered, JSON object elements are not.
-func equal(a, b []jsonld.Scalar) bool {
- if len(a) != len(b) {
- return false
- }
- for i := range a {
- if !a[i].Equal(b[i]) {
- return false
- }
- }
- return true
-}
-
func getPreAuthorizedCodeFromOffer(offer oidc4vci.CredentialOffer) string {
params, ok := offer.Grants[oidc4vci.PreAuthorizedCodeGrant].(map[string]interface{})
if !ok {
@@ -228,7 +200,7 @@ func getPreAuthorizedCodeFromOffer(offer oidc4vci.CredentialOffer) string {
return preAuthorizedCode
}
-func (h openidHandler) retrieveCredential(ctx context.Context, issuerClient oidc4vci.IssuerAPIClient, offer oidc4vci.CredentialOffer, tokenResponse *oidc4vci.TokenResponse) (*vc.VerifiableCredential, error) {
+func (h openidHandler) retrieveCredential(ctx context.Context, issuerClient oidc4vci.IssuerAPIClient, offer *oidc4vci.CredentialDefinition, tokenResponse *oidc4vci.TokenResponse) (*vc.VerifiableCredential, error) {
keyID, err := h.resolver.ResolveSigningKeyID(h.did, nil)
headers := map[string]interface{}{
"typ": oidc4vci.JWTTypeOpenID4VCIProof, // MUST be openid4vci-proof+jwt, which explicitly types the proof JWT as recommended in Section 3.11 of [RFC8725].
@@ -246,7 +218,7 @@ func (h openidHandler) retrieveCredential(ctx context.Context, issuerClient oidc
}
credentialRequest := oidc4vci.CredentialRequest{
- CredentialDefinition: &offer.Credentials[0],
+ CredentialDefinition: offer,
Format: oidc4vci.VerifiableCredentialJSONLDFormat,
Proof: &oidc4vci.CredentialRequestProof{
Jwt: proof,
diff --git a/vcr/holder/openid_test.go b/vcr/holder/openid_test.go
index 3b0ece5906..90f11e5ae5 100644
--- a/vcr/holder/openid_test.go
+++ b/vcr/holder/openid_test.go
@@ -27,7 +27,6 @@ import (
"github.com/nuts-foundation/nuts-node/audit"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/crypto"
- "github.com/nuts-foundation/nuts-node/jsonld"
"github.com/nuts-foundation/nuts-node/vcr/oidc4vci"
"github.com/nuts-foundation/nuts-node/vcr/types"
vdrTypes "github.com/nuts-foundation/nuts-node/vdr/types"
@@ -43,12 +42,12 @@ var holderDID = did.MustParseDID("did:nuts:holder")
var issuerDID = did.MustParseDID("did:nuts:issuer")
func TestNewOIDCWallet(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonld.Reader{})
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
assert.NotNil(t, w)
}
func Test_wallet_Metadata(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonld.Reader{})
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
metadata := w.Metadata()
@@ -60,18 +59,7 @@ func Test_wallet_Metadata(t *testing.T) {
func Test_wallet_HandleCredentialOffer(t *testing.T) {
credentialOffer := oidc4vci.CredentialOffer{
CredentialIssuer: issuerDID.String(),
- Credentials: []map[string]interface{}{
- {
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": []string{
- "https://www.w3.org/2018/credentials/v1",
- "http://example.org/credentials/V1",
- },
- "type": []string{"VerifiableCredential", "HumanCredential"},
- },
- },
- },
+ Credentials: offeredCredential(),
Grants: map[string]interface{}{
"some-other-grant": map[string]interface{}{},
"urn:ietf:params:oauth:grant-type:pre-authorized_code": map[string]interface{}{
@@ -83,7 +71,6 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
CredentialIssuer: issuerDID.String(),
CredentialEndpoint: "credential-endpoint",
}
- jsonldReader := jsonld.Reader{DocumentLoader: jsonld.NewTestJSONLDManager(t).DocumentLoader()}
t.Run("ok", func(t *testing.T) {
ctrl := gomock.NewController(t)
nonce := "nonsens"
@@ -117,7 +104,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
return time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
}
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", credentialStore, jwtSigner, keyResolver, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", credentialStore, jwtSigner, keyResolver).(*openidHandler)
w.issuerClientCreator = func(_ context.Context, httpClient core.HTTPRequestDoer, credentialIssuerIdentifier string) (oidc4vci.IssuerAPIClient, error) {
return issuerAPIClient, nil
}
@@ -129,15 +116,15 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
require.NoError(t, err)
})
t.Run("pre-authorized code grant", func(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil).(*openidHandler)
t.Run("no grants", func(t *testing.T) {
- offer := oidc4vci.CredentialOffer{Credentials: emptyOfferedCredential()}
+ offer := oidc4vci.CredentialOffer{Credentials: offeredCredential()}
err := w.HandleCredentialOffer(audit.TestContext(), offer)
require.EqualError(t, err, "invalid_grant - couldn't find (valid) pre-authorized code grant in credential offer")
})
t.Run("no pre-authorized grant", func(t *testing.T) {
offer := oidc4vci.CredentialOffer{
- Credentials: emptyOfferedCredential(),
+ Credentials: offeredCredential(),
Grants: map[string]interface{}{
"some-other-grant": nil,
},
@@ -147,7 +134,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
})
t.Run("invalid pre-authorized grant", func(t *testing.T) {
offer := oidc4vci.CredentialOffer{
- Credentials: emptyOfferedCredential(),
+ Credentials: offeredCredential(),
Grants: map[string]interface{}{
"urn:ietf:params:oauth:grant-type:pre-authorized_code": map[string]interface{}{
"pre-authorized_code": nil,
@@ -159,24 +146,12 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
})
})
t.Run("error - too many credentials in offer", func(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
offer := oidc4vci.CredentialOffer{
- Credentials: []map[string]interface{}{
- {
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": []string{"a", "b"},
- "type": []string{"VerifiableCredential", "HumanCredential"},
- },
- },
- {
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": []string{"a", "b"},
- "type": []string{"VerifiableCredential", "HumanCredential"},
- },
- },
+ Credentials: []oidc4vci.OfferedCredential{
+ offeredCredential()[0],
+ offeredCredential()[0],
},
}
err := w.HandleCredentialOffer(audit.TestContext(), offer).(oidc4vci.Error)
@@ -189,7 +164,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
issuerAPIClient := oidc4vci.NewMockIssuerAPIClient(ctrl)
issuerAPIClient.EXPECT().RequestAccessToken(gomock.Any(), gomock.Any()).Return(nil, errors.New("request failed"))
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil).(*openidHandler)
w.issuerClientCreator = func(_ context.Context, httpClient core.HTTPRequestDoer, credentialIssuerIdentifier string) (oidc4vci.IssuerAPIClient, error) {
return issuerAPIClient, nil
}
@@ -203,7 +178,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
issuerAPIClient := oidc4vci.NewMockIssuerAPIClient(ctrl)
issuerAPIClient.EXPECT().RequestAccessToken(gomock.Any(), gomock.Any()).Return(&oidc4vci.TokenResponse{}, nil)
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil).(*openidHandler)
w.issuerClientCreator = func(_ context.Context, httpClient core.HTTPRequestDoer, credentialIssuerIdentifier string) (oidc4vci.IssuerAPIClient, error) {
return issuerAPIClient, nil
}
@@ -217,7 +192,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
issuerAPIClient := oidc4vci.NewMockIssuerAPIClient(ctrl)
issuerAPIClient.EXPECT().RequestAccessToken(gomock.Any(), gomock.Any()).Return(&oidc4vci.TokenResponse{AccessToken: "foo"}, nil)
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil).(*openidHandler)
w.issuerClientCreator = func(_ context.Context, httpClient core.HTTPRequestDoer, credentialIssuerIdentifier string) (oidc4vci.IssuerAPIClient, error) {
return issuerAPIClient, nil
}
@@ -227,7 +202,7 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
require.EqualError(t, err, "invalid_token - c_nonce is missing")
})
t.Run("error - no credentials in offer", func(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
err := w.HandleCredentialOffer(audit.TestContext(), oidc4vci.CredentialOffer{}).(oidc4vci.Error)
@@ -235,15 +210,11 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, err.StatusCode)
})
t.Run("error - can't issuer client (metadata can't be loaded)", func(t *testing.T) {
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil, jsonldReader)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
err := w.HandleCredentialOffer(audit.TestContext(), oidc4vci.CredentialOffer{
CredentialIssuer: "http://localhost:87632",
- Credentials: []map[string]interface{}{
- {
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- },
- },
+ Credentials: offeredCredential(),
Grants: map[string]interface{}{
"urn:ietf:params:oauth:grant-type:pre-authorized_code": map[string]interface{}{
"pre-authorized_code": "foo",
@@ -254,13 +225,14 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
assert.EqualError(t, err, "server_error - unable to create issuer client: unable to load Credential Issuer Metadata (identifier=http://localhost:87632): "+
"http request error: Get \"http://localhost:87632/.well-known/openid-credential-issuer\": dial tcp: address 87632: invalid port")
})
- t.Run("error - types do not match", func(t *testing.T) {
+ t.Run("error - credential does not match offer", func(t *testing.T) {
+ offer := offeredCredential()[0]
ctrl := gomock.NewController(t)
issuerAPIClient := oidc4vci.NewMockIssuerAPIClient(ctrl)
issuerAPIClient.EXPECT().Metadata().Return(metadata)
issuerAPIClient.EXPECT().RequestAccessToken(gomock.Any(), gomock.Any()).Return(&oidc4vci.TokenResponse{AccessToken: "access-token", CNonce: "c_nonce"}, nil)
issuerAPIClient.EXPECT().RequestCredential(gomock.Any(), gomock.Any(), gomock.Any()).Return(&vc.VerifiableCredential{
- Context: []ssi.URI{ssi.MustParseURI("https://www.w3.org/2018/credentials/v1")},
+ Context: offer.CredentialDefinition.Context,
Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential")},
}, nil)
jwtSigner := crypto.NewMockJWTSigner(ctrl)
@@ -268,64 +240,50 @@ func Test_wallet_HandleCredentialOffer(t *testing.T) {
keyResolver := vdrTypes.NewMockKeyResolver(ctrl)
keyResolver.EXPECT().ResolveSigningKeyID(holderDID, nil)
- w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, jwtSigner, keyResolver, jsonldReader).(*openidHandler)
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, jwtSigner, keyResolver).(*openidHandler)
w.issuerClientCreator = func(_ context.Context, _ core.HTTPRequestDoer, _ string) (oidc4vci.IssuerAPIClient, error) {
return issuerAPIClient, nil
}
err := w.HandleCredentialOffer(audit.TestContext(), credentialOffer)
- require.EqualError(t, err, "unsupported_credential_type - received credential does not match offer: credential Type do not match")
+ require.EqualError(t, err, "invalid_request - received credential does not match offer: credential does not match credential_definition: type mismatch")
})
-}
+ t.Run("error - unsupported format", func(t *testing.T) {
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
-func Test_credentialTypesMatchOffer(t *testing.T) {
- offer := map[string]any{
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": []string{
- "https://www.w3.org/2018/credentials/v1",
- "http://example.org/credentials/V1",
- },
- "type": []string{"VerifiableCredential", "HumanCredential"},
- },
- }
- credential := vc.VerifiableCredential{
- Context: []ssi.URI{ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"), ssi.MustParseURI("http://example.org/credentials/V1")},
- Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential"), ssi.MustParseURI("HumanCredential")},
- }
- jsonldReader := jsonld.Reader{DocumentLoader: jsonld.NewTestJSONLDManager(t).DocumentLoader()}
+ err := w.HandleCredentialOffer(audit.TestContext(), oidc4vci.CredentialOffer{
+ Credentials: []oidc4vci.OfferedCredential{{Format: "not supported"}},
+ }).(oidc4vci.Error)
- t.Run("ok", func(t *testing.T) {
- assert.NoError(t, credentialTypesMatchOffer(jsonldReader, credential, offer))
- })
- t.Run("error - unsupported credential format", func(t *testing.T) {
- err := credentialTypesMatchOffer(jsonldReader, vc.VerifiableCredential{}, map[string]interface{}{})
- assert.EqualError(t, err, "unsupported credential format")
- })
- t.Run("error - invalid credential_definition", func(t *testing.T) {
- err := credentialTypesMatchOffer(jsonldReader, vc.VerifiableCredential{},
- map[string]interface{}{
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": "",
- })
- assert.EqualError(t, err, "invalid credential_definition in offer: json: cannot unmarshal string into Go value of type map[string]interface {}")
- })
- t.Run("error - invalid credential", func(t *testing.T) {
- err := credentialTypesMatchOffer(jsonldReader, vc.VerifiableCredential{}, offer)
- assert.EqualError(t, err, "invalid credential: invalid property: Dropping property that did not expand into an absolute IRI or keyword.")
+ assert.EqualError(t, err, "unsupported_credential_type - credential offer: unsupported format 'not supported'")
+ assert.Equal(t, http.StatusBadRequest, err.StatusCode)
})
- t.Run("error - types do not match", func(t *testing.T) {
- c := credential
- c.Type[0], c.Type[1] = c.Type[1], c.Type[0]
- defer func() { c.Type[0], c.Type[1] = c.Type[1], c.Type[0] }()
- err := credentialTypesMatchOffer(jsonldReader, credential, offer)
- assert.EqualError(t, err, "credential Type do not match")
+ t.Run("error - credentialSubject not allowed in offer", func(t *testing.T) {
+ w := NewOpenIDHandler(oidc4vci.ClientConfig{}, holderDID, "https://holder.example.com", nil, nil, nil)
+ credentials := offeredCredential()
+ credentials[0].CredentialDefinition.CredentialSubject = new(map[string]interface{})
+
+ err := w.HandleCredentialOffer(audit.TestContext(), oidc4vci.CredentialOffer{Credentials: credentials}).(oidc4vci.Error)
+
+ assert.EqualError(t, err, "invalid_request - credential offer: invalid credential_definition: credentialSubject not allowed in offer")
+ assert.Equal(t, http.StatusBadRequest, err.StatusCode)
})
}
-// emptyOfferedCredential returns a structure that can be used as CredentialOffer.Credentials,
-// specifying an offer with a single credential without properties (which is invalid, but required to pass basic validation).
-func emptyOfferedCredential() []map[string]interface{} {
- return []map[string]interface{}{{"format": oidc4vci.VerifiableCredentialJSONLDFormat}}
+// offeredCredential returns a structure that can be used as CredentialOffer.Credentials,
+func offeredCredential() []oidc4vci.OfferedCredential {
+ return []oidc4vci.OfferedCredential{{
+ Format: oidc4vci.VerifiableCredentialJSONLDFormat,
+ CredentialDefinition: &oidc4vci.CredentialDefinition{
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"),
+ ssi.MustParseURI("http://example.org/credentials/V1"),
+ },
+ Type: []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("HumanCredential"),
+ },
+ },
+ }}
}
diff --git a/vcr/issuer/openid.go b/vcr/issuer/openid.go
index 07d2a8f256..2c9c01d6c2 100644
--- a/vcr/issuer/openid.go
+++ b/vcr/issuer/openid.go
@@ -228,8 +228,20 @@ func (i *openidHandler) OfferCredential(ctx context.Context, credential vc.Verif
}
func (i *openidHandler) HandleCredentialRequest(ctx context.Context, request oidc4vci.CredentialRequest, accessToken string) (*vc.VerifiableCredential, error) {
- // TODO: Verify requested format and credential definition
- // See https://github.com/nuts-foundation/nuts-node/issues/2037
+ if request.Format != oidc4vci.VerifiableCredentialJSONLDFormat {
+ return nil, oidc4vci.Error{
+ Err: fmt.Errorf("credential request: unsupported format '%s'", request.Format),
+ Code: oidc4vci.UnsupportedCredentialType,
+ StatusCode: http.StatusBadRequest,
+ }
+ }
+ if err := request.CredentialDefinition.Validate(false); err != nil {
+ return nil, oidc4vci.Error{
+ Err: fmt.Errorf("credential request: %w", err),
+ Code: oidc4vci.InvalidRequest,
+ StatusCode: http.StatusBadRequest,
+ }
+ }
flow, err := i.store.FindByReference(ctx, accessTokenRefType, accessToken)
if err != nil {
return nil, err
@@ -259,6 +271,14 @@ func (i *openidHandler) HandleCredentialRequest(ctx context.Context, request oid
return nil, err
}
+ if err = oidc4vci.ValidateDefinitionWithCredential(credential, *request.CredentialDefinition); err != nil {
+ return nil, oidc4vci.Error{
+ Err: fmt.Errorf("requested credential does not match offer: %w", err),
+ Code: oidc4vci.InvalidRequest,
+ StatusCode: http.StatusBadRequest,
+ }
+ }
+
// Important: since we (for now) create the VC even before the wallet requests it, we don't know if every VC is actually retrieved by the wallet.
// This is a temporary shortcut, since changing that requires a lot of refactoring.
// To make actually retrieved VC traceable, we log it to the audit log.
@@ -408,11 +428,11 @@ func (i *openidHandler) createOffer(ctx context.Context, credential vc.Verifiabl
}
offer := oidc4vci.CredentialOffer{
CredentialIssuer: i.issuerIdentifierURL,
- Credentials: []map[string]interface{}{{
- "format": oidc4vci.VerifiableCredentialJSONLDFormat,
- "credential_definition": map[string]interface{}{
- "@context": credential.Context,
- "type": credential.Type,
+ Credentials: []oidc4vci.OfferedCredential{{
+ Format: oidc4vci.VerifiableCredentialJSONLDFormat,
+ CredentialDefinition: &oidc4vci.CredentialDefinition{
+ Context: credential.Context,
+ Type: credential.Type,
},
}},
Grants: map[string]interface{}{
diff --git a/vcr/issuer/openid_test.go b/vcr/issuer/openid_test.go
index ae98fd58d5..002cc8e907 100644
--- a/vcr/issuer/openid_test.go
+++ b/vcr/issuer/openid_test.go
@@ -22,6 +22,7 @@ import (
"context"
crypt "crypto"
"errors"
+ ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/audit"
@@ -52,6 +53,14 @@ var issuedVC = vc.VerifiableCredential{
"id": holderDID.String(),
},
},
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"),
+ ssi.MustParseURI("http://example.org/credentials/V1"),
+ },
+ Type: []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("HumanCredential"),
+ },
}
func TestNew(t *testing.T) {
@@ -134,6 +143,16 @@ func Test_memoryIssuer_HandleCredentialRequest(t *testing.T) {
require.NoError(t, err)
return oidc4vci.CredentialRequest{
Format: oidc4vci.VerifiableCredentialJSONLDFormat,
+ CredentialDefinition: &oidc4vci.CredentialDefinition{
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"),
+ ssi.MustParseURI("http://example.org/credentials/V1"),
+ },
+ Type: []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("HumanCredential"),
+ },
+ },
Proof: &oidc4vci.CredentialRequestProof{
Jwt: proof,
ProofType: oidc4vci.ProofTypeJWT,
@@ -159,6 +178,24 @@ func Test_memoryIssuer_HandleCredentialRequest(t *testing.T) {
assert.Equal(t, issuerDID.URI(), response.Issuer)
auditLogs.AssertContains(t, "VCR", "VerifiableCredentialRetrievedEvent", audit.TestActor, "VC retrieved by wallet over OIDC4VCI")
})
+ t.Run("unsupported format", func(t *testing.T) {
+ request := createRequest(createHeaders(), createClaims(cNonce))
+ request.Format = "unsupported format"
+
+ response, err := service.HandleCredentialRequest(ctx, request, accessToken)
+
+ assert.Nil(t, response)
+ assert.EqualError(t, err, "unsupported_credential_type - credential request: unsupported format 'unsupported format'")
+ })
+ t.Run("invalid credential_definition", func(t *testing.T) {
+ request := createRequest(createHeaders(), createClaims(cNonce))
+ request.CredentialDefinition.Type = []ssi.URI{}
+
+ response, err := service.HandleCredentialRequest(ctx, request, accessToken)
+
+ assert.Nil(t, response)
+ assert.EqualError(t, err, "invalid_request - credential request: invalid credential_definition: missing type field")
+ })
t.Run("proof validation", func(t *testing.T) {
t.Run("unsupported proof type", func(t *testing.T) {
invalidRequest := createRequest(createHeaders(), createClaims(""))
@@ -293,6 +330,17 @@ func Test_memoryIssuer_HandleCredentialRequest(t *testing.T) {
assertProtocolError(t, err, http.StatusBadRequest, "invalid_proof - nonce not valid for access token")
assert.Nil(t, response)
})
+ t.Run("request does not match offer", func(t *testing.T) {
+ request := createRequest(createHeaders(), createClaims(cNonce))
+ request.CredentialDefinition.Type = []ssi.URI{
+ ssi.MustParseURI("DifferentCredential"),
+ }
+
+ response, err := service.HandleCredentialRequest(ctx, request, accessToken)
+
+ assert.Nil(t, response)
+ assert.EqualError(t, err, "invalid_request - requested credential does not match offer: credential does not match credential_definition: type mismatch")
+ })
})
t.Run("unknown access token", func(t *testing.T) {
diff --git a/vcr/oidc4vci/issuer_client_test.go b/vcr/oidc4vci/issuer_client_test.go
index d0403eedea..a4fb819449 100644
--- a/vcr/oidc4vci/issuer_client_test.go
+++ b/vcr/oidc4vci/issuer_client_test.go
@@ -88,10 +88,8 @@ func Test_httpIssuerClient_RequestCredential(t *testing.T) {
ctx := context.Background()
httpClient := &http.Client{}
credentialRequest := CredentialRequest{
- CredentialDefinition: &map[string]interface{}{
- "issuer": "issuer",
- },
- Format: VerifiableCredentialJSONLDFormat,
+ CredentialDefinition: &CredentialDefinition{},
+ Format: VerifiableCredentialJSONLDFormat,
}
t.Run("ok", func(t *testing.T) {
setup := setupClientTest(t)
diff --git a/vcr/oidc4vci/types.go b/vcr/oidc4vci/types.go
index 4b4de28c81..9fb295c573 100644
--- a/vcr/oidc4vci/types.go
+++ b/vcr/oidc4vci/types.go
@@ -22,6 +22,7 @@ package oidc4vci
import (
"crypto/tls"
+ ssi "github.com/nuts-foundation/go-did"
"time"
)
@@ -97,11 +98,29 @@ type CredentialOffer struct {
// CredentialIssuer defines the identifier of the credential issuer.
CredentialIssuer string `json:"credential_issuer"`
// Credentials defines the credentials offered by the issuer to the wallet.
- Credentials []map[string]interface{} `json:"credentials"`
+ Credentials []OfferedCredential `json:"credentials"`
// Grants defines the grants offered by the issuer to the wallet.
Grants map[string]interface{} `json:"grants"`
}
+// OfferedCredential defines a single entry in the credentials array of a CredentialOffer. We currently do not support 'JSON string' offers.
+// Specified by https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer-parameters
+// and https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-vc-secured-using-data-integ
+type OfferedCredential struct {
+ // Format specifies the credential format.
+ Format string `json:"format"`
+ // CredentialDefinition contains the 'credential_definition' for the Verifiable Credential Format flows.
+ CredentialDefinition *CredentialDefinition `json:"credential_definition,omitempty"`
+}
+
+// CredentialDefinition defines the 'credential_definition' for Format VerifiableCredentialJSONLDFormat
+// Specified by https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-vc-secured-using-data-integ
+type CredentialDefinition struct {
+ Context []ssi.URI `json:"@context"`
+ Type []ssi.URI `json:"type"`
+ CredentialSubject *map[string]interface{} `json:"credentialSubject,omitempty"` // optional and currently not used
+}
+
// CredentialOfferResponse defines the response for credential offer requests.
// It is an extension to the OIDC4VCI specification to better support server-to-server issuance.
type CredentialOfferResponse struct {
@@ -113,7 +132,7 @@ type CredentialOfferResponse struct {
// Specified by https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-request.
type CredentialRequest struct {
Format string `json:"format"`
- CredentialDefinition *map[string]interface{} `json:"credential_definition,omitempty"`
+ CredentialDefinition *CredentialDefinition `json:"credential_definition,omitempty"`
Proof *CredentialRequestProof `json:"proof,omitempty"`
}
diff --git a/vcr/oidc4vci/validators.go b/vcr/oidc4vci/validators.go
new file mode 100644
index 0000000000..9d6fe585b4
--- /dev/null
+++ b/vcr/oidc4vci/validators.go
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 Nuts community
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package oidc4vci
+
+import (
+ "errors"
+ ssi "github.com/nuts-foundation/go-did"
+ "github.com/nuts-foundation/go-did/vc"
+)
+
+// Validate the CredentialDefinition according to the VerifiableCredentialJSONLDFormat format
+func (cd *CredentialDefinition) Validate(isOffer bool) error {
+ if cd == nil {
+ return errors.New("invalid credential_definition: missing")
+ }
+ if len(cd.Context) == 0 {
+ return errors.New("invalid credential_definition: missing @context field")
+ }
+ if len(cd.Type) == 0 {
+ return errors.New("invalid credential_definition: missing type field")
+ }
+ if cd.CredentialSubject != nil {
+ if isOffer {
+ return errors.New("invalid credential_definition: credentialSubject not allowed in offer")
+ }
+ // TODO: Add credentialSubject validation.
+ // See https://github.com/nuts-foundation/nuts-node/issues/2320
+ }
+ return nil
+}
+
+// ValidateDefinitionWithCredential confirms that the vc.VerifiableCredential is defined by the CredentialDefinition.
+// CredentialDefinition is assumed to be valid, see ValidateCredentialDefinition.
+func ValidateDefinitionWithCredential(credential vc.VerifiableCredential, definition CredentialDefinition) error {
+ // From spec: When the format value is ldp_vc, ..., including credential_definition object, MUST NOT be processed using JSON-LD rules.
+ // https://openid.bitbucket.io/connect/editors-draft/openid-4-verifiable-credential-issuance-1_0.html#name-format-identifier-2
+
+ // compare contexts. The credential may contain extra contexts for signatures or proofs
+ if len(credential.Context) < len(definition.Context) || !isSubset(credential.Context, definition.Context) {
+ return errors.New("credential does not match credential_definition: context mismatch")
+ }
+
+ // compare types. fails when definition.Type contains duplicates
+ if len(credential.Type) != len(definition.Type) || !isSubset(credential.Type, definition.Type) {
+ return errors.New("credential does not match credential_definition: type mismatch")
+ }
+
+ // TODO: Compare credentialSubject
+ // See https://github.com/nuts-foundation/nuts-node/issues/2320
+
+ return nil
+}
+
+// isSubset is true if all elements of subset exist in set. If subset is empty it returns false.
+func isSubset(set, subset []ssi.URI) bool {
+ if len(subset) == 0 {
+ return false
+ }
+ for _, el1 := range subset {
+ found := false
+ for _, el2 := range set {
+ if el2.String() == el1.String() {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vcr/oidc4vci/validators_test.go b/vcr/oidc4vci/validators_test.go
new file mode 100644
index 0000000000..e382ff5510
--- /dev/null
+++ b/vcr/oidc4vci/validators_test.go
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 Nuts community
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package oidc4vci
+
+import (
+ ssi "github.com/nuts-foundation/go-did"
+ "github.com/nuts-foundation/go-did/vc"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func Test_ValidateCredentialDefinition(t *testing.T) {
+ t.Run("definition is nil", func(t *testing.T) {
+ var definition *CredentialDefinition
+ err := definition.Validate(true)
+
+ assert.EqualError(t, err, "invalid credential_definition: missing")
+ })
+ t.Run("missing context", func(t *testing.T) {
+ definition := &CredentialDefinition{}
+
+ err := definition.Validate(true)
+
+ assert.EqualError(t, err, "invalid credential_definition: missing @context field")
+ })
+ t.Run("missing type", func(t *testing.T) {
+ definition := &CredentialDefinition{Context: []ssi.URI{ssi.MustParseURI("http://example.com")}}
+
+ err := definition.Validate(true)
+
+ assert.EqualError(t, err, "invalid credential_definition: missing type field")
+ })
+ t.Run("credentialSubject not allowed in offer", func(t *testing.T) {
+ definition := &CredentialDefinition{
+ Context: []ssi.URI{ssi.MustParseURI("http://example.com")},
+ Type: []ssi.URI{ssi.MustParseURI("SomeCredentialType")},
+ CredentialSubject: new(map[string]any),
+ }
+ err := definition.Validate(true)
+
+ assert.EqualError(t, err, "invalid credential_definition: credentialSubject not allowed in offer")
+ })
+}
+
+func Test_ValidateDefinitionWithCredential(t *testing.T) {
+ makeDefinition := func() CredentialDefinition {
+ return CredentialDefinition{
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"),
+ ssi.MustParseURI("http://example.org/credentials/V1"),
+ },
+ Type: []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("HumanCredential"),
+ },
+ }
+ }
+
+ credential := func() vc.VerifiableCredential {
+ return vc.VerifiableCredential{
+ Context: []ssi.URI{
+ ssi.MustParseURI("https://www.w3.org/2018/credentials/v1"),
+ ssi.MustParseURI("http://example.org/credentials/V1"),
+ },
+ Type: []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("HumanCredential"),
+ },
+ }
+ }
+
+ t.Run("ok", func(t *testing.T) {
+ assert.NoError(t, ValidateDefinitionWithCredential(credential(), makeDefinition()))
+ })
+ t.Run("error - definition contains more contexts", func(t *testing.T) {
+ err := ValidateDefinitionWithCredential(vc.VerifiableCredential{}, makeDefinition())
+ assert.EqualError(t, err, "credential does not match credential_definition: context mismatch")
+ })
+ t.Run("error - different contexts", func(t *testing.T) {
+ definition := makeDefinition()
+ definition.Context = []ssi.URI{
+ ssi.MustParseURI("different context"),
+ }
+ err := ValidateDefinitionWithCredential(credential(), definition)
+ assert.EqualError(t, err, "credential does not match credential_definition: context mismatch")
+ })
+ t.Run("error - number of types do not match", func(t *testing.T) {
+ definition := makeDefinition()
+ definition.Type = []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ }
+ err := ValidateDefinitionWithCredential(credential(), definition)
+ assert.EqualError(t, err, "credential does not match credential_definition: type mismatch")
+ })
+ t.Run("error - types do not match", func(t *testing.T) {
+ definition := makeDefinition()
+ definition.Type = []ssi.URI{
+ ssi.MustParseURI("VerifiableCredential"),
+ ssi.MustParseURI("OtherType"),
+ }
+ err := ValidateDefinitionWithCredential(credential(), definition)
+ assert.EqualError(t, err, "credential does not match credential_definition: type mismatch")
+ })
+}
diff --git a/vcr/oidc4vci/wallet_client_test.go b/vcr/oidc4vci/wallet_client_test.go
index fc09adf63b..5261b564e9 100644
--- a/vcr/oidc4vci/wallet_client_test.go
+++ b/vcr/oidc4vci/wallet_client_test.go
@@ -67,7 +67,7 @@ func Test_httpWalletClient_OfferCredential(t *testing.T) {
err = client.OfferCredential(ctx, CredentialOffer{
CredentialIssuer: setup.issuerMetadata.CredentialIssuer,
- Credentials: []map[string]interface{}{{"issuer": "issuer"}},
+ Credentials: []OfferedCredential{},
Grants: map[string]interface{}{
"grant_type": "pre-authorized_code",
},
@@ -84,7 +84,7 @@ func Test_httpWalletClient_OfferCredential(t *testing.T) {
err = json.Unmarshal([]byte(credentialOfferJSON), &credentialOffer)
require.NoError(t, err)
require.Equal(t, setup.issuerMetadata.CredentialIssuer, credentialOffer["credential_issuer"])
- require.Equal(t, []interface{}{map[string]interface{}{"issuer": "issuer"}}, credentialOffer["credentials"])
+ require.Equal(t, []interface{}{}, credentialOffer["credentials"])
require.Equal(t, map[string]interface{}{"grant_type": "pre-authorized_code"}, credentialOffer["grants"])
})
t.Run("error - invalid response from wallet", func(t *testing.T) {
@@ -95,7 +95,7 @@ func Test_httpWalletClient_OfferCredential(t *testing.T) {
err = client.OfferCredential(ctx, CredentialOffer{
CredentialIssuer: setup.issuerMetadata.CredentialIssuer,
- Credentials: []map[string]interface{}{{"issuer": "issuer"}},
+ Credentials: []OfferedCredential{},
Grants: map[string]interface{}{
"grant_type": "pre-authorized_code",
},
@@ -113,7 +113,7 @@ func Test_httpWalletClient_OfferCredential(t *testing.T) {
err = client.OfferCredential(ctx, CredentialOffer{
CredentialIssuer: setup.issuerMetadata.CredentialIssuer,
- Credentials: []map[string]interface{}{{"issuer": "issuer"}},
+ Credentials: []OfferedCredential{},
Grants: map[string]interface{}{
"grant_type": "pre-authorized_code",
},
diff --git a/vcr/vcr.go b/vcr/vcr.go
index 9ccd85fed7..3c7828cd7f 100644
--- a/vcr/vcr.go
+++ b/vcr/vcr.go
@@ -130,7 +130,7 @@ func (c *vcr) GetOpenIDHolder(ctx context.Context, id did.DID) (holder.OpenIDHan
TLS: c.clientTLSConfig,
HTTPSOnly: c.strictmode,
}
- return holder.NewOpenIDHandler(clientConfig, id, identifier, c, c.keyStore, c.keyResolver, jsonld.Reader{DocumentLoader: c.jsonldManager.DocumentLoader()}), nil
+ return holder.NewOpenIDHandler(clientConfig, id, identifier, c, c.keyStore, c.keyResolver), nil
}
func (c *vcr) resolveOpenID4VCIIdentifier(ctx context.Context, id did.DID) (string, error) {