Skip to content

Commit

Permalink
feat: vcwallet - auth capability support for WebKMS & EDV unlock
Browse files Browse the repository at this point in the history
- Closes hyperledger-archives#2845

Signed-off-by: sudesh.shetty <sudesh.shetty@securekey.com>
  • Loading branch information
sudeshrshetty committed Oct 14, 2021
1 parent e670460 commit 66c2fc4
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 36 deletions.
95 changes: 62 additions & 33 deletions pkg/controller/command/vcwallet/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ const (
emptyRawLength = 4
)

// AuthCapabilityProvider is for providing Authorization Capabilities (ZCAP-LD) feature for
// wallet's EDV and WebKMS components.
type AuthCapabilityProvider interface {
// Returns HTTP Header Signer.
GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner
}

// HTTPHeaderSigner is for http header signing, typically used for zcapld functionality.
type HTTPHeaderSigner interface {
// SignHeader header with capability.
Expand All @@ -121,9 +128,9 @@ type HTTPHeaderSigner interface {
// All properties of this config are optional, but they can be used to customize wallet's webkms and edv client's.
type Config struct {
// EDV header signer, typically used for introducing zcapld feature.
EdvAuthSigner HTTPHeaderSigner
EdvAuthzProvider AuthCapabilityProvider
// Web KMS header signer, typically used for introducing zcapld feature.
WebKMSAuthSigner HTTPHeaderSigner
WebKMSAuthzProvider AuthCapabilityProvider
// option is a performance optimization that speeds up queries by getting full documents from
// the EDV server instead of only document locations.
EDVReturnFullDocumentsOnQuery bool
Expand Down Expand Up @@ -275,14 +282,21 @@ func (o *Command) Open(rw io.Writer, req io.Reader) command.Error {
return command.NewValidationError(InvalidRequestErrorCode, err)
}

opts, err := prepareUnlockOptions(request, o.config)
if err != nil {
logutil.LogInfo(logger, CommandName, OpenMethod, err.Error())

return command.NewValidationError(InvalidRequestErrorCode, err)
}

vcWallet, err := wallet.New(request.UserID, o.ctx)
if err != nil {
logutil.LogInfo(logger, CommandName, OpenMethod, err.Error())

return command.NewExecuteError(OpenWalletErrorCode, err)
}

token, err := vcWallet.Open(prepareUnlockOptions(request, o.config)...)
token, err := vcWallet.Open(opts...)
if err != nil {
logutil.LogInfo(logger, CommandName, OpenMethod, err.Error())

Expand Down Expand Up @@ -684,7 +698,8 @@ func prepareProfileOptions(rqst *CreateOrUpdateProfileRequest) []wallet.ProfileO
}

// prepareUnlockOptions prepares options for unlocking wallet.
func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) []wallet.UnlockOptions { // nolint:funlen,gocyclo
//nolint: lll
func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) ([]wallet.UnlockOptions, error) { // nolint:funlen,gocyclo
var options []wallet.UnlockOptions

if rqst.LocalKMSPassphrase != "" {
Expand All @@ -694,21 +709,28 @@ func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) []wallet.Unlo
var webkmsOpts []webkms.Opt

if rqst.WebKMSAuth != nil {
if rqst.WebKMSAuth.AuthToken != "" {
webkmsOpts = append(webkmsOpts, webkms.WithHeaders(
func(req *http.Request) (*http.Header, error) {
req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken))

return &req.Header, nil
},
))
} else if rqst.WebKMSAuth.Capability != "" && conf.WebKMSAuthSigner != nil {
webkmsOpts = append(webkmsOpts, webkms.WithHeaders(
func(req *http.Request) (*http.Header, error) {
return conf.EdvAuthSigner.SignHeader(req, []byte(rqst.WebKMSAuth.Capability))
},
))
var webKMSHeader func(*http.Request) (*http.Header, error)

if rqst.WebKMSAuth.Capability != "" { // zcap ld signing
if conf.WebKMSAuthzProvider == nil {
return nil, fmt.Errorf("authorization capability for WebKMS is not configured")
}

signer := conf.WebKMSAuthzProvider.GetHeaderSigner(rqst.WebKMSAuth.AuthZKeyStoreURL,
rqst.WebKMSAuth.AuthToken, rqst.WebKMSAuth.SecretShare)

webKMSHeader = func(req *http.Request) (*http.Header, error) {
return signer.SignHeader(req, []byte(rqst.WebKMSAuth.Capability))
}
} else if rqst.WebKMSAuth.AuthToken != "" { // auth token
webKMSHeader = func(req *http.Request) (*http.Header, error) {
req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken))

return &req.Header, nil
}
}

webkmsOpts = append(webkmsOpts, webkms.WithHeaders(webKMSHeader))
}

if conf.WebKMSCacheSize > 0 {
Expand All @@ -718,21 +740,28 @@ func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) []wallet.Unlo
var edvOpts []edv.RESTProviderOption

