Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support dynamic wellknown #1792

Merged
merged 8 commits into from
Nov 6, 2024
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 cmd/vc-rest/startcmd/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ const (
defaultOIDC4CITransactionDataTTL = 15 * time.Minute
defaultOIDC4CIAckDataTTL = 24 * time.Hour
defaultOIDC4CIAuthStateTTL = 15 * time.Minute
defaultDynamicWellKnownTTL = 1 * time.Hour
defaultDataEncryptionKeyLength = 256
)

Expand Down
20 changes: 17 additions & 3 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"github.com/trustbloc/vcs/pkg/storage/redis"
redisclient "github.com/trustbloc/vcs/pkg/storage/redis"
"github.com/trustbloc/vcs/pkg/storage/redis/ackstore"
"github.com/trustbloc/vcs/pkg/storage/redis/dynamicwellknown"
oidc4ciclaimdatastoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4ciclaimdatastore"
oidc4cinoncestoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4cinoncestore"
oidc4cistatestoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4cistatestore"
Expand Down Expand Up @@ -553,10 +554,16 @@
return newHTTPClient(tlsConfig, conf.StartupParameters, metrics, id)
}

dynamicWellKnownStore, err := getDynamicWellKnownStore(redisClient)
if err != nil {
return nil, fmt.Errorf("failed to get dynamic well-known store: %w", err)
}

Check warning on line 560 in cmd/vc-rest/startcmd/start.go

View check run for this annotation

Codecov / codecov/patch

cmd/vc-rest/startcmd/start.go#L559-L560

Added lines #L559 - L560 were not covered by tests

openidCredentialIssuerConfigProviderSvc := wellknownprovider.NewService(&wellknownprovider.Config{
ExternalHostURL: conf.StartupParameters.apiGatewayURL,
KMSRegistry: kmsRegistry,
CryptoJWTSigner: vcCrypto,
ExternalHostURL: conf.StartupParameters.apiGatewayURL,
KMSRegistry: kmsRegistry,
CryptoJWTSigner: vcCrypto,
DynamicWellKnownStore: dynamicWellKnownStore,
})

// Issuer Profile Management API
Expand Down Expand Up @@ -735,6 +742,7 @@
AckService: ackService,
DocumentLoader: documentLoader,
PrepareCredential: prepareCredentialSvc,
WellKnownProvider: openidCredentialIssuerConfigProviderSvc,
})
if err != nil {
return nil, fmt.Errorf("failed to instantiate new oidc4ci service: %w", err)
Expand Down Expand Up @@ -1213,6 +1221,12 @@
return store, nil
}

func getDynamicWellKnownStore(
redisClient *redis.Client,
) (wellknownprovider.DynamicWellKnownStore, error) {
return dynamicwellknown.New(redisClient, defaultDynamicWellKnownTTL), nil
}

