Skip to content
Draft
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
5 changes: 3 additions & 2 deletions auth/oauth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ package oauth

import (
"encoding/json"
"net/url"

"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/nuts-foundation/nuts-node/core"
"net/url"
)

// this file contains constants, variables and helper functions for OAuth related code
Expand Down Expand Up @@ -382,7 +383,7 @@ type OAuthClientMetadata struct {
/*********** OpenID4VCI ***********/

// CredentialOfferEndpoint contains a URL where the pre-authorized_code flow offers a credential.
// https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-client-metadata
// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-client-metadata
// TODO: openid4vci duplicate. Also defined on /.well-known/openid-credential-wallet to be /n2n/identity/{did}/openid4vci/credential_offer
CredentialOfferEndpoint string `json:"credential_offer_endpoint,omitempty"`

Expand Down
56 changes: 32 additions & 24 deletions vcr/issuer/openid.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"time"

"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
Expand All @@ -37,11 +43,6 @@ import (
"github.com/nuts-foundation/nuts-node/vcr/log"
"github.com/nuts-foundation/nuts-node/vcr/openid4vci"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
"io/fs"
"net/http"
"os"
"path/filepath"
"time"
)

// Flow is an active OpenID4VCI credential issuance flow.
Expand Down Expand Up @@ -107,14 +108,14 @@ func NewOpenIDHandler(issuerDID did.DID, issuerIdentifierURL string, definitions
}

type openidHandler struct {
issuerIdentifierURL string
issuerDID did.DID
definitionsDIR string
credentialsSupported []map[string]interface{}
keyResolver resolver.KeyResolver
store OpenIDStore
walletClientCreator func(ctx context.Context, httpClient core.HTTPRequestDoer, walletMetadataURL string) (openid4vci.WalletAPIClient, error)
httpClient core.HTTPRequestDoer
issuerIdentifierURL string
issuerDID did.DID
definitionsDIR string
credentialConfigurationsSupported map[string]interface{}
keyResolver resolver.KeyResolver
store OpenIDStore
walletClientCreator func(ctx context.Context, httpClient core.HTTPRequestDoer, walletMetadataURL string) (openid4vci.WalletAPIClient, error)
httpClient core.HTTPRequestDoer
}

func (i *openidHandler) Metadata() openid4vci.CredentialIssuerMetadata {
Expand All @@ -123,8 +124,8 @@ func (i *openidHandler) Metadata() openid4vci.CredentialIssuerMetadata {
CredentialEndpoint: core.JoinURLPaths(i.issuerIdentifierURL, "/openid4vci/credential"),
}

// deepcopy the i.credentialsSupported slice to prevent concurrent access to the slice.
metadata.CredentialsSupported = deepcopy(i.credentialsSupported)
// deepcopy the i.credentialConfigurationsSupported map to prevent concurrent access to the map.
metadata.CredentialConfigurationsSupported = deepcopy(i.credentialConfigurationsSupported)

return metadata
}
Expand Down Expand Up @@ -443,8 +444,9 @@ func (i *openidHandler) createOffer(ctx context.Context, credential vc.Verifiabl
}

func (i *openidHandler) loadCredentialDefinitions() error {
i.credentialConfigurationsSupported = make(map[string]interface{})

// retrieve the definitions from assets and add to the list of CredentialsSupported
// retrieve the definitions from assets and add to the map of CredentialConfigurationsSupported
definitionsDir, err := assets.FS.ReadDir("definitions")
if err != nil {
return err
Expand All @@ -459,7 +461,9 @@ func (i *openidHandler) loadCredentialDefinitions() error {
if err != nil {
return err
}
i.credentialsSupported = append(i.credentialsSupported, definitionMap)
// Use the filename (without extension) as the credential configuration ID
configID := definition.Name()[:len(definition.Name())-len(filepath.Ext(definition.Name()))]
i.credentialConfigurationsSupported[configID] = definitionMap
}

// now add all credential definition from config.DefinitionsDIR
Expand All @@ -478,7 +482,9 @@ func (i *openidHandler) loadCredentialDefinitions() error {
if err != nil {
return fmt.Errorf("failed to parse credential definition from %s: %w", path, err)
}
i.credentialsSupported = append(i.credentialsSupported, definitionMap)
// Use the filename (without extension) as the credential configuration ID
configID := d.Name()[:len(d.Name())-len(filepath.Ext(d.Name()))]
i.credentialConfigurationsSupported[configID] = definitionMap
}
return nil
})
Expand All @@ -487,12 +493,14 @@ func (i *openidHandler) loadCredentialDefinitions() error {
return err
}

func deepcopy(src []map[string]interface{}) []map[string]interface{} {
dst := make([]map[string]interface{}, len(src))
for i := range src {
dst[i] = make(map[string]interface{})
for k, v := range src[i] {
dst[i][k] = v
func deepcopy(src map[string]interface{}) map[string]interface{} {
dst := make(map[string]interface{}, len(src))
for k, v := range src {
// Deep copy nested maps if needed
if nestedMap, ok := v.(map[string]interface{}); ok {
dst[k] = deepcopy(nestedMap)
} else {
dst[k] = v
}
}
return dst
Expand Down
21 changes: 13 additions & 8 deletions vcr/issuer/openid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ package issuer
import (
"context"
"errors"
"net/http"
"testing"
"time"

ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
Expand All @@ -33,9 +37,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"net/http"
"testing"
"time"
)

var issuerDID = did.MustParseDID("did:nuts:issuer")
Expand Down Expand Up @@ -67,7 +68,7 @@ func TestNew(t *testing.T) {
iss, err := NewOpenIDHandler(issuerDID, issuerIdentifier, "./test/valid", nil, nil, storage.NewTestInMemorySessionDatabase(t))

require.NoError(t, err)
assert.Len(t, iss.(*openidHandler).credentialsSupported, 3)
assert.Len(t, iss.(*openidHandler).credentialConfigurationsSupported, 3)
})

t.Run("error - invalid json", func(t *testing.T) {
Expand All @@ -93,10 +94,14 @@ func Test_memoryIssuer_Metadata(t *testing.T) {

assert.Equal(t, "https://example.com/did:nuts:issuer", metadata.CredentialIssuer)
assert.Equal(t, "https://example.com/did:nuts:issuer/openid4vci/credential", metadata.CredentialEndpoint)
require.Len(t, metadata.CredentialsSupported, 3)
assert.Equal(t, "ldp_vc", metadata.CredentialsSupported[0]["format"])
require.Len(t, metadata.CredentialsSupported[0]["cryptographic_binding_methods_supported"], 1)
assert.Equal(t, metadata.CredentialsSupported[0]["credential_definition"],
require.Len(t, metadata.CredentialConfigurationsSupported, 3)
// Check that NutsAuthorizationCredential is present as a configuration ID
authzConfig, ok := metadata.CredentialConfigurationsSupported["NutsAuthorizationCredential"]
require.True(t, ok, "NutsAuthorizationCredential should be in credential_configurations_supported")
authzConfigMap := authzConfig.(map[string]interface{})
assert.Equal(t, "ldp_vc", authzConfigMap["format"])
require.Len(t, authzConfigMap["cryptographic_binding_methods_supported"], 1)
assert.Equal(t, authzConfigMap["credential_definition"],
map[string]interface{}{
"@context": []interface{}{"https://www.w3.org/2018/credentials/v1", "https://www.nuts.nl/credentials/v1"},
"type": []interface{}{"VerifiableCredential", "NutsAuthorizationCredential"},
Expand Down
8 changes: 5 additions & 3 deletions vcr/openid4vci/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
package openid4vci

import (
ssi "github.com/nuts-foundation/go-did"
"time"

ssi "github.com/nuts-foundation/go-did"
)

// PreAuthorizedCodeGrant is the grant type used for pre-authorized code grant from the OpenID4VCI specification.
Expand Down Expand Up @@ -62,8 +63,9 @@ type CredentialIssuerMetadata struct {
// CredentialEndpoint defines where the wallet can send a request to retrieve a credential.
CredentialEndpoint string `json:"credential_endpoint"`

// CredentialsSupported defines metadata about which credential types the credential issuer can issue.
CredentialsSupported []map[string]interface{} `json:"credentials_supported"`
// CredentialConfigurationsSupported defines metadata about the credential configurations supported by the credential issuer.
// This replaces credentials_supported from draft versions.
CredentialConfigurationsSupported map[string]interface{} `json:"credential_configurations_supported"`
}

// OAuth2ClientMetadata defines the OAuth2 Client Metadata, extended with OpenID4VCI parameters.
Expand Down
3 changes: 2 additions & 1 deletion vcr/openid4vci/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package openid4vci

import (
"errors"

ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/vc"
)
Expand Down Expand Up @@ -49,7 +50,7 @@ func (cd *CredentialDefinition) Validate(isOffer bool) error {
// 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
// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-format-profiles

// compare contexts. The credential may contain extra contexts for signatures or proofs
if len(credential.Context) < len(definition.Context) || !isSubset(credential.Context, definition.Context) {
Expand Down
Loading