if rqst.EDVUnlock != nil {
if rqst.EDVUnlock.AuthToken != "" {
edvOpts = append(edvOpts, edv.WithHeaders(
func(req *http.Request) (*http.Header, error) {
req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken))

return &req.Header, nil
},
))
} else if rqst.EDVUnlock.Capability != "" && conf.EdvAuthSigner != nil {
edvOpts = append(edvOpts, edv.WithHeaders(
func(req *http.Request) (*http.Header, error) {
return conf.EdvAuthSigner.SignHeader(req, []byte(rqst.EDVUnlock.Capability))
},
))
var edvHeader func(*http.Request) (*http.Header, error)

if rqst.EDVUnlock.Capability != "" { // zcap ld signing
if conf.EdvAuthzProvider == nil {
return nil, fmt.Errorf("authorization capability for EDV is not configured")
}

signer := conf.EdvAuthzProvider.GetHeaderSigner(rqst.EDVUnlock.AuthZKeyStoreURL,
rqst.EDVUnlock.AuthToken, rqst.EDVUnlock.SecretShare)

edvHeader = func(req *http.Request) (*http.Header, error) {
return signer.SignHeader(req, []byte(rqst.EDVUnlock.Capability))
}
} else if rqst.EDVUnlock.AuthToken != "" { // auth token
edvHeader = func(req *http.Request) (*http.Header, error) {
req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken))

return &req.Header, nil
}
}

edvOpts = append(edvOpts, edv.WithHeaders(edvHeader))
}

if conf.EDVBatchEndpointExtensionEnabled {
Expand All @@ -745,7 +774,7 @@ func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) []wallet.Unlo

options = append(options, wallet.WithUnlockWebKMSOptions(webkmsOpts...), wallet.WithUnlockEDVOptions(edvOpts...))

return options
return options, nil
}

func prepareProveOptions(rqst *ProveRequest) []wallet.ProveOptions {
Expand Down
52 changes: 51 additions & 1 deletion pkg/controller/command/vcwallet/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"testing"

Expand Down Expand Up @@ -647,7 +648,8 @@ func TestCommand_OpenAndClose(t *testing.T) {

t.Run("successfully unlock & lock wallet (remote kms, capability)", func(t *testing.T) {
cmd := New(mockctx, &Config{
WebKMSCacheSize: 99,
WebKMSCacheSize: 99,
WebKMSAuthzProvider: &mockAuthZCapability{},
})

request := &UnlockWalletRequest{
Expand Down Expand Up @@ -730,6 +732,7 @@ func TestCommand_OpenAndClose(t *testing.T) {
cmd := New(mockctx, &Config{
EDVReturnFullDocumentsOnQuery: true,
EDVBatchEndpointExtensionEnabled: true,
EdvAuthzProvider: &mockAuthZCapability{},
})

request := &UnlockWalletRequest{
Expand Down Expand Up @@ -798,6 +801,37 @@ func TestCommand_OpenAndClose(t *testing.T) {
validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "cannot unmarshal string into Go")
require.Empty(t, b.Len())
b.Reset()

// EDV authz error
request := &UnlockWalletRequest{
UserID: sampleUser3,
LocalKMSPassphrase: samplePassPhrase,
EDVUnlock: &UnlockAuth{
Capability: sampleFakeCapability,
},
}

cmdErr = cmd.Open(&b, getReader(t, &request))
require.Error(t, cmdErr)
validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode,
"authorization capability for EDV is not configured")
require.Empty(t, b.Len())
b.Reset()

// webKMS authz error
request = &UnlockWalletRequest{
UserID: sampleUser3,
WebKMSAuth: &UnlockAuth{
Capability: sampleFakeCapability,
},
}

cmdErr = cmd.Open(&b, getReader(t, &request))
require.Error(t, cmdErr)
validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode,
"authorization capability for WebKMS is not configured")
require.Empty(t, b.Len())
b.Reset()
})
}

Expand Down Expand Up @@ -1906,3 +1940,19 @@ func parsePresentation(t *testing.T, b bytes.Buffer) *verifiable.Presentation {

return vp
}

// mock authz capability.
type mockAuthZCapability struct{}

// GetHeaderSigner mock implementation.
func (s *mockAuthZCapability) GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner {
return &mockHeaderSigner{}
}

// mock header signer.
type mockHeaderSigner struct{}

// SignHeader mock implementation.
func (s *mockHeaderSigner) SignHeader(req *http.Request, capabilityBytes []byte) (*http.Header, error) {
return &http.Header{}, nil
}
11 changes: 9 additions & 2 deletions pkg/controller/command/vcwallet/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,20 @@ type UnlockWalletRequest struct {
// UnlockAuth contains different options for authorizing access to wallet's EDV content store & webkms.
type UnlockAuth struct {
// Http header 'authorization' bearer token to be used.
// Optional, only if required by wallet user.
// Optional, only if required by wallet user (for webkms or edv).
AuthToken string `json:"authToken,omitempty"`

// Capability if ZCAP sign header feature to be used for authorizing access.
// Optional, can be used only if ZCAP sign header feature is configured with command controller.
// Note: will not be considered When provided with `AuthToken` option.
Capability string `json:"capability,omitempty"`

// AuthZKeyStoreURL if ZCAP sign header feature to be used for authorizing access.
// Optional, can be used only if ZCAP sign header feature is configured with command controller.
AuthZKeyStoreURL string `json:"authzKeyStoreURL,omitempty"`

// SecretShare if ZCAP sign header feature to be used for authorizing access.
// Optional, can be used only if ZCAP sign header feature is configured with command controller.
SecretShare string `json:"secretShare,omitempty"`
}

// UnlockWalletResponse contains response for wallet unlock operation.
Expand Down

0 comments on commit 66c2fc4

Please sign in to comment.