func getAckStore(
redisClient *redis.Client,
oidc4ciAckDataTTL int32,
Expand Down
3 changes: 2 additions & 1 deletion component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"encoding/json"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"log/slog"
"net"
Expand All @@ -24,6 +23,8 @@ import (
"strings"
"time"

"go.uber.org/zap"

"github.com/cli/browser"
"github.com/google/uuid"
"github.com/piprate/json-gold/ld"
Expand Down
1 change: 1 addition & 0 deletions pkg/profile/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type OIDCConfig struct {
CredentialResponseAlgValuesSupported []string `json:"credential_response_alg_values_supported"`
CredentialResponseEncValuesSupported []string `json:"credential_response_enc_values_supported"`
CredentialResponseEncryptionRequired bool `json:"credential_response_encryption_required"`
DynamicWellKnownSupported bool `json:"dynamic_well_known_supported"`
ClaimsEndpoint string `json:"claims_endpoint"`
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/restapi/v1/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ func ValidateVPFormat(format VPFormat) (vcsverifiable.Format, error) {
return "", fmt.Errorf("unsupported vp format %s, use one of next [%s, %s]", format, JwtVcJsonLd, LdpVc)
}

func MapToVCFormat(format vcsverifiable.Format) (vcsverifiable.OIDCFormat, error) {
switch format {
case vcsverifiable.Jwt:
return vcsverifiable.JwtVCJson, nil
case vcsverifiable.Ldp:
return vcsverifiable.JwtVCJsonLD, nil
case vcsverifiable.Cwt:
return vcsverifiable.CwtVcLD, nil
}

return "", fmt.Errorf("vc format missmatch %s, rest api supports only [%s, %s]", format, JwtVcJsonLd, LdpVc)
}

func MapToVPFormat(format vcsverifiable.Format) (VPFormat, error) {
switch format {
case vcsverifiable.Jwt:
Expand Down
17 changes: 17 additions & 0 deletions pkg/restapi/v1/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,23 @@ func TestValidateVPFormat(t *testing.T) {
require.Error(t, err)
}

func TestMapVcFormat(t *testing.T) {
got, err := MapToVCFormat(vcsverifiable.Jwt)
require.NoError(t, err)
require.EqualValues(t, JwtVcJson, got)

got, err = MapToVCFormat(vcsverifiable.Ldp)
require.NoError(t, err)
require.EqualValues(t, JwtVcJsonLd, got)

got, err = MapToVCFormat(vcsverifiable.Cwt)
require.NoError(t, err)
require.EqualValues(t, CwtVcLd, got)

_, err = MapToVCFormat("invalid")
require.Error(t, err)
}

func TestValidateDIDMethod(t *testing.T) {
got, err := ValidateDIDMethod(DIDMethodKey)
require.NoError(t, err)
Expand Down
10 changes: 10 additions & 0 deletions pkg/service/oidc4ci/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/trustbloc/vc-go/verifiable"

profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/service/issuecredential"
)

Expand All @@ -24,3 +25,12 @@ type composer interface { // nolint:unused
req *issuecredential.PrepareCredentialsRequest,
) (*verifiable.Credential, error)
}

type wellKnownProvider interface {
AddDynamicConfiguration(
ctx context.Context,
profileID string,
id string,
credSupported *profileapi.CredentialsConfigurationSupported,
) error
}
3 changes: 3 additions & 0 deletions pkg/service/oidc4ci/oidc4ci_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ type Config struct {
AckService ackService
DocumentLoader documentLoader
PrepareCredential credentialIssuer
WellKnownProvider wellKnownProvider
}

// Service implements VCS credential interaction API for OIDC credential issuance.
Expand All @@ -198,6 +199,7 @@ type Service struct {
ackService ackService
documentLoader documentLoader
credentialIssuer credentialIssuer
wellKnownProvider wellKnownProvider
}

// NewService returns a new Service instance.
Expand All @@ -222,6 +224,7 @@ func NewService(config *Config) (*Service, error) {
ackService: config.AckService,
documentLoader: config.DocumentLoader,
credentialIssuer: config.PrepareCredential,
wellKnownProvider: config.WellKnownProvider,
}, nil
}

Expand Down
39 changes: 32 additions & 7 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"github.com/trustbloc/vcs/pkg/doc/verifiable"
profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/restapi/resterr"
"github.com/trustbloc/vcs/pkg/restapi/v1/common"
"github.com/trustbloc/vcs/pkg/service/issuecredential"
)

Expand Down Expand Up @@ -208,16 +209,17 @@
}
}

credentialConfigurationID, _, err := findCredentialConfigurationID(
targetCredentialTemplate.ID, targetCredentialTemplate.Type, profile)
credentialConfigurationID, metaCredentialConfiguration, err := s.findCredentialConfigurationID(
ctx,
targetCredentialTemplate.ID,
targetCredentialTemplate.Type,
profile,
)

if err != nil {
return nil, err
}

profileMeta := profile.CredentialMetaData

metaCredentialConfiguration := profileMeta.CredentialsConfigurationSupported[credentialConfigurationID]

txCredentialConfiguration := &issuecredential.TxCredentialConfiguration{
ID: uuid.NewString(),
CredentialTemplate: targetCredentialTemplate,
Expand Down Expand Up @@ -489,7 +491,8 @@
return profile.CredentialTemplates[0], nil
}

func findCredentialConfigurationID(
func (s *Service) findCredentialConfigurationID(
ctx context.Context,
requestedTemplateID string,
credentialType string,
profile *profileapi.Issuer,
Expand All @@ -500,6 +503,28 @@
}
}

if profile.OIDCConfig.DynamicWellKnownSupported {
id := uuid.NewString()

vcFormat, err := common.MapToVCFormat(profile.VCConfig.Format)
if err != nil {
return "", nil, err
}

Check warning on line 512 in pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go

View check run for this annotation

Codecov / codecov/patch

pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go#L511-L512

Added lines #L511 - L512 were not covered by tests

cfg := &profileapi.CredentialsConfigurationSupported{
Format: vcFormat,
CredentialDefinition: &profileapi.CredentialDefinition{
Type: []string{credentialType},
},
}

if err = s.wellKnownProvider.AddDynamicConfiguration(ctx, profile.ID, id, cfg); err != nil {
return "", nil, err
}

Check warning on line 523 in pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go

View check run for this annotation

Codecov / codecov/patch

pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go#L522-L523

Added lines #L522 - L523 were not covered by tests

return id, cfg, nil
}

