Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
17 changes: 0 additions & 17 deletions jsonld/scalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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")

Expand Down
25 changes: 0 additions & 25 deletions jsonld/scalar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
11 changes: 6 additions & 5 deletions vcr/api/oidc4vci/v0/holder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")},
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion vcr/api/oidc4vci/v0/issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestWrapper_RequestCredential(t *testing.T) {
},
Body: &RequestCredentialJSONRequestBody{
Format: "ldp_vc",
CredentialDefinition: &map[string]interface{}{},
CredentialDefinition: &oidc4vci.CredentialDefinition{},
Proof: nil,
},
})
Expand Down
70 changes: 21 additions & 49 deletions vcr/holder/openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -59,7 +59,6 @@ func NewOpenIDHandler(config oidc4vci.ClientConfig, did did.DID, identifier stri
resolver: resolver,
config: config,
issuerClientCreator: oidc4vci.NewIssuerAPIClient,
jsonldReader: jsonldReader,
}
}

Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -149,18 +163,18 @@ 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),
Code: oidc4vci.ServerError,
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,
}
}
Expand All @@ -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 {
Expand All @@ -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].
Expand All @@ -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,
Expand Down
Loading