Skip to content

Commit

Permalink
feat: Public key store/cache for Observer
Browse files Browse the repository at this point in the history
Implemented a public key storage and cache for the Observer. When verifying a witness proof, the Observer will load the public key of the signer from the cache/database. If it doesn't exist in the DB then the public key is retrieved using the public key fetcher.

Also enhanced the BDD test so that, when creating DIDs on multiple targets, the target will be greylisted if it is down and the create will be performed on another target.

closes #1261

Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
  • Loading branch information
bstasyszyn committed Apr 26, 2022
1 parent 4425233 commit f71bd68
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 33 deletions.
16 changes: 14 additions & 2 deletions cmd/orb-server/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/hyperledger/aries-framework-go/pkg/doc/did"
"github.com/hyperledger/aries-framework-go/pkg/doc/ld"
"github.com/hyperledger/aries-framework-go/pkg/doc/ldcontext/remote"
"github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier"
"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
"github.com/hyperledger/aries-framework-go/pkg/kms"
Expand All @@ -61,6 +62,7 @@ import (
"github.com/spf13/cobra"
"github.com/trustbloc/edge-core/pkg/log"
tlsutils "github.com/trustbloc/edge-core/pkg/utils/tls"
awssvc "github.com/trustbloc/kms/pkg/aws"
casapi "github.com/trustbloc/sidetree-core-go/pkg/api/cas"
"github.com/trustbloc/sidetree-core-go/pkg/api/protocol"
"github.com/trustbloc/sidetree-core-go/pkg/batch"
Expand All @@ -69,7 +71,6 @@ import (
restcommon "github.com/trustbloc/sidetree-core-go/pkg/restapi/common"
"github.com/trustbloc/sidetree-core-go/pkg/restapi/diddochandler"

awssvc "github.com/trustbloc/kms/pkg/aws"
"github.com/trustbloc/orb/internal/pkg/ldcontext"
"github.com/trustbloc/orb/pkg/activitypub/client"
"github.com/trustbloc/orb/pkg/activitypub/client/transport"
Expand Down Expand Up @@ -137,6 +138,7 @@ import (
"github.com/trustbloc/orb/pkg/store/logmonitor"
opstore "github.com/trustbloc/orb/pkg/store/operation"
unpublishedopstore "github.com/trustbloc/orb/pkg/store/operation/unpublished"
"github.com/trustbloc/orb/pkg/store/publickey"
proofstore "github.com/trustbloc/orb/pkg/store/witness"
"github.com/trustbloc/orb/pkg/store/wrapper"
"github.com/trustbloc/orb/pkg/taskmgr"
Expand Down Expand Up @@ -870,6 +872,15 @@ func startOrbServices(parameters *orbParameters) error {
return fmt.Errorf("open store: %w", err)
}

pkStore, err := publickey.New(storeProviders.provider, verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher())
if err != nil {
return fmt.Errorf("create public key storage: %w", err)
}

publicKeyFetcher := func(issuerID, keyID string) (*verifier.PublicKey, error) {
return pkStore.GetPublicKey(issuerID, keyID)
}

// create new observer and start it
providers := &observer.Providers{
ProtocolClientProvider: pcp,
Expand All @@ -881,7 +892,7 @@ func startOrbServices(parameters *orbParameters) error {
WebFingerResolver: resourceResolver,
CASResolver: casResolver,
DocLoader: orbDocumentLoader,
Pkf: verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher(),
Pkf: publicKeyFetcher,
AnchorLinkStore: anchorLinkStore,
}

Expand Down Expand Up @@ -994,6 +1005,7 @@ func startOrbServices(parameters *orbParameters) error {
discoveryclient.WithNamespace(parameters.didNamespace),
discoveryclient.WithHTTPClient(httpClient),
discoveryclient.WithDIDWebHTTP(parameters.enableDevMode),
discoveryclient.WithPublicKeyFetcher(publicKeyFetcher),
)

if parameters.verifyLatestFromAnchorOrigin {
Expand Down
18 changes: 15 additions & 3 deletions pkg/discovery/endpoint/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Client struct {
casReader casReader
authToken string
disableProofCheck bool
publicKeyFetcher verifiable.PublicKeyFetcher
didWebHTTP bool
docLoader ld.DocumentLoader
orbClient orbClient
Expand Down Expand Up @@ -108,13 +109,16 @@ func New(docLoader ld.DocumentLoader, casReader casReader, opts ...Option) (*Cli
if configService.disableProofCheck {
orbClientOpts = append(orbClientOpts, aoprovider.WithDisableProofCheck(configService.disableProofCheck))
} else {
orbClientOpts = append(orbClientOpts, aoprovider.WithPublicKeyFetcher(
verifiable.NewVDRKeyResolver(vdr.New(vdr.WithVDR(&webVDR{
if configService.publicKeyFetcher == nil {
configService.publicKeyFetcher = verifiable.NewVDRKeyResolver(vdr.New(vdr.WithVDR(&webVDR{
http: configService.httpClient,
useHTTP: configService.didWebHTTP,
VDR: web.New(),
}),
)).PublicKeyFetcher()))
)).PublicKeyFetcher()
}

orbClientOpts = append(orbClientOpts, aoprovider.WithPublicKeyFetcher(configService.publicKeyFetcher))
}

orbClient, err := aoprovider.New(configService.namespace, configService.casReader, orbClientOpts...)
Expand Down Expand Up @@ -498,6 +502,14 @@ func WithDisableProofCheck(disable bool) Option {
}
}

// WithPublicKeyFetcher sets the public key fetcher. If not set then
// the default fetcher is used.
func WithPublicKeyFetcher(pkf verifiable.PublicKeyFetcher) Option {
return func(opts *Client) {
opts.publicKeyFetcher = pkf
}
}

// WithDIDWebHTTP use did web http.
func WithDIDWebHTTP(enable bool) Option {
return func(opts *Client) {
Expand Down
15 changes: 15 additions & 0 deletions pkg/discovery/endpoint/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"testing"
"time"

"github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier"
"github.com/stretchr/testify/require"

"github.com/trustbloc/orb/pkg/activitypub/client/transport"
Expand Down Expand Up @@ -48,6 +49,20 @@ func TestNew(t *testing.T) {
require.Equal(t, time.Minute, cs.cacheLifetime)
require.Equal(t, 500, cs.cacheSize)
})

t.Run("success - with public key fetcher", func(t *testing.T) {
cs, err := New(nil, &referenceCASReaderImplementation{},
WithAuthToken("t1"),
WithPublicKeyFetcher(func(issuerID, keyID string) (*verifier.PublicKey, error) {
return &verifier.PublicKey{}, nil
}),
)
require.NoError(t, err)
require.NotNil(t, cs)

require.Equal(t, defaultCacheLifetime, cs.cacheLifetime)
require.Equal(t, defaultCacheSize, cs.cacheSize)
})
}

func TestConfigService_GetEndpointAnchorOrigin(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/store/anchorstatus/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ func (s *Store) processIndex(encodedAnchorID string) error {

err = s.policyHandler.CheckPolicy(anchorID)
if err != nil {
return fmt.Errorf("failed to re-evalue policy for anchorID[%s]: %w", anchorID, err)
return fmt.Errorf("failed to re-evaluate policy for anchorID[%s]: %w", anchorID, err)
}

logger.Debugf("successfully re-evaluated policy for anchorID[%s]", anchorID)
Expand Down
2 changes: 1 addition & 1 deletion pkg/store/anchorstatus/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ func TestStore_processIndex(t *testing.T) {

err = s.processIndex(encoder.EncodeToString([]byte(vcID)))
require.Error(t, err)
require.Contains(t, err.Error(), "failed to re-evalue policy for anchorID[vcID]: policy error")
require.Contains(t, err.Error(), "failed to re-evaluate policy for anchorID[vcID]: policy error")
})

t.Run("error - status not found", func(t *testing.T) {
Expand Down
151 changes: 151 additions & 0 deletions pkg/store/publickey/publickeystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package publickey

import (
"encoding/json"
"errors"
"fmt"

"github.com/bluele/gcache"
"github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier"
"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
"github.com/hyperledger/aries-framework-go/spi/storage"
"github.com/trustbloc/edge-core/pkg/log"
)

var logger = log.New("public-key-store")

const (
storeName = "public-key"
maxCacheSize = 1000
)

// Store manages a persistent store of public keys for issuers. The store also caches
// the public keys for better performance.
type Store struct {
cache gcache.Cache
store storage.Store
fetchPublicKey verifiable.PublicKeyFetcher
}

type cacheKey struct {
issuerID string
keyID string
}

// New returns a new public key store.
func New(p storage.Provider, fetchPublicKey verifiable.PublicKeyFetcher) (*Store, error) {
s, err := p.OpenStore(storeName)
if err != nil {
return nil, fmt.Errorf("open store [%s]: %w", storeName, err)
}

pkStore := &Store{
store: s,
fetchPublicKey: fetchPublicKey,
}

pkCache := gcache.New(maxCacheSize).ARC().
LoaderFunc(
func(k interface{}) (interface{}, error) {
ck := k.(cacheKey) //nolint:errcheck,forcetypeassert

return pkStore.get(ck.issuerID, ck.keyID)
},
).Build()

pkStore.cache = pkCache

logger.Infof("Created public key store [%s]", storeName)

return pkStore, nil
}

// GetPublicKey returns the public key for the given issuer and key ID.
func (c *Store) GetPublicKey(issuerID, keyID string) (*verifier.PublicKey, error) {
pk, err := c.cache.Get(cacheKey{issuerID, keyID})
if err != nil {
return nil, err
}

return pk.(*verifier.PublicKey), nil
}

func (c *Store) get(issuerID, keyID string) (*verifier.PublicKey, error) {
logger.Infof("Loading public key into cache for issuer [%s], key ID [%s]",
issuerID, keyID)

pk, err := c.getFromDB(issuerID, keyID)
if err == nil {
return pk, nil
}

if !errors.Is(err, storage.ErrDataNotFound) {
return nil, fmt.Errorf("get from DB: %w", err)
}

logger.Infof("Public key not found in storage. Fetching public key from server for issuer [%s], key ID [%s]",
issuerID, keyID)

// Public key not found in storage. Retrieve it from the server.
pk, err = c.fetchPublicKey(issuerID, keyID)
if err != nil {
return nil, fmt.Errorf("fetch public key from server - issuer [%s], key ID [%s]: %w",
issuerID, keyID, err)
}

err = c.putToDB(issuerID, keyID, pk)
if err != nil {
// We couldn't store the public key but this shouldn't result in a client error. Just log a warning.
logger.Warnf("Error storing public key for issuer [%s], key ID [%s]: %s", issuerID, keyID, err)
}

return pk, nil
}

func (c *Store) getFromDB(issuerID, keyID string) (*verifier.PublicKey, error) {
key := fmt.Sprintf("%s-%s", issuerID, keyID)

pkBytes, err := c.store.Get(key)
if err != nil {
return nil, fmt.Errorf("get key - issuer [%s], key ID [%s]: %w", issuerID, keyID, err)
}

logger.Infof("Public key found in storage for issuer [%s], key ID [%s]", issuerID, keyID)

pk := &verifier.PublicKey{}

err = json.Unmarshal(pkBytes, pk)
if err != nil {
return nil, fmt.Errorf("unmarshal public key - issuer [%s], key ID [%s]: %w",
issuerID, keyID, err)
}

return pk, nil
}

func (c *Store) putToDB(issuerID, keyID string, pk *verifier.PublicKey) error {
key := fmt.Sprintf("%s-%s", issuerID, keyID)

pkBytes, err := json.Marshal(pk)
if err != nil {
return fmt.Errorf("marshal public key - issuer [%s], key ID [%s]: %w",
issuerID, keyID, err)
}

logger.Infof("Storing public key for issuer [%s], key ID [%s]",
issuerID, keyID)

err = c.store.Put(key, pkBytes)
if err != nil {
return fmt.Errorf("store public key - issuer [%s], key ID [%s]: %w",
issuerID, keyID, err)
}

return nil
}
Loading

0 comments on commit f71bd68

Please sign in to comment.