diff --git a/Makefile b/Makefile index 7c8984d79..047819de4 100644 --- a/Makefile +++ b/Makefile @@ -205,6 +205,8 @@ stress-test: VCS_API_URL= \ ISSUER_PROFILE_ID= \ VERIFIER_PROFILE_ID= \ + VERIFIER_PROFILE_VERSION= \ + VERIFIER_PRESENTATION_ID= \ CREDENTIAL_TEMPLATE_ID= \ TOKEN_CLIENT_ID= \ TOKEN_CLIENT_SECRET= \ diff --git a/component/wallet-cli/internal/httputil/httputil.go b/component/wallet-cli/internal/httputil/httputil.go deleted file mode 100644 index e2d417121..000000000 --- a/component/wallet-cli/internal/httputil/httputil.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package httputil - -import ( - "crypto/tls" - "io" - "log" - "net/http" -) - -// HTTPSDo send https request -func HTTPSDo(method, url, contentType, token string, body io.Reader, tlsConfig *tls.Config) (*http.Response, error) { - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - - if contentType != "" { - req.Header.Add("Content-Type", contentType) - } - - if token != "" { - req.Header.Add("Authorization", "Bearer "+token) - } - - c := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} - - return c.Do(req) -} - -// CloseResponseBody closes the response body. -func CloseResponseBody(respBody io.Closer) { - err := respBody.Close() - if err != nil { - log.Println("Failed to close response body", err) - } -} diff --git a/component/wallet-cli/internal/oauth2util/oauth2util.go b/component/wallet-cli/internal/oauth2util/oauth2util.go deleted file mode 100644 index c7ee0327a..000000000 --- a/component/wallet-cli/internal/oauth2util/oauth2util.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package oauth2util - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - - "golang.org/x/oauth2" - "golang.org/x/oauth2/clientcredentials" -) - -func Token(ctx context.Context, oidcProviderURL, clientID, secret string, scopes []string, tls *tls.Config) (string, error) { - conf := clientcredentials.Config{ - TokenURL: oidcProviderURL + "/oauth2/token", - ClientID: clientID, - ClientSecret: secret, - Scopes: scopes, - AuthStyle: oauth2.AuthStyleInHeader, - } - - ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tls, - }, - }) - - token, err := conf.Token(ctx) - if err != nil { - return "", fmt.Errorf("failed to get token: %w", err) - } - - return token.AccessToken, nil -} diff --git a/component/wallet-cli/pkg/walletrunner/consent/cognito.go b/component/wallet-cli/pkg/consent/cognito.go similarity index 100% rename from component/wallet-cli/pkg/walletrunner/consent/cognito.go rename to component/wallet-cli/pkg/consent/cognito.go diff --git a/component/wallet-cli/pkg/walletrunner/consent/cognito_test.go b/component/wallet-cli/pkg/consent/cognito_test.go similarity index 98% rename from component/wallet-cli/pkg/walletrunner/consent/cognito_test.go rename to component/wallet-cli/pkg/consent/cognito_test.go index 3f972105b..51d923273 100644 --- a/component/wallet-cli/pkg/walletrunner/consent/cognito_test.go +++ b/component/wallet-cli/pkg/consent/cognito_test.go @@ -17,7 +17,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/consent" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/consent" ) func TestCognitoConsent(t *testing.T) { diff --git a/component/wallet-cli/pkg/walletrunner/consent/interfaces.go b/component/wallet-cli/pkg/consent/interfaces.go similarity index 100% rename from component/wallet-cli/pkg/walletrunner/consent/interfaces.go rename to component/wallet-cli/pkg/consent/interfaces.go diff --git a/component/wallet-cli/pkg/oidc4vci/models.go b/component/wallet-cli/pkg/oidc4vci/models.go index bcf13bebb..4bd34cb34 100644 --- a/component/wallet-cli/pkg/oidc4vci/models.go +++ b/component/wallet-cli/pkg/oidc4vci/models.go @@ -6,7 +6,11 @@ SPDX-License-Identifier: Apache-2.0 package oidc4vci -import "github.com/trustbloc/vcs/pkg/doc/verifiable" +import ( + "time" + + "github.com/trustbloc/vcs/pkg/doc/verifiable" +) type JWTProofClaims struct { Issuer string `json:"iss,omitempty"` @@ -34,3 +38,10 @@ type CredentialResponse struct { Format verifiable.OIDCFormat `json:"format"` AckID *string `json:"ack_id"` } + +type PerfInfo struct { + GetIssuerCredentialsOIDCConfig time.Duration `json:"vci_get_issuer_credentials_oidc_config"` + GetAccessToken time.Duration `json:"vci_get_access_token"` + GetCredential time.Duration `json:"vci_get_credential"` + CredentialsAck time.Duration `json:"vci_credentials_ack"` +} diff --git a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go index 99939f8cd..9bd95c539 100644 --- a/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go +++ b/component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go @@ -35,11 +35,11 @@ import ( "github.com/trustbloc/vc-go/verifiable" "golang.org/x/oauth2" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/consent" "github.com/trustbloc/vcs/component/wallet-cli/pkg/credentialoffer" 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/walletrunner/consent" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" "github.com/trustbloc/vcs/pkg/restapi/v1/common" @@ -87,6 +87,7 @@ type Flow struct { pin string walletKeyID string walletKeyType kms.KeyType + perfInfo *PerfInfo } type provider interface { @@ -210,6 +211,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { issuerState: o.issuerState, pin: o.pin, trustRegistryURL: o.trustRegistryURL, + perfInfo: &PerfInfo{}, }, nil } @@ -250,11 +252,15 @@ func (f *Flow) Run(ctx context.Context) (*verifiable.Credential, error) { issuerState = f.issuerState } + start := time.Now() + openIDConfig, err := f.wellKnownService.GetWellKnownOpenIDConfiguration(credentialIssuer) if err != nil { return nil, err } + f.perfInfo.GetIssuerCredentialsOIDCConfig = time.Since(start) + requireWalletAttestation := openIDConfig.TokenEndpointAuthMethodsSupported != nil && lo.Contains(openIDConfig.TokenEndpointAuthMethodsSupported, attestJWTClientAuthType) @@ -292,6 +298,8 @@ func (f *Flow) Run(ctx context.Context) (*verifiable.Credential, error) { var token *oauth2.Token + start = time.Now() + if f.flowType == FlowTypeAuthorizationCode || f.flowType == FlowTypeWalletInitiated { oauthClient := &oauth2.Config{ ClientID: f.clientID, @@ -392,6 +400,8 @@ func (f *Flow) Run(ctx context.Context) (*verifiable.Credential, error) { ) } + f.perfInfo.GetAccessToken = time.Since(start) + vc, err := f.receiveVC(token, openIDConfig, credentialIssuer) if err != nil { return nil, err @@ -678,6 +688,11 @@ func (f *Flow) receiveVC( ) (*verifiable.Credential, error) { credentialEndpoint := wellKnown.CredentialEndpoint + start := time.Now() + defer func() { + f.perfInfo.GetCredential = time.Since(start) + }() + slog.Info("Getting credential", "credential_endpoint", credentialEndpoint, "credential_issuer", credentialIssuer, @@ -814,6 +829,11 @@ func (f *Flow) handleIssuanceAck( return nil } + start := time.Now() + defer func() { + f.perfInfo.CredentialsAck = time.Since(start) + }() + slog.Info("Sending wallet ACK", "ack_id", credResponse.AckID, "endpoint", wellKnown.CredentialAckEndpoint, @@ -857,6 +877,10 @@ func (f *Flow) handleIssuanceAck( return nil } +func (f *Flow) PerfInfo() *PerfInfo { + return f.perfInfo +} + func waitForEnter( done chan<- struct{}, ) { diff --git a/component/wallet-cli/pkg/oidc4vp/models.go b/component/wallet-cli/pkg/oidc4vp/models.go index cbe8e099d..402d54679 100644 --- a/component/wallet-cli/pkg/oidc4vp/models.go +++ b/component/wallet-cli/pkg/oidc4vp/models.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package oidc4vp import ( + "time" + "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/verifiable" ) @@ -71,3 +73,11 @@ type VPTokenClaims struct { Iat int64 `json:"iat"` Jti string `json:"jti"` } + +type PerfInfo struct { + FetchRequestObject time.Duration `json:"vp_fetch_request_object"` + VerifyAuthorizationRequest time.Duration `json:"vp_verify_authorization_request"` + QueryCredentialFromWallet time.Duration `json:"vp_query_credential_from_wallet"` + CreateAuthorizedResponse time.Duration `json:"vp_create_authorized_response"` + SendAuthorizedResponse time.Duration `json:"vp_send_authorized_response"` +} diff --git a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go index a482b640e..ff71b73d2 100644 --- a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go +++ b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go @@ -66,6 +66,7 @@ type Flow struct { disableDomainMatching bool disableSchemaValidation bool trustRegistryURL string + perfInfo *PerfInfo } type provider interface { @@ -131,6 +132,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { disableDomainMatching: o.disableDomainMatching, disableSchemaValidation: o.disableSchemaValidation, trustRegistryURL: o.trustRegistryURL, + perfInfo: &PerfInfo{}, }, nil } @@ -211,6 +213,8 @@ func (f *Flow) fetchRequestObject(ctx context.Context) (*RequestObject, error) { "uri", f.requestURI, ) + start := time.Now() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, f.requestURI, http.NoBody) if err != nil { return nil, fmt.Errorf("new request object request: %w", err) @@ -239,6 +243,13 @@ func (f *Flow) fetchRequestObject(ctx context.Context) (*RequestObject, error) { _ = resp.Body.Close() + f.perfInfo.FetchRequestObject = time.Since(start) + + start = time.Now() + defer func() { + f.perfInfo.VerifyAuthorizationRequest = time.Since(start) + }() + jwtVerifier := defaults.NewDefaultProofChecker( vermethod.NewVDRResolver(f.vdrRegistry), ) @@ -332,6 +343,11 @@ func getServiceType(serviceType interface{}) string { func (f *Flow) queryWallet(pd *presexch.PresentationDefinition) (*verifiable.Presentation, error) { slog.Info("Querying wallet") + start := time.Now() + defer func() { + f.perfInfo.QueryCredentialFromWallet = time.Since(start) + }() + b, err := json.Marshal(pd) if err != nil { return nil, fmt.Errorf("marshal presentation definition: %w", err) @@ -370,6 +386,8 @@ func (f *Flow) sendAuthorizationResponse( "redirect_uri", requestObject.RedirectURI, ) + start := time.Now() + presentationSubmission, ok := vp.CustomFields["presentation_submission"].(*presexch.PresentationSubmission) if !ok { return fmt.Errorf("missing or invalid presentation_submission") @@ -401,6 +419,8 @@ func (f *Flow) sendAuthorizationResponse( "state": {requestObject.State}, } + f.perfInfo.CreateAuthorizedResponse = time.Since(start) + return f.postAuthorizationResponse(ctx, requestObject.RedirectURI, []byte(v.Encode())) } @@ -557,7 +577,7 @@ func (f *Flow) createIDToken( presentationSubmission *presexch.PresentationSubmission, clientID, nonce, requestObjectScope string, ) (string, error) { - scopeAdditionalClaims, err := ExtractCustomScopeClaims(requestObjectScope) + scopeAdditionalClaims, err := extractCustomScopeClaims(requestObjectScope) if err != nil { return "", fmt.Errorf("extractAdditionalClaims: %w", err) } @@ -594,11 +614,47 @@ func (f *Flow) createIDToken( return idTokenJSON, nil } +func extractCustomScopeClaims(requestObjectScope string) (map[string]Claims, error) { + chunks := strings.Split(requestObjectScope, "+") + if len(chunks) == 1 { + return nil, nil + } + + claimsData := make(map[string]Claims, len(chunks)-1) + + for _, scope := range chunks { + switch scope { + case scopeOpenID: + // scopeOpenID is a required specification scope, so no additional claims for this case. + continue + case customScopeTimeDetails: + claimsData[scope] = Claims{ + "timestamp": time.Now().Format(time.RFC3339), + "uuid": uuid.NewString(), + } + case customScopeWalletDetails: + claimsData[scope] = Claims{ + "wallet_version": "1.0", + "uuid": uuid.NewString(), + } + default: + return nil, fmt.Errorf("unexpected custom scope \"%s\" supplied", chunks[1]) + } + } + + return claimsData, nil +} + func (f *Flow) postAuthorizationResponse(ctx context.Context, redirectURI string, body []byte) error { slog.Info("Sending authorization response", "redirect_uri", redirectURI, ) + start := time.Now() + defer func() { + f.perfInfo.SendAuthorizedResponse = time.Since(start) + }() + req, err := http.NewRequestWithContext(ctx, http.MethodPost, redirectURI, bytes.NewBuffer(body)) if err != nil { return fmt.Errorf("new authorization response request: %w", err) @@ -636,6 +692,10 @@ func (f *Flow) postAuthorizationResponse(ctx context.Context, redirectURI string return nil } +func (f *Flow) PerfInfo() *PerfInfo { + return f.perfInfo +} + type options struct { walletDIDIndex int requestURI string @@ -682,35 +742,3 @@ func WithTrustRegistryURL(url string) Opt { opts.trustRegistryURL = url } } - -// ExtractCustomScopeClaims returns Claims associated with custom scope. -func ExtractCustomScopeClaims(requestObjectScope string) (map[string]Claims, error) { - chunks := strings.Split(requestObjectScope, "+") - if len(chunks) == 1 { - return nil, nil - } - - claimsData := make(map[string]Claims, len(chunks)-1) - - for _, scope := range chunks { - switch scope { - case scopeOpenID: - // scopeOpenID is a required specification scope, so no additional claims for this case. - continue - case customScopeTimeDetails: - claimsData[scope] = Claims{ - "timestamp": time.Now().Format(time.RFC3339), - "uuid": uuid.NewString(), - } - case customScopeWalletDetails: - claimsData[scope] = Claims{ - "wallet_version": "1.0", - "uuid": uuid.NewString(), - } - default: - return nil, fmt.Errorf("unexpected custom scope \"%s\" supplied", chunks[1]) - } - } - - return claimsData, nil -} diff --git a/component/wallet-cli/pkg/walletrunner/aries_services.go b/component/wallet-cli/pkg/walletrunner/aries_services.go deleted file mode 100644 index 74e3e7918..000000000 --- a/component/wallet-cli/pkg/walletrunner/aries_services.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "fmt" - - jsonld "github.com/piprate/json-gold/ld" - vdrapi "github.com/trustbloc/did-go/vdr/api" - "github.com/trustbloc/kms-go/spi/storage" - "github.com/trustbloc/kms-go/wrapper/api" -) - -type ariesServices struct { - storageProvider storage.Provider - vdrRegistry vdrapi.Registry - suite api.Suite - documentLoader jsonld.DocumentLoader - mediaTypeProfiles []string -} - -func (p *ariesServices) StorageProvider() storage.Provider { - return p.storageProvider -} - -func (p *ariesServices) SetStorageProvider(sp storage.Provider) { - p.storageProvider = sp -} - -func (p *ariesServices) VDRegistry() vdrapi.Registry { - return p.vdrRegistry -} - -func (p *ariesServices) Suite() api.Suite { - return p.suite -} - -func (p *ariesServices) JSONLDDocumentLoader() jsonld.DocumentLoader { - return p.documentLoader -} - -func (p *ariesServices) MediaTypeProfiles() []string { - return p.mediaTypeProfiles -} - -// Close frees resources being maintained by the framework. -func (p *ariesServices) Close() error { - if p.storageProvider != nil { - err := p.storageProvider.Close() - if err != nil { - return fmt.Errorf("failed to close the store: %w", err) - } - } - - if p.vdrRegistry != nil { - if err := p.vdrRegistry.Close(); err != nil { - return fmt.Errorf("vdr registry close failed: %w", err) - } - } - - return nil -} diff --git a/component/wallet-cli/pkg/walletrunner/contexts/citizenship-v1.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/citizenship-v1.jsonld deleted file mode 100644 index a493be8be..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/citizenship-v1.jsonld +++ /dev/null @@ -1,54 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "@protected": true, - - "name": "http://schema.org/name", - "description": "http://schema.org/description", - "identifier": "http://schema.org/identifier", - "image": {"@id": "http://schema.org/image", "@type": "@id"}, - - "PermanentResidentCard": { - "@id": "https://w3id.org/citizenship#PermanentResidentCard", - "@context": { - "@version": 1.1, - "@protected": true, - - "id": "@id", - "type": "@type", - - "description": "http://schema.org/description", - "name": "http://schema.org/name", - "identifier": "http://schema.org/identifier", - "image": {"@id": "http://schema.org/image", "@type": "@id"} - } - }, - - "PermanentResident": { - "@id": "https://w3id.org/citizenship#PermanentResident", - "@context": { - "@version": 1.1, - "@protected": true, - - "id": "@id", - "type": "@type", - - "ctzn": "https://w3id.org/citizenship#", - "schema": "http://schema.org/", - "xsd": "http://www.w3.org/2001/XMLSchema#", - - "birthCountry": "ctzn:birthCountry", - "birthDate": {"@id": "schema:birthDate", "@type": "xsd:dateTime"}, - "commuterClassification": "ctzn:commuterClassification", - "familyName": "schema:familyName", - "gender": "schema:gender", - "givenName": "schema:givenName", - "lprCategory": "ctzn:lprCategory", - "lprNumber": "ctzn:lprNumber", - "residentSince": {"@id": "ctzn:residentSince", "@type": "xsd:dateTime"} - } - }, - - "Person": "http://schema.org/Person" - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/examples-crude-product-v1.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/examples-crude-product-v1.jsonld deleted file mode 100644 index 18adbae5b..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/examples-crude-product-v1.jsonld +++ /dev/null @@ -1,88 +0,0 @@ -{ - "@context": - { - "@version": 1.1, - "cp": "https://mavennet.github.io/contexts/crude-product-vocab#", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "schema": "http://schema.org/", - "prm": "http://schema.org/ProductModel", - "CrudeProductCredential": "schema:CrudeProductCredential", - "producer": "cp:producer", - "category": "cp:category", - "hsCode": "cp:hsCode", - "identifier": "prm:identifier", - "name": "prm:name", - "description": "prm:description", - "volume": "cp:volume", - "address": "prm:address", - "productionDate": "prm:productionDate", - "predecessorOf": "prm:predecessorOf", - "successorOf": "prm:successorOf", - "physicalSpecs": "cp:physicalSpecs", - "chemicalSpecs": "cp:chemicalSpecs", - "latitude": "cp:latitude", - "longitude": "cp:longitude", - "uom": "cp:uom", - "minimumQuantity": "cp:minimumQuantity", - "apiGravity": "cp:apiGravity", - "viscosityAt10C": "cp:viscosityAt10C", - "viscosityAt20C": "cp:viscosityAt20C", - "viscosityAt30C": "cp:viscosityAt30C", - "viscosityAt40C": "cp:viscosityAt40C", - "viscosityAt45C": "cp:viscosityAt45C", - "pourPoint": "cp:pourPoint", - "vapourPressure": "cp:vapourPressure", - "density": "cp:density", - "naphtha": "cp:naphtha", - "distillateAt350To650F": "cp:distillateAt350To650F", - "gasOilAt650To980F": "cp:gasOilAt650To980F", - "residAt980F": "cp:residAt980F", - "deemedButane": "cp:deemedButane", - "tan": "cp:tan", - "ron": "cp:ron", - "mon": "cp:mon", - "boilingPoint": "cp:boilingPoint", - "freezingPoint": "cp:freezingPoint", - "criticalTemperature": "cp:criticalTemperature", - "criticalPressure": "cp:criticalPressure", - "autoIgnitionTemperatureInAirAt1atm": "cp:autoIgnitionTemperatureInAirAt1atm", - "solubilityInTrichloroethylene": "cp:solubilityInTrichloroethylene", - "penetrationAt25C100g5sec": "cp:penetrationAt25C100g5sec", - "softeningPoint": "cp:softeningPoint", - "ductilityAt25C": "cp:ductilityAt25C", - "olefin": "cp:olefin", - "color": "cp:color", - "odor": "cp:odor", - "grossCalorificValueAt15C": "cp:grossCalorificValueAt15C", - "netCalorificValueAt15C": "cp:netCalorificValueAt15C", - "airRequiredForCombustion": "cp:airRequiredForCombustion", - "copperCorrosionAt38CFor1Hour": "cp:copperCorrosionAt38CFor1Hour", - "microCarbonResidue": "cp:microCarbonResidue", - "aromaticsTotalBTEX": "cp:aromaticsTotalBTEX", - "sedimentAndWater": "cp:sedimentAndWater", - "liquidPhaseH2S": "cp:liquidPhaseH2S", - "mercury": "cp:mercury", - "oxygenates": "cp:oxygenates", - "filterableSolids": "cp:filterableSolids", - "phosphorousVolatile": "cp:phosphorousVolatile", - "mediumChainTriglycerides": "cp:mediumChainTriglycerides", - "benzene": "cp:benzene", - "particulates": "cp:particulates", - "organicChlorides": "cp:organicChlorides", - "nickel": "cp:nickel", - "vanadium": "cp:vanadium", - "water": "cp:water", - "molecularWeight": "cp:molecularWeight", - "sulphur": "cp:sulphur", - "naphthenes": "cp:naphthenes", - "chloride": "cp:chloride", - "arsenic": "cp:arsenic", - "lead": "cp:lead", - "ethene": "cp:ethene", - "propane": "cp:propane", - "isoButane": "cp:isoButane", - "nButane": "cp:nButane", - "hydrocarbonsHeavier": "cp:hydrocarbonsHeavier", - "unsaturatedHydrocarbons": "cp:unsaturatedHydrocarbons" - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/examples-ext-v1.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/examples-ext-v1.jsonld deleted file mode 100644 index 6740cbc02..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/examples-ext-v1.jsonld +++ /dev/null @@ -1,193 +0,0 @@ -{ - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "ex": "https://example.org/examples#", - "name": "http://schema.org/name", - "description": "http://schema.org/description", - "image": { - "@id": "http://schema.org/image", - "@type": "@id" - }, - "hetc": "https://w3c-ccg.github.io/vc-json-schemas/example/cmtr/v0.2/cmtr-v0.2#", - "StudentCard": { - "@id": "ex:StudentCard", - "@context": { - "@version": 1.1, - "@protected": true, - "id": "@id", - "type": "@type", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "ex": "https://example.org/examples#", - "name": "http://schema.org/name", - "description": "http://schema.org/description", - "name": { - "@id": "ex:name", - "@type": "xsd:string" - }, - "email": { - "@id": "ex:email", - "@type": "xsd:string" - }, - "semester": { - "@id": "ex:semester", - "@type": "xsd:string" - }, - "studentid": { - "@id": "ex:studentid", - "@type": "xsd:string" - }, - "university": { - "@id": "ex:university", - "@type": "xsd:string" - } - } - }, - "PRCard": { - "@id": "ex:PRCard", - "@context": { - "@version": 1.1, - "@protected": true, - "id": "@id", - "type": "@type", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "ex": "https://example.org/examples#", - "birthcountry": { - "@id": "ex:birthcountry", - "@type": "xsd:string" - }, - "birthdate": { - "@id": "ex:birthdate", - "@type": "xsd:birthdate" - }, - "familyname": { - "@id": "ex:familyname", - "@type": "xsd:string" - }, - "gender": { - "@id": "ex:gender", - "@type": "xsd:string" - }, - "image": { - "@id": "ex:image", - "@type": "xsd:string" - }, - "lprcategory": { - "@id": "ex:lprcategory", - "@type": "xsd:string" - }, - "lprnumber": { - "@id": "ex:lprnumber", - "@type": "xsd:string" - }, - "name": { - "@id": "ex:name", - "@type": "xsd:string" - }, - "residentsince": { - "@id": "ex:residentsince", - "@type": "xsd:string" - } - } - }, - "TravelCard": { - "@id": "ex:TravelCard", - "@context": { - "@version": 1.1, - "@protected": true, - "id": "@id", - "type": "@type", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "ex": "https://example.org/examples#", - "cardexpires": { - "@id": "ex:cardexpires", - "@type": "xsd:string" - }, - "country": { - "@id": "ex:country", - "@type": "xsd:birthdate" - }, - "dob": { - "@id": "ex:dob", - "@type": "xsd:string" - }, - "issuedate": { - "@id": "ex:issuedate", - "@type": "xsd:string" - }, - "name": "http://schema.org/name", - "sex": { - "@id": "ex:sex", - "@type": "xsd:string" - }, - "lprnumber": { - "@id": "ex:lprnumber", - "@type": "xsd:string" - }, - "travelcardid": { - "@id": "ex:travelcardid", - "@type": "xsd:string" - } - } - }, - "CertifiedMillTestReport": { - "@id": "ex:CertifiedMillTestReport", - "@context": { - "@version": 1.1, - "@protected": true - } - }, - "cmtr": { - "@id": "hetc:cmtr", - "@type": "@json" - }, - "DIDConnection": { - "@id": "ex:DIDConnection", - "@context": { - "@version": 1.1, - "@protected": true, - "id": "@id", - "type": "@type", - "ex": "https://example.org/examples#", - "threadID": { - "@id": "ex:threadID", - "@type": "xsd:string" - }, - "inviteeDID": { - "@id": "ex:inviteeDID", - "@type": "xsd:birthdate" - }, - "inviterDID": { - "@id": "ex:inviterDID", - "@type": "xsd:string" - }, - "inviterLabel": { - "@id": "ex:inviterLabel", - "@type": "xsd:string" - }, - "connectionState": { - "@id": "ex:connectionState", - "@type": "xsd:string" - } - } - }, - "CreditCardStatement": { - "@id": "ex:CreditCardStatement", - "@context": { - "@version": 1.1, - "id": "@id", - "type": "@type", - "ex": "https://example.org/examples#", - "credentialSubject": { - "@id": "ex:credentialSubject", - "@context": { - "@version": 1.1, - "@protected": true, - "stmt": "schema:Invoice" - } - } - } - } - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/examples-v1.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/examples-v1.jsonld deleted file mode 100644 index 629cdaddc..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/examples-v1.jsonld +++ /dev/null @@ -1,49 +0,0 @@ -{ - "@context": [{ - "@version": 1.1 - },"https://www.w3.org/ns/odrl.jsonld", { - "ex": "https://example.org/examples#", - "schema": "http://schema.org/", - "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - - "3rdPartyCorrelation": "ex:3rdPartyCorrelation", - "AllVerifiers": "ex:AllVerifiers", - "Archival": "ex:Archival", - "BachelorDegree": "ex:BachelorDegree", - "Child": "ex:Child", - "CLCredentialDefinition2019": "ex:CLCredentialDefinition2019", - "CLSignature2019": "ex:CLSignature2019", - "IssuerPolicy": "ex:IssuerPolicy", - "HolderPolicy": "ex:HolderPolicy", - "Mother": "ex:Mother", - "RelationshipCredential": "ex:RelationshipCredential", - "UniversityDegreeCredential": "ex:UniversityDegreeCredential", - "ZkpExampleSchema2018": "ex:ZkpExampleSchema2018", - - "issuerData": "ex:issuerData", - "attributes": "ex:attributes", - "signature": "ex:signature", - "signatureCorrectnessProof": "ex:signatureCorrectnessProof", - "primaryProof": "ex:primaryProof", - "nonRevocationProof": "ex:nonRevocationProof", - - "alumniOf": {"@id": "schema:alumniOf", "@type": "rdf:HTML"}, - "child": {"@id": "ex:child", "@type": "@id"}, - "degree": "ex:degree", - "degreeType": "ex:degreeType", - "degreeSchool": "ex:degreeSchool", - "college": "ex:college", - "name": {"@id": "schema:name", "@type": "rdf:HTML"}, - "givenName": "schema:givenName", - "familyName": "schema:familyName", - "parent": {"@id": "ex:parent", "@type": "@id"}, - "referenceId": "ex:referenceId", - "documentPresence": "ex:documentPresence", - "evidenceDocument": "ex:evidenceDocument", - "spouse": "schema:spouse", - "subjectPresence": "ex:subjectPresence", - "verifier": {"@id": "ex:verifier", "@type": "@id"}, - "_sd": "ex:_sd", - "_sd_alg": "ex:_sd_alg" - }] -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/lds-jws2020-v1.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/lds-jws2020-v1.jsonld deleted file mode 100644 index 871dda8e3..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/lds-jws2020-v1.jsonld +++ /dev/null @@ -1,53 +0,0 @@ -{ - "@context": { - "JsonWebKey2020": "https://w3id.org/security#JsonWebKey2020", - "JsonWebSignature2020": { - "@id": "https://w3id.org/security#JsonWebSignature2020", - "@context": { - "@protected": true, - - "id": "@id", - "type": "@type", - - "challenge": "https://w3id.org/security#challenge", - "created": { - "@id": "http://purl.org/dc/terms/created", - "@type": "http://www.w3.org/2001/XMLSchema#dateTime" - }, - "domain": "https://w3id.org/security#domain", - "expires": { - "@id": "https://w3id.org/security#expiration", - "@type": "http://www.w3.org/2001/XMLSchema#dateTime" - }, - "jws": "https://w3id.org/security#jws", - "nonce": "https://w3id.org/security#nonce", - "proofPurpose": { - "@id": "https://w3id.org/security#proofPurpose", - "@type": "@vocab", - "@context": { - "@protected": true, - - "id": "@id", - "type": "@type", - - "assertionMethod": { - "@id": "https://w3id.org/security#assertionMethod", - "@type": "@id", - "@container": "@set" - }, - "authentication": { - "@id": "https://w3id.org/security#authenticationMethod", - "@type": "@id", - "@container": "@set" - } - } - }, - "proofValue": "https://w3id.org/security#proofValue", - "verificationMethod": { - "@id": "https://w3id.org/security#verificationMethod", - "@type": "@id" - } - } - } - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/odrl.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/odrl.jsonld deleted file mode 100644 index 8f29ccf72..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/odrl.jsonld +++ /dev/null @@ -1,200 +0,0 @@ -{ - "@context": { - "odrl": "http://www.w3.org/ns/odrl/2/", - "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - "rdfs": "http://www.w3.org/2000/01/rdf-schema#", - "owl": "http://www.w3.org/2002/07/owl#", - "skos": "http://www.w3.org/2004/02/skos/core#", - "dct": "http://purl.org/dc/terms/", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "vcard": "http://www.w3.org/2006/vcard/ns#", - "foaf": "http://xmlns.com/foaf/0.1/", - "schema": "http://schema.org/", - "cc": "http://creativecommons.org/ns#", - - "uid": "@id", - "type": "@type", - - "Policy": "odrl:Policy", - "Rule": "odrl:Rule", - "profile": {"@type": "@id", "@id": "odrl:profile"}, - - "inheritFrom": {"@type": "@id", "@id": "odrl:inheritFrom"}, - - "ConflictTerm": "odrl:ConflictTerm", - "conflict": {"@type": "@vocab", "@id": "odrl:conflict"}, - "perm": "odrl:perm", - "prohibit": "odrl:prohibit", - "invalid": "odrl:invalid", - - "Agreement": "odrl:Agreement", - "Assertion": "odrl:Assertion", - "Offer": "odrl:Offer", - "Privacy": "odrl:Privacy", - "Invitation": "odrl:Invitation", - "Set": "odrl:Set", - "Ticket": "odrl:Ticket", - - "Asset": "odrl:Asset", - "AssetCollection": "odrl:AssetCollection", - "relation": {"@type": "@id", "@id": "odrl:relation"}, - "hasPolicy": {"@type": "@id", "@id": "odrl:hasPolicy"}, - - "target": {"@type": "@id", "@id": "odrl:target"}, - "output": {"@type": "@id", "@id": "odrl:output"}, - - "partOf": {"@type": "@id", "@id": "odrl:partOf"}, - "source": {"@type": "@id", "@id": "odrl:source"}, - - "Party": "odrl:Party", - "PartyCollection": "odrl:PartyCollection", - "function": {"@type": "@vocab", "@id": "odrl:function"}, - "PartyScope": "odrl:PartyScope", - - "assignee": {"@type": "@id", "@id": "odrl:assignee"}, - "assigner": {"@type": "@id", "@id": "odrl:assigner"}, - "assigneeOf": {"@type": "@id", "@id": "odrl:assigneeOf"}, - "assignerOf": {"@type": "@id", "@id": "odrl:assignerOf"}, - "attributedParty": {"@type": "@id", "@id": "odrl:attributedParty"}, - "attributingParty": {"@type": "@id", "@id": "odrl:attributingParty"}, - "compensatedParty": {"@type": "@id", "@id": "odrl:compensatedParty"}, - "compensatingParty": {"@type": "@id", "@id": "odrl:compensatingParty"}, - "consentingParty": {"@type": "@id", "@id": "odrl:consentingParty"}, - "consentedParty": {"@type": "@id", "@id": "odrl:consentedParty"}, - "informedParty": {"@type": "@id", "@id": "odrl:informedParty"}, - "informingParty": {"@type": "@id", "@id": "odrl:informingParty"}, - "trackingParty": {"@type": "@id", "@id": "odrl:trackingParty"}, - "trackedParty": {"@type": "@id", "@id": "odrl:trackedParty"}, - "contractingParty": {"@type": "@id", "@id": "odrl:contractingParty"}, - "contractedParty": {"@type": "@id", "@id": "odrl:contractedParty"}, - - "Action": "odrl:Action", - "action": {"@type": "@vocab", "@id": "odrl:action"}, - "includedIn": {"@type": "@id", "@id": "odrl:includedIn"}, - "implies": {"@type": "@id", "@id": "odrl:implies"}, - - "Permission": "odrl:Permission", - "permission": {"@type": "@id", "@id": "odrl:permission"}, - - "Prohibition": "odrl:Prohibition", - "prohibition": {"@type": "@id", "@id": "odrl:prohibition"}, - - "obligation": {"@type": "@id", "@id": "odrl:obligation"}, - - "use": "odrl:use", - "grantUse": "odrl:grantUse", - "aggregate": "odrl:aggregate", - "annotate": "odrl:annotate", - "anonymize": "odrl:anonymize", - "archive": "odrl:archive", - "concurrentUse": "odrl:concurrentUse", - "derive": "odrl:derive", - "digitize": "odrl:digitize", - "display": "odrl:display", - "distribute": "odrl:distribute", - "execute": "odrl:execute", - "extract": "odrl:extract", - "give": "odrl:give", - "index": "odrl:index", - "install": "odrl:install", - "modify": "odrl:modify", - "move": "odrl:move", - "play": "odrl:play", - "present": "odrl:present", - "print": "odrl:print", - "read": "odrl:read", - "reproduce": "odrl:reproduce", - "sell": "odrl:sell", - "stream": "odrl:stream", - "textToSpeech": "odrl:textToSpeech", - "transfer": "odrl:transfer", - "transform": "odrl:transform", - "translate": "odrl:translate", - - "Duty": "odrl:Duty", - "duty": {"@type": "@id", "@id": "odrl:duty"}, - "consequence": {"@type": "@id", "@id": "odrl:consequence"}, - "remedy": {"@type": "@id", "@id": "odrl:remedy"}, - - "acceptTracking": "odrl:acceptTracking", - "attribute": "odrl:attribute", - "compensate": "odrl:compensate", - "delete": "odrl:delete", - "ensureExclusivity": "odrl:ensureExclusivity", - "include": "odrl:include", - "inform": "odrl:inform", - "nextPolicy": "odrl:nextPolicy", - "obtainConsent": "odrl:obtainConsent", - "reviewPolicy": "odrl:reviewPolicy", - "uninstall": "odrl:uninstall", - "watermark": "odrl:watermark", - - "Constraint": "odrl:Constraint", - "LogicalConstraint": "odrl:LogicalConstraint", - "constraint": {"@type": "@id", "@id": "odrl:constraint"}, - "refinement": {"@type": "@id", "@id": "odrl:refinement"}, - "Operator": "odrl:Operator", - "operator": {"@type": "@vocab", "@id": "odrl:operator"}, - "RightOperand": "odrl:RightOperand", - "rightOperand": "odrl:rightOperand", - "rightOperandReference":{"@type": "xsd:anyURI", "@id": "odrl:rightOperandReference"}, - "LeftOperand": "odrl:LeftOperand", - "leftOperand": {"@type": "@vocab", "@id": "odrl:leftOperand"}, - "unit": "odrl:unit", - "dataType": {"@type": "xsd:anyType", "@id": "odrl:datatype"}, - "status": "odrl:status", - - "absolutePosition": "odrl:absolutePosition", - "absoluteSpatialPosition": "odrl:absoluteSpatialPosition", - "absoluteTemporalPosition":"odrl:absoluteTemporalPosition", - "absoluteSize": "odrl:absoluteSize", - "count": "odrl:count", - "dateTime": "odrl:dateTime", - "delayPeriod": "odrl:delayPeriod", - "deliveryChannel": "odrl:deliveryChannel", - "elapsedTime": "odrl:elapsedTime", - "event": "odrl:event", - "fileFormat": "odrl:fileFormat", - "industry": "odrl:industry:", - "language": "odrl:language", - "media": "odrl:media", - "meteredTime": "odrl:meteredTime", - "payAmount": "odrl:payAmount", - "percentage": "odrl:percentage", - "product": "odrl:product", - "purpose": "odrl:purpose", - "recipient": "odrl:recipient", - "relativePosition": "odrl:relativePosition", - "relativeSpatialPosition": "odrl:relativeSpatialPosition", - "relativeTemporalPosition":"odrl:relativeTemporalPosition", - "relativeSize": "odrl:relativeSize", - "resolution": "odrl:resolution", - "spatial": "odrl:spatial", - "spatialCoordinates": "odrl:spatialCoordinates", - "systemDevice": "odrl:systemDevice", - "timeInterval": "odrl:timeInterval", - "unitOfCount": "odrl:unitOfCount", - "version": "odrl:version", - "virtualLocation": "odrl:virtualLocation", - - "eq": "odrl:eq", - "gt": "odrl:gt", - "gteq": "odrl:gteq", - "lt": "odrl:lt", - "lteq": "odrl:lteq", - "neq": "odrl:neg", - "isA": "odrl:isA", - "hasPart": "odrl:hasPart", - "isPartOf": "odrl:isPartOf", - "isAllOf": "odrl:isAllOf", - "isAnyOf": "odrl:isAnyOf", - "isNoneOf": "odrl:isNoneOf", - "or": "odrl:or", - "xone": "odrl:xone", - "and": "odrl:and", - "andSequence": "odrl:andSequence", - - "policyUsage": "odrl:policyUsage" - - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/contexts/revocation-list-2021.jsonld b/component/wallet-cli/pkg/walletrunner/contexts/revocation-list-2021.jsonld deleted file mode 100644 index d0ba640b0..000000000 --- a/component/wallet-cli/pkg/walletrunner/contexts/revocation-list-2021.jsonld +++ /dev/null @@ -1,50 +0,0 @@ -{ - "@context": { - "@protected": true, - "StatusList2021Credential": { - "@id": "https://w3id.org/vc/status-list#StatusList2021Credential", - "@context": { - "@protected": true, - "id": "@id", - "type": "@type", - "description": "http://schema.org/description", - "name": "http://schema.org/name" - } - }, - "RevocationList2021": { - "@id": "https://w3id.org/vc/status-list#RevocationList2021", - "@context": { - "@protected": true, - "id": "@id", - "type": "@type", - "encodedList": "https://w3id.org/vc/status-list#encodedList" - } - }, - "RevocationList2021Status": { - "@id": "https://w3id.org/vc/status-list#RevocationList2021Status", - "@context": { - "@protected": true, - "id": "@id", - "type": "@type", - "statusListCredential": { - "@id": "https://w3id.org/vc/status-list#statusListCredential", - "@type": "@id" - }, - "statusListIndex": "https://w3id.org/vc/status-list#statusListIndex" - } - }, - "SuspensionList2021Status": { - "@id": "https://w3id.org/vc/status-list#SuspensionList2021Status", - "@context": { - "@protected": true, - "id": "@id", - "type": "@type", - "statusListCredential": { - "@id": "https://w3id.org/vc/status-list#statusListCredential", - "@type": "@id" - }, - "statusListIndex": "https://w3id.org/vc/status-list#statusListIndex" - } - } - } -} \ No newline at end of file diff --git a/component/wallet-cli/pkg/walletrunner/didconfig.go b/component/wallet-cli/pkg/walletrunner/didconfig.go deleted file mode 100644 index f88c898b7..000000000 --- a/component/wallet-cli/pkg/walletrunner/didconfig.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "encoding/json" - "fmt" - "strings" - - didconfigclient "github.com/trustbloc/vc-go/didconfig/client" -) - -const ( - linkedDomainsService = "LinkedDomains" -) - -type serviceEndpoint struct { - Origins []string `json:"origins"` -} - -func (s *Service) runLinkedDomainVerification(didID string) error { - didDocResolution, vdrErr := s.ariesServices.vdrRegistry.Resolve(didID) - if vdrErr != nil { - return fmt.Errorf("failed to resolve DID %s, err: %w", didID, vdrErr) - } - - for _, service := range didDocResolution.DIDDocument.Service { - serviceType := getServiceType(service.Type) - if serviceType != linkedDomainsService { - continue - } - - serviceEndpointBytes, err := service.ServiceEndpoint.MarshalJSON() - if err != nil { - return fmt.Errorf("failed to get LinkedDomains service endpoint: %w", err) - } - - serviceEndpoint := &serviceEndpoint{} - - if err := json.Unmarshal(serviceEndpointBytes, serviceEndpoint); err != nil { - return err - } - - didConfigurationClient := didconfigclient.New( - didconfigclient.WithJSONLDDocumentLoader(s.ariesServices.documentLoader), - didconfigclient.WithVDRegistry(s.ariesServices.vdrRegistry), - didconfigclient.WithHTTPClient(s.httpClient), - ) - - if err = didConfigurationClient.VerifyDIDAndDomain(didID, - strings.TrimSuffix(serviceEndpoint.Origins[0], "/")); err != nil { - return err - } - - return nil - } - - return fmt.Errorf("no LinkedDomains service in DID %s", didID) -} - -func getServiceType(serviceType interface{}) string { - var val string - - switch t := serviceType.(type) { - case string: - val = t - case []string: - if len(t) > 0 { - val = t[0] - } - case []interface{}: - if len(t) > 0 { - if str, ok := t[0].(string); ok { - val = str - } - } - } - - return val -} diff --git a/component/wallet-cli/pkg/walletrunner/http.go b/component/wallet-cli/pkg/walletrunner/http.go deleted file mode 100644 index 0a7eed2a1..000000000 --- a/component/wallet-cli/pkg/walletrunner/http.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "context" - "net/http" -) - -type httpClientKey = struct{} - -func WithHttpClient(ctx context.Context, client *http.Client) context.Context { - return context.WithValue(ctx, httpClientKey{}, client) -} - -func HttpClientFromContext(ctx context.Context, fallback *http.Client) *http.Client { - val := ctx.Value(httpClientKey{}) - if val != nil { - return val.(*http.Client) - } - - return fallback -} diff --git a/component/wallet-cli/pkg/walletrunner/models.go b/component/wallet-cli/pkg/walletrunner/models.go deleted file mode 100644 index d2dbe3f9f..000000000 --- a/component/wallet-cli/pkg/walletrunner/models.go +++ /dev/null @@ -1,107 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "github.com/trustbloc/vc-go/presexch" - "github.com/trustbloc/vc-go/verifiable" - - verifiable2 "github.com/trustbloc/vcs/pkg/doc/verifiable" -) - -type RequestObject struct { - JTI string `json:"jti"` - IAT int64 `json:"iat"` - ResponseType string `json:"response_type"` - ResponseMode string `json:"response_mode"` - Scope string `json:"scope"` - Nonce string `json:"nonce"` - ClientID string `json:"client_id"` - RedirectURI string `json:"redirect_uri"` - State string `json:"state"` - Exp int64 `json:"exp"` - Registration RequestObjectRegistration `json:"registration"` - Claims RequestObjectClaims `json:"claims"` -} - -type RequestObjectRegistration struct { - ClientName string `json:"client_name"` - SubjectSyntaxTypesSupported []string `json:"subject_syntax_types_supported"` - VPFormats *presexch.Format `json:"vp_formats"` - ClientPurpose string `json:"client_purpose"` -} - -type RequestObjectClaims struct { - VPToken VPToken `json:"vp_token"` -} - -type VPToken struct { - PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition"` -} - -type IDTokenVPToken struct { - PresentationSubmission *presexch.PresentationSubmission `json:"presentation_submission"` -} - -type Claims = map[string]interface{} - -type IDTokenClaims struct { - // ScopeAdditionalClaims stores additional claims retrieved using custom scope. - ScopeAdditionalClaims map[string]Claims `json:"_scope,omitempty"` // custom scope -> additional claims - VPToken IDTokenVPToken `json:"_vp_token"` - Nonce string `json:"nonce"` - Exp int64 `json:"exp"` - Iss string `json:"iss"` - Aud string `json:"aud"` - Sub string `json:"sub"` - Nbf int64 `json:"nbf"` - Iat int64 `json:"iat"` - Jti string `json:"jti"` -} - -type VPTokenClaims struct { - VP *verifiable.Presentation `json:"vp"` - Nonce string `json:"nonce"` - Exp int64 `json:"exp"` - Iss string `json:"iss"` - Aud string `json:"aud"` - Nbf int64 `json:"nbf"` - Iat int64 `json:"iat"` - Jti string `json:"jti"` -} - -type InitiateOIDC4VPResponse struct { - AuthorizationRequest string `json:"authorizationRequest"` - TxId string `json:"txID"` -} - -type JWTProofClaims struct { - Issuer string `json:"iss,omitempty"` - Audience string `json:"aud,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Nonce string `json:"nonce,omitempty"` -} - -type CredentialRequest struct { - Format string `json:"format,omitempty"` - Types []string `json:"types"` - Proof JWTProof `json:"proof,omitempty"` -} - -type JWTProof struct { - JWT string `json:"jwt"` - ProofType string `json:"proof_type"` -} - -type CredentialResponse struct { - AcceptanceToken string `json:"acceptance_token,omitempty"` - CNonce string `json:"c_nonce,omitempty"` - CNonceExpiresIn int `json:"c_nonce_expires_in,omitempty"` - Credential interface{} `json:"credential"` - Format verifiable2.OIDCFormat `json:"format"` - AckID *string `json:"ack_id"` -} diff --git a/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go b/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go deleted file mode 100644 index 4469162b3..000000000 --- a/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package vcprovider - -import ( - "crypto/tls" - "fmt" - - vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" -) - -// VCProvider represents the API for credentials provider. -type VCProvider interface { - // GetConfig returns *Config. - GetConfig() *Config - // GetCredentials returns the map of signed credentials. - GetCredentials() (map[string][]byte, error) -} - -type Config struct { - TLS *tls.Config - WalletParams *WalletParams - UniResolverURL string - ContextProviderURL string - OidcProviderURL string - IssueVCURL string - DidDomain string - DidServiceAuthToken string - VCFormat vcs.OIDCFormat - OrgName string - OrgSecret string - Debug bool - SkipSchemaValidation bool - - WalletUserId string // initial config - WalletPassPhrase string // initial config - StorageProvider string // initial config - StorageProviderConnString string // initial config - OIDC4VPShouldFetchCredentials bool - WalletDidCount int - WalletDidKeyID string - WalletDidID string - DidMethod string - DidKeyType string - KeepWalletOpen bool - LinkedDomainVerificationEnabled bool -} - -type WalletParams struct { - Token string - Passphrase string - UserID string - DidID []string - DidKeyID []string - SignType vcs.SignatureType -} - -type ConfigOption func(c *Config) - -func GetProvider(vcProviderType string, opts ...ConfigOption) (VCProvider, error) { - switch vcProviderType { - case ProviderVCS: - return newVCSCredentialsProvider(opts...), nil - default: - return nil, fmt.Errorf("unsupported vc provider type %s", vcProviderType) - } -} diff --git a/component/wallet-cli/pkg/walletrunner/vcprovider/vcs.go b/component/wallet-cli/pkg/walletrunner/vcprovider/vcs.go deleted file mode 100644 index 8cfc06acb..000000000 --- a/component/wallet-cli/pkg/walletrunner/vcprovider/vcs.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package vcprovider - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - - "github.com/google/uuid" - "github.com/trustbloc/did-go/legacy/mem" - "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/vc-go/verifiable" - - "github.com/trustbloc/vcs/component/wallet-cli/internal/httputil" - "github.com/trustbloc/vcs/component/wallet-cli/internal/ldutil" - "github.com/trustbloc/vcs/component/wallet-cli/internal/oauth2util" - vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" - issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" -) - -const ( - ProviderVCS = "vcs" - - didDomain = "https://testnet.orb.local" - didServiceAuthToken = "tk1" - oidcProviderURL = "https://localhost:4444" - issueCredentialURL = "https://api-gateway.trustbloc.local:5566/issuer/profiles/i_myprofile_ud_es256_jwt/latest/credentials/issue" - testDataPath = "testdata/vcs" -) - -// defaultVCSLocalConfig returns default *Config for VCS that refers to the local containers. -func defaultVCSLocalConfig() *Config { - return &Config{ - TLS: &tls.Config{ - InsecureSkipVerify: true, - }, - WalletParams: &WalletParams{}, - ContextProviderURL: "", - OidcProviderURL: oidcProviderURL, - IssueVCURL: issueCredentialURL, - DidDomain: didDomain, - DidServiceAuthToken: didServiceAuthToken, - VCFormat: vcsverifiable.JwtVCJsonLD, - OrgName: "test_org", - OrgSecret: "test-org-secret", - DidKeyType: kms.ED25519, - DidMethod: "ion", - WalletDidCount: 1, - } -} - -type vcsCredentialsProvider struct { - conf *Config -} - -func newVCSCredentialsProvider(opts ...ConfigOption) *vcsCredentialsProvider { - conf := defaultVCSLocalConfig() - - for _, f := range opts { - f(conf) - } - - return &vcsCredentialsProvider{ - conf: conf, - } -} - -func (p *vcsCredentialsProvider) GetConfig() *Config { - return p.conf -} - -func (p *vcsCredentialsProvider) GetCredentials() (map[string][]byte, error) { - testData := map[string][]byte{} - - token, err := p.authorizeOrganization(p.conf.OrgName, p.conf.OrgSecret) - if err != nil { - return nil, fmt.Errorf("error creating oauth token: %w", err) - } - - files, err := os.ReadDir(testDataPath) - if err != nil { - return nil, fmt.Errorf("error reading tesdata dir: %w", err) - } - - for _, file := range files { - var vcData []byte - vcData, err = os.ReadFile(filepath.Join(testDataPath, file.Name())) //nolint: gosec - if err != nil { - return nil, fmt.Errorf("error read VC file: %w", err) - } - - vcData, err = p.createVCSCredential(string(vcData), token) - if err != nil { - return nil, err - } - - testData[file.Name()] = vcData - } - - return testData, nil -} - -func (p *vcsCredentialsProvider) authorizeOrganization(clientID, secret string) (string, error) { - accessToken, err := oauth2util.Token(context.Background(), p.conf.OidcProviderURL, - clientID, secret, []string{"org_admin"}, p.conf.TLS) - if err != nil { - return "", err - } - - return accessToken, nil -} - -func (p *vcsCredentialsProvider) createVCSCredential(credential, authToken string) ([]byte, error) { - loader, err := ldutil.DocumentLoader(mem.NewProvider()) - if err != nil { - return nil, fmt.Errorf("create document loader: %w", err) - } - - cred, err := verifiable.ParseCredential([]byte(credential), - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(loader)) - if err != nil { - return nil, fmt.Errorf("err parsing credentials: %w", err) - } - - subjs := cred.Contents().Subject - subjs[0].ID = p.conf.WalletParams.DidID[0] - - cred = cred. - WithModifiedID(uuid.New().URN()). - WithModifiedSubject(subjs) - - reqData, err := GetIssueCredentialRequestData(cred, p.conf.VCFormat) - if err != nil { - return nil, fmt.Errorf("unable to get issue credential request data: %w", err) - } - - req := &issuerv1.IssueCredentialData{ - Credential: &reqData, - } - - requestBytes, err := json.Marshal(req) - if err != nil { - return nil, err - } - - resp, err := httputil.HTTPSDo(http.MethodPost, p.conf.IssueVCURL, "application/json", authToken, //nolint: bodyclose - bytes.NewBuffer(requestBytes), p.conf.TLS) - if err != nil { - return nil, err - } - - defer httputil.CloseResponseBody(resp.Body) - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, resp.StatusCode, respBytes) - } - - return respBytes, nil -} - -func GetIssueCredentialRequestData(vc *verifiable.Credential, desiredFormat vcsverifiable.OIDCFormat) (interface{}, error) { - switch desiredFormat { - case vcsverifiable.JwtVCJsonLD, vcsverifiable.JwtVCJson: - claims, err := vc.JWTClaims(false) - if err != nil { - return nil, err - } - - return claims.MarshalUnsecuredJWT() - case vcsverifiable.LdpVC: - return vc, nil - - default: - return nil, fmt.Errorf("unsupported format %s", desiredFormat) - } -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet.go b/component/wallet-cli/pkg/walletrunner/wallet.go deleted file mode 100644 index 25f4b85d4..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "encoding/json" - "fmt" - "strings" - "sync" - "time" - - "github.com/google/uuid" - "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/vc-go/verifiable" - - "github.com/trustbloc/vcs/component/wallet-cli/internal/vdrutil" - vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" -) - -const ( - didMethodVeres = "v1" - didMethodElement = "elem" - didMethodSov = "sov" - didMethodWeb = "web" - didMethodFactom = "factom" - didMethodORB = "orb" - didMethodKey = "key" - didMethodION = "ion" -) - -// Wallet provides verifiable credential storing, fetching, and presentation definition querying. -type Wallet interface { - // Open opens wallet. - Open(passPhrase string) string - // Close closes wallet. - Close() bool - // Add adds a marshalled credential to the wallet. - Add(content json.RawMessage) error - // GetAll returns all stored credentials. - GetAll() (map[string]json.RawMessage, error) - // Query runs the given presentation definition on the stored credentials. - Query(pdBytes []byte) ([]*verifiable.Presentation, error) -} - -func (s *Service) GetWallet() Wallet { - return s.wallet -} - -func (s *Service) CreateWallet() error { - shouldCreateWallet := s.vcProviderConf.WalletUserId == "" - - if shouldCreateWallet { - s.print("Creating wallet") - s.vcProviderConf.WalletParams.UserID = "testUserID" + uuid.NewString() - s.vcProviderConf.WalletParams.Passphrase = "passphrase122334" - } else { - s.print("Using existing wallet") - s.vcProviderConf.WalletParams.UserID = s.vcProviderConf.WalletUserId - s.vcProviderConf.WalletParams.Passphrase = s.vcProviderConf.WalletPassPhrase - } - - if s.wallet == nil { - services, err := s.createAgentServices(s.vcProviderConf) - if err != nil { - return fmt.Errorf("wallet services setup failed: %w", err) - } - - s.ariesServices = services - - w, err := newWallet( - shouldCreateWallet, - s.vcProviderConf.WalletParams.UserID, - s.vcProviderConf.WalletParams.Passphrase, - s.ariesServices, - ) - if err != nil { - return err - } - - s.wallet = w - } - - var err error - - token := s.wallet.Open(s.vcProviderConf.WalletParams.Passphrase) - - if token != "" { - s.vcProviderConf.WalletParams.Token = token - } - - creator, err := s.ariesServices.suite.RawKeyCreator() - if err != nil { - return err - } - - if shouldCreateWallet { - var createRes *vdrutil.CreateResult - for i := 0; i < s.vcProviderConf.WalletDidCount; i++ { - createRes, err = vdrutil.DefaultVdrUtil.Create( - s.vcProviderConf.DidMethod, - kms.KeyType(s.vcProviderConf.DidKeyType), - s.ariesServices.vdrRegistry, - creator, - ) - if err != nil { - return err - } - - s.vcProviderConf.WalletParams.DidID = append(s.vcProviderConf.WalletParams.DidID, createRes.DidID) - s.vcProviderConf.WalletParams.DidKeyID = append(s.vcProviderConf.WalletParams.DidKeyID, createRes.KeyID) - } - } else { - s.vcProviderConf.WalletParams.DidID = append(s.vcProviderConf.WalletParams.DidID, s.vcProviderConf.WalletDidID) - s.vcProviderConf.WalletParams.DidKeyID = append(s.vcProviderConf.WalletParams.DidKeyID, s.vcProviderConf.WalletDidKeyID) - } - - switch s.vcProviderConf.DidKeyType { - case "ED25519": - s.vcProviderConf.WalletParams.SignType = vcs.EdDSA - case "ECDSAP256DER": - s.vcProviderConf.WalletParams.SignType = vcs.ES256 - case "ECDSAP384DER": - s.vcProviderConf.WalletParams.SignType = vcs.ES384 - } - - for i := 0; i < s.vcProviderConf.WalletDidCount; i++ { - for j := 1; j <= vdrResolveMaxRetry; j++ { - _, err = s.ariesServices.vdrRegistry.Resolve(s.vcProviderConf.WalletParams.DidID[i]) - if err == nil { - break - } - - time.Sleep(1 * time.Second) - } - } - - storageType := strings.ToLower(s.vcProviderConf.StorageProvider) - if storageType == "" { - storageType = "in-memory" - } - - fmt.Printf("\tStorage: %s\n", storageType) - fmt.Printf("\tWallet UserID: [%s]\n", s.vcProviderConf.WalletParams.UserID) - fmt.Printf("\tWallet DID: %s\n", s.vcProviderConf.WalletParams.DidID) - fmt.Printf("\tWallet DID KeyID: %s\n\n", s.vcProviderConf.WalletParams.DidKeyID) - - if shouldCreateWallet { - fmt.Printf("[HINT] Set the following environment vars to simplify further interaction with wallet:\n\n") - fmt.Printf("export WALLET_CLI_USER_ID=%s\n", s.vcProviderConf.WalletParams.UserID) - fmt.Printf("export WALLET_CLI_DID=%s\n", s.vcProviderConf.WalletParams.DidID[0]) - fmt.Printf("export WALLET_CLI_DID_KEY_ID=%s\n\n", s.vcProviderConf.WalletParams.DidKeyID[0]) - } - - return nil -} - -func newWallet(shouldCreate bool, userID string, passphrase string, services *ariesServices) (Wallet, error) { - store, err := services.storageProvider.OpenStore("wallet:credential") - if err != nil { - return nil, err - } - - return &walletImpl{ - credStore: store, - ldLoader: services.documentLoader, - storeLock: sync.RWMutex{}, - }, nil -} - -func (s *Service) SaveCredentialInWallet(vc []byte) error { - err := s.wallet.Add(vc) - if err != nil { - return fmt.Errorf("wallet add credential failed: %w", err) - } - - return nil -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet_impl.go b/component/wallet-cli/pkg/walletrunner/wallet_impl.go deleted file mode 100644 index d394f281c..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet_impl.go +++ /dev/null @@ -1,216 +0,0 @@ -/* -Copyright Gen Digital Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "crypto/sha256" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "strings" - "sync" - - "github.com/piprate/json-gold/ld" - "github.com/trustbloc/kms-go/spi/storage" - "github.com/trustbloc/vc-go/presexch" - "github.com/trustbloc/vc-go/verifiable" -) - -type walletImpl struct { - credStore storage.Store - ldLoader ld.DocumentLoader - storeLock sync.RWMutex -} - -func (w *walletImpl) Open(string) string { - return "token" -} - -func (w *walletImpl) Close() bool { - return true -} - -const credentialTag = "credential" - -func (w *walletImpl) Add(content json.RawMessage) error { - key, err := getContentID(content) - if err != nil { - return err - } - - w.storeLock.Lock() - defer w.storeLock.Unlock() - - err = w.credStore.Put(key, content, storage.Tag{Name: credentialTag}) - if err != nil { - return err - } - - return nil -} - -type contentID struct { - ID string `json:"id"` -} - -func getContentID(content json.RawMessage) (string, error) { - key, err := getJWTContentID(string(content)) - if err == nil && strings.TrimSpace(key) != "" { - return key, nil - } - - var cid contentID - if err := json.Unmarshal(content, &cid); err != nil { - return "", fmt.Errorf("failed to read content to be saved : %w", err) - } - - key = cid.ID - if strings.TrimSpace(key) == "" { - // use document hash as key to avoid duplicates if id is missing - digest := sha256.Sum256(content) - - key = hex.EncodeToString(digest[0:]) - } - - return key, nil -} - -type hasJTI struct { - JTI string `json:"jti"` -} - -func getJWTContentID(jwtStr string) (string, error) { - parts := strings.Split(unQuote(jwtStr), ".") - if len(parts) != 3 { // nolint: gomnd - return "", nil // assume not a jwt - } - - credBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return "", fmt.Errorf("decode base64 JWT data: %w", err) - } - - cred := &hasJTI{} - - err = json.Unmarshal(credBytes, cred) - if err != nil { - return "", fmt.Errorf("failed to unmarshal JWT data: %w", err) - } - - if cred.JTI == "" { - return "", fmt.Errorf("JWT data has no ID") - } - - return cred.JTI, nil -} - -func unQuote(s string) string { - if len(s) <= 1 { - return s - } - - if s[0] == '"' && s[len(s)-1] == '"' { - return s[1 : len(s)-1] - } - - return s -} - -func (w *walletImpl) GetAll() (map[string]json.RawMessage, error) { - w.storeLock.RLock() - defer w.storeLock.RUnlock() - - iter, err := w.credStore.Query(credentialTag) - if err != nil { - return nil, err - } - - result := make(map[string]json.RawMessage) - - for { - ok, err := iter.Next() - if err != nil { - return nil, err - } - - if !ok { - break - } - - key, err := iter.Key() - if err != nil { - return nil, err - } - - val, err := iter.Value() - if err != nil { - return nil, err - } - - result[key] = val - } - - return result, nil -} - -func (w *walletImpl) Query(pdBytes []byte) ([]*verifiable.Presentation, error) { - vcContents, err := w.GetAll() - if err != nil { - return nil, fmt.Errorf("failed to query credentials: %w", err) - } - - if len(vcContents) == 0 { - return nil, errors.New("no result found") - } - - creds, err := parseCredentialContents(vcContents, w.ldLoader) - if err != nil { - return nil, err - } - - var presDefinition presexch.PresentationDefinition - - err = json.Unmarshal(pdBytes, &presDefinition) - if err != nil { - return nil, err - } - - result, err := presDefinition.CreateVP(creds, w.ldLoader, presexch.WithSDCredentialOptions( - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(w.ldLoader))) - - if errors.Is(err, presexch.ErrNoCredentials) { - return nil, errors.New("no result found") - } - - if err != nil { - return nil, err - } - - return []*verifiable.Presentation{result}, nil -} - -func parseCredentialContents( - raws map[string]json.RawMessage, - documentLoader ld.DocumentLoader, -) ([]*verifiable.Credential, error) { - var result []*verifiable.Credential - - for _, raw := range raws { - vc, err := verifiable.ParseCredential(raw, verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(documentLoader)) - if err != nil { - return nil, err - } - - result = append(result, vc) - } - - return result, nil -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet_runner.go b/component/wallet-cli/pkg/walletrunner/wallet_runner.go deleted file mode 100644 index 5b5f524b7..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet_runner.go +++ /dev/null @@ -1,450 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "crypto/tls" - _ "embed" - "errors" - "fmt" - "net/http" - "net/http/cookiejar" - "strings" - "time" - - "github.com/google/uuid" - "github.com/henvic/httpretty" - jsonld "github.com/piprate/json-gold/ld" - "github.com/samber/lo" - "github.com/trustbloc/did-go/doc/did" - ldcontext "github.com/trustbloc/did-go/doc/ld/context" - "github.com/trustbloc/did-go/doc/ld/context/remote" - ld "github.com/trustbloc/did-go/doc/ld/documentloader" - ldstore "github.com/trustbloc/did-go/doc/ld/store" - "github.com/trustbloc/did-go/legacy/mem" - "github.com/trustbloc/did-go/method/httpbinding" - "github.com/trustbloc/did-go/method/jwk" - "github.com/trustbloc/did-go/method/key" - longform "github.com/trustbloc/did-go/method/sidetreelongform" - "github.com/trustbloc/did-go/method/web" - "github.com/trustbloc/did-go/vdr" - vdrapi "github.com/trustbloc/did-go/vdr/api" - "github.com/trustbloc/kms-go/kms" - "github.com/trustbloc/kms-go/secretlock/noop" - kmsapi "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/kms-go/spi/secretlock" - "github.com/trustbloc/kms-go/spi/storage" - "github.com/trustbloc/kms-go/wrapper/localsuite" - "github.com/trustbloc/vc-go/verifiable" - "golang.org/x/oauth2" - - "github.com/trustbloc/vcs/component/wallet-cli/internal/formatter" - "github.com/trustbloc/vcs/component/wallet-cli/internal/storage/leveldb" - "github.com/trustbloc/vcs/component/wallet-cli/internal/storage/mongodb" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/vcprovider" -) - -// nolint:gochecknoglobals //embedded test contexts -var ( - //go:embed contexts/lds-jws2020-v1.jsonld - jws2020V1Vocab []byte - //go:embed contexts/citizenship-v1.jsonld - citizenshipVocab []byte - //go:embed contexts/examples-v1.jsonld - examplesVocab []byte - //go:embed contexts/examples-ext-v1.jsonld - examplesExtVocab []byte - //go:embed contexts/examples-crude-product-v1.jsonld - examplesCrudeProductVocab []byte - //go:embed contexts/odrl.jsonld - odrl []byte - //go:embed contexts/revocation-list-2021.jsonld - revocationList2021 []byte -) - -const ( - vdrResolveMaxRetry = 10 - discoverableClientIDScheme = "urn:ietf:params:oauth:client-id-scheme:oauth-discoverable-client" -) - -var extraContexts = []ldcontext.Document{ //nolint:gochecknoglobals - { - URL: "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json", - Content: jws2020V1Vocab, - }, - { - URL: "https://w3id.org/citizenship/v1", - DocumentURL: "https://w3c-ccg.github.io/citizenship-vocab/contexts/citizenship-v1.jsonld", // resolvable - Content: citizenshipVocab, - }, - { - URL: "https://www.w3.org/2018/credentials/examples/v1", - Content: examplesVocab, - }, - { - URL: "https://trustbloc.github.io/context/vc/examples-ext-v1.jsonld", - Content: examplesExtVocab, - }, - { - URL: "https://trustbloc.github.io/context/vc/examples-crude-product-v1.jsonld", - Content: examplesCrudeProductVocab, - }, - { - URL: "https://www.w3.org/ns/odrl.jsonld", - Content: odrl, - }, - { - URL: "https://w3c-ccg.github.io/vc-revocation-list-2021/contexts/v1.jsonld", - DocumentURL: "https://raw.githubusercontent.com/w3c-ccg/vc-status-list-2021/343b8b59cddba4525e1ef355356ae760fc75904e/contexts/v1.jsonld", - Content: revocationList2021, - }, -} - -type Service struct { - ariesServices *ariesServices - wallet Wallet - vcProvider vcprovider.VCProvider - vcProviderConf *vcprovider.Config - httpClient *http.Client - oauthClient *oauth2.Config - token *oauth2.Token - perfInfo *PerfInfo - vpFlowExecutor *VPFlowExecutor - keepWalletOpen bool - debug bool -} - -func New(vcProviderType string, opts ...vcprovider.ConfigOption) (*Service, error) { - vcProvider, err := vcprovider.GetProvider(vcProviderType, opts...) - if err != nil { - return nil, fmt.Errorf("GetVCProvider err: %w", err) - } - - config := vcProvider.GetConfig() - - cookie, err := cookiejar.New(&cookiejar.Options{}) - if err != nil { - return nil, fmt.Errorf("init cookie jar: %w", err) - } - - httpClient := &http.Client{ - Jar: cookie, - Transport: &http.Transport{ - TLSClientConfig: config.TLS, - }, - } - - if config.Debug { - httpLogger := &httpretty.Logger{ - RequestHeader: true, - RequestBody: true, - ResponseHeader: true, - ResponseBody: true, - SkipSanitize: true, - Colors: true, - SkipRequestInfo: true, - Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}, &formatter.JWTFormatter{}}, - MaxResponseBody: 1e+7, - } - - httpClient.Transport = httpLogger.RoundTripper(httpClient.Transport) - } - - return &Service{ - vcProvider: vcProvider, - vcProviderConf: config, - httpClient: httpClient, - perfInfo: &PerfInfo{}, - debug: config.Debug, - keepWalletOpen: config.KeepWalletOpen, - }, nil -} - -func (s *Service) SignJwtClaims( - claims interface{}, - headers map[string]interface{}, -) (string, error) { - jws, err := signTokenJWT(claims, - s.vcProviderConf.WalletParams.DidKeyID[0], - s.ariesServices.suite, - s.vcProviderConf.WalletParams.SignType, - headers, - ) - if err != nil { - return "", errors.Join(err, errors.New("can not sign claims")) - } - - return jws, nil -} - -func (s *Service) GetConfig() *vcprovider.Config { - return s.vcProviderConf -} - -type PerfInfo struct { - CreateWallet time.Duration `json:"vci_create_wallet"` - GetIssuerCredentialsOIDCConfig time.Duration `json:"vci_get_issuer_credentials_oidc_config"` - GetAccessToken time.Duration `json:"vci_get_access_token"` - GetCredential time.Duration `json:"vci_get_credential"` - CredentialsAck time.Duration `json:"vci_credentials_ack"` - FetchRequestObject time.Duration `json:"vp_fetch_request_object"` - VerifyAuthorizationRequest time.Duration `json:"vp_verify_authorization_request"` - QueryCredentialFromWallet time.Duration `json:"vp_query_credential_from_wallet"` - CreateAuthorizedResponse time.Duration `json:"vp_create_authorized_response"` - SendAuthorizedResponse time.Duration `json:"vp_send_authorized_response"` - VcsCIFlowDuration time.Duration `json:"_vcs_ci_flow_duration"` - VcsVPFlowDuration time.Duration `json:"_vcs_vp_flow_duration"` -} - -func (s *Service) GetPerfInfo() *PerfInfo { - return s.perfInfo -} - -func (s *Service) GetVCProviderConf() *vcprovider.Config { - return s.vcProviderConf -} - -func (s *Service) createAgentServices(vcProviderConf *vcprovider.Config) (*ariesServices, error) { - var storageProvider storage.Provider - switch strings.ToLower(s.vcProviderConf.StorageProvider) { - case "mongodb": - p, err := mongodb.NewProvider(s.vcProviderConf.StorageProviderConnString, nil) - if err != nil { - return nil, err - } - storageProvider = p - case "leveldb": - storageProvider = leveldb.NewProvider(s.vcProviderConf.StorageProviderConnString) - default: - storageProvider = mem.NewProvider() - } - - provider := &ariesServices{ - storageProvider: storageProvider, - } - - ldStore, err := createLDStore(provider.storageProvider) - if err != nil { - return nil, err - } - - loader, err := createJSONLDDocumentLoader(ldStore, vcProviderConf.TLS, - []string{s.vcProviderConf.ContextProviderURL}, true) - if err != nil { - return nil, fmt.Errorf("create document loader: %w", err) - } - - provider.documentLoader = loader - - kmsStore, err := kms.NewAriesProviderWrapper(provider.storageProvider) - if err != nil { - return nil, fmt.Errorf("failed to create Aries KMS store wrapper") - } - - provider.suite, err = localsuite.NewLocalCryptoSuite("local-lock://agentSDK", kmsStore, &noop.NoLock{}) - if err != nil { - return nil, fmt.Errorf("failed to create local crypto suite: %w", err) - } - - vrd, err := createVDR(vcProviderConf) - if err != nil { - return nil, err - } - - provider.vdrRegistry = vrd - - return provider, nil -} - -func createLDStore(storageProvider storage.Provider) (*ldStoreProvider, error) { - contextStore, err := ldstore.NewContextStore(storageProvider) - if err != nil { - return nil, fmt.Errorf("create JSON-LD context store: %w", err) - } - - remoteProviderStore, err := ldstore.NewRemoteProviderStore(storageProvider) - if err != nil { - return nil, fmt.Errorf("create remote provider store: %w", err) - } - - return &ldStoreProvider{ - ContextStore: contextStore, - RemoteProviderStore: remoteProviderStore, - }, nil -} - -func createJSONLDDocumentLoader(ldStore *ldStoreProvider, tlsConfig *tls.Config, - providerURLs []string, contextEnableRemote bool) (jsonld.DocumentLoader, error) { - loaderOpts := []ld.Opts{ld.WithExtraContexts(extraContexts...)} - - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - } - - for _, url := range providerURLs { - if url == "" { - continue - } - - loaderOpts = append(loaderOpts, - ld.WithRemoteProvider( - remote.NewProvider(url, remote.WithHTTPClient(httpClient)), - ), - ) - } - - if contextEnableRemote { - loaderOpts = append(loaderOpts, - ld.WithRemoteDocumentLoader(jsonld.NewDefaultDocumentLoader(http.DefaultClient))) - } - - loader, err := ld.NewDocumentLoader(ldStore, loaderOpts...) - if err != nil { - return nil, err - } - - return loader, nil -} - -// acceptsDID returns if given did method is accepted by VC REST api -func acceptsDID(method string) bool { - return method == didMethodVeres || method == didMethodElement || method == didMethodSov || - method == didMethodWeb || method == didMethodFactom || method == didMethodORB || - method == didMethodKey || method == didMethodION -} - -func createVDR(vcProviderConf *vcprovider.Config) (vdrapi.Registry, error) { - var opts []vdr.Option - - if vcProviderConf.UniResolverURL != "" { - universalResolverVDRI, err := httpbinding.New(vcProviderConf.UniResolverURL, - httpbinding.WithAccept(acceptsDID), httpbinding.WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: vcProviderConf.TLS, - }, - })) - if err != nil { - return nil, fmt.Errorf("failed to create new universal resolver vdr: %w", err) - } - - // add universal resolver vdr - opts = append(opts, vdr.WithVDR(universalResolverVDRI)) - } - - longForm, err := longform.New() - if err != nil { - return nil, err - } - - opts = append(opts, - vdr.WithVDR(longForm), - vdr.WithVDR(key.New()), - vdr.WithVDR(jwk.New()), - vdr.WithVDR( - &webVDR{ - http: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: vcProviderConf.TLS, - }, - }, - VDR: web.New(), - }, - ), - ) - - return vdr.New(opts...), nil -} - -func (s *Service) createAttestationVP() (string, error) { - if s.wallet == nil { - return "", fmt.Errorf("wallet is not initialized") - } - - content, err := s.wallet.GetAll() - if err != nil { - return "", fmt.Errorf("get wallet content: %w", err) - } - - var attestationVC *verifiable.Credential - - for _, vcData := range content { - vc, parseErr := verifiable.ParseCredential( - vcData, - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(s.ariesServices.JSONLDDocumentLoader()), - ) - if parseErr != nil { - return "", parseErr - } - - if lo.Contains(vc.Contents().Types, "WalletAttestationCredential") { - attestationVC = vc - break - } - } - - if attestationVC == nil { - return "", fmt.Errorf("no attestation vc found") - } - - attestationVP, err := verifiable.NewPresentation() - if err != nil { - return "", fmt.Errorf("create attestation vp: %w", err) - } - - attestationVP.AddCredentials(attestationVC) - attestationVP.ID = uuid.New().String() - - claims, err := attestationVP.JWTClaims([]string{}, false) - if err != nil { - return "", fmt.Errorf("create attestation jwt claims: %w", err) - } - - return s.SignJwtClaims(claims, nil) -} - -type kmsProvider struct { - store kmsapi.Store - secretLockService secretlock.Service -} - -func (k kmsProvider) StorageProvider() kmsapi.Store { - return k.store -} - -func (k kmsProvider) SecretLock() secretlock.Service { - return k.secretLockService -} - -type ldStoreProvider struct { - ContextStore ldstore.ContextStore - RemoteProviderStore ldstore.RemoteProviderStore -} - -func (p *ldStoreProvider) JSONLDContextStore() ldstore.ContextStore { - return p.ContextStore -} - -func (p *ldStoreProvider) JSONLDRemoteProviderStore() ldstore.RemoteProviderStore { - return p.RemoteProviderStore -} - -type webVDR struct { - http *http.Client - *web.VDR -} - -func (w *webVDR) Read(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { - docRes, err := w.VDR.Read(didID, append(opts, vdrapi.WithOption(web.HTTPClientOpt, w.http))...) - if err != nil { - return nil, fmt.Errorf("failed to read did web: %w", err) - } - - return docRes, nil -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci.go b/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci.go deleted file mode 100644 index cfdc1d362..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci.go +++ /dev/null @@ -1,692 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net" - "net/http" - "net/url" - "strings" - "time" - - "github.com/cli/browser" - "github.com/google/uuid" - "github.com/samber/lo" - "github.com/trustbloc/did-go/method/jwk" - didkey "github.com/trustbloc/did-go/method/key" - "github.com/trustbloc/kms-go/doc/jose" - "github.com/trustbloc/vc-go/jwt" - "github.com/trustbloc/vc-go/verifiable" - "golang.org/x/oauth2" - - "github.com/trustbloc/vcs/component/wallet-cli/pkg/credentialoffer" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/consent" - "github.com/trustbloc/vcs/pkg/kms/signer" - "github.com/trustbloc/vcs/pkg/restapi/v1/common" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" -) - -const ( - jwtProofTypHeader = "openid4vci-proof+jwt" -) - -type OIDC4VCIConfig struct { - CredentialOfferURI string - ClientID string - Scopes []string - RedirectURI string - CredentialType string - CredentialFormat string - Pin string - Login string - Password string - IssuerState string - EnableDiscoverableClientID bool - EnableClientAttestation bool -} - -type credentialRequestOpts struct { - signerKeyID string - signature string - nonce string -} - -type OauthClientOpt func(config *oauth2.Config) - -type CredentialRequestOpt func(credentialRequestOpts *credentialRequestOpts) - -type Hooks struct { - BeforeTokenRequest []OauthClientOpt - BeforeCredentialRequest []CredentialRequestOpt -} - -func WithClientID(clientID string) OauthClientOpt { - return func(config *oauth2.Config) { - config.ClientID = clientID - } -} - -// WithSignerKeyID overrides signerKeyID in credentials request. For testing purpose only. -func WithSignerKeyID(keyID string) CredentialRequestOpt { - return func(credentialRequestOpts *credentialRequestOpts) { - credentialRequestOpts.signerKeyID = keyID - } -} - -// WithSignatureValue overrides signature in credentials request. For testing purpose only. -func WithSignatureValue(signature string) CredentialRequestOpt { - return func(credentialRequestOpts *credentialRequestOpts) { - credentialRequestOpts.signature = signature - } -} - -// WithNonce overrides nonce in credentials request. For testing purpose only. -func WithNonce(nonce string) CredentialRequestOpt { - return func(credentialRequestOpts *credentialRequestOpts) { - credentialRequestOpts.nonce = nonce - } -} - -func (s *Service) RunOIDC4VCI(config *OIDC4VCIConfig, hooks *Hooks) error { - log.Println("Starting OIDC4VCI authorized code flow") - log.Printf("Credential Offer URI:\n\n\t%s\n\n", config.CredentialOfferURI) - - ctx := context.Background() - - err := s.CreateWallet() - if err != nil { - return fmt.Errorf("create wallet: %w", err) - } - - parser := &credentialoffer.Parser{ - HTTPClient: s.httpClient, - VDRRegistry: s.ariesServices.vdrRegistry, - } - - credentialOfferResponse, err := parser.Parse(config.CredentialOfferURI) - if err != nil { - return fmt.Errorf("parse credential offer uri: %w", err) - } - - s.print("Getting issuer OIDC config") - - oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration( - credentialOfferResponse.CredentialIssuer, - ) - if err != nil { - return fmt.Errorf("get OIDC credential issuer metadata: %w", err) - } - - redirectURI, err := url.Parse(config.RedirectURI) - if err != nil { - return fmt.Errorf("parse redirect uri: %w", err) - } - - var listener net.Listener - - if config.Login == "" { // bind listener for callback server to support login with a browser - listener, err = net.Listen("tcp4", "127.0.0.1:0") - if err != nil { - return fmt.Errorf("listen: %w", err) - } - - redirectURI.Host = fmt.Sprintf( - "%s:%d", - redirectURI.Hostname(), - listener.Addr().(*net.TCPAddr).Port, - ) - } - - s.oauthClient = &oauth2.Config{ - ClientID: config.ClientID, - RedirectURL: redirectURI.String(), - Scopes: config.Scopes, - Endpoint: oauth2.Endpoint{ - AuthURL: oidcIssuerCredentialConfig.AuthorizationEndpoint, - TokenURL: oidcIssuerCredentialConfig.TokenEndpoint, - AuthStyle: oauth2.AuthStyleInHeader, - }, - } - - opState := credentialOfferResponse.Grants.AuthorizationCode.IssuerState - state := uuid.New().String() - - b, err := json.Marshal(&common.AuthorizationDetails{ - Type: "openid_credential", - Types: []string{ - "VerifiableCredential", - config.CredentialType, - }, - Format: lo.ToPtr(config.CredentialFormat), - }) - if err != nil { - return fmt.Errorf("marshal authorization details: %w", err) - } - - authCodeOptions := []oauth2.AuthCodeOption{ - oauth2.SetAuthURLParam("issuer_state", opState), - oauth2.SetAuthURLParam("code_challenge", "MLSjJIlPzeRQoN9YiIsSzziqEuBSmS4kDgI3NDjbfF8"), - oauth2.SetAuthURLParam("code_challenge_method", "S256"), - oauth2.SetAuthURLParam("authorization_details", string(b)), - } - - if config.EnableDiscoverableClientID { - authCodeOptions = append(authCodeOptions, - oauth2.SetAuthURLParam("client_id_scheme", discoverableClientIDScheme)) - } - - authCodeURL := s.oauthClient.AuthCodeURL(state, authCodeOptions...) - - var authCode string - - if config.Login == "" { // interactive mode: login with a browser - authCode, err = s.getAuthCodeFromBrowser(listener, authCodeURL) - if err != nil { - return fmt.Errorf("get auth code from browser: %w", err) - } - } else { - authCode, err = s.getAuthCode(config, authCodeURL) - if err != nil { - return fmt.Errorf("get auth code: %w", err) - } - } - - if authCode == "" { - return fmt.Errorf("auth code is empty") - } - - ctx = context.WithValue(ctx, oauth2.HTTPClient, s.httpClient) - - var beforeTokenRequestHooks []OauthClientOpt - var beforeCredentialsRequestHooks []CredentialRequestOpt - - if hooks != nil { - beforeTokenRequestHooks = hooks.BeforeTokenRequest - beforeCredentialsRequestHooks = hooks.BeforeCredentialRequest - } - - for _, f := range beforeTokenRequestHooks { - f(s.oauthClient) - } - - s.print("Exchanging authorization code for access token") - - token, err := s.oauthClient.Exchange(ctx, authCode, - oauth2.SetAuthURLParam("code_verifier", "xalsLDydJtHwIQZukUyj6boam5vMUaJRWv-BnGCAzcZi3ZTs"), - ) - if err != nil { - return fmt.Errorf("exchange code for token: %w", err) - } - - s.token = token - - s.print("Getting credential") - - credResponse, _, err := s.getCredential( - oidcIssuerCredentialConfig.CredentialEndpoint, - config.CredentialType, - config.CredentialFormat, - credentialOfferResponse.CredentialIssuer, - beforeCredentialsRequestHooks..., - ) - if err != nil { - return fmt.Errorf("get credential: %w", err) - } - - vc := credResponse.Credential - b, err = json.Marshal(vc) - if err != nil { - return fmt.Errorf("marshal vc: %w", err) - } - - s.print("Adding credential to wallet") - if err = s.wallet.Add(b); err != nil { - return fmt.Errorf("add credential: %w", err) - } - - vcParsed, err := verifiable.ParseCredential(b, - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader( - s.ariesServices.JSONLDDocumentLoader())) - if err != nil { - return fmt.Errorf("parse vc: %w", err) - } - - log.Printf( - "Credential with ID [%s] and type [%v] added successfully", - vcParsed.Contents().ID, - config.CredentialType, - ) - - if !s.keepWalletOpen { - s.wallet.Close() - } - - if err = s.handleIssuanceAck(oidcIssuerCredentialConfig, credResponse); err != nil { - return err - } - - return nil -} - -func (s *Service) RunOIDC4CIWalletInitiated(config *OIDC4VCIConfig, hooks *Hooks) error { - log.Println("Starting OIDC4VCI authorized code flow Wallet initiated") - ctx := context.Background() - - issuerUrl := oidc4ci.ExtractIssuerURL(config.IssuerState) - if issuerUrl == "" { - return errors.New( - "undefined scopes supplied. " + - "Make sure one of the provided scopes is in the VCS issuer URL format") - } - - err := s.CreateWallet() - if err != nil { - return fmt.Errorf("create wallet: %w", err) - } - - oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration( - issuerUrl, - ) - if err != nil { - return fmt.Errorf("get issuer OIDC issuer config: %w", err) - } - - redirectURL, err := url.Parse(config.RedirectURI) - if err != nil { - return fmt.Errorf("parse redirect url: %w", err) - } - - var listener net.Listener - - if config.Login == "" { // bind listener for callback server to support log in with a browser - listener, err = net.Listen("tcp4", "127.0.0.1:0") - if err != nil { - return fmt.Errorf("listen: %w", err) - } - - redirectURL.Host = fmt.Sprintf( - "%s:%d", - redirectURL.Hostname(), - listener.Addr().(*net.TCPAddr).Port, - ) - } - - s.oauthClient = &oauth2.Config{ - ClientID: config.ClientID, - RedirectURL: redirectURL.String(), - Scopes: config.Scopes, - Endpoint: oauth2.Endpoint{ - AuthURL: oidcIssuerCredentialConfig.AuthorizationEndpoint, - TokenURL: oidcIssuerCredentialConfig.TokenEndpoint, - AuthStyle: oauth2.AuthStyleInHeader, - }, - } - - state := uuid.New().String() - - b, err := json.Marshal(&common.AuthorizationDetails{ - Type: "openid_credential", - Types: []string{ - "VerifiableCredential", - config.CredentialType, - }, - Format: lo.ToPtr(config.CredentialFormat), - }) - if err != nil { - return fmt.Errorf("marshal authorization details: %w", err) - } - - authCodeURL := s.oauthClient.AuthCodeURL(state, - oauth2.SetAuthURLParam("issuer_state", issuerUrl), - oauth2.SetAuthURLParam("code_challenge", "MLSjJIlPzeRQoN9YiIsSzziqEuBSmS4kDgI3NDjbfF8"), - oauth2.SetAuthURLParam("code_challenge_method", "S256"), - oauth2.SetAuthURLParam("authorization_details", string(b)), - ) - - var authCode string - - if config.Login == "" { // interactive mode: login with a browser - authCode, err = s.getAuthCodeFromBrowser(listener, authCodeURL) - if err != nil { - return fmt.Errorf("get auth code from browser: %w", err) - } - } else { - authCode, err = s.getAuthCode(config, authCodeURL) - if err != nil { - return fmt.Errorf("get auth code: %w", err) - } - } - - if authCode == "" { - return fmt.Errorf("auth code is empty") - } - - ctx = context.WithValue(ctx, oauth2.HTTPClient, s.httpClient) - - var beforeTokenRequestHooks []OauthClientOpt - var beforeCredentialsRequestHooks []CredentialRequestOpt - - if hooks != nil { - beforeTokenRequestHooks = hooks.BeforeTokenRequest - beforeCredentialsRequestHooks = hooks.BeforeCredentialRequest - } - - for _, f := range beforeTokenRequestHooks { - f(s.oauthClient) - } - - s.print("Exchanging authorization code for access token") - token, err := s.oauthClient.Exchange(ctx, authCode, - oauth2.SetAuthURLParam("code_verifier", "xalsLDydJtHwIQZukUyj6boam5vMUaJRWv-BnGCAzcZi3ZTs"), - ) - if err != nil { - return fmt.Errorf("exchange code for token: %w", err) - } - - s.token = token - - s.print("Getting credential") - credResponse, _, err := s.getCredential( - oidcIssuerCredentialConfig.CredentialEndpoint, - config.CredentialType, - config.CredentialFormat, - issuerUrl, - beforeCredentialsRequestHooks..., - ) - if err != nil { - return fmt.Errorf("get credential: %w", err) - } - - vc := credResponse.Credential - b, err = json.Marshal(vc) - if err != nil { - return fmt.Errorf("marshal vc: %w", err) - } - - s.print("Adding credential to wallet") - if err = s.wallet.Add(b); err != nil { - return fmt.Errorf("add credential: %w", err) - } - - vcParsed, err := verifiable.ParseCredential(b, - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader( - s.ariesServices.JSONLDDocumentLoader())) - if err != nil { - return fmt.Errorf("parse VC: %w", err) - } - - log.Printf( - "Credential with ID [%s] and type [%v] added successfully", - vcParsed.Contents().ID, - config.CredentialType, - ) - - if !s.keepWalletOpen { - s.wallet.Close() - } - - if err = s.handleIssuanceAck(oidcIssuerCredentialConfig, credResponse); err != nil { - return err - } - - return nil -} - -func (s *Service) getAuthCode( - config *OIDC4VCIConfig, - authCodeURL string, -) (string, error) { - // var loginURL, consentURL *url.URL - var authCode string - - httpClient := &http.Client{ - Jar: s.httpClient.Jar, - Transport: s.httpClient.Transport, - } - - httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { - if strings.Contains(req.URL.String(), ".amazoncognito.com/login") { - s.print("got cognito consent screen") - return consent.NewCognito( - httpClient, - httpClient.Jar.Cookies(req.URL), - req.URL.String(), - config.Login, config.Password, - ).Execute() - } - - // intercept client auth code - if strings.HasPrefix(req.URL.String(), config.RedirectURI) { - authCode = req.URL.Query().Get("code") - - return http.ErrUseLastResponse - } - - return nil - } - - s.print("Getting authorization code") - resp, err := httpClient.Get(authCodeURL) - if err != nil { - return "", fmt.Errorf("get auth code: %w", err) - } - _ = resp.Body.Close() - - return authCode, nil -} - -func (s *Service) getAuthCodeFromBrowser( - listener net.Listener, - authCodeURL string, -) (string, error) { - server := &callbackServer{ - listener: listener, - codeChan: make(chan string, 1), - } - - go func() { - http.Serve(listener, server) - }() - - log.Printf( - "Log in with a browser:\n\n%s\n\nor press [Enter] to open link in your default browser\n", - authCodeURL, - ) - - done := make(chan struct{}) - - go waitForEnter(done) - - for { - select { - case <-done: - if err := browser.OpenURL(authCodeURL); err != nil { - return "", fmt.Errorf("open browser: %w", err) - } - case authCode := <-server.codeChan: - log.Printf("Received authorization code: %s", authCode) - return authCode, nil - case <-time.After(5 * time.Minute): - return "", fmt.Errorf("timed out") - } - } -} - -func (s *Service) getCredential( - credentialEndpoint, - credentialType, - credentialFormat, - issuerURI string, - beforeCredentialRequestOpts ...CredentialRequestOpt, -) (*CredentialResponse, time.Duration, error) { - credentialsRequestParamsOverride := &credentialRequestOpts{} - for _, f := range beforeCredentialRequestOpts { - f(credentialsRequestParamsOverride) - } - - didKeyID := s.vcProviderConf.WalletParams.DidKeyID[0] - - fks, err := s.ariesServices.Suite().FixedKeyMultiSigner(strings.Split(didKeyID, "#")[1]) - if err != nil { - return nil, 0, fmt.Errorf("create kms signer: %w", err) - } - - kmsSigner := signer.NewKMSSigner(fks, s.vcProviderConf.WalletParams.SignType, nil) - - nonce := s.token.Extra("c_nonce").(string) - if credentialsRequestParamsOverride.nonce != "" { - nonce = credentialsRequestParamsOverride.nonce - } - - claims := &JWTProofClaims{ - Issuer: s.oauthClient.ClientID, - IssuedAt: time.Now().Unix(), - Audience: issuerURI, - Nonce: nonce, - } - - signerKeyID := didKeyID - - if strings.Contains(didKeyID, "did:key") { - res, err := didkey.New().Read(strings.Split(didKeyID, "#")[0]) - if err != nil { - return nil, 0, err - } - - signerKeyID = res.DIDDocument.VerificationMethod[0].ID - } else if strings.Contains(didKeyID, "did:jwk") { - res, err := jwk.New().Read(strings.Split(didKeyID, "#")[0]) - if err != nil { - return nil, 0, err - } - - signerKeyID = res.DIDDocument.VerificationMethod[0].ID - } - - if credentialsRequestParamsOverride.signerKeyID != "" { - signerKeyID = credentialsRequestParamsOverride.signerKeyID - } - - headers := map[string]interface{}{ - jose.HeaderType: jwtProofTypHeader, - } - - signedJWT, err := jwt.NewJoseSigned(claims, headers, - NewJWSSigner(signerKeyID, string(s.vcProviderConf.WalletParams.SignType), kmsSigner)) - if err != nil { - return nil, 0, fmt.Errorf("create signed jwt: %w", err) - } - - jws, err := signedJWT.Serialize(false) - if err != nil { - return nil, 0, fmt.Errorf("serialize signed jwt: %w", err) - } - - if credentialsRequestParamsOverride.signature != "" { - chunks := strings.Split(jws, ".") - jws = strings.Join([]string{chunks[0], chunks[1], credentialsRequestParamsOverride.signature}, ".") - } - - b, err := json.Marshal(CredentialRequest{ - Format: credentialFormat, - Types: []string{"VerifiableCredential", credentialType}, - Proof: JWTProof{ - ProofType: "jwt", - JWT: jws, - }, - }) - if err != nil { - return nil, 0, fmt.Errorf("marshal credential request: %w", err) - } - - ctx := context.WithValue(context.Background(), oauth2.HTTPClient, s.httpClient) - - httpClient := s.oauthClient.Client(ctx, s.token) - - vcsStart := time.Now() - finalDuration := time.Duration(0) - resp, err := httpClient.Post(credentialEndpoint, "application/json", bytes.NewBuffer(b)) - finalDuration = time.Since(vcsStart) - if err != nil { - return nil, 0, fmt.Errorf("get credential: %w", err) - } - - if resp.StatusCode != http.StatusOK { - b, _ := io.ReadAll(resp.Body) - return nil, finalDuration, fmt.Errorf( - "get credential: status %s and body %s", - resp.Status, - string(b), - ) - } - - var credentialResp CredentialResponse - - if err = json.NewDecoder(resp.Body).Decode(&credentialResp); err != nil { - return nil, finalDuration, fmt.Errorf("decode credential response: %w", err) - } - - return &credentialResp, finalDuration, nil -} - -func (s *Service) print( - msg string, -) { - if s.debug { - fmt.Println() - } - - log.Printf("%s\n\n", msg) -} - -func waitForEnter( - done chan<- struct{}, -) { - _, _ = fmt.Scanln() - done <- struct{}{} -} - -type callbackServer struct { - listener net.Listener - codeChan chan string -} - -func (s *callbackServer) ServeHTTP( - w http.ResponseWriter, - r *http.Request, -) { - if r.URL.Path != "/callback" { - http.NotFound(w, r) - - return - } - - defer func() { - _ = s.listener.Close() - }() - - code := r.URL.Query().Get("code") - if code == "" { - http.Error(w, "code is empty", http.StatusBadRequest) - - return - } - - s.codeChan <- code - - w.Header().Add("content-type", "text/html") - _, _ = fmt.Fprintf(w, "
Authorization code received! You may now close this page.
") -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci_pre_auth.go b/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci_pre_auth.go deleted file mode 100644 index d02e00218..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vci_pre_auth.go +++ /dev/null @@ -1,223 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "time" - - "github.com/samber/lo" - "github.com/trustbloc/vc-go/verifiable" - "golang.org/x/oauth2" - - "github.com/trustbloc/vcs/component/wallet-cli/pkg/credentialoffer" - issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" - "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4ci" -) - -func (s *Service) RunOIDC4CIPreAuth(config *OIDC4VCIConfig, hooks *Hooks) (*verifiable.Credential, error) { - log.Println("Starting OIDC4VCI pre-authorized code flow") - - startTime := time.Now() - err := s.CreateWallet() - if err != nil { - return nil, fmt.Errorf("failed to create wallet: %w", err) - } - s.perfInfo.CreateWallet = time.Since(startTime) - - log.Printf("Initiate issuance URL:\n\n\t%s\n\n", config.CredentialOfferURI) - - parser := &credentialoffer.Parser{ - HTTPClient: s.httpClient, - VDRRegistry: s.ariesServices.vdrRegistry, - } - - credentialOfferResponse, err := parser.Parse(config.CredentialOfferURI) - if err != nil { - return nil, fmt.Errorf("parse credential offer uri: %w", err) - } - - s.print("Getting issuer OIDC config") - startTime = time.Now() - oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration(credentialOfferResponse.CredentialIssuer) - s.perfInfo.VcsCIFlowDuration += time.Since(startTime) // oidc config - s.perfInfo.GetIssuerCredentialsOIDCConfig = time.Since(startTime) - - if err != nil { - return nil, fmt.Errorf("get issuer OIDC issuer config: %w", err) - } - - tokenValues := url.Values{ - "grant_type": []string{"urn:ietf:params:oauth:grant-type:pre-authorized_code"}, - "pre-authorized_code": []string{credentialOfferResponse.Grants.PreAuthorizationGrant.PreAuthorizedCode}, - "client_id": []string{config.ClientID}, - } - - if config.EnableClientAttestation { - jwtVP, createErr := s.createAttestationVP() - if createErr != nil { - return nil, createErr - } - - tokenValues.Add("client_assertion_type", "attest_jwt_client_auth") - tokenValues.Add("client_assertion", jwtVP) - } - - if credentialOfferResponse.Grants.PreAuthorizationGrant.UserPinRequired { - if len(config.Pin) == 0 { - log.Println("Enter PIN:") - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - config.Pin = scanner.Text() - } - - tokenValues.Add("user_pin", config.Pin) - } - - s.print("Getting access token") - startTime = time.Now() - tokenResp, tokenErr := s.httpClient.PostForm(oidcIssuerCredentialConfig.TokenEndpoint, tokenValues) - s.perfInfo.GetAccessToken = time.Since(startTime) - s.perfInfo.VcsCIFlowDuration += time.Since(startTime) - if tokenErr != nil { - return nil, tokenErr - } - - if tokenResp.StatusCode != http.StatusOK { - b, _ := io.ReadAll(tokenResp.Body) - return nil, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, tokenResp.StatusCode, string(b)) - } - - var token oidc4ci.AccessTokenResponse - if err = json.NewDecoder(tokenResp.Body).Decode(&token); err != nil { - return nil, err - } - _ = tokenResp.Body.Close() - - s.oauthClient = &oauth2.Config{ - ClientID: "oidc4vc_client", - Endpoint: oauth2.Endpoint{ - TokenURL: oidcIssuerCredentialConfig.TokenEndpoint, - }, - } // todo dynamic client registration - s.token = lo.ToPtr(oauth2.Token{AccessToken: token.AccessToken}).WithExtra(map[string]interface{}{ - "c_nonce": *token.CNonce, - }) - - var beforeCredentialsRequestHooks []CredentialRequestOpt - - if hooks != nil { - beforeCredentialsRequestHooks = hooks.BeforeCredentialRequest - } - - s.print("Getting credential") - startTime = time.Now() - credResponse, vcsDuration, err := s.getCredential( - oidcIssuerCredentialConfig.CredentialEndpoint, - config.CredentialType, - config.CredentialFormat, - credentialOfferResponse.CredentialIssuer, - beforeCredentialsRequestHooks..., - ) - if err != nil { - return nil, fmt.Errorf("get credential: %w", err) - } - s.perfInfo.VcsCIFlowDuration += vcsDuration - s.perfInfo.GetCredential = time.Since(startTime) - - vc := credResponse.Credential - b, err := json.Marshal(vc) - if err != nil { - return nil, fmt.Errorf("marshal vc: %w", err) - } - - s.print("Adding credential to wallet") - - if err = s.wallet.Add(b); err != nil { - return nil, fmt.Errorf("add credential to wallet: %w", err) - } - - vcParsed, err := verifiable.ParseCredential(b, - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader( - s.ariesServices.JSONLDDocumentLoader())) - if err != nil { - return nil, fmt.Errorf("parse vc: %w", err) - } - - log.Printf("Credential with ID [%s] and type [%v] added successfully", - vcParsed.Contents().ID, config.CredentialType) - - if !s.keepWalletOpen { - s.wallet.Close() - } - - startTime = time.Now() - if err = s.handleIssuanceAck(oidcIssuerCredentialConfig, credResponse); err != nil { - return nil, err - } - s.perfInfo.CredentialsAck = time.Since(startTime) - - return vcParsed, nil -} - -func (s *Service) handleIssuanceAck( - wellKnown *issuerv1.WellKnownOpenIDIssuerConfiguration, - credResponse *CredentialResponse, -) error { - if wellKnown == nil || credResponse == nil { - return nil - } - - if wellKnown.CredentialAckEndpoint == "" || lo.FromPtr(credResponse.AckID) == "" { - return nil - } - - s.print("Sending wallet ACK") - - ctx := context.WithValue(context.Background(), oauth2.HTTPClient, s.httpClient) - httpClient := s.oauthClient.Client(ctx, s.token) - - b, err := json.Marshal(oidc4ci.AckRequest{ - Credentials: []oidc4ci.AcpRequestItem{ - { - AckId: *credResponse.AckID, - ErrorDescription: nil, - Status: "success", - IssuerIdentifier: &wellKnown.CredentialIssuer, - }, - }, - }) - if err != nil { - return err - } - - resp, err := httpClient.Post(wellKnown.CredentialAckEndpoint, "application/json", bytes.NewBuffer(b)) - if err != nil { - return err - } - - s.print(fmt.Sprintf("Wallet ACK sent with status code %v", resp.StatusCode)) - - b, _ = io.ReadAll(resp.Body) // nolint - if resp.StatusCode != http.StatusNoContent { - return fmt.Errorf("expected to receive status code %d but got status code %d with response body %s", - http.StatusNoContent, resp.StatusCode, string(b)) - } - - return nil -} diff --git a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go b/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go deleted file mode 100644 index 98ca58701..000000000 --- a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go +++ /dev/null @@ -1,751 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/url" - "strings" - "time" - - "github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp" - - "github.com/google/uuid" - "github.com/trustbloc/did-go/method/jwk" - "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/kms-go/wrapper/api" - "github.com/trustbloc/vc-go/proof/defaults" - "github.com/trustbloc/vc-go/vermethod" - "github.com/valyala/fastjson" - - didkey "github.com/trustbloc/did-go/method/key" - "github.com/trustbloc/kms-go/doc/jose" - "github.com/trustbloc/vc-go/jwt" - "github.com/trustbloc/vc-go/presexch" - "github.com/trustbloc/vc-go/verifiable" - - "github.com/trustbloc/vcs/component/wallet-cli/internal/httputil" - "github.com/trustbloc/vcs/pkg/doc/vc" - vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" - vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" - vcskms "github.com/trustbloc/vcs/pkg/kms" - "github.com/trustbloc/vcs/pkg/kms/signer" - "github.com/trustbloc/vcs/pkg/observability/metrics/noop" -) - -type RPConfigOverride func(rpc *RPConfig) - -func WithSupportedVPFormat(vpFormat vcs.Format) RPConfigOverride { - return func(rpc *RPConfig) { - rpc.supportedVPFormat = vpFormat - } -} - -type OIDC4VPHooks struct { - CreateAuthorizedResponse []RPConfigOverride -} - -func (s *Service) RunOIDC4VPFlow(ctx context.Context, authorizationRequest string, hooks *OIDC4VPHooks) error { - log.Println("Start OIDC4VP flow") - log.Println("AuthorizationRequest:", authorizationRequest) - - err := s.CreateWallet() - if err != nil { - return fmt.Errorf("failed to create wallet: %w", err) - } - - if s.vcProviderConf.OIDC4VPShouldFetchCredentials { - log.Println("Issuing credentials") - vcData, err := s.vcProvider.GetCredentials() - if err != nil { - return fmt.Errorf("failed getting VC: %w", err) - } - - log.Println("Saving credentials to wallet") - for _, vcBytes := range vcData { - err = s.SaveCredentialInWallet(vcBytes) - if err != nil { - return fmt.Errorf("error save VC to wallet : %w", err) - } - } - log.Println(len(vcData), "credentials were saved to wallet") - } else { - log.Println("Using existing credentials") - } - - s.vpFlowExecutor = s.NewVPFlowExecutor(s.vcProviderConf.SkipSchemaValidation) - - log.Println("Fetching request object") - startTime := time.Now() - rawRequestObject, dur, err := s.vpFlowExecutor.FetchRequestObject(authorizationRequest) - s.perfInfo.FetchRequestObject = dur - s.perfInfo.VcsVPFlowDuration += dur - if err != nil { - return err - } - - log.Println("Resolving request object") - startTime = time.Now() - err = s.vpFlowExecutor.VerifyAuthorizationRequestAndDecodeClaims(rawRequestObject) - if err != nil { - return err - } - - if s.vcProviderConf.LinkedDomainVerificationEnabled { - if err := s.runLinkedDomainVerification(s.vpFlowExecutor.requestObject.ClientID); err != nil { - return fmt.Errorf("linked domain verification failed: %w", err) - } - } - - s.perfInfo.VerifyAuthorizationRequest = time.Since(startTime) - - log.Println("Querying VC from wallet") - startTime = time.Now() - err = s.vpFlowExecutor.QueryCredentialFromWalletSingleVP() - if err != nil { - return err - } - s.perfInfo.QueryCredentialFromWallet = time.Since(startTime) - if !s.vcProviderConf.KeepWalletOpen { - s.wallet.Close() - } - - var createAuthorizedResponseHooks []RPConfigOverride - if hooks != nil { - createAuthorizedResponseHooks = hooks.CreateAuthorizedResponse - } - - log.Println("Creating authorized response") - startTime = time.Now() - authorizedResponse, err := s.vpFlowExecutor.CreateAuthorizedResponse(createAuthorizedResponseHooks...) - if err != nil { - return err - } - s.perfInfo.CreateAuthorizedResponse = time.Since(startTime) - - log.Println("Sending authorized response") - startTime = time.Now() - dur, err = s.vpFlowExecutor.SendAuthorizedResponse(ctx, authorizedResponse) - s.perfInfo.SendAuthorizedResponse = dur - s.perfInfo.VcsVPFlowDuration += dur - if err != nil { - return err - } - - log.Println("Credentials shared with verifier") - return nil -} - -type VPFlowExecutor struct { - tlsConfig *tls.Config - ariesServices *ariesServices - wallet Wallet - walletToken string - walletDidID []string - walletDidKeyID []string - walletDidKeyType kms.KeyType - walletSignType vcs.SignatureType - requestObject *RequestObject - requestPresentation []*verifiable.Presentation - requestPresentationSubmission *presexch.PresentationSubmission - - skipSchemaValidation bool - httpClient *http.Client -} - -func (s *Service) NewVPFlowExecutor(skipSchemaValidation bool) *VPFlowExecutor { - return &VPFlowExecutor{ - tlsConfig: s.vcProviderConf.TLS, - ariesServices: s.ariesServices, - wallet: s.wallet, - walletToken: s.vcProviderConf.WalletParams.Token, - walletDidID: s.vcProviderConf.WalletParams.DidID, - walletDidKeyID: s.vcProviderConf.WalletParams.DidKeyID, - walletDidKeyType: kms.KeyType(s.vcProviderConf.DidKeyType), - walletSignType: s.vcProviderConf.WalletParams.SignType, - - skipSchemaValidation: skipSchemaValidation, - httpClient: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: s.vcProviderConf.TLS, - }, - }, - } -} - -func (s *Service) GetVPFlowExecutor() *VPFlowExecutor { - return s.vpFlowExecutor -} - -func (e *VPFlowExecutor) InitiateInteraction(url, authToken string, body io.Reader) (*InitiateOIDC4VPResponse, error) { - resp, err := httputil.HTTPSDo(http.MethodPost, url, "application/json", authToken, //nolint: bodyclose - body, e.tlsConfig) - if err != nil { - return nil, err - } - - defer httputil.CloseResponseBody(resp.Body) - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, resp.StatusCode, respBytes) - } - - result := &InitiateOIDC4VPResponse{} - - return result, json.Unmarshal(respBytes, result) -} - -func (e *VPFlowExecutor) FetchRequestObject(authorizationRequest string) (string, time.Duration, error) { - endpointURL := strings.TrimPrefix(authorizationRequest, "openid-vc://?request_uri=") - - st := time.Now() - resp, err := httputil.HTTPSDo(http.MethodGet, endpointURL, "", "", nil, e.tlsConfig) - dur := time.Since(st) - if err != nil { - return "", dur, err - } - defer httputil.CloseResponseBody(resp.Body) - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", dur, err - } - - if resp.StatusCode != http.StatusOK { - return "", dur, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, resp.StatusCode, respBytes) - } - - return string(respBytes), dur, nil -} - -func (e *VPFlowExecutor) RequestPresentations() []*verifiable.Presentation { - return e.requestPresentation -} - -func (e *VPFlowExecutor) RetrieveInteractionsClaim(url, authToken string) ([]byte, error) { - resp, err := httputil.HTTPSDo(http.MethodGet, url, "application/json", authToken, //nolint: bodyclose - nil, e.tlsConfig) - if err != nil { - return nil, err - } - defer httputil.CloseResponseBody(resp.Body) - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, resp.StatusCode, respBytes) - } - - return respBytes, nil -} - -func (e *VPFlowExecutor) VerifyAuthorizationRequestAndDecodeClaims(rawRequestObject string) error { - jwtVerifier := defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(e.ariesServices.vdrRegistry)) - - rawData, err := verifyTokenSignature(rawRequestObject, jwtVerifier) - if err != nil { - return err - } - - var requestObject *RequestObject - if err = json.Unmarshal(rawData, &requestObject); err != nil { - return fmt.Errorf("requestObject decode claims: %w", err) - } - - e.requestObject = requestObject - - return nil -} - -func verifyTokenSignature(rawJwt string, verifier jwt.ProofChecker) ([]byte, error) { - _, rawData, err := jwt.ParseAndCheckProof( - rawJwt, - verifier, true, - jwt.WithIgnoreClaimsMapDecoding(true), - ) - if err != nil { - return nil, fmt.Errorf("parse JWT: %w", err) - } - - return rawData, nil -} - -func (e *VPFlowExecutor) QueryCredentialFromWalletSingleVP() error { - if e.skipSchemaValidation && len(e.requestObject.Claims.VPToken.PresentationDefinition.InputDescriptors) > 0 { // bypass - oldScheme := e.requestObject.Claims.VPToken.PresentationDefinition.InputDescriptors[0].Schema - e.requestObject.Claims.VPToken.PresentationDefinition.InputDescriptors[0].Schema = nil - defer func() { - e.requestObject.Claims.VPToken.PresentationDefinition.InputDescriptors[0].Schema = oldScheme - }() - } - - pdBytes, err := json.Marshal(e.requestObject.Claims.VPToken.PresentationDefinition) - - if err != nil { - return fmt.Errorf("presentation definition marshal: %w", err) - } - - // This query will always return one VP - so far no plans to change this - vps, err := e.wallet.Query(pdBytes) - - if err != nil { - return fmt.Errorf("query vc using presentation definition: %w", err) - } - - vps[0].Context = []string{"https://www.w3.org/2018/credentials/v1"} - - e.requestPresentation = vps - e.requestPresentationSubmission = vps[0].CustomFields["presentation_submission"].(*presexch.PresentationSubmission) - - return nil -} - -func (e *VPFlowExecutor) QueryCredentialFromWalletMultiVP() error { - pdBytes, err := json.Marshal(e.requestObject.Claims.VPToken.PresentationDefinition) - if err != nil { - return fmt.Errorf("marshal presentation definition: %w", err) - } - - // This query will always return one VP - so far no plans to change this - // We will only use this to get relevant credentials from wallet - legacyVP, err := e.wallet.Query(pdBytes) - if err != nil { - return fmt.Errorf("query credentials from wallet: %w", err) - } - - credentials := legacyVP[0].Credentials() - - // Create a list of verifiable presentations, with one presentation for each provided credential. - vps, ps, err := e.requestObject.Claims.VPToken.PresentationDefinition.CreateVPArray( - credentials, e.ariesServices.documentLoader, - presexch.WithSDCredentialOptions(verifiable.WithJSONLDDocumentLoader(e.ariesServices.documentLoader))) - if err != nil { - return fmt.Errorf("failed to create VP array from selected credentials: %w", err) - } - - e.requestPresentation = vps - e.requestPresentationSubmission = ps - - return nil -} - -func (e *VPFlowExecutor) getIDTokenClaims(requestPresentationSubmission *presexch.PresentationSubmission, requestObjectScope string) (*IDTokenClaims, error) { - scopeAdditionalClaims, err := oidc4vp.ExtractCustomScopeClaims(requestObjectScope) - if err != nil { - return nil, fmt.Errorf("ExtractCustomScopeClaims: %w", err) - } - - return &IDTokenClaims{ - ScopeAdditionalClaims: scopeAdditionalClaims, - VPToken: IDTokenVPToken{ - PresentationSubmission: requestPresentationSubmission, - }, - Nonce: e.requestObject.Nonce, - Exp: time.Now().Unix() + 600, - Iss: "https://self-issued.me/v2/openid-vc", - Aud: e.requestObject.ClientID, - Sub: e.walletDidID[0], - Nbf: time.Now().Unix(), - Iat: time.Now().Unix(), - Jti: uuid.NewString(), - }, nil -} - -func (e *VPFlowExecutor) signIDTokenJWT(idToken *IDTokenClaims, signatureType vcs.SignatureType) (string, error) { - idTokenJWS, err := signTokenJWT( - idToken, - e.walletDidKeyID[0], - e.ariesServices.suite, - signatureType, - map[string]interface{}{"typ": "JWT"}, - ) - if err != nil { - return "", fmt.Errorf("sign id_token: %w", err) - } - - return idTokenJWS, nil -} - -func (e *VPFlowExecutor) CreateAuthorizedResponse(o ...RPConfigOverride) (string, error) { - configRP, err := e.getRPConfig() - if err != nil { - return "", err - } - - for _, f := range o { - f(configRP) - } - - var tokens []string - - for i, vp := range e.requestPresentation { - delete(vp.CustomFields, "presentation_submission") - - var didID string - - didID, err = e.GetSubjectID(vp.Credentials()) - if err != nil { - return "", err - } - - didIDIndex := e.getDIDIndex(didID) - didKeyID := e.walletDidKeyID[didIDIndex] - - var signedVPToken string - - switch configRP.supportedVPFormat { - case vcs.Jwt: - e.requestPresentationSubmission.DescriptorMap[i].Format = "jwt_vp" - signedVPToken, err = e.signPresentationJWT(vp, e.walletSignType, didID, didKeyID) - case vcs.Ldp: - e.requestPresentationSubmission.DescriptorMap[i].Format = "ldp_vp" - signedVPToken, err = e.signPresentationLDP(vp, configRP.supportedSignatureType, didKeyID, - e.walletDidKeyType) - } - if err != nil { - return "", fmt.Errorf("format %s sign VP: %w", configRP.supportedVPFormat, err) - } - - tokens = append(tokens, signedVPToken) - } - - tokensJSON := tokens[0] - if len(tokens) > 1 { - var tokensJSONBytes []byte - - tokensJSONBytes, err = json.Marshal(tokens) - if err != nil { - return "", fmt.Errorf("marshal tokens: %w", err) - } - - tokensJSON = string(tokensJSONBytes) - } - - var signedIDToken string - - idToken, err := e.getIDTokenClaims(e.requestPresentationSubmission, e.requestObject.Scope) - if err != nil { - return "", err - } - - signedIDToken, err = e.signIDTokenJWT(idToken, e.walletSignType) - if err != nil { - return "", err - } - - data := url.Values{} - data.Set("id_token", signedIDToken) - data.Set("vp_token", tokensJSON) - data.Set("state", e.requestObject.State) - - return data.Encode(), nil -} - -type RPConfig struct { - supportedVPFormat, supportedVCFormat vcs.Format - supportedSignatureType vcs.SignatureType -} - -func (e *VPFlowExecutor) getRPConfig() (*RPConfig, error) { - config := &RPConfig{} - - roVPFormats := e.requestObject.Registration.VPFormats - switch { - case roVPFormats.JwtVP != nil: - config.supportedVPFormat = vcs.Jwt - config.supportedSignatureType = vcs.SignatureType(roVPFormats.JwtVP.Alg[0]) - case roVPFormats.LdpVP != nil: - config.supportedVPFormat = vcs.Ldp - config.supportedSignatureType = vcs.SignatureType(roVPFormats.LdpVP.ProofType[0]) - default: - return nil, fmt.Errorf("RP supported VP format is not defiled, request object: %+v", roVPFormats) - } - - switch { - case roVPFormats.JwtVC != nil: - config.supportedVCFormat = vcs.Jwt - case roVPFormats.LdpVC != nil: - config.supportedVCFormat = vcs.Ldp - default: - return nil, fmt.Errorf("RP supported VC format is not defiled, request object: %+v", roVPFormats) - } - - return config, nil -} - -func (e *VPFlowExecutor) signPresentationLDP(vp *verifiable.Presentation, - signatureType vcs.SignatureType, didKeyID string, keyType kms.KeyType) (string, error) { - vcCryptoSigner := vccrypto.New(e.ariesServices.vdrRegistry, e.ariesServices.documentLoader) - vp.Context = append(vp.Context, "https://w3id.org/security/suites/jws-2020/v1") - - signedVP, err := vcCryptoSigner.SignPresentation( - &vc.Signer{ - Creator: didKeyID, - KeyType: keyType, - KMSKeyID: strings.Split(didKeyID, "#")[1], - SignatureType: signatureType, - SignatureRepresentation: verifiable.SignatureProofValue, - KMS: vcskms.GetAriesKeyManager(e.ariesServices.suite, vcskms.Local, noop.GetMetrics()), - }, - vp, - vccrypto.WithChallenge(e.requestObject.Nonce), - vccrypto.WithDomain(e.requestObject.ClientID), - ) - if err != nil { - return "", fmt.Errorf("sign presentation LDP: %w", err) - } - - var b []byte - b, err = signedVP.MarshalJSON() - if err != nil { - return "", fmt.Errorf("marshal signed VP: %w", err) - } - - return string(b), nil -} - -func (e *VPFlowExecutor) signPresentationJWT(vp *verifiable.Presentation, signatureType vcs.SignatureType, didID, didKeyID string) (string, error) { - vpTokenBytes, err := json.Marshal(e.getJWTVPTokenClaims(vp, didID)) - if err != nil { - return "", err - } - - vpTokenJWS := strings.ReplaceAll(string(vpTokenBytes), `"type":"VerifiablePresentation"`, `"type":["VerifiablePresentation"]`) - - vpTokenJWS, err = signTokenJWT( - vpTokenJWS, - didKeyID, - e.ariesServices.suite, - signatureType, - map[string]interface{}{"typ": "JWT"}, - ) - if err != nil { - return "", fmt.Errorf("sign vp_token: %w", err) - } - - return vpTokenJWS, nil -} - -func (e *VPFlowExecutor) getJWTVPTokenClaims(vp *verifiable.Presentation, didID string) VPTokenClaims { - nowSec := time.Now().Unix() - - return VPTokenClaims{ - VP: vp, - Nonce: e.requestObject.Nonce, - Exp: nowSec + 600, - Iss: didID, - Aud: e.requestObject.ClientID, - Nbf: nowSec, - Iat: nowSec, - Jti: uuid.NewString(), - } -} - -func (e *VPFlowExecutor) getCredentials(creds []interface{}) ([]*verifiable.Credential, error) { - var credentials []*verifiable.Credential - - for _, cred := range creds { - vcBytes, err := json.Marshal(cred) - if err != nil { - return nil, err - } - - credentialParsed, err := verifiable.ParseCredential(vcBytes, - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(e.ariesServices.documentLoader)) - if err != nil { - return nil, fmt.Errorf("fail to parse credential: %w", err) - } - - credentials = append(credentials, credentialParsed) - } - - return credentials, nil -} - -func (e *VPFlowExecutor) getDIDIndex(did string) int { - for index, walletDID := range e.walletDidID { - if did == walletDID { - return index - } - } - - return -1 -} - -func (e *VPFlowExecutor) GetSubjectID(creds []*verifiable.Credential) (string, error) { - subjectIDMap := make(map[string]bool) - - var subjectID string - var err error - - for _, vc := range creds { - subjectID, err = verifiable.SubjectID(vc.Contents().Subject) - if err != nil { - return "", fmt.Errorf("failed to get subject ID: %w", err) - } - - if vc.IsJWT() { - // We use this strange code, because cred.JWTClaims(false) not take to account "sub" claim from jwt - _, rawClaims, credErr := jwt.Parse( - vc.JWTEnvelope.JWT, - jwt.WithIgnoreClaimsMapDecoding(true), - ) - if credErr != nil { - return "", fmt.Errorf("fail to parse credential as jwt: %w", credErr) - } - - subjectID = fmt.Sprint(fastjson.GetString(rawClaims, "sub")) - } - - subjectIDMap[subjectID] = true - } - - if len(subjectIDMap) > 1 { - fmt.Println("WARNING ... more than one subject ID found in VP") - } - - return subjectID, nil -} - -func signTokenJWT( - claims interface{}, - didKeyID string, - suite api.Suite, - signType vcs.SignatureType, - headers map[string]interface{}, -) (string, error) { - fks, err := suite.FixedKeyMultiSigner(strings.Split(didKeyID, "#")[1]) - if err != nil { - return "", fmt.Errorf("create kms signer: %w", err) - } - - kmsSigner := signer.NewKMSSigner(fks, signType, nil) - - signerKeyID := didKeyID - - if strings.Contains(didKeyID, "did:key") { - res, err := didkey.New().Read(strings.Split(didKeyID, "#")[0]) - if err != nil { - return "", err - } - - signerKeyID = res.DIDDocument.VerificationMethod[0].ID - } else if strings.Contains(didKeyID, "did:jwk") { - res, err := jwk.New().Read(strings.Split(didKeyID, "#")[0]) - if err != nil { - return "", err - } - - signerKeyID = res.DIDDocument.VerificationMethod[0].ID - } - - token, err := jwt.NewJoseSigned(claims, headers, NewJWSSigner(signerKeyID, - string(signType), kmsSigner)) - if err != nil { - return "", fmt.Errorf("initiate oidc interaction: sign token failed: %w", err) - } - - tokenBytes, err := token.Serialize(false) - if err != nil { - return "", fmt.Errorf("initiate oidc interaction: serialize token failed: %w", err) - } - - return tokenBytes, nil -} - -func (e *VPFlowExecutor) SendAuthorizedResponse(ctx context.Context, responseBody string) (time.Duration, error) { - log.Printf("auth req: %s\n", responseBody) - - req, err := http.NewRequestWithContext( - ctx, - http.MethodPost, - e.requestObject.RedirectURI, - bytes.NewBuffer([]byte(responseBody)), - ) - if err != nil { - return 0, err - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - client := HttpClientFromContext(ctx, e.httpClient) - st := time.Now() - resp, err := client.Do(req) - dur := time.Since(st) - - if err != nil { - return dur, err - } - - defer httputil.CloseResponseBody(resp.Body) - - respBytes, err := io.ReadAll(resp.Body) - if err != nil { - return dur, err - } - - if resp.StatusCode != http.StatusOK { - return dur, fmt.Errorf("expected status code %d but got status code %d with response body %s instead", - http.StatusOK, resp.StatusCode, respBytes) - } - - return dur, nil -} - -type JWSSigner struct { - keyID string - signingAlgorithm string - signer vc.SignerAlgorithm -} - -func NewJWSSigner(keyID string, signingAlgorithm string, signer vc.SignerAlgorithm) *JWSSigner { - return &JWSSigner{ - keyID: keyID, - signingAlgorithm: signingAlgorithm, - signer: signer, - } -} - -// Sign signs. -func (s *JWSSigner) Sign(data []byte) ([]byte, error) { - return s.signer.Sign(data) -} - -// Headers provides JWS headers. "alg" header must be provided (see https://tools.ietf.org/html/rfc7515#section-4.1) -func (s *JWSSigner) Headers() jose.Headers { - return jose.Headers{ - jose.HeaderKeyID: s.keyID, - jose.HeaderAlgorithm: s.signingAlgorithm, - } -} - -// noVerifier is used when no JWT signature verification is needed. -// To be used with precaution. -type noVerifier struct{} - -func (v noVerifier) CheckJWTProof(_ jose.Headers, _, _, _ []byte) error { - return nil -} diff --git a/component/wallet-cli/pkg/walletrunner/wellknown.go b/component/wallet-cli/pkg/walletrunner/wellknown.go deleted file mode 100644 index b8d575d1c..000000000 --- a/component/wallet-cli/pkg/walletrunner/wellknown.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright Avast Software. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package walletrunner - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - vdrapi "github.com/trustbloc/did-go/vdr/api" - "github.com/trustbloc/vc-go/jwt" - "github.com/trustbloc/vc-go/proof/defaults" - "github.com/trustbloc/vc-go/vermethod" - "github.com/valyala/fastjson" - - issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" -) - -// GetWellKnownOpenIDConfiguration returns OIDC Configuration. -func (s *Service) GetWellKnownOpenIDConfiguration( - issuerURL string, -) (*issuerv1.WellKnownOpenIDIssuerConfiguration, error) { - // GET /issuer/{profileID}/.well-known/openid-credential-issuer - resp, err := s.httpClient.Get(issuerURL + "/.well-known/openid-credential-issuer") - if err != nil { - return nil, fmt.Errorf("get issuer well-known: %w", err) - } - - defer func() { - _ = resp.Body.Close() - }() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("get issuer well-known: status code %d", resp.StatusCode) - } - - var oidcConfig issuerv1.WellKnownOpenIDIssuerConfiguration - - wellKnownOpenIDIssuerConfigurationPayload, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("read issuer configuration payload body: %w", err) - } - - if jwt.IsJWS(string(wellKnownOpenIDIssuerConfigurationPayload)) { - wellKnownOpenIDIssuerConfigurationPayload, err = - getWellKnownOpenIDConfigurationJWTPayload( - string(wellKnownOpenIDIssuerConfigurationPayload), s.ariesServices.vdrRegistry) - if err != nil { - return nil, err - } - } - - if err = json.Unmarshal(wellKnownOpenIDIssuerConfigurationPayload, &oidcConfig); err != nil { - return nil, fmt.Errorf("decode issuer well-known: %w", err) - } - - return &oidcConfig, nil -} - -func getWellKnownOpenIDConfigurationJWTPayload(rawResponse string, vdrRegistry vdrapi.Registry) ([]byte, error) { - jwtVerifier := defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(vdrRegistry)) - - _, credentialOfferPayload, err := jwt.ParseAndCheckProof( - rawResponse, - jwtVerifier, true, - jwt.WithIgnoreClaimsMapDecoding(true), - ) - if err != nil { - return nil, fmt.Errorf("parse issuer configuration JWT: %w", err) - } - - var fastParser fastjson.Parser - v, err := fastParser.ParseBytes(credentialOfferPayload) - if err != nil { - return nil, fmt.Errorf("decode claims: %w", err) - } - - sb, err := v.Get("well_known_openid_issuer_configuration").Object() - if err != nil { - return nil, fmt.Errorf("fastjson.Parser Get well_known_openid_issuer_configuration: %w", err) - } - - return sb.MarshalTo([]byte{}), nil -} diff --git a/test/bdd/go.mod b/test/bdd/go.mod index 44eb2643b..339e7ad02 100644 --- a/test/bdd/go.mod +++ b/test/bdd/go.mod @@ -27,7 +27,7 @@ require ( github.com/trustbloc/logutil-go v1.0.0-rc1 github.com/trustbloc/vc-go v1.0.3-0.20231117124429-a8a3b24ef734 github.com/trustbloc/vcs v0.1.9-0.20230210204445-f2870a36f0ea - github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20231222131742-742a7ae591ba + github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20240103173902-7fbe030659b2 github.com/trustbloc/vcs/test/stress v0.0.0-00010101000000-000000000000 go.uber.org/zap v1.23.0 golang.org/x/oauth2 v0.7.0 @@ -91,7 +91,6 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/henvic/httpretty v0.1.0 // indirect github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.3.5 // indirect @@ -138,7 +137,6 @@ require ( github.com/spf13/viper v1.16.0 // indirect github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/syndtr/goleveldb v1.0.0 // indirect github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/test/bdd/go.sum b/test/bdd/go.sum index 8b27b0132..ce79929fc 100644 --- a/test/bdd/go.sum +++ b/test/bdd/go.sum @@ -302,7 +302,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -402,9 +401,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/henvic/httpretty v0.1.0 h1:Htk66UUEbXTD4JR0qJZaw8YAMKw+9I24ZZOnDe/ti+E= -github.com/henvic/httpretty v0.1.0/go.mod h1:ViEsly7wgdugYtymX54pYp6Vv2wqZmNHayJ6q8tlKCc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= @@ -555,11 +551,7 @@ github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0 github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -675,8 +667,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 h1:RBkacARv7qY5laaXGlF4wFB/tk5rnthhPb8oIBGoagY= github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8/go.mod h1:9PdLyPiZIiW3UopXyRnPYyjUXSpiQNHRLu8fOsR3o8M= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -1154,7 +1144,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -1162,7 +1151,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/bdd/pkg/v1/oidc4vc/stress_steps.go b/test/bdd/pkg/v1/oidc4vc/stress_steps.go index a0507fd03..c07f910a6 100644 --- a/test/bdd/pkg/v1/oidc4vc/stress_steps.go +++ b/test/bdd/pkg/v1/oidc4vc/stress_steps.go @@ -56,18 +56,20 @@ func (s *Steps) runStressTest(ctx context.Context) error { return errors.New("CLAIM_DATA should not be empty") } run := stress.NewStressRun(&stress.Config{ - TLSConfig: s.tlsConfig, - ApiURL: os.Getenv("VCS_API_URL"), - TokenClientID: os.Getenv("TOKEN_CLIENT_ID"), - TokenClientSecret: os.Getenv("TOKEN_CLIENT_SECRET"), - UserCount: s.usersNum, - ConcurrentRequests: s.concurrentReq, - IssuerProfileID: os.Getenv("ISSUER_PROFILE_ID"), - IssuerProfileVersion: os.Getenv("ISSUER_PROFILE_VERSION"), - VerifierProfileID: os.Getenv("VERIFIER_PROFILE_ID"), - CredentialTemplateID: os.Getenv("CREDENTIAL_TEMPLATE_ID"), - CredentialType: os.Getenv("CREDENTIAL_TYPE"), - ClaimData: targetClaimData, + TLSConfig: s.tlsConfig, + ApiURL: os.Getenv("VCS_API_URL"), + TokenClientID: os.Getenv("TOKEN_CLIENT_ID"), + TokenClientSecret: os.Getenv("TOKEN_CLIENT_SECRET"), + UserCount: s.usersNum, + ConcurrentRequests: s.concurrentReq, + IssuerProfileID: os.Getenv("ISSUER_PROFILE_ID"), + IssuerProfileVersion: os.Getenv("ISSUER_PROFILE_VERSION"), + VerifierProfileID: os.Getenv("VERIFIER_PROFILE_ID"), + VerifierProfileVersion: os.Getenv("VERIFIER_PROFILE_VERSION"), + VerifierPresentationID: os.Getenv("VERIFIER_PRESENTATION_ID"), + CredentialTemplateID: os.Getenv("CREDENTIAL_TEMPLATE_ID"), + CredentialType: os.Getenv("CREDENTIAL_TYPE"), + ClaimData: targetClaimData, }) result, err := run.Run(ctx) diff --git a/test/bdd/pkg/v1/vc/credential.go b/test/bdd/pkg/v1/vc/credential.go index af049027c..5b91e7fff 100644 --- a/test/bdd/pkg/v1/vc/credential.go +++ b/test/bdd/pkg/v1/vc/credential.go @@ -18,7 +18,6 @@ import ( "github.com/trustbloc/vc-go/verifiable" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/vcprovider" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" "github.com/trustbloc/vcs/test/bdd/pkg/v1/model" @@ -100,7 +99,7 @@ func (e *Steps) createCredential( issuerVCFormat := e.bddContext.IssuerProfiles[fmt.Sprintf("%s/%s", profileID, profileVersion)].VCConfig.Format oidcVCFormat := vcsFormatToOIDC4CI[issuerVCFormat] - reqData, err := vcprovider.GetIssueCredentialRequestData(cred, oidcVCFormat) + reqData, err := getIssueCredentialRequestData(cred, oidcVCFormat) if err != nil { return cred.Contents().ID, fmt.Errorf("unable to get issue credential request data: %w", err) } @@ -140,6 +139,23 @@ func (e *Steps) createCredential( return cred.Contents().ID, nil } +func getIssueCredentialRequestData(vc *verifiable.Credential, desiredFormat vcsverifiable.OIDCFormat) (interface{}, error) { + switch desiredFormat { + case vcsverifiable.JwtVCJsonLD, vcsverifiable.JwtVCJson: + claims, err := vc.JWTClaims(false) + if err != nil { + return nil, err + } + + return claims.MarshalUnsecuredJWT() + case vcsverifiable.LdpVC: + return vc, nil + + default: + return nil, fmt.Errorf("unsupported format %s", desiredFormat) + } +} + func (e *Steps) verifyVC(profileVersionedID string) error { chunks := strings.Split(profileVersionedID, "/") profileID, profileVersion := chunks[0], chunks[1] diff --git a/test/stress/go.mod b/test/stress/go.mod index bdc55c037..83df993ba 100644 --- a/test/stress/go.mod +++ b/test/stress/go.mod @@ -12,11 +12,14 @@ require ( github.com/imroc/req/v3 v3.42.0 github.com/joho/godotenv v1.4.0 github.com/labstack/echo/v4 v4.11.1 + github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f github.com/redis/go-redis/v9 v9.0.3 github.com/samber/lo v1.38.1 + github.com/trustbloc/did-go v1.0.2-0.20231117120416-ed019bda587f + github.com/trustbloc/kms-go v1.0.1-0.20231116141347-14d6bea5727a github.com/trustbloc/logutil-go v1.0.0-rc1 github.com/trustbloc/vc-go v1.0.3-0.20231117124429-a8a3b24ef734 - github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20231222131742-742a7ae591ba + github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20240103173902-7fbe030659b2 github.com/trustbloc/vcs/test/bdd v0.0.0-00010101000000-000000000000 golang.org/x/oauth2 v0.7.0 ) @@ -83,9 +86,9 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/henvic/httpretty v0.1.0 // indirect github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/copier v0.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kawamuray/jsonpath v0.0.0-20201211160320-7483bafabd7e // indirect github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 // indirect @@ -116,7 +119,6 @@ require ( github.com/ory/x v0.0.573 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect - github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -133,15 +135,12 @@ require ( github.com/spf13/viper v1.16.0 // indirect github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/syndtr/goleveldb v1.0.0 // indirect github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/trustbloc/bbs-signature-go v1.0.1 // indirect - github.com/trustbloc/did-go v1.0.2-0.20231117120416-ed019bda587f // indirect - github.com/trustbloc/kms-go v1.0.1-0.20231116141347-14d6bea5727a // indirect github.com/trustbloc/sidetree-go v0.0.0-20231117115139-d71ec9786d12 // indirect github.com/trustbloc/vcs v0.1.9-0.20230210204445-f2870a36f0ea // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/test/stress/go.sum b/test/stress/go.sum index 46a027d36..3ff9ac736 100644 --- a/test/stress/go.sum +++ b/test/stress/go.sum @@ -268,7 +268,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -338,9 +337,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/henvic/httpretty v0.1.0 h1:Htk66UUEbXTD4JR0qJZaw8YAMKw+9I24ZZOnDe/ti+E= -github.com/henvic/httpretty v0.1.0/go.mod h1:ViEsly7wgdugYtymX54pYp6Vv2wqZmNHayJ6q8tlKCc= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M= github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= @@ -464,8 +460,6 @@ github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0 github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -555,8 +549,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 h1:RBkacARv7qY5laaXGlF4wFB/tk5rnthhPb8oIBGoagY= github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8/go.mod h1:9PdLyPiZIiW3UopXyRnPYyjUXSpiQNHRLu8fOsR3o8M= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -1008,13 +1000,11 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/stress/pkg/stress/providers.go b/test/stress/pkg/stress/providers.go new file mode 100644 index 000000000..9dd6069f0 --- /dev/null +++ b/test/stress/pkg/stress/providers.go @@ -0,0 +1,144 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package stress + +import ( + "fmt" + "net/http" + + "github.com/piprate/json-gold/ld" + "github.com/trustbloc/did-go/doc/did" + ldstore "github.com/trustbloc/did-go/doc/ld/store" + "github.com/trustbloc/did-go/method/web" + vdrapi "github.com/trustbloc/did-go/vdr/api" + storageapi "github.com/trustbloc/kms-go/spi/storage" + "github.com/trustbloc/kms-go/wrapper/api" + + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" +) + +type walletProvider struct { + storageProvider storageapi.Provider + documentLoader ld.DocumentLoader + vdRegistry vdrapi.Registry + keyCreator api.RawKeyCreator +} + +func (p *walletProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *walletProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *walletProvider) VDRegistry() vdrapi.Registry { + return p.vdRegistry +} + +func (p *walletProvider) KeyCreator() api.RawKeyCreator { + return p.keyCreator +} + +type oidc4vciProvider struct { + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + wallet *wallet.Wallet + wellKnownService *wellknown.Service +} + +func (p *oidc4vciProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *oidc4vciProvider) HTTPClient() *http.Client { + return p.httpClient +} + +func (p *oidc4vciProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *oidc4vciProvider) VDRegistry() vdrapi.Registry { + return p.vdrRegistry +} + +func (p *oidc4vciProvider) CryptoSuite() api.Suite { + return p.cryptoSuite +} + +func (p *oidc4vciProvider) Wallet() *wallet.Wallet { + return p.wallet +} + +func (p *oidc4vciProvider) WellKnownService() *wellknown.Service { + return p.wellKnownService +} + +type oidc4vpProvider struct { + storageProvider storageapi.Provider + httpClient *http.Client + documentLoader ld.DocumentLoader + vdrRegistry vdrapi.Registry + cryptoSuite api.Suite + wallet *wallet.Wallet +} + +func (p *oidc4vpProvider) StorageProvider() storageapi.Provider { + return p.storageProvider +} + +func (p *oidc4vpProvider) HTTPClient() *http.Client { + return p.httpClient +} + +func (p *oidc4vpProvider) DocumentLoader() ld.DocumentLoader { + return p.documentLoader +} + +func (p *oidc4vpProvider) VDRegistry() vdrapi.Registry { + return p.vdrRegistry +} + +func (p *oidc4vpProvider) CryptoSuite() api.Suite { + return p.cryptoSuite +} + +func (p *oidc4vpProvider) Wallet() *wallet.Wallet { + return p.wallet +} + +type ldStoreProvider struct { + ContextStore ldstore.ContextStore + RemoteProviderStore ldstore.RemoteProviderStore +} + +func (p *ldStoreProvider) JSONLDContextStore() ldstore.ContextStore { + return p.ContextStore +} + +func (p *ldStoreProvider) JSONLDRemoteProviderStore() ldstore.RemoteProviderStore { + return p.RemoteProviderStore +} + +type webVDR struct { + httpClient *http.Client + *web.VDR +} + +func (w *webVDR) Read(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { + docRes, err := w.VDR.Read(didID, append(opts, vdrapi.WithOption(web.HTTPClientOpt, w.httpClient))...) + if err != nil { + return nil, fmt.Errorf("read did web: %w", err) + } + + return docRes, nil +} diff --git a/test/stress/pkg/stress/stress.go b/test/stress/pkg/stress/stress.go index ff5fc81a9..38e6352ca 100644 --- a/test/stress/pkg/stress/stress.go +++ b/test/stress/pkg/stress/stress.go @@ -1,3 +1,9 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + package stress import ( @@ -13,7 +19,6 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/vcprovider" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" ) @@ -115,9 +120,6 @@ func (r *Run) Run(ctx context.Context) (*Result, error) { st := time.Now() for i := 0; i < r.cfg.UserCount; i++ { testCase, err := NewTestCase( - WithVCProviderOption(func(c *vcprovider.Config) { - c.KeepWalletOpen = true - }), WithVCSAPIURL(vcsAPIURL), WithIssuerProfileID(r.cfg.IssuerProfileID), WithIssuerProfileVersion(r.cfg.IssuerProfileVersion), diff --git a/test/stress/pkg/stress/stress_test_case.go b/test/stress/pkg/stress/stress_test_case.go index 569f295ad..514aef932 100644 --- a/test/stress/pkg/stress/stress_test_case.go +++ b/test/stress/pkg/stress/stress_test_case.go @@ -14,19 +14,33 @@ import ( "fmt" "io" "net/http" + "strings" "time" + "github.com/trustbloc/did-go/legacy/mem" + "github.com/trustbloc/did-go/method/jwk" + "github.com/trustbloc/did-go/method/key" + longform "github.com/trustbloc/did-go/method/sidetreelongform" + "github.com/trustbloc/did-go/method/web" + "github.com/trustbloc/did-go/vdr" + "github.com/trustbloc/kms-go/kms" + "github.com/trustbloc/kms-go/secretlock/noop" + "github.com/trustbloc/kms-go/wrapper/localsuite" "github.com/trustbloc/logutil-go/pkg/log" "github.com/trustbloc/vc-go/verifiable" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner" - "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/vcprovider" + "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/wallet" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wellknown" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" "github.com/trustbloc/vcs/test/bdd/pkg/v1/model" ) type TestCase struct { - walletRunner *walletrunner.Service + oidc4vciProvider *oidc4vciProvider + oidc4vpProvider *oidc4vpProvider + wallet *wallet.Wallet httpClient *http.Client vcsAPIURL string issuerProfileID string @@ -44,7 +58,6 @@ type TestCase struct { } type TestCaseOptions struct { - vcProviderOptions []vcprovider.ConfigOption httpClient *http.Client vcsAPIURL string issuerProfileID string @@ -93,13 +106,83 @@ func NewTestCase(options ...TestCaseOption) (*TestCase, error) { return nil, fmt.Errorf("credential type is empty") } - runner, err := walletrunner.New(vcprovider.ProviderVCS, opts.vcProviderOptions...) + documentLoader, err := bddutil.DocumentLoader() if err != nil { - return nil, fmt.Errorf("create wallet runner: %w", err) + return nil, fmt.Errorf("init document loader: %w", err) + } + + longForm, err := longform.New() + if err != nil { + return nil, fmt.Errorf("init ion vdr: %w", err) + } + + vdRegistry := vdr.New( + vdr.WithVDR(longForm), + vdr.WithVDR(key.New()), + vdr.WithVDR(jwk.New()), + vdr.WithVDR( + &webVDR{ + httpClient: opts.httpClient, + VDR: web.New(), + }, + ), + ) + + storageProvider := mem.NewProvider() + + kmsStore, err := kms.NewAriesProviderWrapper(storageProvider) + if err != nil { + return nil, fmt.Errorf("init kms store: %w", err) + } + + suite, err := localsuite.NewLocalCryptoSuite("local-lock://wallet-cli", kmsStore, &noop.NoLock{}) + if err != nil { + return nil, fmt.Errorf("init local crypto suite: %w", err) + } + + keyCreator, err := suite.RawKeyCreator() + if err != nil { + return nil, fmt.Errorf("init key creator: %w", err) + } + + w, err := wallet.New( + &walletProvider{ + storageProvider: storageProvider, + documentLoader: documentLoader, + vdRegistry: vdRegistry, + keyCreator: keyCreator, + }, + wallet.WithNewDID("ion"), + wallet.WithKeyType("ECDSAP384DER"), + ) + if err != nil { + return nil, fmt.Errorf("init wallet: %w", err) + } + + wellKnownService := &wellknown.Service{ + HTTPClient: opts.httpClient, + VDRRegistry: vdRegistry, } return &TestCase{ - walletRunner: runner, + oidc4vciProvider: &oidc4vciProvider{ + storageProvider: storageProvider, + httpClient: opts.httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + wallet: w, + wellKnownService: wellKnownService, + }, + oidc4vpProvider: &oidc4vpProvider{ + storageProvider: storageProvider, + httpClient: opts.httpClient, + documentLoader: documentLoader, + vdrRegistry: vdRegistry, + cryptoSuite: suite, + wallet: w, + }, + wallet: w, httpClient: opts.httpClient, vcsAPIURL: opts.vcsAPIURL, issuerProfileID: opts.issuerProfileID, @@ -117,12 +200,6 @@ func NewTestCase(options ...TestCaseOption) (*TestCase, error) { }, nil } -func WithVCProviderOption(opt vcprovider.ConfigOption) TestCaseOption { - return func(opts *TestCaseOptions) { - opts.vcProviderOptions = append(opts.vcProviderOptions, opt) - } -} - func WithDisableRevokeTestCase(disableRevokeTestCase bool) TestCaseOption { return func(opts *TestCaseOptions) { opts.disableRevokeTestCase = disableRevokeTestCase @@ -210,55 +287,94 @@ func (c *TestCase) Invoke() (string, interface{}, error) { } // run pre-auth flow and save credential in the wallet - credentials, err := c.walletRunner.RunOIDC4CIPreAuth(&walletrunner.OIDC4VCIConfig{ - CredentialOfferURI: credentialOfferURL, - CredentialType: c.credentialType, - CredentialFormat: c.credentialFormat, - Pin: pin, - }, nil) + vciFlow, err := oidc4vci.NewFlow(c.oidc4vciProvider, + oidc4vci.WithFlowType(oidc4vci.FlowTypePreAuthorizedCode), + oidc4vci.WithCredentialOffer(credentialOfferURL), + oidc4vci.WithCredentialType(c.credentialType), + oidc4vci.WithCredentialFormat(c.credentialFormat), + oidc4vci.WithPin(pin), + ) + if err != nil { + return "", nil, fmt.Errorf("init pre-auth flow: %w", err) + } + + credentials, err := vciFlow.Run(context.Background()) + if err != nil { + return "", nil, fmt.Errorf("run pre-auth flow: %w", err) + } credID := "" + if credentials != nil { credID = credentials.Contents().ID } + if err != nil { - return credID, nil, fmt.Errorf("CredId [%v]. run pre-auth issuance: %w", credID, err) + return credID, nil, fmt.Errorf("cred id [%v]; run pre-auth issuance: %w", credID, err) } - providerConf := c.walletRunner.GetConfig() - providerConf.WalletUserId = providerConf.WalletParams.UserID - providerConf.WalletPassPhrase = providerConf.WalletParams.Passphrase - providerConf.WalletDidID = providerConf.WalletParams.DidID[0] - providerConf.WalletDidKeyID = providerConf.WalletParams.DidKeyID[0] - providerConf.SkipSchemaValidation = true + perfInfo := make(stressTestPerfInfo) if !c.disableVPTestCase { - authorizationRequest, err := c.fetchAuthorizationRequest() + var ( + authorizationRequest string + vpFlow *oidc4vp.Flow + b []byte + ) + + authorizationRequest, err = c.fetchAuthorizationRequest() if err != nil { - return credID, nil, fmt.Errorf("CredId [%v]. fetch authorization request: %w", credID, err) + return credID, nil, fmt.Errorf("cred id [%v]; fetch authorization request: %w", credID, err) + } + + vpFlow, err = oidc4vp.NewFlow(c.oidc4vpProvider, + oidc4vp.WithRequestURI(strings.TrimPrefix(authorizationRequest, "openid-vc://?request_uri=")), + oidc4vp.WithDomainMatchingDisabled(), + oidc4vp.WithSchemaValidationDisabled(), + ) + if err != nil { + return "", nil, fmt.Errorf("cred id [%v]; init flow: %w", credID, err) + } + + if err = vpFlow.Run(context.Background()); err != nil { + return "", nil, fmt.Errorf("cred id [%v]; run vp flow: %w", credID, err) } - err = c.walletRunner.RunOIDC4VPFlow(context.TODO(), authorizationRequest, nil) + var vpPerfInfo map[string]time.Duration + + b, err = json.Marshal(vpFlow.PerfInfo()) if err != nil { - return credID, nil, fmt.Errorf("CredId [%v]. run vp: %w", credID, err) + return credID, nil, fmt.Errorf("cred id [%v]; marshal vp perf info: %w", credID, err) + } + + if err = json.Unmarshal(b, &vpPerfInfo); err != nil { + return credID, nil, fmt.Errorf("unmarshal vp perf info into stressTestPerfInfo: %w", err) + } + + for k, v := range vpPerfInfo { + perfInfo[k] = v } } - b, err := json.Marshal(c.walletRunner.GetPerfInfo()) + var vciPerfInfo map[string]time.Duration + + b, err := json.Marshal(vciFlow.PerfInfo()) if err != nil { - return credID, nil, fmt.Errorf("CredId [%v]. marshal perf info: %w", credID, err) + return credID, nil, fmt.Errorf("cred id [%v]; marshal vci perf info: %w", credID, err) } - var perfInfo stressTestPerfInfo + if err = json.Unmarshal(b, &vciPerfInfo); err != nil { + return credID, nil, fmt.Errorf("unmarshal vci perf info into stressTestPerfInfo: %w", err) + } - if err = json.Unmarshal(b, &perfInfo); err != nil { - return credID, nil, fmt.Errorf("unmarshal perf info into stressTestPerfInfo: %w", err) + for k, v := range vciPerfInfo { + perfInfo[k] = v } if !c.disableRevokeTestCase && credentials.Contents().Status != nil && credentials.Contents().Status.Type != "" { st := time.Now() if err = c.revokeVC(credentials); err != nil { - return credID, nil, fmt.Errorf("CredId [%v]. can not revokeVc; %w", credID, err) + return credID, nil, fmt.Errorf("cred id [%v]; can not revokeVc; %w", credID, err) } perfInfo["_vp_revoke_credentials"] = time.Since(st) @@ -274,6 +390,8 @@ func (c *TestCase) revokeVC(cred *verifiable.Credential) error { Status: "true", Type: cred.Contents().Status.Type, }, + ProfileID: c.issuerProfileID, + ProfileVersion: c.issuerProfileVersion, } requestBytes, err := json.Marshal(req) @@ -281,8 +399,7 @@ func (c *TestCase) revokeVC(cred *verifiable.Credential) error { return err } - endpointURL := fmt.Sprintf("%s/issuer/profiles/%v/credentials/status", - c.vcsAPIURL, c.issuerProfileID) + endpointURL := fmt.Sprintf("%s/issuer/credentials/status", c.vcsAPIURL) resp, err := bddutil.HTTPSDo(http.MethodPost, endpointURL, "application/json", c.token, //nolint: bodyclose bytes.NewBuffer(requestBytes), &tls.Config{ @@ -336,10 +453,6 @@ func (c *TestCase) fetchCredentialOfferURL() (string, string, error) { return "", "", fmt.Errorf("send initiate oidc4ci request: %w", err) } - if resp.StatusCode != http.StatusOK { - return "", "", fmt.Errorf("initiate oidc4ci request failed: %v", resp.Status) - } - if resp.Body != nil { defer func() { err = resp.Body.Close() @@ -349,6 +462,11 @@ func (c *TestCase) fetchCredentialOfferURL() (string, string, error) { }() } + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) //nolint + return "", "", fmt.Errorf("initiate oidc4ci request failed: %v; response: %s", resp.Status, string(body)) + } + var parsedResp initiateOIDC4CIResponse if err = json.NewDecoder(resp.Body).Decode(&parsedResp); err != nil { diff --git a/test/stress/pkg/stress/types.go b/test/stress/pkg/stress/types.go index 3050b2a26..147919723 100644 --- a/test/stress/pkg/stress/types.go +++ b/test/stress/pkg/stress/types.go @@ -1,3 +1,9 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + package stress import "time"