return "", nil, resterr.NewValidationError(resterr.InvalidValue, "credential_template_id",
fmt.Errorf("credential configuration not found for requested template id %s", requestedTemplateID))
}
Expand Down
43 changes: 43 additions & 0 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type mocks struct {
ackService *MockAckService
documentLoader *jsonld.DefaultDocumentLoader
composer *Mockcomposer
wellKnown *MockwellKnownProvider
}

func TestService_InitiateIssuance(t *testing.T) {
Expand Down Expand Up @@ -1335,6 +1336,45 @@ func TestService_InitiateIssuance(t *testing.T) {
require.Nil(t, resp)
},
},
{
name: "Success with Dynamic WellKnown",
setup: func(mocks *mocks) {
initialOpState := ""
claimData := degreeClaims

b, err := json.Marshal(testProfile) //nolint
assert.NoError(t, err)

assert.NoError(t, json.Unmarshal(b, &profile))
delete(profile.CredentialMetaData.CredentialsConfigurationSupported, "UniversityDegreeCredentialIdentifier")
profile.OIDCConfig.DynamicWellKnownSupported = true

mocks.wellKnown.EXPECT().
AddDynamicConfiguration(gomock.Any(), profile.ID, gomock.Any(), gomock.Any()).
Return(nil)

mocks.jsonSchemaValidator.EXPECT().Validate(gomock.Any(), gomock.Any(), gomock.Any()).
Return(errors.New("schema validation err"))

issuanceReq = &oidc4ci.InitiateIssuanceRequest{
ClientWellKnownURL: walletWellKnownURL,
OpState: initialOpState,
UserPinRequired: false,
GrantType: oidc4ci.GrantTypePreAuthorizedCode,
Scope: []string{"openid", "profile"},
CredentialConfiguration: []oidc4ci.InitiateIssuanceCredentialConfiguration{
{
CredentialTemplateID: "templateID2",
ClaimData: claimData,
},
},
}
},
check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) {
require.ErrorContains(t, err, "schema validation err")
require.Nil(t, resp)
},
},
{
name: "Error because of event publishing",
setup: func(mocks *mocks) {
Expand Down Expand Up @@ -1533,6 +1573,7 @@ func TestService_InitiateIssuance(t *testing.T) {
}

profile = &testProfile
profile.OIDCConfig.DynamicWellKnownSupported = false
},
check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) {
require.Nil(t, resp)
Expand Down Expand Up @@ -1866,6 +1907,7 @@ func TestService_InitiateIssuance(t *testing.T) {
crypto: NewMockDataProtector(gomock.NewController(t)),
jsonSchemaValidator: NewMockJSONSchemaValidator(gomock.NewController(t)),
documentLoader: jsonld.NewDefaultDocumentLoader(http.DefaultClient),
wellKnown: NewMockwellKnownProvider(gomock.NewController(t)),
}

tt.setup(m)
Expand All @@ -1881,6 +1923,7 @@ func TestService_InitiateIssuance(t *testing.T) {
DataProtector: m.crypto,
JSONSchemaValidator: m.jsonSchemaValidator,
DocumentLoader: m.documentLoader,
WellKnownProvider: m.wellKnown,
})
require.NoError(t, err)

Expand Down
1 change: 1 addition & 0 deletions pkg/service/verifycredential/verifycredential_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (s *Service) verifyVC(
return fmt.Errorf("get data integrity verifier: %w", err)
}

logger.Info(fmt.Sprintf("verifying VC with challenge[%s] and domain[%s]", challenge, domain))
opts := []verifiable.CredentialOpt{
verifiable.WithProofChecker(
defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(s.vdr)),
Expand Down
9 changes: 9 additions & 0 deletions pkg/service/wellknown/provider/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package provider

type DynamicWellKnownStore dynamicWellKnownStore
24 changes: 24 additions & 0 deletions pkg/service/wellknown/provider/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package provider

import (
"context"

profileapi "github.com/trustbloc/vcs/pkg/profile"
)

//go:generate mockgen -destination interfaces_mocks_test.go -package provider -source=interfaces.go

type dynamicWellKnownStore interface {
Upsert(
ctx context.Context,
profileID string,
item map[string]*profileapi.CredentialsConfigurationSupported,
) error
Get(ctx context.Context, profileID string) (map[string]*profileapi.CredentialsConfigurationSupported, error)
}
Loading
Loading