From 532d6321ce474d1090cf4dd3946ad0ba69ad838c Mon Sep 17 00:00:00 2001 From: Andrii Holovko Date: Wed, 28 Feb 2024 08:16:47 +0200 Subject: [PATCH] feat: client attestation enhancements Signed-off-by: Andrii Holovko --- cmd/vc-rest/startcmd/start.go | 15 +- component/wallet-cli/cmd/attest_wallet_cmd.go | 69 +++-- component/wallet-cli/cmd/oidc4vci_cmd.go | 83 +++-- component/wallet-cli/cmd/oidc4vp_cmd.go | 76 +++-- .../pkg/attestation/attestation_client.go | 224 -------------- .../pkg/attestation/attestation_service.go | 282 +++++++++++++++++ .../wallet-cli/pkg/oidc4vci/oidc4vci_flow.go | 134 ++++----- component/wallet-cli/pkg/oidc4vp/models.go | 1 + .../wallet-cli/pkg/oidc4vp/oidc4vp_flow.go | 79 +++-- .../pkg/trustregistry/trustregistry.go | 48 +-- pkg/kms/aws/service_mocks.go | 2 +- pkg/kms/mocks/kms_mocks.go | 2 +- pkg/profile/api.go | 10 +- pkg/restapi/v1/verifier/controller.go | 9 +- pkg/service/oidc4ci/oidc4ci_service.go | 15 +- ...t.go => oidc4ci_service_check_policies.go} | 13 +- ...=> oidc4ci_service_check_policies_test.go} | 51 +--- .../oidc4ci/oidc4ci_service_exchange_code.go | 8 +- .../oidc4ci_service_exchange_code_test.go | 3 - pkg/service/oidc4ci/oidc4ci_service_test.go | 3 - pkg/service/oidc4vp/api.go | 1 + pkg/service/oidc4vp/oidc4vp_service.go | 69 ++++- pkg/service/oidc4vp/oidc4vp_service_test.go | 59 +++- pkg/service/trustregistry/api.go | 4 +- .../trustregistry/trustregistry_service.go | 180 +++-------- .../trustregistry_service_test.go | 191 +++--------- .../verifypresentation_service.go | 74 +---- .../verifypresentation_service_test.go | 284 ++---------------- test/bdd/features/oidc4vc_api.feature | 2 +- test/bdd/fixtures/profile/profiles.json | 31 +- test/bdd/pkg/v1/oidc4vc/oidc4vci.go | 72 ++--- test/bdd/pkg/v1/oidc4vc/oidc4vp.go | 24 +- test/bdd/pkg/v1/oidc4vc/steps.go | 77 ++++- test/bdd/trustregistry/go.mod | 7 +- test/bdd/trustregistry/go.sum | 4 + test/bdd/trustregistry/models.go | 21 ++ test/bdd/trustregistry/server.go | 92 ++++++ test/stress/pkg/stress/providers.go | 50 ++- test/stress/pkg/stress/stress_test_case.go | 80 ++++- 39 files changed, 1185 insertions(+), 1264 deletions(-) delete mode 100644 component/wallet-cli/pkg/attestation/attestation_client.go create mode 100644 component/wallet-cli/pkg/attestation/attestation_service.go rename pkg/service/oidc4ci/{oidc4ci_service_authenticate_client.go => oidc4ci_service_check_policies.go} (83%) rename pkg/service/oidc4ci/{oidc4ci_service_authenticate_client_test.go => oidc4ci_service_check_policies_test.go} (82%) diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 0865b72f1..0a6fddcb5 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -689,10 +689,9 @@ func buildEchoHandler( trustRegistryService := trustregistry.NewService( &trustregistry.Config{ - HTTPClient: getHTTPClient(metricsProvider.ClientAttestationService), - DocumentLoader: documentLoader, - ProofChecker: proofChecker, - VCStatusVerifier: verifyCredentialSvc, + HTTPClient: getHTTPClient(metricsProvider.ClientAttestationService), + DocumentLoader: documentLoader, + ProofChecker: proofChecker, }, ) @@ -880,10 +879,9 @@ func buildEchoHandler( var verifyPresentationSvc verifypresentation.ServiceInterface verifyPresentationSvc = verifypresentation.New(&verifypresentation.Config{ - VcVerifier: verifyCredentialSvc, - DocumentLoader: documentLoader, - VDR: conf.VDR, - TrustRegistryService: trustRegistryService, + VcVerifier: verifyCredentialSvc, + DocumentLoader: documentLoader, + VDR: conf.VDR, }) if conf.IsTraceEnabled { @@ -954,6 +952,7 @@ func buildEchoHandler( DocumentLoader: documentLoader, ProfileService: verifierProfileSvc, PresentationVerifier: verifyPresentationSvc, + TrustRegistryService: trustRegistryService, RedirectURL: conf.StartupParameters.apiGatewayURL + oidc4VPCheckEndpoint, TokenLifetime: 15 * time.Minute, Metrics: metrics, diff --git a/component/wallet-cli/cmd/attest_wallet_cmd.go b/component/wallet-cli/cmd/attest_wallet_cmd.go index 141b2c8ea..dc6f35503 100644 --- a/component/wallet-cli/cmd/attest_wallet_cmd.go +++ b/component/wallet-cli/cmd/attest_wallet_cmd.go @@ -8,16 +8,16 @@ package cmd import ( "context" - "encoding/json" "fmt" "net/http" + "github.com/piprate/json-gold/ld" "github.com/spf13/cobra" + storageapi "github.com/trustbloc/kms-go/spi/storage" + "github.com/trustbloc/kms-go/wrapper/api" "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" - jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" - kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" ) type attestCommandFlags struct { @@ -58,37 +58,23 @@ func NewAttestWalletCommand() *cobra.Command { didInfo = w.DIDs()[len(w.DIDs())-1] } - signer, err := svc.CryptoSuite().FixedKeyMultiSigner(didInfo.KeyID) - if err != nil { - return fmt.Errorf("create signer: %w", err) - } - - jwsSigner := jwssigner.NewJWSSigner( - fmt.Sprintf("%s#%s", didInfo.ID, didInfo.KeyID), - string(w.SignatureType()), - kmssigner.NewKMSSigner(signer, w.SignatureType(), nil), - ) - - attestationVC, err := attestation.NewClient( - &attestation.Config{ - HTTPClient: httpClient, - DocumentLoader: svc.DocumentLoader(), - Signer: jwsSigner, - WalletDID: didInfo.ID, - AttestationURL: flags.attestationURL, + attestationService, err := attestation.NewService( + &attestationServiceProvider{ + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + cryptoSuite: svc.CryptoSuite(), }, - ).GetAttestationVC(context.Background()) - if err != nil { - return fmt.Errorf("get attestation vc: %w", err) - } - - vcBytes, err := json.Marshal(attestationVC) + flags.attestationURL, + didInfo, + w.SignatureType(), + ) if err != nil { - return fmt.Errorf("marshal attestation vc: %w", err) + return fmt.Errorf("create attestation service: %w", err) } - if err = w.Add(vcBytes); err != nil { - return fmt.Errorf("add attestation vc to wallet: %w", err) + if _, err = attestationService.GetAttestation(context.Background()); err != nil { + return fmt.Errorf("get attestation: %w", err) } return nil @@ -103,3 +89,26 @@ func NewAttestWalletCommand() *cobra.Command { return cmd } + +type attestationServiceProvider struct { + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + cryptoSuite api.Suite +} + +func (p *attestationServiceProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *attestationServiceProvider) HTTPClient() *http.Client { + return p.httpClient +} + +func (p *attestationServiceProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *attestationServiceProvider) CryptoSuite() api.Suite { + return p.cryptoSuite +} diff --git a/component/wallet-cli/cmd/oidc4vci_cmd.go b/component/wallet-cli/cmd/oidc4vci_cmd.go index 5a0935c8e..fdfb33638 100644 --- a/component/wallet-cli/cmd/oidc4vci_cmd.go +++ b/component/wallet-cli/cmd/oidc4vci_cmd.go @@ -22,7 +22,9 @@ import ( "github.com/trustbloc/kms-go/wrapper/api" "github.com/trustbloc/vcs/component/wallet-cli/internal/formatter" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vci" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" @@ -54,7 +56,8 @@ type oidc4vciCommandFlags struct { enableDiscoverableClientID bool enableTracing bool proxyURL string - trustRegistryURL string + trustRegistryHost string + attestationURL string } func NewOIDC4VCICommand() *cobra.Command { @@ -154,19 +157,47 @@ func NewOIDC4VCICommand() *cobra.Command { } } + var walletDIDIndex int + + if flags.walletDIDIndex != -1 { + walletDIDIndex = flags.walletDIDIndex + } else { + walletDIDIndex = len(w.DIDs()) - 1 + } + + attestationService, err := attestation.NewService( + &attestationServiceProvider{ + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + cryptoSuite: svc.CryptoSuite(), + }, + flags.attestationURL, + w.DIDs()[walletDIDIndex], + w.SignatureType(), + ) + if err != nil { + return fmt.Errorf("create attestation service: %w", err) + } + wellKnownService := &wellknown.Service{ HTTPClient: httpClient, VDRRegistry: svc.VDR(), } provider := &oidc4vciProvider{ - storageProvider: svc.StorageProvider(), - httpClient: httpClient, - documentLoader: svc.DocumentLoader(), - vdrRegistry: svc.VDR(), - cryptoSuite: svc.CryptoSuite(), - wallet: w, - wellKnownService: wellKnownService, + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + vdrRegistry: svc.VDR(), + cryptoSuite: svc.CryptoSuite(), + attestationService: attestationService, + wallet: w, + wellKnownService: wellKnownService, + } + + if flags.trustRegistryHost != "" { + provider.trustRegistry = trustregistry.NewClient(httpClient, flags.trustRegistryHost) } var flow *oidc4vci.Flow @@ -175,7 +206,6 @@ func NewOIDC4VCICommand() *cobra.Command { oidc4vci.WithCredentialType(flags.credentialType), oidc4vci.WithOIDCCredentialFormat(flags.oidcCredentialFormat), oidc4vci.WithClientID(flags.clientID), - oidc4vci.WithTrustRegistryURL(flags.trustRegistryURL), } if walletInitiatedFlow { @@ -184,14 +214,6 @@ func NewOIDC4VCICommand() *cobra.Command { opts = append(opts, oidc4vci.WithCredentialOffer(credentialOffer)) } - var walletDIDIndex int - - if flags.walletDIDIndex != -1 { - walletDIDIndex = flags.walletDIDIndex - } else { - walletDIDIndex = len(w.DIDs()) - 1 - } - if flags.proofType == "cwt" { opts = append(opts, oidc4vci.WithProofBuilder( oidc4vci.NewCWTProofBuilder(), @@ -280,7 +302,8 @@ func NewOIDC4VCICommand() *cobra.Command { cmd.Flags().StringVar(&flags.issuerState, "issuer-state", "", "issuer state in wallet-initiated flow") cmd.Flags().StringVar(&flags.pin, "pin", "", "pin for pre-authorized code flow") cmd.Flags().BoolVar(&flags.enableDiscoverableClientID, "enable-discoverable-client-id", false, "enables discoverable client id scheme for dynamic client registration") - cmd.Flags().StringVar(&flags.trustRegistryURL, "trust-registry-url", "", "if supplied, wallet will run issuer verification in trust registry") + cmd.Flags().StringVar(&flags.attestationURL, "attestation-url", "", "attestation url with profile id and profile version, i.e. /profiles/{profileID}/{profileVersion}/wallet/attestation") + cmd.Flags().StringVar(&flags.trustRegistryHost, "trust-registry-host", "", "/wallet/interactions/issuance to validate that the issuer is trusted according to policy") cmd.Flags().BoolVar(&flags.enableTracing, "enable-tracing", false, "enables http tracing") cmd.Flags().StringVar(&flags.proxyURL, "proxy-url", "", "proxy url for http client") @@ -291,13 +314,15 @@ func NewOIDC4VCICommand() *cobra.Command { } type oidc4vciProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet - wellKnownService *wellknown.Service + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet + wellKnownService *wellknown.Service } func (p *oidc4vciProvider) StorageProvider() storageapi.Provider { @@ -320,6 +345,14 @@ func (p *oidc4vciProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vciProvider) AttestationService() oidc4vci.AttestationService { + return p.attestationService +} + +func (p *oidc4vciProvider) TrustRegistry() oidc4vci.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vciProvider) Wallet() *wallet.Wallet { return p.wallet } diff --git a/component/wallet-cli/cmd/oidc4vp_cmd.go b/component/wallet-cli/cmd/oidc4vp_cmd.go index 3454d0a15..84fa9fc71 100644 --- a/component/wallet-cli/cmd/oidc4vp_cmd.go +++ b/component/wallet-cli/cmd/oidc4vp_cmd.go @@ -9,6 +9,8 @@ package cmd import ( "context" "fmt" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "net/http" "net/url" "strings" @@ -33,7 +35,8 @@ type oidc4vpCommandFlags struct { enableLinkedDomainVerification bool enableTracing bool disableDomainMatching bool - trustRegistryURL string + trustRegistryHost string + attestationURL string proxyURL string } @@ -85,13 +88,41 @@ func NewOIDC4VPCommand() *cobra.Command { httpClient.Transport = httpLogger.RoundTripper(httpClient.Transport) } + var walletDIDIndex int + + if flags.walletDIDIndex != -1 { + walletDIDIndex = flags.walletDIDIndex + } else { + walletDIDIndex = len(w.DIDs()) - 1 + } + + attestationService, err := attestation.NewService( + &attestationServiceProvider{ + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + cryptoSuite: svc.CryptoSuite(), + }, + flags.attestationURL, + w.DIDs()[walletDIDIndex], + w.SignatureType(), + ) + if err != nil { + return fmt.Errorf("create attestation service: %w", err) + } + provider := &oidc4vpProvider{ - storageProvider: svc.StorageProvider(), - httpClient: httpClient, - documentLoader: svc.DocumentLoader(), - vdrRegistry: svc.VDR(), - cryptoSuite: svc.CryptoSuite(), - wallet: w, + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + vdrRegistry: svc.VDR(), + cryptoSuite: svc.CryptoSuite(), + attestationService: attestationService, + wallet: w, + } + + if flags.trustRegistryHost != "" { + provider.trustRegistry = trustregistry.NewClient(httpClient, flags.trustRegistryHost) } var authorizationRequest string @@ -113,11 +144,7 @@ func NewOIDC4VPCommand() *cobra.Command { opts := []oidc4vp.Opt{ oidc4vp.WithRequestURI(requestURI), - oidc4vp.WithTrustRegistryURL(flags.trustRegistryURL), - } - - if flags.walletDIDIndex != -1 { - opts = append(opts, oidc4vp.WithWalletDIDIndex(flags.walletDIDIndex)) + oidc4vp.WithWalletDIDIndex(walletDIDIndex), } if flags.enableLinkedDomainVerification { @@ -154,19 +181,22 @@ 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", "", "if supplied, wallet will run verifier verification in trust registry") + cmd.Flags().StringVar(&flags.attestationURL, "attestation-url", "", "attestation url with profile id and profile version, i.e. /profiles/{profileID}/{profileVersion}/wallet/attestation") + cmd.Flags().StringVar(&flags.trustRegistryHost, "trust-registry-host", "", "/wallet/interactions/presentation to validate that the verifier is trusted as per policy") cmd.Flags().BoolVar(&flags.enableTracing, "enable-tracing", false, "enables http tracing") cmd.Flags().StringVar(&flags.proxyURL, "proxy-url", "", "proxy url for http client") } type oidc4vpProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet } func (p *oidc4vpProvider) StorageProvider() storageapi.Provider { @@ -189,6 +219,14 @@ func (p *oidc4vpProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vpProvider) AttestationService() oidc4vp.AttestationService { + return p.attestationService +} + +func (p *oidc4vpProvider) TrustRegistry() oidc4vp.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vpProvider) Wallet() *wallet.Wallet { return p.wallet } diff --git a/component/wallet-cli/pkg/attestation/attestation_client.go b/component/wallet-cli/pkg/attestation/attestation_client.go deleted file mode 100644 index efaf1c26a..000000000 --- a/component/wallet-cli/pkg/attestation/attestation_client.go +++ /dev/null @@ -1,224 +0,0 @@ -/* -Copyright Gen Digital Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package attestation - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/piprate/json-gold/ld" - "github.com/trustbloc/kms-go/doc/jose" - "github.com/trustbloc/logutil-go/pkg/log" - "github.com/trustbloc/vc-go/jwt" - "github.com/trustbloc/vc-go/verifiable" - "go.uber.org/zap" -) - -const ( - jwtProofTypeHeader = "openid4vci-proof+jwt" -) - -var logger = log.New("attestation-svc-client") - -type Client struct { - httpClient *http.Client - documentLoader ld.DocumentLoader - signer jose.Signer - walletDID string - attestationURL string -} - -type Config struct { - HTTPClient *http.Client - DocumentLoader ld.DocumentLoader - Signer jose.Signer - WalletDID string - AttestationURL string -} - -func NewClient(config *Config) *Client { - return &Client{ - httpClient: config.HTTPClient, - documentLoader: config.DocumentLoader, - signer: config.Signer, - walletDID: config.WalletDID, - attestationURL: config.AttestationURL, - } -} - -type options struct { - attestationRequest *AttestWalletInitRequest -} - -type Opt func(*options) - -func WithAttestationRequest(value *AttestWalletInitRequest) Opt { - return func(o *options) { - o.attestationRequest = value - } -} - -func (c *Client) GetAttestationVC(ctx context.Context, opts ...Opt) (*verifiable.Credential, error) { - logger.Debug("get attestation vc", zap.String("walletDID", c.walletDID)) - - options := &options{} - - for _, opt := range opts { - opt(options) - } - - initResp, err := c.attestationInit(ctx, options.attestationRequest) - if err != nil { - return nil, fmt.Errorf("attestation init: %w", err) - } - - completeResp, err := c.attestationComplete(ctx, initResp.SessionID, initResp.Challenge) - if err != nil { - return nil, fmt.Errorf("attestation complete: %w", err) - } - - attestationVC, err := verifiable.ParseCredential( - []byte(completeResp.WalletAttestationVC), - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(c.documentLoader), - ) - if err != nil { - return nil, fmt.Errorf("parse attestation vc: %w", err) - } - - return attestationVC, nil -} - -func (c *Client) attestationInit(ctx context.Context, req *AttestWalletInitRequest) (*AttestWalletInitResponse, error) { - logger.Debug("attestation init started", zap.String("walletDID", c.walletDID)) - - if req == nil { - req = &AttestWalletInitRequest{ - Assertions: []string{ - "wallet_authentication", - }, - WalletAuthentication: map[string]interface{}{ - "wallet_id": c.walletDID, - }, - WalletMetadata: map[string]interface{}{ - "wallet_name": "wallet-cli", - }, - } - } - - body, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("marshal request: %w", err) - } - - var resp AttestWalletInitResponse - - if err = c.doRequest(ctx, c.attestationURL+"/init", body, &resp); err != nil { - return nil, fmt.Errorf("do request: %w", err) - } - - logger.Debug("attestation init succeeded", - zap.String("walletDID", c.walletDID), - zap.String("sessionID", resp.SessionID), - zap.String("challenge", resp.Challenge), - ) - - return &resp, nil -} - -func (c *Client) attestationComplete( - ctx context.Context, - sessionID, - challenge string, -) (*AttestWalletCompleteResponse, error) { - logger.Debug("attestation complete started", - zap.String("sessionID", sessionID), - zap.String("challenge", challenge), - ) - - claims := &JwtProofClaims{ - Issuer: c.walletDID, - Audience: c.attestationURL, - IssuedAt: time.Now().Unix(), - Exp: time.Now().Add(time.Minute * 5).Unix(), - Nonce: challenge, - } - - headers := map[string]interface{}{ - jose.HeaderType: jwtProofTypeHeader, - } - - signedJWT, err := jwt.NewJoseSigned(claims, headers, c.signer) - if err != nil { - return nil, fmt.Errorf("create signed jwt: %w", err) - } - - jws, err := signedJWT.Serialize(false) - if err != nil { - return nil, fmt.Errorf("serialize signed jwt: %w", err) - } - - req := &AttestWalletCompleteRequest{ - AssuranceLevel: "low", - Proof: Proof{ - Jwt: jws, - ProofType: "jwt", - }, - SessionID: sessionID, - } - - body, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("marshal request: %w", err) - } - - var resp AttestWalletCompleteResponse - - if err = c.doRequest(ctx, c.attestationURL+"/complete", body, &resp); err != nil { - return nil, fmt.Errorf("do request: %w", err) - } - - logger.Debug("attestation complete succeeded", - zap.String("sessionID", sessionID), - zap.String("challenge", challenge), - zap.String("attestationVC", resp.WalletAttestationVC), - ) - - return &resp, nil -} - -func (c *Client) doRequest(ctx context.Context, policyURL string, body []byte, response interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodPost, policyURL, bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("create request: %w", err) - } - - req.Header.Add("content-type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("send request: %w", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - b, _ := io.ReadAll(resp.Body) - return fmt.Errorf("unexpected status code: %d; response: %s", resp.StatusCode, string(b)) - } - - if err = json.NewDecoder(resp.Body).Decode(response); err != nil { - return fmt.Errorf("read response: %w", err) - } - - return nil -} diff --git a/component/wallet-cli/pkg/attestation/attestation_service.go b/component/wallet-cli/pkg/attestation/attestation_service.go new file mode 100644 index 000000000..5fb2380e8 --- /dev/null +++ b/component/wallet-cli/pkg/attestation/attestation_service.go @@ -0,0 +1,282 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package attestation + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "github.com/trustbloc/logutil-go/pkg/log" + "io" + "net/http" + "time" + + "github.com/google/uuid" + "github.com/piprate/json-gold/ld" + "github.com/trustbloc/kms-go/doc/jose" + storageapi "github.com/trustbloc/kms-go/spi/storage" + "github.com/trustbloc/kms-go/wrapper/api" + "github.com/trustbloc/vc-go/jwt" + "github.com/trustbloc/vc-go/verifiable" + "go.uber.org/zap" + + jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" + vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" + kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" +) + +const ( + attestationStore = "attestation" + attestationVCKey = "vc" + + jwtProofTypeHeader = "openid4vci-proof+jwt" +) + +var logger = log.New("attestation-svc-client") + +type Service struct { + store storageapi.Store + documentLoader ld.DocumentLoader + signer jose.Signer + httpClient *http.Client + walletDID string + attestationEndpoint string +} + +type provider interface { + StorageProvider() storageapi.Provider + HTTPClient() *http.Client + DocumentLoader() ld.DocumentLoader + CryptoSuite() api.Suite +} + +func NewService( + p provider, + attestationEndpoint string, + didInfo *wallet.DIDInfo, + signatureType vcsverifiable.SignatureType, +) (*Service, error) { + store, err := p.StorageProvider().OpenStore(attestationStore) + if err != nil { + return nil, fmt.Errorf("open attestation store: %w", err) + } + + signer, err := p.CryptoSuite().FixedKeyMultiSigner(didInfo.KeyID) + if err != nil { + return nil, fmt.Errorf("create signer: %w", err) + } + + jwsSigner := jwssigner.NewJWSSigner( + fmt.Sprintf("%s#%s", didInfo.ID, didInfo.KeyID), + string(signatureType), + kmssigner.NewKMSSigner(signer, signatureType, nil), + ) + + return &Service{ + store: store, + documentLoader: p.DocumentLoader(), + signer: jwsSigner, + httpClient: p.HTTPClient(), + walletDID: didInfo.ID, + attestationEndpoint: attestationEndpoint, + }, nil +} + +func (s *Service) GetAttestation(ctx context.Context) (string, error) { + b, err := s.store.Get(attestationVCKey) + if err != nil { + if errors.Is(err, storageapi.ErrDataNotFound) { + b, err = s.requestAttestationVC(ctx) + if err != nil { + return "", fmt.Errorf("request attestation vc: %w", err) + } + + if err = s.store.Put(attestationVCKey, b); err != nil { + return "", fmt.Errorf("store attestation vc: %w", err) + } + } else { + return "", fmt.Errorf("get attestation vc from store: %w", err) + } + } + + attestationVC, err := verifiable.ParseCredential( + b, + verifiable.WithJSONLDDocumentLoader(s.documentLoader), + verifiable.WithDisabledProofCheck(), + ) + if err != nil { + return "", fmt.Errorf("parse attestation vc: %w", err) + } + + attestationVP, err := verifiable.NewPresentation(verifiable.WithCredentials(attestationVC)) + if err != nil { + return "", fmt.Errorf("create vp: %w", err) + } + + attestationVP.ID = uuid.New().String() + + claims, err := attestationVP.JWTClaims([]string{}, false) + if err != nil { + return "", fmt.Errorf("get attestation claims: %w", err) + } + + headers := map[string]interface{}{ + jose.HeaderType: jwtProofTypeHeader, + } + + signedJWT, err := jwt.NewJoseSigned(claims, headers, s.signer) + if err != nil { + return "", fmt.Errorf("create signed jwt: %w", err) + } + + jws, err := signedJWT.Serialize(false) + if err != nil { + return "", fmt.Errorf("serialize signed jwt: %w", err) + } + + return jws, nil +} + +func (s *Service) requestAttestationVC(ctx context.Context) ([]byte, error) { + initResponse, err := s.attestationInit(ctx) + if err != nil { + return nil, fmt.Errorf("attestation init: %w", err) + } + + completeResponse, err := s.attestationComplete(ctx, initResponse.SessionID, initResponse.Challenge) + if err != nil { + return nil, fmt.Errorf("attestation complete: %w", err) + } + + return []byte(completeResponse.WalletAttestationVC), nil +} + +func (s *Service) attestationInit(ctx context.Context) (*AttestWalletInitResponse, error) { + logger.Debug("attestation init started", zap.String("walletDID", s.walletDID)) + + req := &AttestWalletInitRequest{ + Assertions: []string{ + "wallet_authentication", + }, + WalletAuthentication: map[string]interface{}{ + "wallet_id": s.walletDID, + }, + WalletMetadata: map[string]interface{}{ + "wallet_name": "wallet-cli", + }, + } + + body, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request: %w", err) + } + + var resp AttestWalletInitResponse + + if err = s.doRequest(ctx, s.attestationEndpoint+"/init", body, &resp); err != nil { + return nil, fmt.Errorf("do request: %w", err) + } + + logger.Debug("attestation init succeeded", + zap.String("walletDID", s.walletDID), + zap.String("sessionID", resp.SessionID), + zap.String("challenge", resp.Challenge), + ) + + return &resp, nil +} + +func (s *Service) attestationComplete( + ctx context.Context, + sessionID, + challenge string, +) (*AttestWalletCompleteResponse, error) { + logger.Debug("attestation complete started", + zap.String("sessionID", sessionID), + zap.String("challenge", challenge), + ) + + claims := &JwtProofClaims{ + Issuer: s.walletDID, + Audience: s.attestationEndpoint, + IssuedAt: time.Now().Unix(), + Exp: time.Now().Add(time.Minute * 5).Unix(), + Nonce: challenge, + } + + headers := map[string]interface{}{ + jose.HeaderType: jwtProofTypeHeader, + } + + signedJWT, err := jwt.NewJoseSigned(claims, headers, s.signer) + if err != nil { + return nil, fmt.Errorf("create signed jwt: %w", err) + } + + jws, err := signedJWT.Serialize(false) + if err != nil { + return nil, fmt.Errorf("serialize signed jwt: %w", err) + } + + req := &AttestWalletCompleteRequest{ + AssuranceLevel: "low", + Proof: Proof{ + Jwt: jws, + ProofType: "jwt", + }, + SessionID: sessionID, + } + + body, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request: %w", err) + } + + var resp AttestWalletCompleteResponse + + if err = s.doRequest(ctx, s.attestationEndpoint+"/complete", body, &resp); err != nil { + return nil, fmt.Errorf("do request: %w", err) + } + + logger.Debug("attestation complete succeeded", + zap.String("sessionID", sessionID), + zap.String("challenge", challenge), + zap.String("attestationVC", resp.WalletAttestationVC), + ) + + return &resp, nil +} + +func (s *Service) doRequest(ctx context.Context, policyURL string, body []byte, response interface{}) error { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, policyURL, bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("create request: %w", err) + } + + req.Header.Add("content-type", "application/json") + + resp, err := s.httpClient.Do(req) + if err != nil { + return fmt.Errorf("send request: %w", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(resp.Body) + return fmt.Errorf("unexpected status code: %d; response: %s", resp.StatusCode, string(b)) + } + + if err = json.NewDecoder(resp.Body).Decode(response); err != nil { + return fmt.Errorf("read response: %w", err) + } + + return nil +} diff --git a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go index 4d306bc7e..ce5be0d53 100644 --- a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go +++ b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go @@ -66,13 +66,17 @@ const ( FlowTypePreAuthorizedCode = "pre-authorized_code" ) -type trustRegistry interface { +type AttestationService interface { + GetAttestation(ctx context.Context) (string, error) +} + +type TrustRegistry interface { ValidateIssuer( ctx context.Context, issuerDID string, issuerDomain string, credentialOffers []trustregistry.CredentialOffer, - ) error + ) (bool, error) } type Flow struct { @@ -83,8 +87,8 @@ type Flow struct { proofBuilder ProofBuilder wallet *wallet.Wallet wellKnownService *wellknown.Service - trustRegistryURL string - trustRegistryClient trustRegistry + attestationService AttestationService + trustRegistry TrustRegistry flowType FlowType credentialOffer string credentialType string @@ -108,6 +112,8 @@ type provider interface { DocumentLoader() ld.DocumentLoader VDRegistry() vdrapi.Registry CryptoSuite() api.Suite + AttestationService() AttestationService + TrustRegistry() TrustRegistry Wallet() *wallet.Wallet WellKnownService() *wellknown.Service } @@ -183,14 +189,6 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { proofBuilder = NewJWTProofBuilder() } - var trustRegistry trustRegistry - - if o.trustRegistry != nil { - trustRegistry = o.trustRegistry - } else if o.trustRegistryURL != "" { - trustRegistry = trustregistry.NewClient(p.HTTPClient(), o.trustRegistryURL) - } - return &Flow{ httpClient: p.HTTPClient(), documentLoader: p.DocumentLoader(), @@ -199,6 +197,8 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { proofBuilder: proofBuilder, wallet: p.Wallet(), wellKnownService: p.WellKnownService(), + attestationService: p.AttestationService(), + trustRegistry: p.TrustRegistry(), walletKeyID: walletDIDInfo.KeyID, walletKeyType: walletDIDInfo.KeyType, flowType: o.flowType, @@ -214,8 +214,6 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { userPassword: o.userPassword, issuerState: o.issuerState, pin: o.pin, - trustRegistryURL: o.trustRegistryURL, - trustRegistryClient: trustRegistry, perfInfo: &PerfInfo{}, }, nil } @@ -267,51 +265,6 @@ func (f *Flow) Run(ctx context.Context) (*verifiable.Credential, error) { f.perfInfo.GetIssuerCredentialsOIDCConfig = time.Since(start) - tokenEndpointAuthMethodsSupported := lo.FromPtr(openIDConfig.TokenEndpointAuthMethodsSupported) - requireWalletAttestation := lo.Contains(tokenEndpointAuthMethodsSupported, attestJWTClientAuthType) - - if f.trustRegistryClient != nil { - if credentialOfferResponse == nil || len(credentialOfferResponse.CredentialConfigurationIDs) == 0 { - return nil, fmt.Errorf("credential offer is empty") - } - - issuerDID := f.wellKnownService.GetIssuerDID() - - if issuerDID == "" { - slog.Warn("Issuer DID is empty. Does '/.well-known/openid-credential-issuer' return jwt?") - } - - slog.Info("Validating issuer", "did", issuerDID, "url", f.trustRegistryURL) - - configurationID := credentialOfferResponse.CredentialConfigurationIDs[0] - credentialConfiguration := openIDConfig.CredentialConfigurationsSupported.AdditionalProperties[configurationID] - - var credentialType string - - for _, t := range credentialConfiguration.CredentialDefinition.Type { - if t != "VerifiableCredential" { - credentialType = t - break - } - } - - if err = f.trustRegistryClient. - ValidateIssuer( - ctx, - issuerDID, - "", - []trustregistry.CredentialOffer{ - { - ClientAttestationRequested: lo.Contains(tokenEndpointAuthMethodsSupported, attestJWTClientAuthType), - CredentialFormat: credentialConfiguration.Format, - CredentialType: credentialType, - }, - }, - ); err != nil { - return nil, fmt.Errorf("validate issuer: %w", err) - } - } - var token *oauth2.Token start = time.Now() @@ -365,12 +318,55 @@ func (f *Flow) Run(ctx context.Context) (*verifiable.Credential, error) { tokenValues.Add("tx_code", f.pin) } - if requireWalletAttestation { + issuerDID := f.wellKnownService.GetIssuerDID() + + if issuerDID == "" { + slog.Warn("Issuer DID is empty. Does '/.well-known/openid-credential-issuer' return jwt?") + } + + if len(credentialOfferResponse.CredentialConfigurationIDs) == 0 { + return nil, fmt.Errorf("no credential configuration id defined in credential offer") + } + + configurationID := credentialOfferResponse.CredentialConfigurationIDs[0] + credentialConfiguration := openIDConfig.CredentialConfigurationsSupported.AdditionalProperties[configurationID] + + var credentialType string + + for _, t := range credentialConfiguration.CredentialDefinition.Type { + if t != "VerifiableCredential" { + credentialType = t + break + } + } + + var attestationRequired bool + + if f.trustRegistry != nil { + attestationRequired, err = f.trustRegistry.ValidateIssuer(ctx, + issuerDID, + "", + []trustregistry.CredentialOffer{ + { + ClientAttestationRequested: lo.Contains( + lo.FromPtr(openIDConfig.TokenEndpointAuthMethodsSupported), + attestJWTClientAuthType, + ), + CredentialFormat: credentialConfiguration.Format, + CredentialType: credentialType, + }, + }) + if err != nil { + return nil, fmt.Errorf("validate issuer: %w", err) + } + } + + if attestationRequired { var jwtVP string - jwtVP, err = f.getAttestationVP() + jwtVP, err = f.attestationService.GetAttestation(ctx) if err != nil { - return nil, fmt.Errorf("get attestation vp: %w", err) + return nil, fmt.Errorf("get attestation: %w", err) } tokenValues.Add("client_assertion_type", attestJWTClientAuthType) @@ -1048,8 +1044,6 @@ type options struct { userPassword string issuerState string pin string - trustRegistryURL string - trustRegistry trustRegistry walletDIDIndex int } @@ -1133,18 +1127,6 @@ func WithPin(pin string) Opt { } } -func WithTrustRegistryURL(url string) Opt { - return func(opts *options) { - opts.trustRegistryURL = url - } -} - -func WithTrustRegistry(value trustRegistry) Opt { - return func(opts *options) { - opts.trustRegistry = value - } -} - func WithWalletDIDIndex(idx int) Opt { return func(opts *options) { opts.walletDIDIndex = idx diff --git a/component/wallet-cli/pkg/oidc4vp/models.go b/component/wallet-cli/pkg/oidc4vp/models.go index 402d54679..48ccf8287 100644 --- a/component/wallet-cli/pkg/oidc4vp/models.go +++ b/component/wallet-cli/pkg/oidc4vp/models.go @@ -53,6 +53,7 @@ type IDTokenClaims struct { // ScopeAdditionalClaims stores claims retrieved using custom scope. ScopeAdditionalClaims map[string]Claims `json:"_scope,omitempty"` //custom scope -> additional claims VPToken IDTokenVPToken `json:"_vp_token"` + AttestationVP string `json:"_attestation_vp"` Nonce string `json:"nonce"` Exp int64 `json:"exp"` Iss string `json:"iss"` diff --git a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go index e0535acb2..768181657 100644 --- a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go +++ b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go @@ -34,7 +34,6 @@ import ( "github.com/trustbloc/vc-go/vermethod" jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/pkg/doc/vc" vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" @@ -53,13 +52,17 @@ const ( customScopeWalletDetails = "walletdetails" ) -type trustRegistry interface { +type AttestationService interface { + GetAttestation(ctx context.Context) (string, error) +} + +type TrustRegistry interface { ValidateVerifier( ctx context.Context, verifierDID, verifierDomain string, credentials []*verifiable.Credential, - ) error + ) (bool, error) } type Flow struct { @@ -68,15 +71,15 @@ type Flow struct { vdrRegistry vdrapi.Registry cryptoSuite api.Suite signer jose.Signer + attestationService AttestationService + trustRegistry TrustRegistry wallet *wallet.Wallet walletDID *did.DID requestURI string enableLinkedDomainVerification bool disableDomainMatching bool disableSchemaValidation bool - trustRegistryURL string perfInfo *PerfInfo - trustRegistryClient trustRegistry } type provider interface { @@ -84,6 +87,8 @@ type provider interface { DocumentLoader() ld.DocumentLoader VDRegistry() vdrapi.Registry CryptoSuite() api.Suite + AttestationService() AttestationService + TrustRegistry() TrustRegistry Wallet() *wallet.Wallet } @@ -129,27 +134,20 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { kmssigner.NewKMSSigner(signer, signatureType, nil), ) - var trustRegistry trustRegistry - - if o.trustRegistry != nil { - trustRegistry = o.trustRegistry - } else if o.trustRegistryURL != "" { - trustRegistry = trustregistry.NewClient(p.HTTPClient(), o.trustRegistryURL) - } - return &Flow{ httpClient: p.HTTPClient(), documentLoader: p.DocumentLoader(), vdrRegistry: p.VDRegistry(), cryptoSuite: p.CryptoSuite(), signer: jwsSigner, + attestationService: p.AttestationService(), + trustRegistry: p.TrustRegistry(), wallet: p.Wallet(), walletDID: walletDID, requestURI: o.requestURI, enableLinkedDomainVerification: o.enableLinkedDomainVerification, disableDomainMatching: o.disableDomainMatching, disableSchemaValidation: o.disableSchemaValidation, - trustRegistryClient: trustRegistry, perfInfo: &PerfInfo{}, }, nil } @@ -161,7 +159,6 @@ func (f *Flow) Run(ctx context.Context) error { "enable_linked_domain_verification", f.enableLinkedDomainVerification, "disable_domain_matching", f.disableDomainMatching, "disable_schema_validation", f.disableSchemaValidation, - "trust_registry_url", f.trustRegistryURL, ) requestObject, err := f.fetchRequestObject(ctx) @@ -195,18 +192,9 @@ func (f *Flow) Run(ctx context.Context) error { return fmt.Errorf("query wallet: %w", err) } - if f.trustRegistryClient != nil { - slog.Info("validate verifier", "url", f.trustRegistryURL) - - if err = f.trustRegistryClient. - ValidateVerifier( - ctx, - requestObject.ClientID, - "", - vp.Credentials(), - ); err != nil { - return fmt.Errorf("validate verifier: %w", err) - } + attestationRequired, err := f.trustRegistry.ValidateVerifier(ctx, requestObject.ClientID, "", vp.Credentials()) + if err != nil { + return fmt.Errorf("validate verifier: %w", err) } if !f.disableDomainMatching { @@ -220,7 +208,7 @@ func (f *Flow) Run(ctx context.Context) error { } } - if err = f.sendAuthorizationResponse(ctx, requestObject, vp); err != nil { + if err = f.sendAuthorizationResponse(ctx, requestObject, vp, attestationRequired); err != nil { return fmt.Errorf("send authorization response: %w", err) } @@ -400,6 +388,7 @@ func (f *Flow) sendAuthorizationResponse( ctx context.Context, requestObject *RequestObject, vp *verifiable.Presentation, + attestationRequired bool, ) error { slog.Info("Sending authorization response", "redirect_uri", requestObject.RedirectURI, @@ -427,7 +416,12 @@ func (f *Flow) sendAuthorizationResponse( return fmt.Errorf("create vp token: %w", err) } - idToken, err := f.createIDToken(presentationSubmission, requestObject.ClientID, requestObject.Nonce, requestObject.Scope) + idToken, err := f.createIDToken( + ctx, + presentationSubmission, + requestObject.ClientID, requestObject.Nonce, requestObject.Scope, + attestationRequired, + ) if err != nil { return fmt.Errorf("create id token: %w", err) } @@ -593,8 +587,10 @@ func (f *Flow) signPresentationLDP( } func (f *Flow) createIDToken( + ctx context.Context, presentationSubmission *presexch.PresentationSubmission, clientID, nonce, requestObjectScope string, + attestationRequired bool, ) (string, error) { scopeAdditionalClaims, err := extractCustomScopeClaims(requestObjectScope) if err != nil { @@ -616,6 +612,17 @@ func (f *Flow) createIDToken( Jti: uuid.NewString(), } + if attestationRequired { + var jwtVP string + + jwtVP, err = f.attestationService.GetAttestation(ctx) + if err != nil { + return "", fmt.Errorf("get attestation: %w", err) + } + + idToken.AttestationVP = jwtVP + } + signedIDToken, err := jwt.NewJoseSigned( idToken, map[string]interface{}{"typ": "JWT"}, @@ -721,8 +728,6 @@ type options struct { enableLinkedDomainVerification bool disableDomainMatching bool disableSchemaValidation bool - trustRegistryURL string - trustRegistry trustRegistry } type Opt func(opts *options) @@ -756,15 +761,3 @@ func WithSchemaValidationDisabled() Opt { opts.disableSchemaValidation = true } } - -func WithTrustRegistryURL(url string) Opt { - return func(opts *options) { - opts.trustRegistryURL = url - } -} - -func WithTrustRegistry(value trustRegistry) Opt { - return func(opts *options) { - opts.trustRegistry = value - } -} diff --git a/component/wallet-cli/pkg/trustregistry/trustregistry.go b/component/wallet-cli/pkg/trustregistry/trustregistry.go index bee869701..6ff5c8a33 100644 --- a/component/wallet-cli/pkg/trustregistry/trustregistry.go +++ b/component/wallet-cli/pkg/trustregistry/trustregistry.go @@ -12,8 +12,10 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" + "github.com/samber/lo" "github.com/trustbloc/logutil-go/pkg/log" "github.com/trustbloc/vc-go/verifiable" ) @@ -25,23 +27,26 @@ var ( type Client struct { httpClient *http.Client - policyURL string + host string } -func NewClient(httpClient *http.Client, policyURL string) *Client { +func NewClient(httpClient *http.Client, host string) *Client { return &Client{ httpClient: httpClient, - policyURL: policyURL, + host: host, } } +// ValidateIssuer validates that the issuer is trusted according to policy. func (c *Client) ValidateIssuer( ctx context.Context, issuerDID string, issuerDomain string, credentialOffers []CredentialOffer, -) error { - logger.Debug("issuer validation begin", log.WithURL(c.policyURL)) +) (bool, error) { + endpoint := fmt.Sprintf("%s/wallet/interactions/issuance", c.host) + + logger.Debug("issuer validation begin", log.WithURL(endpoint)) req := &WalletIssuanceRequest{ IssuerDID: issuerDID, @@ -51,21 +56,21 @@ func (c *Client) ValidateIssuer( body, err := json.Marshal(req) if err != nil { - return fmt.Errorf("marshal wallet issuance request: %w", err) + return false, fmt.Errorf("marshal wallet issuance request: %w", err) } - resp, err := c.doRequest(ctx, c.policyURL, body) + resp, err := c.doRequest(ctx, endpoint, body) if err != nil { - return err + return false, err } if !resp.Allowed { - return ErrInteractionRestricted + return false, ErrInteractionRestricted } - logger.Debug("issuer validation succeed", log.WithURL(c.policyURL)) + logger.Debug("issuer validation succeed", log.WithURL(endpoint)) - return nil + return resp.Payload != nil && lo.FromPtr(resp.Payload)["attestations_required"] != nil, nil } func (c *Client) ValidateVerifier( @@ -73,8 +78,10 @@ func (c *Client) ValidateVerifier( verifierDID, verifierDomain string, credentials []*verifiable.Credential, -) error { - logger.Debug("verifier validation begin", log.WithURL(c.policyURL)) +) (bool, error) { + endpoint := fmt.Sprintf("%s/wallet/interactions/presentation", c.host) + + logger.Debug("verifier validation begin", log.WithURL(endpoint)) req := &WalletPresentationRequest{ VerifierDID: verifierDID, @@ -90,21 +97,21 @@ func (c *Client) ValidateVerifier( body, err := json.Marshal(req) if err != nil { - return fmt.Errorf("marshal wallet presentation request: %w", err) + return false, fmt.Errorf("marshal wallet presentation request: %w", err) } - resp, err := c.doRequest(ctx, c.policyURL, body) + resp, err := c.doRequest(ctx, endpoint, body) if err != nil { - return err + return false, err } if !resp.Allowed { - return ErrInteractionRestricted + return false, ErrInteractionRestricted } - logger.Debug("verifier validation succeed", log.WithURL(c.policyURL)) + logger.Debug("verifier validation succeed", log.WithURL(endpoint)) - return nil + return resp.Payload != nil && lo.FromPtr(resp.Payload)["attestations_required"] != nil, nil } func getCredentialMetadata(content verifiable.CredentialContents) CredentialMetadata { @@ -142,7 +149,8 @@ func (c *Client) doRequest(ctx context.Context, policyURL string, body []byte) ( defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("status code: %d, msg: %s", resp.StatusCode, string(b)) } var policyEvaluationResp *PolicyEvaluationResponse diff --git a/pkg/kms/aws/service_mocks.go b/pkg/kms/aws/service_mocks.go index 3675c654a..ac8806377 100644 --- a/pkg/kms/aws/service_mocks.go +++ b/pkg/kms/aws/service_mocks.go @@ -337,4 +337,4 @@ func (m *MockmetricsProvider) VerifyTime(value time.Duration) { func (mr *MockmetricsProviderMockRecorder) VerifyTime(value interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTime", reflect.TypeOf((*MockmetricsProvider)(nil).VerifyTime), value) -} \ No newline at end of file +} diff --git a/pkg/kms/mocks/kms_mocks.go b/pkg/kms/mocks/kms_mocks.go index 5fe7d704d..8ca0def08 100644 --- a/pkg/kms/mocks/kms_mocks.go +++ b/pkg/kms/mocks/kms_mocks.go @@ -96,4 +96,4 @@ func (m *MockVCSKeyManager) SupportedKeyTypes() []kms.KeyType { func (mr *MockVCSKeyManagerMockRecorder) SupportedKeyTypes() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportedKeyTypes", reflect.TypeOf((*MockVCSKeyManager)(nil).SupportedKeyTypes)) -} \ No newline at end of file +} diff --git a/pkg/profile/api.go b/pkg/profile/api.go index e339edeea..921f9f2c4 100644 --- a/pkg/profile/api.go +++ b/pkg/profile/api.go @@ -218,16 +218,14 @@ type OIDC4VPConfig struct { // VerificationChecks are checks to be performed for verifying credentials and presentations. type VerificationChecks struct { - Credential CredentialChecks `json:"credential,omitempty"` - Presentation *PresentationChecks `json:"presentation,omitempty"` - Policy PolicyCheck `json:"policy,omitempty"` - ClientAttestationCheck ClientAttestationCheck `json:"clientAttestationCheck,omitempty"` + Credential CredentialChecks `json:"credential,omitempty"` + Presentation *PresentationChecks `json:"presentation,omitempty"` + Policy PolicyCheck `json:"policy,omitempty"` } // IssuanceChecks are checks to be performed for issuance credentials and presentations. type IssuanceChecks struct { - Policy PolicyCheck `json:"policy,omitempty"` - ClientAttestationCheck ClientAttestationCheck `json:"clientAttestationCheck,omitempty"` + Policy PolicyCheck `json:"policy,omitempty"` } // PolicyCheck stores policy check configuration. diff --git a/pkg/restapi/v1/verifier/controller.go b/pkg/restapi/v1/verifier/controller.go index c2e266d9b..12f718343 100644 --- a/pkg/restapi/v1/verifier/controller.go +++ b/pkg/restapi/v1/verifier/controller.go @@ -75,6 +75,7 @@ type IDTokenClaims struct { // CustomScopeClaims stores claims retrieved using custom scope. CustomScopeClaims map[string]oidc4vp.Claims `json:"_scope,omitempty"` VPToken IDTokenVPToken `json:"_vp_token"` + AttestationVP string `json:"_attestation_vp"` Nonce string `json:"nonce"` Aud string `json:"aud"` Exp int64 `json:"exp"` @@ -625,6 +626,7 @@ func (c *Controller) verifyAuthorizationResponseTokens( return &oidc4vp.AuthorizationResponseParsed{ CustomScopeClaims: idTokenClaims.CustomScopeClaims, VPTokens: processedVPTokens, + AttestationVP: idTokenClaims.AttestationVP, }, nil } @@ -666,9 +668,10 @@ func validateIDToken(idToken string, verifier jwt.ProofChecker) (*IDTokenClaims, VPToken: IDTokenVPToken{ PresentationSubmission: presentationSubmission, }, - Nonce: string(v.GetStringBytes("nonce")), - Aud: string(v.GetStringBytes("aud")), - Exp: v.GetInt64("exp"), + AttestationVP: string(v.GetStringBytes("_attestation_vp")), + Nonce: string(v.GetStringBytes("nonce")), + Aud: string(v.GetStringBytes("aud")), + Exp: v.GetInt64("exp"), } if idTokenClaims.Exp < time.Now().Unix() { diff --git a/pkg/service/oidc4ci/oidc4ci_service.go b/pkg/service/oidc4ci/oidc4ci_service.go index 1726e3c3b..9a563c3ee 100644 --- a/pkg/service/oidc4ci/oidc4ci_service.go +++ b/pkg/service/oidc4ci/oidc4ci_service.go @@ -120,7 +120,12 @@ type jsonSchemaValidator interface { } type trustRegistryService interface { - ValidateIssuance(ctx context.Context, profile *profileapi.Issuer, jwtVP string) error + ValidateIssuance( + ctx context.Context, + profile *profileapi.Issuer, + attestationVP string, + credentialTypes []string, + ) error } type ackStore interface { @@ -553,7 +558,13 @@ func (s *Service) ValidatePreAuthorizedCodeRequest( //nolint:gocognit,nolintlint } } - if err = s.CheckPolicies(ctx, profile, clientAssertionType, clientAssertion); err != nil { + var credentialTypes []string + + if tx.CredentialTemplate != nil { + credentialTypes = append(credentialTypes, tx.CredentialTemplate.Type) + } + + if err = s.CheckPolicies(ctx, profile, clientAssertionType, clientAssertion, credentialTypes); err != nil { return nil, resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, err) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_authenticate_client.go b/pkg/service/oidc4ci/oidc4ci_service_check_policies.go similarity index 83% rename from pkg/service/oidc4ci/oidc4ci_service_authenticate_client.go rename to pkg/service/oidc4ci/oidc4ci_service_check_policies.go index e7fe9d6b8..ac3e912c2 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_authenticate_client.go +++ b/pkg/service/oidc4ci/oidc4ci_service_check_policies.go @@ -22,13 +22,14 @@ func (s *Service) CheckPolicies( ctx context.Context, profile *profile.Issuer, clientAssertionType, - clientAssertion string) error { + clientAssertion string, + credentialTypes []string) error { if err := s.validateClientAssertionConfig(profile, clientAssertionType, clientAssertion); err != nil { return err } if profile.Checks.Policy.PolicyURL != "" { - if err := s.trustRegistryService.ValidateIssuance(ctx, profile, clientAssertion); err != nil { + if err := s.trustRegistryService.ValidateIssuance(ctx, profile, clientAssertion, credentialTypes); err != nil { return resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, err) } } @@ -50,18 +51,12 @@ func (s *Service) validateClientAssertionConfig( return errors.New("client attestation is required but policy url not set for profile") } - // TODO: This check should be removed. - if !profile.Checks.ClientAttestationCheck.Enabled { - // This is a profile configuration error - return errors.New("client attestation check not set for profile") - } - if clientAssertionType == "" { return resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, errors.New("no client assertion type specified")) } - if clientAssertionType != "attest_jwt_client_auth" { + if clientAssertionType != attestJWTClientAuthType { return resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, errors.New("only supported client assertion type is attest_jwt_client_auth")) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_authenticate_client_test.go b/pkg/service/oidc4ci/oidc4ci_service_check_policies_test.go similarity index 82% rename from pkg/service/oidc4ci/oidc4ci_service_authenticate_client_test.go rename to pkg/service/oidc4ci/oidc4ci_service_check_policies_test.go index 87ab44e69..67d04af1a 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_authenticate_client_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_check_policies_test.go @@ -22,7 +22,7 @@ const ( issuerDID = "did:oin:abc" ) -func TestService_AuthenticateClient(t *testing.T) { +func TestService_CheckPolicies(t *testing.T) { var ( trustRegistryService *MockTrustRegistryService profile *profileapi.Issuer @@ -47,9 +47,6 @@ func TestService_AuthenticateClient(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://policy.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } @@ -62,6 +59,7 @@ func TestService_AuthenticateClient(t *testing.T) { context.Background(), profile, clientAssertion, + []string{"VerifiedEmployee"}, ).Times(1).Return(nil) }, check: func(t *testing.T, err error) { @@ -93,11 +91,7 @@ func TestService_AuthenticateClient(t *testing.T) { OIDCConfig: &profileapi.OIDCConfig{ TokenEndpointAuthMethodsSupported: []string{"attest_jwt_client_auth"}, }, - Checks: profileapi.IssuanceChecks{ - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, - }, + Checks: profileapi.IssuanceChecks{}, } clientAssertionType = "attest_jwt_client_auth" @@ -140,9 +134,6 @@ func TestService_AuthenticateClient(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://policy.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } @@ -166,9 +157,6 @@ func TestService_AuthenticateClient(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://policy.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } @@ -192,9 +180,6 @@ func TestService_AuthenticateClient(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://policy.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } @@ -219,9 +204,6 @@ func TestService_AuthenticateClient(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://policy.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } @@ -234,35 +216,13 @@ func TestService_AuthenticateClient(t *testing.T) { context.Background(), profile, clientAssertion, + []string{"VerifiedEmployee"}, ).Return(errors.New("validate error")) }, check: func(t *testing.T, err error) { require.ErrorContains(t, err, "validate error") }, }, - { - name: "client attestation check not set", - setup: func() { - profile = &profileapi.Issuer{ - OIDCConfig: &profileapi.OIDCConfig{ - TokenEndpointAuthMethodsSupported: []string{"attest_jwt_client_auth"}, - }, - Checks: profileapi.IssuanceChecks{ - Policy: profileapi.PolicyCheck{ - PolicyURL: "https://policy.example.com", - }, - }, - } - - clientAssertionType = "attest_jwt_client_auth" - clientAssertion = "" - - trustRegistryService = NewMockTrustRegistryService(gomock.NewController(t)) - }, - check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "client attestation check not set for profile") - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -273,7 +233,8 @@ func TestService_AuthenticateClient(t *testing.T) { }) require.NoError(t, err) - err = svc.CheckPolicies(context.Background(), profile, clientAssertionType, clientAssertion) + err = svc.CheckPolicies(context.Background(), profile, clientAssertionType, clientAssertion, + []string{"VerifiedEmployee"}) tt.check(t, err) }) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go index f955386d0..d239696fc 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go @@ -51,7 +51,13 @@ func (s *Service) ExchangeAuthorizationCode( return nil, e } - if err = s.CheckPolicies(ctx, profile, clientAssertionType, clientAssertion); err != nil { + var credentialTypes []string + + if tx.CredentialTemplate != nil { + credentialTypes = append(credentialTypes, tx.CredentialTemplate.Type) + } + + if err = s.CheckPolicies(ctx, profile, clientAssertionType, clientAssertion, credentialTypes); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) return nil, resterr.NewCustomError(resterr.OIDCClientAuthenticationFailed, err) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go index ab75c2980..401017493 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go @@ -168,9 +168,6 @@ func TestExchangeCodeAuthenticateClientError(t *testing.T) { Policy: profile.PolicyCheck{ PolicyURL: "https://localhost/policy", }, - ClientAttestationCheck: profile.ClientAttestationCheck{ - Enabled: true, - }, }, }, nil) diff --git a/pkg/service/oidc4ci/oidc4ci_service_test.go b/pkg/service/oidc4ci/oidc4ci_service_test.go index c5fde5235..c0a54ce2a 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_test.go @@ -1962,9 +1962,6 @@ func TestValidatePreAuthCode(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://localhost/policy", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, }, nil) diff --git a/pkg/service/oidc4vp/api.go b/pkg/service/oidc4vp/api.go index 7a7d9e2df..885706dd0 100644 --- a/pkg/service/oidc4vp/api.go +++ b/pkg/service/oidc4vp/api.go @@ -29,6 +29,7 @@ type AuthorizationResponseParsed struct { // caused by custom scope as a part of Initiate Credential Presentation request. CustomScopeClaims map[string]Claims VPTokens []*ProcessedVPToken + AttestationVP string } type ProcessedVPToken struct { diff --git a/pkg/service/oidc4vp/oidc4vp_service.go b/pkg/service/oidc4vp/oidc4vp_service.go index 6684e3a17..5638e7f16 100644 --- a/pkg/service/oidc4vp/oidc4vp_service.go +++ b/pkg/service/oidc4vp/oidc4vp_service.go @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -//go:generate mockgen -destination oidc4vp_service_mocks_test.go -self_package mocks -package oidc4vp_test -source=oidc4vp_service.go -mock_names transactionManager=MockTransactionManager,events=MockEvents,kmsRegistry=MockKMSRegistry,requestObjectPublicStore=MockRequestObjectPublicStore,profileService=MockProfileService,presentationVerifier=MockPresentationVerifier +//go:generate mockgen -destination oidc4vp_service_mocks_test.go -self_package mocks -package oidc4vp_test -source=oidc4vp_service.go -mock_names transactionManager=MockTransactionManager,events=MockEvents,kmsRegistry=MockKMSRegistry,requestObjectPublicStore=MockRequestObjectPublicStore,profileService=MockProfileService,presentationVerifier=MockPresentationVerifier,trustRegistryService=MockTrustRegistryService package oidc4vp @@ -42,6 +42,7 @@ import ( noopMetricsProvider "github.com/trustbloc/vcs/pkg/observability/metrics/noop" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/trustregistry" "github.com/trustbloc/vcs/pkg/service/verifypresentation" ) @@ -91,6 +92,15 @@ type presentationVerifier interface { ) } +type trustRegistryService interface { + ValidatePresentation( + ctx context.Context, + profile *profileapi.Verifier, + jwtVP string, + metadata []trustregistry.CredentialMetadata, + ) error +} + type RequestObjectClaims struct { VPToken VPToken `json:"vp_token"` } @@ -126,6 +136,7 @@ type Config struct { EventTopic string PresentationVerifier presentationVerifier VDR vdrapi.Registry + TrustRegistryService trustRegistryService RedirectURL string TokenLifetime time.Duration @@ -146,6 +157,7 @@ type Service struct { profileService profileService presentationVerifier presentationVerifier vdr vdrapi.Registry + trustRegistryService trustRegistryService redirectURL string tokenLifetime time.Duration @@ -180,6 +192,7 @@ func NewService(cfg *Config) *Service { redirectURL: cfg.RedirectURL, tokenLifetime: cfg.TokenLifetime, vdr: cfg.VDR, + trustRegistryService: cfg.TrustRegistryService, metrics: metrics, } } @@ -427,6 +440,10 @@ func (s *Service) VerifyOIDCVerifiablePresentation( logger.Debugc(ctx, "VerifyOIDCVerifiablePresentation profile fetched", logfields.WithProfileID(profile.ID)) + if err = s.checkPolicy(ctx, profile, authResponse.AttestationVP, authResponse.VPTokens); err != nil { + return err + } + logger.Debugc(ctx, fmt.Sprintf("VerifyOIDCVerifiablePresentation count of tokens is %v", len(authResponse.VPTokens))) verifiedPresentations, err := s.verifyTokens(ctx, tx, profile, authResponse.VPTokens) @@ -451,6 +468,56 @@ func (s *Service) VerifyOIDCVerifiablePresentation( return nil } +func (s *Service) checkPolicy( + ctx context.Context, + profile *profileapi.Verifier, + attestationVP string, + vpTokens []*ProcessedVPToken, +) error { + if profile.Checks.Policy.PolicyURL == "" { + return nil + } + + st := time.Now() + + metadata := make([]trustregistry.CredentialMetadata, 0) + + for _, token := range vpTokens { + for _, credential := range token.Presentation.Credentials() { + vcc := credential.Contents() + + var iss, exp string + + if vcc.Issued != nil { + iss = vcc.Issued.FormatToString() + } + + if vcc.Expired != nil { + exp = vcc.Expired.FormatToString() + } + + metadata = append(metadata, trustregistry.CredentialMetadata{ + CredentialID: vcc.ID, + Types: vcc.Types, + IssuerID: vcc.Issuer.ID, + Issued: iss, + Expired: exp, + }) + } + } + + if err := s.trustRegistryService.ValidatePresentation(ctx, profile, attestationVP, metadata); err != nil { + return fmt.Errorf("check policy: %w", err) + } + + logger.Debugc(ctx, "VerifyOIDCVerifiablePresentation policy checked", + logfields.WithProfileID(profile.ID), + log.WithDuration(time.Since(st)), + ) + + return nil +} + func (s *Service) GetTx(_ context.Context, id TxID) (*Transaction, error) { return s.transactionManager.Get(id) } diff --git a/pkg/service/oidc4vp/oidc4vp_service_test.go b/pkg/service/oidc4vp/oidc4vp_service_test.go index ae904f08d..ab5a5bc5a 100644 --- a/pkg/service/oidc4vp/oidc4vp_service_test.go +++ b/pkg/service/oidc4vp/oidc4vp_service_test.go @@ -56,9 +56,10 @@ var ( ) const ( - profileID = "testProfileID" - profileVersion = "v1.0" - customScope = "customScope" + profileID = "testProfileID" + profileVersion = "v1.0" + customScope = "customScope" + presentationPolicyURL = "https://trust-registry.dev/verifier/policies/{policyID}/{policyVersion}/interactions/presentation" //nolint:lll ) func TestService_InitiateOidcInteraction(t *testing.T) { @@ -262,6 +263,8 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { txManager := NewMockTransactionManager(gomock.NewController(t)) profileService := NewMockProfileService(gomock.NewController(t)) presentationVerifier := NewMockPresentationVerifier(gomock.NewController(t)) + trustRegistry := NewMockTrustRegistryService(gomock.NewController(t)) + vp, pd, issuer, vdr, loader := newVPWithPD(t, w) s := oidc4vp.NewService(&oidc4vp.Config{ @@ -272,6 +275,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: loader, VDR: vdr, + TrustRegistryService: trustRegistry, }) txManager.EXPECT().GetByOneTimeToken("nonce1").AnyTimes().Return(&oidc4vp.Transaction{ @@ -294,12 +298,18 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { vcsverifiable.Jwt, }, }, + Policy: profileapi.PolicyCheck{ + PolicyURL: presentationPolicyURL, + }, }, }, nil) presentationVerifier.EXPECT().VerifyPresentation(context.Background(), gomock.Any(), gomock.Any(), gomock.Any()). AnyTimes().Return(nil, nil, nil) + trustRegistry.EXPECT().ValidatePresentation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes().Return(nil) + t.Run("Success without custom claims", func(t *testing.T) { txManager2 := NewMockTransactionManager(gomock.NewController(t)) @@ -325,6 +335,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: loader, VDR: vdr, + TrustRegistryService: trustRegistry, }) err = s2.VerifyOIDCVerifiablePresentation(context.Background(), "txID1", @@ -342,7 +353,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { require.NoError(t, err) }) - t.Run("Success - two VP tokens (merged) with custom claims", func(t *testing.T) { + t.Run("Success - two VP tokens (merged) with custom claims and attestation vp", func(t *testing.T) { var descriptors []*presexch.InputDescriptor err = json.Unmarshal([]byte(twoInputDescriptors), &descriptors) require.NoError(t, err) @@ -399,6 +410,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: testLoader, VDR: combinedDIDResolver, + TrustRegistryService: trustRegistry, }) txManager2.EXPECT().GetByOneTimeToken("nonce1").AnyTimes().Return(&oidc4vp.Transaction{ @@ -427,6 +439,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { "key1": "value1", }, }, + AttestationVP: "attestation_vp.jwt", VPTokens: []*oidc4vp.ProcessedVPToken{ { Nonce: "nonce1", @@ -520,6 +533,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: testLoader, VDR: combinedDIDResolver, + TrustRegistryService: trustRegistry, }) txManager2.EXPECT().GetByOneTimeToken("nonce1").AnyTimes().Return(&oidc4vp.Transaction{ @@ -725,6 +739,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: loader, VDR: vdr, + TrustRegistryService: trustRegistry, }) err := withError.VerifyOIDCVerifiablePresentation(context.Background(), "txID1", @@ -772,6 +787,7 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { ProfileService: profileService, DocumentLoader: loader, VDR: vdr, + TrustRegistryService: trustRegistry, }) err := withError.VerifyOIDCVerifiablePresentation(context.Background(), "txID1", @@ -786,6 +802,35 @@ func TestService_VerifyOIDCVerifiablePresentation(t *testing.T) { require.Contains(t, err.Error(), "store error") }) + + t.Run("Trust Registry error", func(t *testing.T) { + errTrustRegistry := NewMockTrustRegistryService(gomock.NewController(t)) + errTrustRegistry.EXPECT().ValidatePresentation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes().Return(errors.New("validate error")) + + withError := oidc4vp.NewService(&oidc4vp.Config{ + EventSvc: &mockEvent{}, + EventTopic: spi.VerifierEventTopic, + TransactionManager: txManager, + PresentationVerifier: presentationVerifier, + ProfileService: profileService, + DocumentLoader: loader, + VDR: vdr, + TrustRegistryService: errTrustRegistry, + }) + + err := withError.VerifyOIDCVerifiablePresentation(context.Background(), "txID1", + &oidc4vp.AuthorizationResponseParsed{ + CustomScopeClaims: nil, + VPTokens: []*oidc4vp.ProcessedVPToken{{ + Nonce: "nonce1", + Presentation: vp, + SignerDIDID: "did:example123:ebfeb1f712ebc6f1c276e12ec21", + VpTokenFormat: vcsverifiable.Jwt, + }}}) + + require.Contains(t, err.Error(), "check policy") + }) } func TestService_GetTx(t *testing.T) { @@ -1023,6 +1068,9 @@ func newVC(issuer string, ctx []string, customTypes []string) verifiable.Credent Issued: &util.TimeWrapper{ Time: time.Now(), }, + Expired: &util.TimeWrapper{ + Time: time.Now().AddDate(1, 0, 0), + }, Subject: []verifiable.Subject{{ ID: issuer, }}, @@ -1044,6 +1092,9 @@ func newDegreeVC(issuer string, degreeType string, ctx []string, customTypes []s Issued: &util.TimeWrapper{ Time: time.Now(), }, + Expired: &util.TimeWrapper{ + Time: time.Now().AddDate(1, 0, 0), + }, Subject: []verifiable.Subject{{ ID: issuer, CustomFields: map[string]interface{}{ diff --git a/pkg/service/trustregistry/api.go b/pkg/service/trustregistry/api.go index e65db9419..5d3184fce 100644 --- a/pkg/service/trustregistry/api.go +++ b/pkg/service/trustregistry/api.go @@ -52,6 +52,6 @@ type PolicyEvaluationResponse struct { // ServiceInterface defines an interface for client attestation service. The task of service is to validate and confirm // the device binding and authentication of the client instance by validating attestation VP and evaluating policy. type ServiceInterface interface { - ValidateIssuance(ctx context.Context, profile *profileapi.Issuer, jwtVP string) error - ValidatePresentation(ctx context.Context, profile *profileapi.Verifier, jwtVP string) error + ValidateIssuance(ctx context.Context, profile *profileapi.Issuer, attestationVP string, credentialTypes []string) error //nolint: lll + ValidatePresentation(ctx context.Context, profile *profileapi.Verifier, attestationVP string, metadata []CredentialMetadata) error //nolint: lll } diff --git a/pkg/service/trustregistry/trustregistry_service.go b/pkg/service/trustregistry/trustregistry_service.go index 73d3bc57d..afab21a24 100644 --- a/pkg/service/trustregistry/trustregistry_service.go +++ b/pkg/service/trustregistry/trustregistry_service.go @@ -4,7 +4,7 @@ Copyright Gen Digital Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -//go:generate mockgen -destination trustregistry_service_mocks_test.go -package trustregistry_test -source=trustregistry_service.go -mock_names httpClient=MockHTTPClient,vcStatusVerifier=MockVCStatusVerifier +//go:generate mockgen -destination trustregistry_service_mocks_test.go -package trustregistry_test -source=trustregistry_service.go -mock_names httpClient=MockHTTPClient package trustregistry @@ -12,8 +12,8 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" + "io" "net/http" "time" @@ -35,33 +35,26 @@ type httpClient interface { Do(req *http.Request) (*http.Response, error) } -type vcStatusVerifier interface { - ValidateVCStatus(ctx context.Context, vcStatus *verifiable.TypedID, issuer *verifiable.Issuer) error -} - // Config defines configuration for Service. type Config struct { - HTTPClient httpClient - DocumentLoader ld.DocumentLoader - ProofChecker verifiable.CombinedProofChecker - VCStatusVerifier vcStatusVerifier + HTTPClient httpClient + DocumentLoader ld.DocumentLoader + ProofChecker verifiable.CombinedProofChecker } // Service implements attestation functionality for OAuth 2.0 Attestation-Based Client Authentication. type Service struct { - httpClient httpClient - documentLoader ld.DocumentLoader - proofChecker verifiable.CombinedProofChecker - vcStatusVerifier vcStatusVerifier + httpClient httpClient + documentLoader ld.DocumentLoader + proofChecker verifiable.CombinedProofChecker } // NewService returns a new Service instance. func NewService(config *Config) *Service { return &Service{ - httpClient: config.HTTPClient, - documentLoader: config.DocumentLoader, - proofChecker: config.ProofChecker, - vcStatusVerifier: config.VCStatusVerifier, + httpClient: config.HTTPClient, + documentLoader: config.DocumentLoader, + proofChecker: config.ProofChecker, } } @@ -69,49 +62,37 @@ func NewService(config *Config) *Service { func (s *Service) ValidateIssuance( ctx context.Context, profile *profileapi.Issuer, - jwtVP string, + attestationVP string, + credentialTypes []string, ) error { logger.Debugc(ctx, "validate issuance", zap.String("profileID", profile.ID), zap.String("profileVersion", profile.Version), zap.String("policyURL", profile.Checks.Policy.PolicyURL), - zap.Bool("attestationCheckEnabled", profile.Checks.ClientAttestationCheck.Enabled), - zap.String("jwtVP", jwtVP), + zap.String("attestationVP", attestationVP), + zap.Strings("credentialTypes", credentialTypes), ) if profile.Checks.Policy.PolicyURL == "" { - if profile.Checks.ClientAttestationCheck.Enabled { - return errors.New("client attestation checks are enabled but policy URL is not configured") - } - return nil } + if attestationVP == "" { + return fmt.Errorf("attestation vp is required") + } + req := &IssuancePolicyEvaluationRequest{ IssuerDID: profile.SigningDID.DID, - CredentialTypes: getCredentialTypes(profile), + CredentialTypes: credentialTypes, } - if profile.Checks.ClientAttestationCheck.Enabled { - attestationVCs, err := s.validateAttestationVP(ctx, jwtVP) - if err != nil { - return err - } - - attestations := make([]string, len(attestationVCs)) - - for i, vc := range attestationVCs { - jwtVC, convertErr := vc.ToJWTString() - if convertErr != nil { - return fmt.Errorf("convert attestation vc to jwt: %w", convertErr) - } - - attestations[i] = jwtVC - } - - req.AttestationVC = lo.ToPtr(attestations) + attestationVCs, err := s.parseAttestationVP(attestationVP) + if err != nil { + return err } + req.AttestationVC = lo.ToPtr(attestationVCs) + payload, err := json.Marshal(req) if err != nil { return fmt.Errorf("marshal request: %w", err) @@ -135,90 +116,35 @@ func (s *Service) ValidateIssuance( func (s *Service) ValidatePresentation( ctx context.Context, profile *profileapi.Verifier, - jwtVP string, + attestationVP string, + metadata []CredentialMetadata, ) error { logger.Debugc(ctx, "validate presentation", zap.String("profileID", profile.ID), zap.String("profileVersion", profile.Version), zap.String("policyURL", profile.Checks.Policy.PolicyURL), - zap.Bool("attestationCheckEnabled", profile.Checks.ClientAttestationCheck.Enabled), - zap.String("jwtVP", jwtVP), + zap.String("attestationVP", attestationVP), ) if profile.Checks.Policy.PolicyURL == "" { - if profile.Checks.ClientAttestationCheck.Enabled { - return errors.New("client attestation checks are enabled but policy URL is not configured") - } - return nil } - req := &PresentationPolicyEvaluationRequest{ - VerifierDID: profile.SigningDID.DID, + if attestationVP == "" { + return fmt.Errorf("attestation vp is required") } - if profile.Checks.ClientAttestationCheck.Enabled { - attestationVCs, err := s.validateAttestationVP(ctx, jwtVP) - if err != nil { - return err - } - - jwtVCs := make([]string, len(attestationVCs)) - - for i, vc := range attestationVCs { - jwtVC, marshalErr := vc.ToJWTString() - if marshalErr != nil { - return fmt.Errorf("marshal attestation vc to jwt: %w", marshalErr) - } - - jwtVCs[i] = jwtVC - } - - req.AttestationVC = lo.ToPtr(jwtVCs) + req := &PresentationPolicyEvaluationRequest{ + VerifierDID: profile.SigningDID.DID, + CredentialMetadata: metadata, } - credentialMetadata := make([]CredentialMetadata, 0) - - vp, err := verifiable.ParsePresentation( - []byte(jwtVP), - // The verification of proof is conducted manually, along with an extra verification to ensure that signer of - // the VP matches the subject of the attestation VC. - verifiable.WithPresDisabledProofCheck(), - verifiable.WithPresJSONLDDocumentLoader(s.documentLoader), - ) + attestationVCs, err := s.parseAttestationVP(attestationVP) if err != nil { - return fmt.Errorf("parse presentation vp: %w", err) + return err } - for _, vc := range vp.Credentials() { - if lo.Contains(vc.Contents().Types, WalletAttestationVCType) { - continue - } - - vcc := vc.Contents() - - var iss, exp string - - if vcc.Issued != nil { - iss = vcc.Issued.FormatToString() - } - - if vcc.Expired != nil { - exp = vcc.Expired.FormatToString() - } - - credentialMetadata = append(credentialMetadata, CredentialMetadata{ - CredentialID: vcc.ID, - Types: vcc.Types, - IssuerID: vcc.Issuer.ID, - Issued: iss, - Expired: exp, - }) - } - - if len(credentialMetadata) > 0 { - req.CredentialMetadata = credentialMetadata - } + req.AttestationVC = lo.ToPtr(attestationVCs) payload, err := json.Marshal(req) if err != nil { @@ -237,11 +163,7 @@ func (s *Service) ValidatePresentation( return nil } -//nolint:gocritic -func (s *Service) validateAttestationVP( - _ context.Context, - jwtVP string, -) ([]*verifiable.Credential, error) { +func (s *Service) parseAttestationVP(jwtVP string) ([]string, error) { attestationVP, err := verifiable.ParsePresentation( []byte(jwtVP), // The verification of proof is conducted manually, along with an extra verification to ensure that signer of @@ -253,7 +175,7 @@ func (s *Service) validateAttestationVP( return nil, fmt.Errorf("parse attestation vp: %w", err) } - attestationVCs := make([]*verifiable.Credential, 0) + attestationVCs := make([]string, 0) for _, vc := range attestationVP.Credentials() { if !lo.Contains(vc.Contents().Types, WalletAttestationVCType) { @@ -285,17 +207,12 @@ func (s *Service) validateAttestationVP( return nil, fmt.Errorf("check attestation vp proof: %w", err) } - // check attestation VC status - // TODO: status list check should be mandatory for attestation VC - // if err = s.vcStatusVerifier.ValidateVCStatus(ctx, vcc.Status, vcc.Issuer); err != nil { - // return nil, nil, fmt.Errorf("validate attestation vc status: %w", err) - // } - - attestationVCs = append(attestationVCs, vc) - } + jwtVC, marshalErr := vc.ToJWTString() + if marshalErr != nil { + return nil, fmt.Errorf("marshal attestation vc to jwt: %w", marshalErr) + } - if len(attestationVCs) == 0 { - return nil, errors.New("no attestation vc found") + attestationVCs = append(attestationVCs, jwtVC) } return attestationVCs, nil @@ -326,7 +243,8 @@ func (s *Service) requestPolicyEvaluation( defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("status code: %d, msg: %s", resp.StatusCode, string(b)) } var result *PolicyEvaluationResponse @@ -337,11 +255,3 @@ func (s *Service) requestPolicyEvaluation( return result, nil } - -func getCredentialTypes(profile *profileapi.Issuer) []string { - return lo.Map(profile.CredentialTemplates, - func(item *profileapi.CredentialTemplate, index int) string { - return item.Type - }, - ) -} diff --git a/pkg/service/trustregistry/trustregistry_service_test.go b/pkg/service/trustregistry/trustregistry_service_test.go index 9e72c391c..d5e8bf4c2 100644 --- a/pkg/service/trustregistry/trustregistry_service_test.go +++ b/pkg/service/trustregistry/trustregistry_service_test.go @@ -47,7 +47,6 @@ const ( func TestService_ValidateIssuance(t *testing.T) { httpClient := NewMockHTTPClient(gomock.NewController(t)) - // vcStatusVerifier := NewMockVCStatusVerifier(gomock.NewController(t)) proofCreators, defaultProofChecker := testsupport.NewKMSSignersAndVerifier(t, []testsupport.SigningKey{ @@ -68,8 +67,8 @@ func TestService_ValidateIssuance(t *testing.T) { var proofChecker *checker.ProofChecker var ( - jwtVP string - profile *profileapi.Issuer + attestationVP string + profile *profileapi.Issuer ) tests := []struct { @@ -82,8 +81,6 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -97,7 +94,7 @@ func TestService_ValidateIssuance(t *testing.T) { attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) // prepare wallet attestation VP (in jwt_vp format) signed by wallet DID - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, check: func(t *testing.T, err error) { @@ -105,33 +102,31 @@ func TestService_ValidateIssuance(t *testing.T) { }, }, { - name: "fail to parse attestation vp", + name: "attestation vp is required", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) - jwtVP = "invalid-jwt-vp" + attestationVP = "" profile = createIssuerProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "parse attestation vp") + require.ErrorContains(t, err, "attestation vp is required") }, }, { - name: "no attestation vc found", + name: "fail to parse attestation vp", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) - jwtVP = createAttestationVP(t, nil, walletProofCreator) + attestationVP = "invalid-jwt-vp" profile = createIssuerProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "no attestation vc found") + require.ErrorContains(t, err, "parse attestation vp") }, }, { @@ -139,11 +134,10 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, true) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, @@ -156,11 +150,10 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, "invalid-subject", false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, @@ -168,36 +161,15 @@ func TestService_ValidateIssuance(t *testing.T) { require.ErrorContains(t, err, "check attestation vp proof") }, }, - // { - // name: "fail to validate attestation vc status", - // setup: func() { - // proofChecker = defaultProofChecker - // - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()). - // Return(errors.New("validate status error")) - // - // httpClient.EXPECT().Do(gomock.Any()).Times(0) - // - // attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - // jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) - // - // profile = createIssuerProfile(t) - // }, - // check: func(t *testing.T, err error) { - // require.ErrorContains(t, err, "validate attestation vc status") - // }, - // }, { name: "policy url not set in profile", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = &profileapi.Issuer{ SigningDID: &profileapi.SigningDID{ @@ -209,40 +181,11 @@ func TestService_ValidateIssuance(t *testing.T) { require.NoError(t, err) }, }, - { - name: "client attestation enabled but policy url not set in profile", - setup: func() { - proofChecker = defaultProofChecker - - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - - httpClient.EXPECT().Do(gomock.Any()).Times(0) - - attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) - - profile = &profileapi.Issuer{ - SigningDID: &profileapi.SigningDID{ - DID: issuerDID, - }, - Checks: profileapi.IssuanceChecks{ - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, - }, - } - }, - check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "client attestation checks are enabled but policy URL is not configured") - }, - }, { name: "fail to send request to policy evaluation service", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return nil, errors.New("send request error") @@ -250,7 +193,7 @@ func TestService_ValidateIssuance(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, @@ -263,8 +206,6 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -275,12 +216,12 @@ func TestService_ValidateIssuance(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "unexpected status code") + require.ErrorContains(t, err, "status code") }, }, { @@ -288,8 +229,6 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -300,7 +239,7 @@ func TestService_ValidateIssuance(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, @@ -313,8 +252,6 @@ func TestService_ValidateIssuance(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -325,7 +262,7 @@ func TestService_ValidateIssuance(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createIssuerProfile(t) }, @@ -344,9 +281,8 @@ func TestService_ValidateIssuance(t *testing.T) { HTTPClient: httpClient, DocumentLoader: testutil.DocumentLoader(t), ProofChecker: proofChecker, - // VCStatusVerifier: vcStatusVerifier, }, - ).ValidateIssuance(context.Background(), profile, jwtVP), + ).ValidateIssuance(context.Background(), profile, attestationVP, nil), ) }) } @@ -354,7 +290,6 @@ func TestService_ValidateIssuance(t *testing.T) { func TestService_ValidatePresentation(t *testing.T) { httpClient := NewMockHTTPClient(gomock.NewController(t)) - // vcStatusVerifier := NewMockVCStatusVerifier(gomock.NewController(t)) proofCreators, defaultProofChecker := testsupport.NewKMSSignersAndVerifier(t, []testsupport.SigningKey{ @@ -375,8 +310,8 @@ func TestService_ValidatePresentation(t *testing.T) { var proofChecker *checker.ProofChecker var ( - jwtVP string - profile *profileapi.Verifier + attestationVP string + profile *profileapi.Verifier ) tests := []struct { @@ -389,8 +324,6 @@ func TestService_ValidatePresentation(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -413,7 +346,7 @@ func TestService_ValidatePresentation(t *testing.T) { ) // prepare wallet attestation VP (in jwt_vp format) signed by wallet DID - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator, requestedVC) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator, requestedVC) profile = createVerifierProfile(t) }, check: func(t *testing.T, err error) { @@ -421,33 +354,32 @@ func TestService_ValidatePresentation(t *testing.T) { }, }, { - name: "fail to parse attestation vp", + name: "attestation vp is required", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) - jwtVP = "invalid-jwt-vp" + attestationVP = "" profile = createVerifierProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "parse attestation vp") + require.ErrorContains(t, err, "attestation vp is required") }, }, { - name: "no attestation vc found", + name: "fail to parse attestation vp", setup: func() { proofChecker = defaultProofChecker // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) httpClient.EXPECT().Do(gomock.Any()).Times(0) - jwtVP = createAttestationVP(t, nil, walletProofCreator) + attestationVP = "invalid-jwt-vp" profile = createVerifierProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "no attestation vc found") + require.ErrorContains(t, err, "parse attestation vp") }, }, { @@ -459,7 +391,7 @@ func TestService_ValidatePresentation(t *testing.T) { httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, true) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createVerifierProfile(t) }, @@ -476,7 +408,7 @@ func TestService_ValidatePresentation(t *testing.T) { httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, "invalid-subject", false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createVerifierProfile(t) }, @@ -484,25 +416,6 @@ func TestService_ValidatePresentation(t *testing.T) { require.ErrorContains(t, err, "check attestation vp proof") }, }, - // { - // name: "fail to validate attestation vc status", - // setup: func() { - // proofChecker = defaultProofChecker - // - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()). - // Return(errors.New("validate status error")) - // - // httpClient.EXPECT().Do(gomock.Any()).Times(0) - // - // attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - // jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) - // - // profile = createVerifierProfile(t) - // }, - // check: func(t *testing.T, err error) { - // require.ErrorContains(t, err, "validate attestation vc status") - // }, - // }, { name: "policy url not set in profile", setup: func() { @@ -513,7 +426,7 @@ func TestService_ValidatePresentation(t *testing.T) { httpClient.EXPECT().Do(gomock.Any()).Times(0) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = &profileapi.Verifier{ SigningDID: &profileapi.SigningDID{ @@ -526,38 +439,11 @@ func TestService_ValidatePresentation(t *testing.T) { require.NoError(t, err) }, }, - { - name: "client attestation enabled but policy url not set in profile", - setup: func() { - proofChecker = defaultProofChecker - - httpClient.EXPECT().Do(gomock.Any()).Times(0) - - attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) - - profile = &profileapi.Verifier{ - SigningDID: &profileapi.SigningDID{ - DID: issuerDID, - }, - Checks: &profileapi.VerificationChecks{ - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, - }, - } - }, - check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "client attestation checks are enabled but policy URL is not configured") - }, - }, { name: "fail to request policy evaluation", setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -568,12 +454,12 @@ func TestService_ValidatePresentation(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createVerifierProfile(t) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "unexpected status code") + require.ErrorContains(t, err, "status code") }, }, { @@ -581,8 +467,6 @@ func TestService_ValidatePresentation(t *testing.T) { setup: func() { proofChecker = defaultProofChecker - // vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - httpClient.EXPECT().Do(gomock.Any()).DoAndReturn( func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -593,7 +477,7 @@ func TestService_ValidatePresentation(t *testing.T) { ) attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - jwtVP = createAttestationVP(t, attestationVC, walletProofCreator) + attestationVP = createAttestationVP(t, attestationVC, walletProofCreator) profile = createVerifierProfile(t) }, @@ -612,9 +496,8 @@ func TestService_ValidatePresentation(t *testing.T) { HTTPClient: httpClient, DocumentLoader: testutil.DocumentLoader(t), ProofChecker: proofChecker, - // VCStatusVerifier: vcStatusVerifier, }, - ).ValidatePresentation(context.Background(), profile, jwtVP), + ).ValidatePresentation(context.Background(), profile, attestationVP, nil), ) }) } @@ -734,9 +617,6 @@ func createIssuerProfile(t *testing.T) *profileapi.Issuer { Policy: profileapi.PolicyCheck{ PolicyURL: issuancePolicyURL, }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, CredentialTemplates: []*profileapi.CredentialTemplate{ { @@ -759,9 +639,6 @@ func createVerifierProfile(t *testing.T) *profileapi.Verifier { Policy: profileapi.PolicyCheck{ PolicyURL: presentationPolicyURL, }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, } diff --git a/pkg/service/verifypresentation/verifypresentation_service.go b/pkg/service/verifypresentation/verifypresentation_service.go index 71f0b8e2c..39e9f2f6f 100644 --- a/pkg/service/verifypresentation/verifypresentation_service.go +++ b/pkg/service/verifypresentation/verifypresentation_service.go @@ -29,40 +29,29 @@ import ( profileapi "github.com/trustbloc/vcs/pkg/profile" ) -const ( - walletAttestationVCType = "WalletAttestationCredential" -) - type vcVerifier interface { ValidateCredentialProof(ctx context.Context, vc *verifiable.Credential, proofChallenge, proofDomain string, vcInVPValidation, strictValidation bool) error //nolint:lll ValidateVCStatus(ctx context.Context, vcStatus *verifiable.TypedID, issuer *verifiable.Issuer) error ValidateLinkedDomain(ctx context.Context, signingDID string) error } -type trustRegistryService interface { - ValidatePresentation(ctx context.Context, profile *profileapi.Verifier, jwtVP string) error -} - type Config struct { - VDR vdrapi.Registry - DocumentLoader ld.DocumentLoader - VcVerifier vcVerifier - TrustRegistryService trustRegistryService + VDR vdrapi.Registry + DocumentLoader ld.DocumentLoader + VcVerifier vcVerifier } type Service struct { - vdr vdrapi.Registry - documentLoader ld.DocumentLoader - vcVerifier vcVerifier - trustRegistryService trustRegistryService + vdr vdrapi.Registry + documentLoader ld.DocumentLoader + vcVerifier vcVerifier } func New(config *Config) *Service { return &Service{ - vdr: config.VDR, - documentLoader: config.DocumentLoader, - vcVerifier: config.VcVerifier, - trustRegistryService: config.TrustRegistryService, + vdr: config.VDR, + documentLoader: config.DocumentLoader, + vcVerifier: config.VcVerifier, } } @@ -89,24 +78,6 @@ func (s *Service) VerifyPresentation( //nolint:funlen,gocognit var targetPresentation interface{} targetPresentation = presentation - if profile.Checks.Policy.PolicyURL != "" { - st := time.Now() - - err := s.trustRegistryService.ValidatePresentation( - ctx, - profile, - presentation.JWT, - ) - if err != nil { - result = append(result, PresentationVerificationCheckResult{ - Check: "clientAttestation", - Error: err.Error(), - }) - } - - logger.Debugc(ctx, "Checks.Policy", log.WithDuration(time.Since(st))) - } - if profile.Checks.Presentation.Proof { st := time.Now() @@ -135,10 +106,8 @@ func (s *Service) VerifyPresentation( //nolint:funlen,gocognit } } - attestationEnabled := profile.Checks.ClientAttestationCheck.Enabled - if profile.Checks.Credential.CredentialExpiry { - err := s.checkCredentialExpiry(ctx, credentials, attestationEnabled) + err := s.checkCredentialExpiry(credentials) if err != nil { result = append(result, PresentationVerificationCheckResult{ Check: "credentialExpiry", @@ -150,7 +119,7 @@ func (s *Service) VerifyPresentation( //nolint:funlen,gocognit if profile.Checks.Credential.Proof { st := time.Now() - err := s.validateCredentialsProof(ctx, presentation.JWT, credentials, attestationEnabled) + err := s.validateCredentialsProof(ctx, presentation.JWT, credentials) if err != nil { result = append(result, PresentationVerificationCheckResult{ Check: "credentialProof", @@ -164,7 +133,7 @@ func (s *Service) VerifyPresentation( //nolint:funlen,gocognit if profile.Checks.Credential.Status { st := time.Now() - err := s.validateCredentialsStatus(ctx, credentials, attestationEnabled) + err := s.validateCredentialsStatus(ctx, credentials) if err != nil { result = append(result, PresentationVerificationCheckResult{ Check: "credentialStatus", @@ -257,17 +226,10 @@ func (s *Service) checkCredentialStrict( return claimKeysDict, nil } -func (s *Service) checkCredentialExpiry( - _ context.Context, - credentials []*verifiable.Credential, - attestationEnabled bool) error { +func (s *Service) checkCredentialExpiry(credentials []*verifiable.Credential) error { for _, credential := range credentials { vcc := credential.Contents() - if attestationEnabled && lo.Contains(vcc.Types, walletAttestationVCType) { - continue - } - if vcc.Expired != nil && time.Now().UTC().After(vcc.Expired.Time) { return errors.New("credential expired") } @@ -387,13 +349,8 @@ func (s *Service) validateCredentialsProof( ctx context.Context, vpJWT string, credentials []*verifiable.Credential, - attestationEnabled bool, ) error { for _, cred := range credentials { - if attestationEnabled && lo.Contains(cred.Contents().Types, walletAttestationVCType) { - continue - } - err := s.vcVerifier.ValidateCredentialProof(ctx, cred, "", "", true, vpJWT == "") if err != nil { return err @@ -406,13 +363,8 @@ func (s *Service) validateCredentialsProof( func (s *Service) validateCredentialsStatus( ctx context.Context, credentials []*verifiable.Credential, - attestationEnabled bool, ) error { for _, cred := range credentials { - if attestationEnabled && lo.Contains(cred.Contents().Types, walletAttestationVCType) { - continue - } - typedID, issuer := s.extractCredentialStatus(cred) if typedID != nil { diff --git a/pkg/service/verifypresentation/verifypresentation_service_test.go b/pkg/service/verifypresentation/verifypresentation_service_test.go index 10a72612c..08b793a84 100644 --- a/pkg/service/verifypresentation/verifypresentation_service_test.go +++ b/pkg/service/verifypresentation/verifypresentation_service_test.go @@ -55,17 +55,15 @@ func TestNew(t *testing.T) { name: "OK", args: args{ config: &Config{ - VDR: &mockvdr.VDRegistry{}, - DocumentLoader: testutil.DocumentLoader(t), - VcVerifier: NewMockVcVerifier(ctrl), - TrustRegistryService: NewMockTrustRegistryService(ctrl), + VDR: &mockvdr.VDRegistry{}, + DocumentLoader: testutil.DocumentLoader(t), + VcVerifier: NewMockVcVerifier(ctrl), }, }, want: &Service{ - vdr: &mockvdr.VDRegistry{}, - documentLoader: testutil.DocumentLoader(t), - vcVerifier: NewMockVcVerifier(ctrl), - trustRegistryService: NewMockTrustRegistryService(ctrl), + vdr: &mockvdr.VDRegistry{}, + documentLoader: testutil.DocumentLoader(t), + vcVerifier: NewMockVcVerifier(ctrl), }, }, } @@ -84,9 +82,8 @@ func TestService_VerifyPresentation(t *testing.T) { signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Ldp) type fields struct { - getVDR func() vdrapi.Registry - getVcVerifier func(t *testing.T) vcVerifier - getTrustRegistrySrv func(t *testing.T) trustRegistryService + getVDR func() vdrapi.Registry + getVcVerifier func(t *testing.T) vcVerifier } type args struct { @@ -104,10 +101,10 @@ func TestService_VerifyPresentation(t *testing.T) { wantErr bool }{ { - name: "OK with Trust registry validation enabled and client attestation VC included in VP", + name: "OK", fields: fields{ getVDR: func() vdrapi.Registry { - return signedClientAttestationVP.VDR + return signedRequestedCredentialsVP.VDR }, getVcVerifier: func(t *testing.T) vcVerifier { mockVerifier := NewMockVcVerifier(gomock.NewController(t)) @@ -127,87 +124,10 @@ func TestService_VerifyPresentation(t *testing.T) { gomock.Any()).Times(1).Return(nil) return mockVerifier }, - getTrustRegistrySrv: func(t *testing.T) trustRegistryService { - tr := NewMockTrustRegistryService(gomock.NewController(t)) - - tr.EXPECT().ValidatePresentation( - context.Background(), - gomock.Any(), - gomock.Any(), - ).Return(nil) - - return tr - }, - }, - args: args{ - getPresentation: func(t *testing.T) *verifiable.Presentation { - return signedClientAttestationVP.Presentation - }, - profile: &profileapi.Verifier{ - SigningDID: &profileapi.SigningDID{DID: verifierDID}, - Checks: &profileapi.VerificationChecks{ - Presentation: &profileapi.PresentationChecks{ - Proof: true, - Format: nil, - }, - Credential: profileapi.CredentialChecks{ - Proof: true, - Status: true, - LinkedDomain: true, - Format: nil, - CredentialExpiry: true, - Strict: true, - IssuerTrustList: map[string]profileapi.TrustList{ - "https://example.edu/issuers/14": {}, - }, - }, - Policy: profileapi.PolicyCheck{ - PolicyURL: "https://trustregistry.example.com", - }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, - }, - }, - opts: &Options{ - Domain: crypto.Domain, - Challenge: crypto.Challenge, - }, - }, - want: nil, - wantErr: false, - }, - { - name: "OK with Trust registry validation disabled and client attestation VC included in VP", - fields: fields{ - getVDR: func() vdrapi.Registry { - return signedClientAttestationVP.VDR - }, - getVcVerifier: func(t *testing.T) vcVerifier { - mockVerifier := NewMockVcVerifier(gomock.NewController(t)) - mockVerifier.EXPECT().ValidateCredentialProof( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any()).Times(2).Return(nil) - mockVerifier.EXPECT().ValidateVCStatus( - context.Background(), - gomock.Any(), - gomock.Any()).Times(2).Return(nil) - mockVerifier.EXPECT().ValidateLinkedDomain( - context.Background(), - gomock.Any()).Times(1).Return(nil) - return mockVerifier - }, - getTrustRegistrySrv: func(t *testing.T) trustRegistryService { - return NewMockTrustRegistryService(gomock.NewController(t)) - }, }, args: args{ getPresentation: func(t *testing.T) *verifiable.Presentation { - return signedClientAttestationVP.Presentation + return signedRequestedCredentialsVP.Presentation }, profile: &profileapi.Verifier{ SigningDID: &profileapi.SigningDID{DID: verifierDID}, @@ -261,9 +181,6 @@ func TestService_VerifyPresentation(t *testing.T) { gomock.Any()).Times(1).Return(nil) return mockVerifier }, - getTrustRegistrySrv: func(t *testing.T) trustRegistryService { - return NewMockTrustRegistryService(gomock.NewController(t)) - }, }, args: args{ getPresentation: func(t *testing.T) *verifiable.Presentation { @@ -315,9 +232,6 @@ func TestService_VerifyPresentation(t *testing.T) { getVcVerifier: func(t *testing.T) vcVerifier { return nil }, - getTrustRegistrySrv: func(t *testing.T) trustRegistryService { - return nil - }, }, args: args{ getPresentation: func(t *testing.T) *verifiable.Presentation { @@ -365,17 +279,6 @@ func TestService_VerifyPresentation(t *testing.T) { gomock.Any()).Times(1).Return(errors.New("some error")) return mockVerifier }, - getTrustRegistrySrv: func(t *testing.T) trustRegistryService { - ca := NewMockTrustRegistryService(gomock.NewController(t)) - - ca.EXPECT().ValidatePresentation( - context.Background(), - gomock.Any(), - gomock.Any(), - ).Return(errors.New("some error")) - - return ca - }, }, args: args{ getPresentation: func(t *testing.T) *verifiable.Presentation { @@ -400,9 +303,6 @@ func TestService_VerifyPresentation(t *testing.T) { Policy: profileapi.PolicyCheck{ PolicyURL: "https://trustregistry.example.com", }, - ClientAttestationCheck: profileapi.ClientAttestationCheck{ - Enabled: true, - }, }, }, opts: &Options{ @@ -411,10 +311,6 @@ func TestService_VerifyPresentation(t *testing.T) { }, }, want: []PresentationVerificationCheckResult{ - { - Check: "clientAttestation", - Error: "some error", - }, { Check: "issuerTrustList", Error: "issuer with id: https://example.edu/issuers/14 is not a member of trustlist", @@ -438,10 +334,9 @@ func TestService_VerifyPresentation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Service{ - vdr: tt.fields.getVDR(), - documentLoader: loader, - vcVerifier: tt.fields.getVcVerifier(t), - trustRegistryService: tt.fields.getTrustRegistrySrv(t), + vdr: tt.fields.getVDR(), + documentLoader: loader, + vcVerifier: tt.fields.getVcVerifier(t), } got, _, err := s.VerifyPresentation(context.Background(), tt.args.getPresentation(t), tt.args.opts, tt.args.profile) @@ -772,8 +667,7 @@ func TestService_validateCredentialsProof(t *testing.T) { getVcVerifier func(t *testing.T) vcVerifier } type args struct { - trustRegistryValidationEnabled bool - getCredentials func(t *testing.T) []*verifiable.Credential + getCredentials func(t *testing.T) []*verifiable.Credential } tests := []struct { name string @@ -782,7 +676,7 @@ func TestService_validateCredentialsProof(t *testing.T) { wantErr bool }{ { - name: "OK with trustRegistryValidationEnabled == true and Wallet Attestation VC included", + name: "OK", fields: fields{ getVcVerifier: func(t *testing.T) vcVerifier { mockVerifier := NewMockVcVerifier(gomock.NewController(t)) @@ -797,7 +691,6 @@ func TestService_validateCredentialsProof(t *testing.T) { }, }, args: args{ - trustRegistryValidationEnabled: true, getCredentials: func(t *testing.T) []*verifiable.Credential { credContent := verifiable.CredentialContents{ Types: []string{ @@ -809,60 +702,7 @@ func TestService_validateCredentialsProof(t *testing.T) { credential, err := verifiable.CreateCredential(credContent, nil) assert.NoError(t, err) - attestationVCContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "WalletAttestationCredential", - }, - } - - attestationVC, err := verifiable.CreateCredential(attestationVCContent, nil) - assert.NoError(t, err) - - return []*verifiable.Credential{credential, attestationVC} - }, - }, - wantErr: false, - }, - { - name: "OK with trustRegistryValidationEnabled == false and Wallet Attestation VC included", - fields: fields{ - getVcVerifier: func(t *testing.T) vcVerifier { - mockVerifier := NewMockVcVerifier(gomock.NewController(t)) - mockVerifier.EXPECT().ValidateCredentialProof( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any()).Times(2).Return(nil) - return mockVerifier - }, - }, - args: args{ - trustRegistryValidationEnabled: false, - getCredentials: func(t *testing.T) []*verifiable.Credential { - credContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "UniversityDegreeCredential", - }, - } - - credential, err := verifiable.CreateCredential(credContent, nil) - assert.NoError(t, err) - - attestationVCContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "WalletAttestationCredential", - }, - } - - attestationVC, err := verifiable.CreateCredential(attestationVCContent, nil) - assert.NoError(t, err) - - return []*verifiable.Credential{credential, attestationVC} + return []*verifiable.Credential{credential} }, }, wantErr: false, @@ -883,7 +723,6 @@ func TestService_validateCredentialsProof(t *testing.T) { }, }, args: args{ - trustRegistryValidationEnabled: false, getCredentials: func(t *testing.T) []*verifiable.Credential { credContent := verifiable.CredentialContents{ Types: []string{ @@ -911,7 +750,6 @@ func TestService_validateCredentialsProof(t *testing.T) { context.Background(), "", tt.args.getCredentials(t), - tt.args.trustRegistryValidationEnabled, ); (err != nil) != tt.wantErr { t.Errorf("validateCredentialsProof() error = %v, wantErr %v", err, tt.wantErr) } @@ -924,8 +762,7 @@ func TestService_validateCredentialsStatus(t *testing.T) { getVcVerifier func(t *testing.T) vcVerifier } type args struct { - getCredentials func(t *testing.T) []*verifiable.Credential - trustRegistryValidationEnabled bool + getCredentials func(t *testing.T) []*verifiable.Credential } tests := []struct { name string @@ -934,7 +771,7 @@ func TestService_validateCredentialsStatus(t *testing.T) { wantErr bool }{ { - name: "OK with trustRegistryValidationEnabled == true and Wallet Attestation VC", + name: "OK", fields: fields{ getVcVerifier: func(t *testing.T) vcVerifier { mockVerifier := NewMockVcVerifier(gomock.NewController(t)) @@ -947,7 +784,6 @@ func TestService_validateCredentialsStatus(t *testing.T) { }, }, args: args{ - trustRegistryValidationEnabled: true, getCredentials: func(t *testing.T) []*verifiable.Credential { credContent := verifiable.CredentialContents{ Types: []string{ @@ -961,52 +797,7 @@ func TestService_validateCredentialsStatus(t *testing.T) { cred1, err := verifiable.CreateCredential(credContent, nil) assert.NoError(t, err) - attestationVCContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "WalletAttestationCredential", - }, - Status: &verifiable.TypedID{ID: "TypedID"}, - Issuer: &verifiable.Issuer{ID: "IssuerID"}, - } - - attestationVC, err := verifiable.CreateCredential(attestationVCContent, nil) - assert.NoError(t, err) - - return []*verifiable.Credential{cred1, attestationVC} - }, - }, - wantErr: false, - }, - { - name: "OK with trustRegistryValidationEnabled == false and Wallet Attestation VC", - fields: fields{ - getVcVerifier: func(t *testing.T) vcVerifier { - mockVerifier := NewMockVcVerifier(gomock.NewController(t)) - mockVerifier.EXPECT().ValidateVCStatus( - context.Background(), - &verifiable.TypedID{ID: "TypedID"}, - &verifiable.Issuer{ID: "IssuerID"}, - ).Times(1).Return(nil) - return mockVerifier - }, - }, - args: args{ - trustRegistryValidationEnabled: false, - getCredentials: func(t *testing.T) []*verifiable.Credential { - attestationVCContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "WalletAttestationCredential", - }, - Status: &verifiable.TypedID{ID: "TypedID"}, - Issuer: &verifiable.Issuer{ID: "IssuerID"}, - } - - attestationVC, err := verifiable.CreateCredential(attestationVCContent, nil) - assert.NoError(t, err) - - return []*verifiable.Credential{attestationVC} + return []*verifiable.Credential{cred1} }, }, wantErr: false, @@ -1019,7 +810,6 @@ func TestService_validateCredentialsStatus(t *testing.T) { }, }, args: args{ - trustRegistryValidationEnabled: false, getCredentials: func(t *testing.T) []*verifiable.Credential { credContent := verifiable.CredentialContents{ Types: []string{ @@ -1077,8 +867,7 @@ func TestService_validateCredentialsStatus(t *testing.T) { } if err := s.validateCredentialsStatus( context.Background(), - tt.args.getCredentials(t), - tt.args.trustRegistryValidationEnabled); (err != nil) != tt.wantErr { + tt.args.getCredentials(t)); (err != nil) != tt.wantErr { t.Errorf("validateCredentialsStatus() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -1202,13 +991,12 @@ func TestCheckTrustList(t *testing.T) { func TestService_checkCredentialExpiry(t *testing.T) { tests := []struct { - name string - getCredentials func() []*verifiable.Credential - wantErr assert.ErrorAssertionFunc - trustRegistryValidationEnabled bool + name string + getCredentials func() []*verifiable.Credential + wantErr assert.ErrorAssertionFunc }{ { - name: "Success with trustRegistryValidationEnabled == true and expired Wallet Attestation VC", + name: "Success", getCredentials: func() []*verifiable.Credential { credContent := verifiable.CredentialContents{ Types: []string{ @@ -1218,24 +1006,12 @@ func TestService_checkCredentialExpiry(t *testing.T) { Expired: timeutil.NewTime(time.Now().Add(time.Hour)), } - attestationVCContent := verifiable.CredentialContents{ - Types: []string{ - "VerifiableCredential", - "WalletAttestationCredential", - }, - Expired: timeutil.NewTime(time.Now().Add(-time.Hour)), - } - cred1, err := verifiable.CreateCredential(credContent, nil) assert.NoError(t, err) - attestationVC, err := verifiable.CreateCredential(attestationVCContent, nil) - assert.NoError(t, err) - - return []*verifiable.Credential{cred1, attestationVC} + return []*verifiable.Credential{cred1} }, - trustRegistryValidationEnabled: true, - wantErr: assert.NoError, + wantErr: assert.NoError, }, { name: "Error with expired VC", @@ -1253,7 +1029,6 @@ func TestService_checkCredentialExpiry(t *testing.T) { return []*verifiable.Credential{cred1} }, - trustRegistryValidationEnabled: true, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorContains(t, err, "credential expired") }, @@ -1261,11 +1036,10 @@ func TestService_checkCredentialExpiry(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() credentials := tt.getCredentials() tt.wantErr(t, - (&Service{}).checkCredentialExpiry(ctx, credentials, tt.trustRegistryValidationEnabled), - fmt.Sprintf("checkCredentialExpiry(%v, %v)", ctx, credentials), + (&Service{}).checkCredentialExpiry(credentials), + fmt.Sprintf("checkCredentialExpiry(%v)", credentials), ) }) } diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 6bd4072e4..cac9367ff 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -288,7 +288,7 @@ Feature: OIDC4VC REST API When User interacts with Wallet to initiate credential issuance using pre authorization code flow with client attestation enabled Then credential is issued - Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_client_attestation/v1.0" profile with presentation definition ID "attestation-vc-single-field" and fields "attestation_vc_type,degree_type_id" + Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_client_attestation/v1.0" profile with presentation definition ID "attestation-vc-single-field" and fields "degree_type_id" And Verifier with profile "v_myprofile_jwt_client_attestation/v1.0" retrieves interactions claims Then we wait 2 seconds And Verifier with profile "v_myprofile_jwt_client_attestation/v1.0" requests deleted interactions claims diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index dfd4f3e6b..c7fb989a1 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -1685,7 +1685,8 @@ "attest_jwt_client_auth" ], "pre-authorized_grant_anonymous_access_supported": true, - "claims_endpoint": "https://mock-login-consent.example.com:8099/claim-data?credentialType=PermanentResidentCard" + "claims_endpoint": "https://mock-login-consent.example.com:8099/claim-data?credentialType=PermanentResidentCard", + "signed_issuer_metadata_supported": true }, "credentialTemplates": [ { @@ -2578,34 +2579,6 @@ { "id": "attestation-vc-single-field", "input_descriptors": [ - { - "id": "attestation_vc_type", - "name": "type", - "purpose": "Wallet Attestation VC required", - "schema": [ - { - "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" - } - ], - "constraints": { - "fields": [ - { - "path": [ - "$.type" - ], - "id": "attestation_vc_type", - "purpose": "Wallet Attestation VC required", - "filter": { - "type": "array", - "contains": { - "type": "string", - "pattern": "WalletAttestationCredential" - } - } - } - ] - } - }, { "id": "degree_type_id", "name": "degree", diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go index a2ff9a10c..955a908c7 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go @@ -34,23 +34,20 @@ import ( "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vci" - jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" - kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" ) const ( vcsAPIGateway = "https://api-gateway.trustbloc.local:5566" initiateCredentialIssuanceURLFormat = vcsAPIGateway + "/issuer/profiles/%s/%s/interactions/initiate-oidc" - issueCredentialURLFormat = vcsAPIGateway + "/issuer/profiles/%s/%s/credentials/issue" issuedCredentialHistoryURL = vcsAPIGateway + "/issuer/profiles/%s/issued-credentials" vcsIssuerURL = vcsAPIGateway + "/oidc/idp/%s/%s" oidcProviderURL = "http://cognito-auth.local:8094/cognito" claimDataURL = "https://mock-login-consent.example.com:8099/claim-data" - attestationServiceURL = "https://mock-attestation.trustbloc.local:8097/profiles/profileID/profileVersion/wallet/attestation" ) func (s *Steps) authorizeIssuerProfileUser(profileVersionedID, username, password string) error { @@ -256,10 +253,6 @@ func (s *Steps) setProofType(proofType string) { } func (s *Steps) runOIDC4CIPreAuthWithClientAttestation() error { - if err := s.requestAttestationVC(); err != nil { - return fmt.Errorf("request attestation vc: %w", err) - } - claims, err := s.fetchClaimData(s.issuedCredentialType) if err != nil { return fmt.Errorf("fetchClaimData: %w", err) @@ -310,45 +303,6 @@ func (s *Steps) addProofBuilder(opt []oidc4vci.Opt) []oidc4vci.Opt { } } -func (s *Steps) requestAttestationVC() error { - didInfo := s.wallet.DIDs()[0] - - signer, err := s.oidc4vpProvider.cryptoSuite.FixedKeyMultiSigner(didInfo.KeyID) - if err != nil { - return fmt.Errorf("create signer: %w", err) - } - - jwsSigner := jwssigner.NewJWSSigner( - fmt.Sprintf("%s#%s", didInfo.ID, didInfo.KeyID), - string(s.wallet.SignatureType()), - kmssigner.NewKMSSigner(signer, s.wallet.SignatureType(), nil), - ) - - attestationVC, err := attestation.NewClient( - &attestation.Config{ - HTTPClient: s.oidc4vpProvider.httpClient, - DocumentLoader: s.oidc4vpProvider.documentLoader, - Signer: jwsSigner, - WalletDID: didInfo.ID, - AttestationURL: attestationServiceURL, - }, - ).GetAttestationVC(context.Background()) - if err != nil { - return fmt.Errorf("get attestation vc: %w", err) - } - - vcBytes, err := json.Marshal(attestationVC) - if err != nil { - return fmt.Errorf("marshal attestation vc: %w", err) - } - - if err = s.wallet.Add(vcBytes); err != nil { - return fmt.Errorf("add attestation vc to wallet: %w", err) - } - - return nil -} - func (s *Steps) runOIDC4CIPreAuthWithError(errorContains string) error { err := s.runOIDC4CIPreAuthWithValidClaims() if err == nil { @@ -1072,13 +1026,15 @@ func checkIssuer(vc *verifiable.Credential, expected string) error { } type oidc4vciProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet - wellKnownService *wellknown.Service + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet + wellKnownService *wellknown.Service } func (p *oidc4vciProvider) StorageProvider() storageapi.Provider { @@ -1101,6 +1057,14 @@ func (p *oidc4vciProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vciProvider) AttestationService() oidc4vci.AttestationService { + return p.attestationService +} + +func (p *oidc4vciProvider) TrustRegistry() oidc4vci.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vciProvider) Wallet() *wallet.Wallet { return p.wallet } diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vp.go b/test/bdd/pkg/v1/oidc4vc/oidc4vp.go index 29cd9de01..896b19186 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vp.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vp.go @@ -24,7 +24,9 @@ import ( "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/verifiable" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/pkg/event/spi" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" @@ -341,12 +343,14 @@ func (s *Steps) waitForEvent(eventType string) (string, error) { } type oidc4vpProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet } func (p *oidc4vpProvider) StorageProvider() storageapi.Provider { @@ -369,6 +373,14 @@ func (p *oidc4vpProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vpProvider) AttestationService() oidc4vp.AttestationService { + return p.attestationService +} + +func (p *oidc4vpProvider) TrustRegistry() oidc4vp.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vpProvider) Wallet() *wallet.Wallet { return p.wallet } diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index 7270dc1b4..1ca6ad97f 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -27,6 +27,8 @@ import ( "github.com/trustbloc/kms-go/wrapper/localsuite" longform "github.com/trustbloc/sidetree-go/pkg/vdr/sidetreelongform" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" profileapi "github.com/trustbloc/vcs/pkg/profile" @@ -35,6 +37,11 @@ import ( "github.com/trustbloc/vcs/test/stress/pkg/stress" ) +const ( + attestationServiceURL = "https://mock-attestation.trustbloc.local:8097/profiles/profileID/profileVersion/wallet/attestation" + trustRegistryHost = "https://mock-trustregistry.trustbloc.local:8098" +) + // Steps defines context for OIDC4VC scenario steps. type Steps struct { bddContext *bddcontext.BDDContext @@ -211,23 +218,44 @@ func (s *Steps) ResetAndSetup() error { s.wellKnownService = wellKnownService + attestationService, err := attestation.NewService( + &attestationServiceProvider{ + storageProvider: storageProvider, + httpClient: httpClient, + documentLoader: documentLoader, + cryptoSuite: suite, + }, + attestationServiceURL, + w.DIDs()[0], + w.SignatureType(), + ) + if err != nil { + return fmt.Errorf("create attestation service: %w", err) + } + + trustRegistry := trustregistry.NewClient(httpClient, trustRegistryHost) + s.oidc4vciProvider = &oidc4vciProvider{ - storageProvider: storageProvider, - httpClient: httpClient, - documentLoader: documentLoader, - vdrRegistry: vdRegistry, - cryptoSuite: suite, - wallet: w, - wellKnownService: wellKnownService, + storageProvider: storageProvider, + httpClient: httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + attestationService: attestationService, + trustRegistry: trustRegistry, + wallet: w, + wellKnownService: wellKnownService, } s.oidc4vpProvider = &oidc4vpProvider{ - storageProvider: storageProvider, - httpClient: httpClient, - documentLoader: documentLoader, - vdrRegistry: vdRegistry, - cryptoSuite: suite, - wallet: w, + storageProvider: storageProvider, + httpClient: httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + attestationService: attestationService, + trustRegistry: trustRegistry, + wallet: w, } return nil @@ -255,3 +283,26 @@ func (p *walletProvider) VDRegistry() vdrapi.Registry { func (p *walletProvider) KeyCreator() api.RawKeyCreator { return p.keyCreator } + +type attestationServiceProvider struct { + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + cryptoSuite api.Suite +} + +func (p *attestationServiceProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *attestationServiceProvider) HTTPClient() *http.Client { + return p.httpClient +} + +func (p *attestationServiceProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *attestationServiceProvider) CryptoSuite() api.Suite { + return p.cryptoSuite +} diff --git a/test/bdd/trustregistry/go.mod b/test/bdd/trustregistry/go.mod index 3a74e2d65..9d4e229ac 100644 --- a/test/bdd/trustregistry/go.mod +++ b/test/bdd/trustregistry/go.mod @@ -6,4 +6,9 @@ module github.com/trustbloc/vcs/test/bdd/trustregistry go 1.21 -require github.com/gorilla/mux v1.8.0 +require ( + github.com/gorilla/mux v1.8.0 + github.com/samber/lo v1.39.0 +) + +require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect diff --git a/test/bdd/trustregistry/go.sum b/test/bdd/trustregistry/go.sum index 535028803..10cc839bf 100644 --- a/test/bdd/trustregistry/go.sum +++ b/test/bdd/trustregistry/go.sum @@ -1,2 +1,6 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= diff --git a/test/bdd/trustregistry/models.go b/test/bdd/trustregistry/models.go index 3150691ae..64acc700e 100644 --- a/test/bdd/trustregistry/models.go +++ b/test/bdd/trustregistry/models.go @@ -6,6 +6,27 @@ SPDX-License-Identifier: Apache-2.0 package main +// WalletIssuanceRequest is a model for wallet issuance policy evaluation. +type WalletIssuanceRequest struct { + CredentialOffers *[]CredentialOffer `json:"credential_offers,omitempty"` + IssuerDID string `json:"issuer_did"` + IssuerDomain *string `json:"issuer_domain,omitempty"` +} + +// CredentialOffer is a model for CredentialOffer. +type CredentialOffer struct { + ClientAttestationRequested *bool `json:"client_attestation_requested,omitempty"` + CredentialFormat *string `json:"credential_format,omitempty"` + CredentialType *string `json:"credential_type,omitempty"` +} + +// WalletPresentationRequest is a model for wallet presentation policy evaluation. +type WalletPresentationRequest struct { + CredentialMetadata []CredentialMetadata `json:"credential_metadata"` + VerifierDID string `json:"verifier_did"` + VerifierDomain *string `json:"verifier_domain,omitempty"` +} + // IssuerIssuanceRequest is a model for issuer issuance policy evaluation. type IssuerIssuanceRequest struct { AttestationVC *[]string `json:"attestation_vc,omitempty"` diff --git a/test/bdd/trustregistry/server.go b/test/bdd/trustregistry/server.go index 0245ca11e..5ea21173a 100644 --- a/test/bdd/trustregistry/server.go +++ b/test/bdd/trustregistry/server.go @@ -13,6 +13,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/samber/lo" ) type server struct { @@ -26,6 +27,8 @@ func newServer() *server { router: router, } + router.HandleFunc("/wallet/interactions/issuance", srv.evaluateWalletIssuancePolicy).Methods(http.MethodPost) + router.HandleFunc("/wallet/interactions/presentation", srv.evaluateWalletPresentationPolicy).Methods(http.MethodPost) router.HandleFunc("/issuer/policies/{policyID}/{policyVersion}/interactions/issuance", srv.evaluateIssuerIssuancePolicy).Methods(http.MethodPost) router.HandleFunc("/verifier/policies/{policyID}/{policyVersion}/interactions/presentation", srv.evaluateVerifierPresentationPolicy).Methods(http.MethodPost) @@ -36,6 +39,89 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } +func (s *server) evaluateWalletIssuancePolicy(w http.ResponseWriter, r *http.Request) { + var request WalletIssuanceRequest + + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + s.writeResponse( + w, http.StatusBadRequest, fmt.Sprintf("decode issuance policy request: %s", err.Error())) + + return + } + + if request.IssuerDID == "" { + log.Println("WARNING! issuer did is empty") + } + + if len(lo.FromPtr(request.CredentialOffers)) == 0 { + s.writeResponse( + w, http.StatusBadRequest, "no credential offers supplied") + + return + } + + log.Printf("handling request: %s with payload %v", r.URL.String(), request) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + response := &PolicyEvaluationResponse{ + Allowed: true, + Payload: &map[string]interface{}{ + "attestations_required": []string{"wallet_authentication", "wallet_compliance"}, + }, + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + log.Printf("failed to write response: %s", err.Error()) + } +} + +func (s *server) evaluateWalletPresentationPolicy(w http.ResponseWriter, r *http.Request) { + var request WalletPresentationRequest + + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + s.writeResponse( + w, http.StatusBadRequest, fmt.Sprintf("decode presentation policy request: %s", err.Error())) + + return + } + + if request.VerifierDID == "" { + s.writeResponse( + w, http.StatusBadRequest, "verifier did is empty") + + return + } + + if len(request.CredentialMetadata) == 0 { + s.writeResponse( + w, http.StatusBadRequest, "no credential metadata supplied") + + return + } + + log.Printf("handling request: %s with payload %v", r.URL.String(), request) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + response := &PolicyEvaluationResponse{ + Allowed: true, + Payload: &map[string]interface{}{ + "attestations_required": []string{"wallet_authentication", "wallet_compliance"}, + }, + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + log.Printf("failed to write response: %s", err.Error()) + } +} + func (s *server) evaluateIssuerIssuancePolicy(w http.ResponseWriter, r *http.Request) { var request IssuerIssuanceRequest @@ -68,6 +154,9 @@ func (s *server) evaluateIssuerIssuancePolicy(w http.ResponseWriter, r *http.Req response := &PolicyEvaluationResponse{ Allowed: true, + Payload: &map[string]interface{}{ + "attestations_required": []string{"wallet_authentication", "wallet_compliance"}, + }, } err = json.NewEncoder(w).Encode(response) @@ -115,6 +204,9 @@ func (s *server) evaluateVerifierPresentationPolicy(w http.ResponseWriter, r *ht response := &PolicyEvaluationResponse{ Allowed: true, + Payload: &map[string]interface{}{ + "attestations_required": []string{"wallet_authentication", "wallet_compliance"}, + }, } err = json.NewEncoder(w).Encode(response) diff --git a/test/stress/pkg/stress/providers.go b/test/stress/pkg/stress/providers.go index 9dd6069f0..e0b953c14 100644 --- a/test/stress/pkg/stress/providers.go +++ b/test/stress/pkg/stress/providers.go @@ -18,6 +18,10 @@ import ( storageapi "github.com/trustbloc/kms-go/spi/storage" "github.com/trustbloc/kms-go/wrapper/api" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vci" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" ) @@ -46,13 +50,15 @@ func (p *walletProvider) KeyCreator() api.RawKeyCreator { } type oidc4vciProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet - wellKnownService *wellknown.Service + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet + wellKnownService *wellknown.Service } func (p *oidc4vciProvider) StorageProvider() storageapi.Provider { @@ -75,6 +81,14 @@ func (p *oidc4vciProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vciProvider) AttestationService() oidc4vci.AttestationService { + return p.attestationService +} + +func (p *oidc4vciProvider) TrustRegistry() oidc4vci.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vciProvider) Wallet() *wallet.Wallet { return p.wallet } @@ -84,12 +98,14 @@ func (p *oidc4vciProvider) WellKnownService() *wellknown.Service { } type oidc4vpProvider struct { - storageProvider storageapi.Provider - httpClient *http.Client - documentLoader ld.DocumentLoader - vdrRegistry vdrapi.Registry - cryptoSuite api.Suite - wallet *wallet.Wallet + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + attestationService *attestation.Service + trustRegistry *trustregistry.Client + wallet *wallet.Wallet } func (p *oidc4vpProvider) StorageProvider() storageapi.Provider { @@ -112,6 +128,14 @@ func (p *oidc4vpProvider) CryptoSuite() api.Suite { return p.cryptoSuite } +func (p *oidc4vpProvider) AttestationService() oidc4vp.AttestationService { + return p.attestationService +} + +func (p *oidc4vpProvider) TrustRegistry() oidc4vp.TrustRegistry { + return p.trustRegistry +} + func (p *oidc4vpProvider) Wallet() *wallet.Wallet { return p.wallet } diff --git a/test/stress/pkg/stress/stress_test_case.go b/test/stress/pkg/stress/stress_test_case.go index 3d2a0d5cf..5385543e1 100644 --- a/test/stress/pkg/stress/stress_test_case.go +++ b/test/stress/pkg/stress/stress_test_case.go @@ -17,6 +17,7 @@ import ( "strings" "time" + "github.com/piprate/json-gold/ld" "github.com/trustbloc/did-go/legacy/mem" "github.com/trustbloc/did-go/method/jwk" "github.com/trustbloc/did-go/method/key" @@ -24,13 +25,17 @@ import ( "github.com/trustbloc/did-go/vdr" "github.com/trustbloc/kms-go/kms" "github.com/trustbloc/kms-go/secretlock/noop" + storageapi "github.com/trustbloc/kms-go/spi/storage" + "github.com/trustbloc/kms-go/wrapper/api" "github.com/trustbloc/kms-go/wrapper/localsuite" "github.com/trustbloc/logutil-go/pkg/log" longform "github.com/trustbloc/sidetree-go/pkg/vdr/sidetreelongform" "github.com/trustbloc/vc-go/verifiable" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vci" "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/trustregistry" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" @@ -38,6 +43,11 @@ import ( "github.com/trustbloc/vcs/test/bdd/pkg/v1/model" ) +const ( + attestationServiceURL = "https://mock-attestation.trustbloc.local:8097/profiles/profileID/profileVersion/wallet/attestation" + trustRegistryHost = "https://mock-trustregistry.trustbloc.local:8098" +) + type TestCase struct { oidc4vciProvider *oidc4vciProvider oidc4vpProvider *oidc4vpProvider @@ -165,23 +175,44 @@ func NewTestCase(options ...TestCaseOption) (*TestCase, error) { VDRRegistry: vdRegistry, } - return &TestCase{ - oidc4vciProvider: &oidc4vciProvider{ - storageProvider: storageProvider, - httpClient: opts.httpClient, - documentLoader: documentLoader, - vdrRegistry: vdRegistry, - cryptoSuite: suite, - wallet: w, - wellKnownService: wellKnownService, - }, - oidc4vpProvider: &oidc4vpProvider{ + attestationService, err := attestation.NewService( + &attestationServiceProvider{ storageProvider: storageProvider, httpClient: opts.httpClient, documentLoader: documentLoader, - vdrRegistry: vdRegistry, cryptoSuite: suite, - wallet: w, + }, + attestationServiceURL, + w.DIDs()[0], + w.SignatureType(), + ) + if err != nil { + return nil, fmt.Errorf("create attestation service: %w", err) + } + + trustRegistry := trustregistry.NewClient(opts.httpClient, trustRegistryHost) + + return &TestCase{ + oidc4vciProvider: &oidc4vciProvider{ + storageProvider: storageProvider, + httpClient: opts.httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + attestationService: attestationService, + trustRegistry: trustRegistry, + wallet: w, + wellKnownService: wellKnownService, + }, + oidc4vpProvider: &oidc4vpProvider{ + storageProvider: storageProvider, + httpClient: opts.httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + attestationService: attestationService, + trustRegistry: trustRegistry, + wallet: w, }, wallet: w, httpClient: opts.httpClient, @@ -536,3 +567,26 @@ func (c *TestCase) fetchAuthorizationRequest() (string, error) { return parsedResp.AuthorizationRequest, nil } + +type attestationServiceProvider struct { + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + cryptoSuite api.Suite +} + +func (p *attestationServiceProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *attestationServiceProvider) HTTPClient() *http.Client { + return p.httpClient +} + +func (p *attestationServiceProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *attestationServiceProvider) CryptoSuite() api.Suite { + return p.cryptoSuite +}