Skip to content

Commit

Permalink
feat: [OIDC4VP] Request Attestation VC by Verifier
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Sizov <[email protected]>
  • Loading branch information
mishasizov-SK committed Nov 23, 2023
1 parent af4b18c commit f62c5fa
Show file tree
Hide file tree
Showing 25 changed files with 897 additions and 3 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
echo '127.0.0.1 cognito-mock.trustbloc.local' | sudo tee -a /etc/hosts
echo '127.0.0.1 mock-login-consent.example.com' | sudo tee -a /etc/hosts
echo '127.0.0.1 cognito-auth.local' | sudo tee -a /etc/hosts
echo '127.0.0.1 mock-trustregistry.trustbloc.local' | sudo tee -a /etc/hosts
make bdd-test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ mock-login-consent-docker:
--build-arg GO_PROXY=$(GOPROXY) \
--build-arg GO_IMAGE=$(GO_IMAGE) test/bdd/loginconsent

.PHONY: mock-trustregistry-docker
mock-trustregistry-docker:
@echo "Building mock Trust Registry server"
@docker build -f ./images/mocks/trustregistry/Dockerfile --no-cache -t vcs/mock-trustregistry:latest \
--build-arg GO_VER=$(GO_VER) \
--build-arg ALPINE_VER=$(GO_ALPINE_VER) \
--build-arg GO_PROXY=$(GOPROXY) \
--build-arg GO_IMAGE=$(GO_IMAGE) test/bdd/trustregistry

.PHONY: sample-cognito-auth
sample-cognito-auth:
@echo "Building sample cognito auth server"
Expand All @@ -142,7 +151,7 @@ sample-cognito-auth-docker:


.PHONY: bdd-test
bdd-test: clean vc-rest-docker sample-cognito-auth-docker sample-webhook-docker mock-login-consent-docker generate-test-keys build-krakend-plugin
bdd-test: clean vc-rest-docker sample-cognito-auth-docker sample-webhook-docker mock-login-consent-docker mock-trustregistry-docker generate-test-keys build-krakend-plugin
@cd test/bdd && GOPROXY=$(GOPROXY) go test -count=1 -v -cover . -p 1 -timeout=10m -race

.PHONY: unit-test
Expand Down
3 changes: 3 additions & 0 deletions component/wallet-cli/cmd/oidc4vp_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type oidc4vpCommandFlags struct {
enableLinkedDomainVerification bool
enableTracing bool
disableDomainMatching bool
trustRegistryURL string
}

