Skip to content

Commit

Permalink
refactor: Refactor CSL event handler (#1812)
Browse files Browse the repository at this point in the history
Extract the code that retrieves/signs CSLs into a separate service.

Signed-off-by: Bob Stasyszyn <bob.stasyszyn@gendigital.com>
  • Loading branch information
bstasyszyn authored Dec 4, 2024
1 parent 017b029 commit e63d8b2
Show file tree
Hide file tree
Showing 9 changed files with 919 additions and 785 deletions.
16 changes: 11 additions & 5 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import (
"github.com/trustbloc/vcs/pkg/service/clientidscheme"
clientmanagersvc "github.com/trustbloc/vcs/pkg/service/clientmanager"
credentialstatustypes "github.com/trustbloc/vcs/pkg/service/credentialstatus"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/cslservice"
"github.com/trustbloc/vcs/pkg/service/didconfiguration"
"github.com/trustbloc/vcs/pkg/service/issuecredential"
"github.com/trustbloc/vcs/pkg/service/oidc4ci"
Expand Down Expand Up @@ -580,17 +581,21 @@ func buildEchoHandler(
return nil, err
}

cslService := cslservice.New(&cslservice.Config{
CSLStore: cslVCStore,
ProfileService: issuerProfileSvc,
KMSRegistry: kmsRegistry,
Crypto: vcCrypto,
DocumentLoader: documentLoader,
})

// Create event service
eventSvc, err := event.Initialize(event.Config{
TLSConfig: tlsConfig,
CMD: cmd,
CSLVCStore: cslVCStore,
ProfileService: issuerProfileSvc,
KMSRegistry: kmsRegistry,
Crypto: vcCrypto,
Tracer: conf.Tracer,
IsTraceEnabled: conf.IsTraceEnabled,
DocumentLoader: documentLoader,
CSLService: cslService,
})
if err != nil {
return nil, err
Expand All @@ -606,6 +611,7 @@ func buildEchoHandler(
RequestTokens: conf.StartupParameters.requestTokens,
DocumentLoader: documentLoader,
CSLVCStore: cslVCStore,
CSLIndexStore: cslIndexStore,
CSLManager: cslManager,
VCStatusStore: vcStatusStore,
ProfileService: issuerProfileSvc,
Expand Down
3 changes: 3 additions & 0 deletions component/credentialstatus/credentialstatus_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type Config struct {
RequestTokens map[string]string
VDR vdrapi.Registry
CSLVCStore credentialstatus.CSLVCStore
CSLIndexStore credentialstatus.CSLIndexStore
CSLManager cslManager
VCStatusStore vcStatusStore
Crypto vcCrypto
Expand All @@ -111,6 +112,7 @@ type Service struct {
requestTokens map[string]string
vdr vdrapi.Registry
cslVCStore credentialstatus.CSLVCStore
cslIndexStore credentialstatus.CSLIndexStore
cslMgr cslManager
vcStatusStore vcStatusStore
crypto vcCrypto
Expand All @@ -131,6 +133,7 @@ func New(config *Config) (*Service, error) {
requestTokens: config.RequestTokens,
vdr: config.VDR,
cslVCStore: config.CSLVCStore,
cslIndexStore: config.CSLIndexStore,
cslMgr: config.CSLManager,
vcStatusStore: config.VCStatusStore,
crypto: config.Crypto,
Expand Down
29 changes: 19 additions & 10 deletions component/credentialstatus/credentialstatus_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/trustbloc/vcs/pkg/event/spi"
profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/service/credentialstatus"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/cslservice"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/eventhandler"
)

Expand Down Expand Up @@ -387,13 +388,17 @@ func TestCredentialStatusList_UpdateVCStatus(t *testing.T) {
})
require.NoError(t, err)

cslService := cslservice.New(&cslservice.Config{
CSLStore: cslVCStore,
ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
Crypto: crypto,
DocumentLoader: loader,
})

mockEventPublisher := &mockedEventPublisher{
eventHandler: eventhandler.New(&eventhandler.Config{
CSLVCStore: cslVCStore,
ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
Crypto: crypto,
DocumentLoader: loader,
CSLService: cslService,
}),
}

Expand Down Expand Up @@ -820,13 +825,17 @@ func TestCredentialStatusList_UpdateVCStatus(t *testing.T) {

require.NoError(t, err)

cslService := cslservice.New(&cslservice.Config{
CSLStore: cslVCStore,
ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
Crypto: crypto,
DocumentLoader: loader,
})

mockEventPublisher := &mockedEventPublisher{
eventHandler: eventhandler.New(&eventhandler.Config{
CSLVCStore: cslVCStore,
ProfileService: mockProfileSrv,
KMSRegistry: mockKMSRegistry,
Crypto: crypto,
DocumentLoader: loader,
CSLService: cslService,
}),
}

Expand Down
27 changes: 2 additions & 25 deletions component/event/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,14 @@ import (
"crypto/tls"
"sync"

"github.com/piprate/json-gold/ld"
"github.com/spf13/cobra"
"github.com/trustbloc/logutil-go/pkg/log"
"github.com/trustbloc/vc-go/verifiable"
"go.opentelemetry.io/otel/trace"

"github.com/trustbloc/vcs/internal/logfields"
"github.com/trustbloc/vcs/pkg/doc/vc"
vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto"
"github.com/trustbloc/vcs/pkg/event/spi"
vcskms "github.com/trustbloc/vcs/pkg/kms"
"github.com/trustbloc/vcs/pkg/lifecycle"
profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/service/credentialstatus"
"github.com/trustbloc/vcs/pkg/service/credentialstatus/eventhandler"
)

var logger = log.New("event-bus")
Expand All @@ -33,30 +27,13 @@ const (
defaultBufferSize = 250
)

type profileService interface {
GetProfile(profileID profileapi.ID, profileVersion profileapi.Version) (*profileapi.Issuer, error)
}

type kmsRegistry interface {
GetKeyManager(config *vcskms.Config) (vcskms.VCSKeyManager, error)
}

type vcCrypto interface {
SignCredential(signerData *vc.Signer, vc *verifiable.Credential,
opts ...vccrypto.SigningOpts) (*verifiable.Credential, error)
}

// Config holds the configuration for the publisher/subscriber.
type Config struct {
TLSConfig *tls.Config
CMD *cobra.Command
CSLVCStore credentialstatus.CSLVCStore
ProfileService profileService
KMSRegistry kmsRegistry
Crypto vcCrypto
Tracer trace.Tracer
IsTraceEnabled bool
DocumentLoader ld.DocumentLoader
CSLService eventhandler.CSLService
}

// Bus implements a publisher/subscriber using Go channels. This implementation
Expand Down
6 changes: 1 addition & 5 deletions component/event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ func Initialize(cfg Config) (*Bus, error) {
}

service := credentialstatuseventhandler.New(&credentialstatuseventhandler.Config{
CSLVCStore: cfg.CSLVCStore,
ProfileService: cfg.ProfileService,
KMSRegistry: cfg.KMSRegistry,
Crypto: cfg.Crypto,
DocumentLoader: cfg.DocumentLoader,
CSLService: cfg.CSLService,
})

var credentialStatusEventHandler eventHandlerWithContext = service.HandleEvent
Expand Down
201 changes: 201 additions & 0 deletions pkg/service/credentialstatus/cslservice/cslservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package cslservice

import (
"context"
"fmt"

"github.com/piprate/json-gold/ld"
"github.com/trustbloc/vc-go/dataintegrity/models"
"github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/vcs/pkg/doc/vc"
vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto"
vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable"
vcskms "github.com/trustbloc/vcs/pkg/kms"
profileapi "github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/service/credentialstatus"
)

//go:generate mockgen -destination cslservice_mocks_test.go -self_package mocks -package cslservice -source=cslservice.go -mock_names profileService=MockProfileService,kmsRegistry=MockKMSRegistry

const (
defaultRepresentation = "jws"

jsonKeyProofValue = "proofValue"
jsonKeyProofPurpose = "proofPurpose"
jsonKeyVerificationMethod = "verificationMethod"
jsonKeySignatureOfType = "type"
)

type profileService interface {
GetProfile(profileID profileapi.ID, profileVersion profileapi.Version) (*profileapi.Issuer, error)
}

type kmsRegistry interface {
GetKeyManager(config *vcskms.Config) (vcskms.VCSKeyManager, error)
}

type vcCrypto interface {
SignCredential(signerData *vc.Signer, vc *verifiable.Credential,
opts ...vccrypto.SigningOpts) (*verifiable.Credential, error)
}

type Config struct {
CSLStore credentialstatus.CSLVCStore
ProfileService profileService
KMSRegistry kmsRegistry
Crypto vcCrypto
DocumentLoader ld.DocumentLoader
}

type Service struct {
cslStore credentialstatus.CSLVCStore
profileService profileService
kmsRegistry kmsRegistry
crypto vcCrypto
documentLoader ld.DocumentLoader
}

func New(cfg *Config) *Service {
return &Service{
cslStore: cfg.CSLStore,
profileService: cfg.ProfileService,
kmsRegistry: cfg.KMSRegistry,
crypto: cfg.Crypto,
documentLoader: cfg.DocumentLoader,
}
}

func (s *Service) SignCSL(profileID, profileVersion string, csl *verifiable.Credential) ([]byte, error) {
issuerProfile, err := s.profileService.GetProfile(profileID, profileVersion)
if err != nil {
return nil, fmt.Errorf("failed to get profile: %w", err)
}

keyManager, err := s.kmsRegistry.GetKeyManager(issuerProfile.KMSConfig)
if err != nil {
return nil, fmt.Errorf("failed to get KMS: %w", err)
}

signer := &vc.Signer{
Format: issuerProfile.VCConfig.Format,
DID: issuerProfile.SigningDID.DID,
Creator: issuerProfile.SigningDID.Creator,
KMSKeyID: issuerProfile.SigningDID.KMSKeyID,
SignatureType: issuerProfile.VCConfig.SigningAlgorithm,
KeyType: issuerProfile.VCConfig.KeyType,
KMS: keyManager,
SignatureRepresentation: issuerProfile.VCConfig.SignatureRepresentation,
VCStatusListType: issuerProfile.VCConfig.Status.Type,
SDJWT: vc.SDJWT{Enable: false},
DataIntegrityProof: issuerProfile.VCConfig.DataIntegrityProof,
}

signOpts, err := prepareSigningOpts(signer, csl.Proofs())
if err != nil {
return nil, fmt.Errorf("prepareSigningOpts failed: %w", err)
}

signedCredential, err := s.crypto.SignCredential(signer, csl, signOpts...)
if err != nil {
return nil, fmt.Errorf("sign CSL failed: %w", err)
}

return signedCredential.MarshalJSON()
}

func (s *Service) GetCSLVCWrapper(ctx context.Context, cslURL string) (*credentialstatus.CSLVCWrapper, error) {
vcWrapper, err := s.cslStore.Get(ctx, cslURL)
if err != nil {
return nil, fmt.Errorf("failed to get CSL from store: %w", err)
}

cslVC, err := verifiable.ParseCredential(vcWrapper.VCByte,
verifiable.WithDisabledProofCheck(),
verifiable.WithJSONLDDocumentLoader(s.documentLoader))
if err != nil {
return nil, fmt.Errorf("failed to parse CSL: %w", err)
}

vcWrapper.VC = cslVC

return vcWrapper, nil
}

func (s *Service) UpsertCSLVCWrapper(ctx context.Context, cslURL string, wrapper *credentialstatus.CSLVCWrapper) error {
if err := s.cslStore.Upsert(ctx, cslURL, wrapper); err != nil {
return fmt.Errorf("failed to upsert CSL: %w", err)
}

return nil
}

// prepareSigningOpts prepares signing opts from recently issued proof of given credential.
func prepareSigningOpts(profile *vc.Signer, proofs []verifiable.Proof) ([]vccrypto.SigningOpts, error) {
var signingOpts []vccrypto.SigningOpts

if len(proofs) == 0 {
return signingOpts, nil
}

// pick latest proof if there are multiple
proof := proofs[len(proofs)-1]

representation := defaultRepresentation
if _, ok := proof[jsonKeyProofValue]; ok {
representation = jsonKeyProofValue
}

signingOpts = append(signingOpts, vccrypto.WithSigningRepresentation(representation))

purpose, err := getStringValue(jsonKeyProofPurpose, proof)
if err != nil {
return nil, err
}

signingOpts = append(signingOpts, vccrypto.WithPurpose(purpose))

vm, err := getStringValue(jsonKeyVerificationMethod, proof)
if err != nil {
return nil, err
}

// add verification method option only when it is not matching profile creator
if vm != profile.Creator {
signingOpts = append(signingOpts, vccrypto.WithVerificationMethod(vm))
}

signTypeName, err := getStringValue(jsonKeySignatureOfType, proof)
if err != nil {
return nil, err
}

if signTypeName != "" && signTypeName != models.DataIntegrityProof {
signType, err := vcsverifiable.GetSignatureTypeByName(signTypeName)
if err != nil {
return nil, err
}

signingOpts = append(signingOpts, vccrypto.WithSignatureType(signType))
}

return signingOpts, nil
}

func getStringValue(key string, vMap map[string]interface{}) (string, error) {
if val, ok := vMap[key]; ok {
if s, ok := val.(string); ok {
return s, nil
}

return "", fmt.Errorf("invalid '%s' type", key)
}

return "", nil
}
Loading

0 comments on commit e63d8b2

Please sign in to comment.