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 21, 2023
1 parent 1f94a3e commit b395b84
Show file tree
Hide file tree
Showing 22 changed files with 492 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
117 changes: 117 additions & 0 deletions component/wallet-cli/internal/trustregistry/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package trustregistry

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

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

var InteractionRestrictedErr = errors.New("interaction restricted")

const (
walletAttestationVCType = "WalletAttestationCredential"
)

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

type VerifierValidationConfig struct {
VerifierDID string `json:"verifier_did"`
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"`
}

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

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

func (r *Registry) ValidateVerifier(verifierDID string, presentationCredentials []*verifiable.Credential) error {
verifierValidationConfig := &VerifierValidationConfig{
VerifierDID: verifierDID,
Metadata: make([]*CredentialMetadata, len(presentationCredentials)),
}

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

var iss, exp string
if content.Issued != nil {
iss = content.Issued.String()
}
if content.Expired != nil {
exp = content.Expired.String()
}

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

verifierValidationConfig.Metadata[i] = credentialMetadata
}

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

resp, err := r.httpClient.Post(r.url, "application/json", bytes.NewReader(req))
if err != nil {
return fmt.Errorf("send request: %w", err)
}

defer resp.Body.Close()

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

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

log.Printf("Trust registry response %+v\n", responseDecoded)

if !responseDecoded.Allowed {
return InteractionRestrictedErr
}

return nil
}
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 @@ -31,6 +31,7 @@ import (
"github.com/trustbloc/vc-go/verifiable"
"github.com/trustbloc/vc-go/vermethod"

"github.com/trustbloc/vcs/component/wallet-cli/internal/trustregistry"
jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet"
"github.com/trustbloc/vcs/pkg/doc/vc"
Expand Down Expand Up @@ -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 @@ -19,6 +19,7 @@ import (
"strings"
"time"

"github.com/trustbloc/vcs/component/wallet-cli/internal/trustregistry"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp"

"github.com/google/uuid"
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"]
1 change: 1 addition & 0 deletions scripts/generate_test_keys.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DNS.4 = file-server.trustbloc.local
DNS.5 = oidc-provider.example.com
DNS.6 = mock-login-consent.example.com
DNS.7 = api-gateway.trustbloc.local
DNS.8 = mock-trustregistry.trustbloc.local
" >> "$tmp"

#create CA
Expand Down
15 changes: 15 additions & 0 deletions test/bdd/features/oidc4vc_api.feature
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,18 @@ Feature: OIDC4VC REST API
| issuerProfile | credentialType | clientRegistrationMethod | credentialTemplate | verifierProfile | presentationDefinitionID | fields |
# SDJWT issuer, JWT verifier, no limit disclosure in PD query.
| bank_issuer/v1.0 | UniversityDegreeCredential | dynamic | universityDegreeTemplateID | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id |

@oidc4vc_rest_auth_flow_attestation_vc
Scenario: OIDC credential issuance and verification Auth flow with Attestation VC
When User inits wallet with Trust Registry verifier validation enabled
And New verifiable credentials is created from table:
| IssuerProfile | UserName | Password | Credential | DIDIndex |
| i_myprofile_ud_es256k_jwt/v1.0 | profile-user-issuer-1 | profile-user-issuer-1-pwd | attestation_vc.jwt | 0 |
| i_myprofile_ud_es256k_jwt/v1.0 | profile-user-issuer-1 | profile-user-issuer-1-pwd | university_degree.json | 0 |
And User saves issued credentials
And Profile "v_myprofile_jwt_no_strict/v1.0" verifier has been authorized with username "profile-user-verifier-1" and password "profile-user-verifier-1-pwd"

Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_no_strict/v1.0" profile with presentation definition ID "y6s13jos-attestation-vc-single-field" and fields "attestation_vc_type,degree_type_id"
And Verifier with profile "v_myprofile_jwt_no_strict/v1.0" retrieves interactions claims
Then we wait 2 seconds
And Verifier with profile "v_myprofile_jwt_no_strict/v1.0" requests deleted interactions claims
14 changes: 14 additions & 0 deletions test/bdd/fixtures/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,20 @@ services:
networks:
- bdd_net

mock-trustregistry.trustbloc.local: # mock for Trust registry
container_name: mock-trustregistry.trustbloc.local
image: vcs/mock-trustregistry:latest
environment:
- LISTEN_ADDR=:8098
- TLS_CERT_PATH=/etc/tls/ec-pubCert.pem
- TLS_KEY_PATH=/etc/tls/ec-key.pem
ports:
- "8098:8098"
volumes:
- ./keys/tls:/etc/tls
networks:
- bdd_net

oidc4vp-store.example.com: # oidc4vp request object public store
container_name: oidc4vp-store.example.com
image: nginx:latest
Expand Down
Loading

0 comments on commit b395b84

Please sign in to comment.