// NewOIDC4VPCommand returns a new command for running OIDC4VP flow.
Expand Down Expand Up @@ -142,6 +143,7 @@ func NewOIDC4VPCommand() *cobra.Command {

opts := []oidc4vp.Opt{
oidc4vp.WithRequestURI(requestURI),
oidc4vp.WithTrustRegistryURL(flags.trustRegistryURL),
}

if flags.walletDIDIndex != -1 {
Expand Down Expand Up @@ -182,6 +184,7 @@ func createFlags(cmd *cobra.Command, flags *oidc4vpCommandFlags) {
cmd.Flags().BoolVar(&flags.enableLinkedDomainVerification, "enable-linked-domain-verification", false, "enables linked domain verification")
cmd.Flags().BoolVar(&flags.disableDomainMatching, "disable-domain-matching", false, "disables domain matching for issuer and verifier when presenting credentials (only for did:web)")
cmd.Flags().IntVar(&flags.walletDIDIndex, "wallet-did-index", -1, "index of wallet did, if not set the most recently created DID is used")
cmd.Flags().StringVar(&flags.trustRegistryURL, "trust-registry-url", "", "Trust Registry URL. If supplied, Wallet will run Verifier verification against Trust Registry")

cmd.Flags().BoolVar(&flags.enableTracing, "enable-tracing", false, "enables http tracing")
}
Expand Down
23 changes: 23 additions & 0 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
vcskms "github.com/trustbloc/vcs/pkg/kms"
kmssigner "github.com/trustbloc/vcs/pkg/kms/signer"
"github.com/trustbloc/vcs/pkg/observability/metrics/noop"
"github.com/trustbloc/vcs/pkg/service/trustregistry"
)

const (
Expand All @@ -61,6 +62,7 @@ type Flow struct {
requestURI string
enableLinkedDomainVerification bool
disableDomainMatching bool
trustRegistryURL string
}

type provider interface {
Expand Down Expand Up @@ -124,6 +126,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) {
requestURI: o.requestURI,
enableLinkedDomainVerification: o.enableLinkedDomainVerification,
disableDomainMatching: o.disableDomainMatching,
trustRegistryURL: o.trustRegistryURL,
}, nil
}

Expand All @@ -144,6 +147,19 @@ func (f *Flow) Run(ctx context.Context) error {
return fmt.Errorf("query wallet: %w", err)
}

if trustRegistryURL := f.trustRegistryURL; trustRegistryURL != "" {
slog.Info("Run Trust Registry Verifier validation", "url", trustRegistryURL)

trustRegistry := trustregistry.New(&trustregistry.Config{
TrustRegistryURL: trustRegistryURL,
HTTPClient: f.httpClient,
})

if err = trustRegistry.ValidateVerifier(requestObject.ClientID, credentials); err != nil {
return fmt.Errorf("trust registry verifier validation: %w", err)
}
}

if !f.disableDomainMatching {
for i := len(credentials) - 1; i >= 0; i-- {
credential := credentials[i]
Expand Down Expand Up @@ -655,6 +671,7 @@ type options struct {
requestURI string
enableLinkedDomainVerification bool
disableDomainMatching bool
trustRegistryURL string
}

type Opt func(opts *options)
Expand Down Expand Up @@ -683,6 +700,12 @@ func WithDomainMatchingDisabled() Opt {
}
}

func WithTrustRegistryURL(url string) Opt {
return func(opts *options) {
opts.trustRegistryURL = url
}
}

// ExtractCustomScopeClaims returns Claims associated with custom scope.
func ExtractCustomScopeClaims(requestObjectScope string) (map[string]Claims, error) {
chunks := strings.Split(requestObjectScope, "+")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Config struct {
UniResolverURL string
ContextProviderURL string
OidcProviderURL string
TrustRegistryURL string
IssueVCURL string
DidDomain string
DidServiceAuthToken string
Expand Down
17 changes: 17 additions & 0 deletions component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
vcskms "github.com/trustbloc/vcs/pkg/kms"
"github.com/trustbloc/vcs/pkg/kms/signer"
"github.com/trustbloc/vcs/pkg/observability/metrics/noop"
"github.com/trustbloc/vcs/pkg/service/trustregistry"
)

type RPConfigOverride func(rpc *RPConfig)
Expand Down Expand Up @@ -121,6 +122,22 @@ func (s *Service) RunOIDC4VPFlow(ctx context.Context, authorizationRequest strin
s.wallet.Close()
}

// Run Trust Registry verification only if TrustRegistryURL supplied
if trustRegistryURL := s.vcProviderConf.TrustRegistryURL; trustRegistryURL != "" {
log.Println("Run Trust Registry Verifier validation")

trustRegistry := trustregistry.New(&trustregistry.Config{
TrustRegistryURL: trustRegistryURL,
HTTPClient: s.httpClient,
})

credentials := s.vpFlowExecutor.requestPresentation[0].Credentials()

if err = trustRegistry.ValidateVerifier(s.vpFlowExecutor.requestObject.ClientID, credentials); err != nil {
return fmt.Errorf("trust registry verifier validation: %w", err)
}
}

var createAuthorizedResponseHooks []RPConfigOverride
if hooks != nil {
createAuthorizedResponseHooks = hooks.CreateAuthorizedResponse
Expand Down
1 change: 1 addition & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ To run BDD tests (`make bdd-test`) you need to modify your hosts file (`/etc/hos
127.0.0.1 cognito-mock.trustbloc.local
127.0.0.1 mock-login-consent.example.com
127.0.0.1 cognito-auth.local
127.0.0.1 mock-trustregistry.trustbloc.local
29 changes: 29 additions & 0 deletions images/mocks/trustregistry/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Copyright Avast Software. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#

ARG GO_VER
ARG GO_IMAGE
ARG ALPINE_VER

FROM ${GO_IMAGE}:${GO_VER}-alpine${ALPINE_VER} as builder

RUN apk update && apk add git && apk add ca-certificates
RUN adduser -D -g '' appuser
COPY . $GOPATH/src/github.com/trustbloc/vcs/test/bdd/trustregistry/
WORKDIR $GOPATH/src/github.com/trustbloc/vcs/test/bdd/trustregistry/
ARG GO_PROXY
RUN GOPROXY=${GO_PROXY} CGO_ENABLED=0 go build -o /usr/bin/mock-trustregistry

FROM scratch

LABEL org.opencontainers.image.source https://github.com/trustbloc/vcs/test/bdd/trustregistry

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/bin/mock-trustregistry /usr/bin/mock-trustregistry
USER appuser

ENTRYPOINT ["/usr/bin/mock-trustregistry"]
30 changes: 30 additions & 0 deletions pkg/service/trustregistry/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package trustregistry

type VerifierValidationConfig struct {
VerifierDID string `json:"verifier_did"`
Metadata []*CredentialMetadata `json:"metadata"`
}

type PresentationValidationConfig struct {
PolicyID string `json:"policy_id"`
AttestationVC interface{} `json:"attestation_vc"`
Metadata []*CredentialMetadata `json:"metadata"`
}

type CredentialMetadata struct {
CredentialID string `json:"credential_id"`
Types []string `json:"types"`
Issuer string `json:"issuer"`
Issued string `json:"issued"`
Expired string `json:"expired"`
}

type Response struct {
Allowed bool `json:"allowed"`
}
158 changes: 158 additions & 0 deletions pkg/service/trustregistry/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package trustregistry

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/samber/lo"

"github.com/trustbloc/logutil-go/pkg/log"
"github.com/trustbloc/vc-go/verifiable"
)

var (
ErrInteractionRestricted = errors.New("interaction restricted")
logger = log.New("trustregistry")
)

const (
walletAttestationVCType = "WalletAttestationCredential"
)

type Service struct {
url string
httpClient *http.Client
}

type Config struct {
TrustRegistryURL string
HTTPClient *http.Client
}

func New(conf *Config) *Service {
return &Service{
url: conf.TrustRegistryURL,
httpClient: conf.HTTPClient,
}
}

func (s *Service) ValidateVerifier(verifierDID string, presentationCredentials []*verifiable.Credential) error {
logger.Debug("ValidateVerifier begin")
verifierValidationConfig := &VerifierValidationConfig{
VerifierDID: verifierDID,
Metadata: make([]*CredentialMetadata, len(presentationCredentials)),
}

for i, credential := range presentationCredentials {
content := credential.Contents()

verifierValidationConfig.Metadata[i] = s.getCredentialMetadata(content)
}

req, err := json.Marshal(verifierValidationConfig)
if err != nil {
return fmt.Errorf("encode verifier config: %w", err)
}

responseDecoded, err := s.doRequest(req)
if err != nil {
return err
}

if !responseDecoded.Allowed {
return ErrInteractionRestricted
}

logger.Debug("ValidateVerifier succeed")

return nil
}

func (s *Service) ValidatePresentation(policyID string, presentationCredentials []*verifiable.Credential) error {
logger.Debug("ValidatePresentation begin")

presentationValidationConfig := &PresentationValidationConfig{
PolicyID: policyID,
Metadata: make([]*CredentialMetadata, len(presentationCredentials)),
}

for i, credential := range presentationCredentials {
content := credential.Contents()

if lo.Contains(content.Types, walletAttestationVCType) {
attestationVC, err := credential.ToUniversalForm()
if err == nil {
presentationValidationConfig.AttestationVC = attestationVC
}
}

presentationValidationConfig.Metadata[i] = s.getCredentialMetadata(content)
}

req, err := json.Marshal(presentationValidationConfig)
if err != nil {
return fmt.Errorf("encode presentation config: %w", err)
}

responseDecoded, err := s.doRequest(req)
if err != nil {
return err
}

if !responseDecoded.Allowed {
return ErrInteractionRestricted
}

logger.Debug("ValidatePresentation succeed")

return nil
}

func (s *Service) doRequest(req []byte) (*Response, error) {
resp, err := s.httpClient.Post(s.url, "application/json", bytes.NewReader(req)) //nolint:noctx
if err != nil {
return nil, fmt.Errorf("send request: %w", err)
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

var responseDecoded *Response
err = json.NewDecoder(resp.Body).Decode(&responseDecoded)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}

return responseDecoded, nil
}

func (s *Service) getCredentialMetadata(content verifiable.CredentialContents) *CredentialMetadata {
var iss, exp string
if content.Issued != nil {
iss = content.Issued.FormatToString()
}

if content.Expired != nil {
exp = content.Expired.FormatToString()
}

return &CredentialMetadata{
CredentialID: content.ID,
Types: content.Types,
Issuer: content.Issuer.ID,
Issued: iss,
Expired: exp,
}
}
Loading

0 comments on commit f62c5fa

Please sign in to comment.