Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OID4VCI combining metadata file #1438

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 141 additions & 140 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,11 +548,10 @@ func buildEchoHandler(

// Issuer Profile Management API
issuerProfileSvc, err := profilereader.NewIssuerReader(&profilereader.Config{
OpenidIssuerConfigProvider: openidCredentialIssuerConfigProviderSvc,
TLSConfig: tlsConfig,
KMSRegistry: kmsRegistry,
CMD: cmd,
HTTPClient: getHTTPClient(metricsProvider.ClientIssuerProfile),
TLSConfig: tlsConfig,
KMSRegistry: kmsRegistry,
CMD: cmd,
HTTPClient: getHTTPClient(metricsProvider.ClientIssuerProfile),
})
if err != nil {
return nil, err
Expand Down
15 changes: 4 additions & 11 deletions component/profile/reader/file/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"github.com/trustbloc/vcs/internal/logfields"
vcskms "github.com/trustbloc/vcs/pkg/kms"
profileapi "github.com/trustbloc/vcs/pkg/profile"
issuerrestapi "github.com/trustbloc/vcs/pkg/restapi/v1/issuer"
)

const (
Expand All @@ -45,18 +44,12 @@ type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}

type openidCredentialIssuerConfigProvider interface {
GetOpenIDCredentialIssuerConfig(
issuerProfile *profileapi.Issuer) (*issuerrestapi.WellKnownOpenIDIssuerConfiguration, string, error)
}

// Config contain config.
type Config struct {
KMSRegistry *vcskms.Registry
TLSConfig *tls.Config
CMD *cobra.Command
HTTPClient httpClient
OpenidIssuerConfigProvider openidCredentialIssuerConfigProvider
KMSRegistry *vcskms.Registry
TLSConfig *tls.Config
CMD *cobra.Command
HTTPClient httpClient
}

// IssuerReader read issuer profiles.
Expand Down
1 change: 0 additions & 1 deletion component/wallet-cli/pkg/walletrunner/wallet_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ func (s *Service) GetConfig() *vcprovider.Config {

type PerfInfo struct {
CreateWallet time.Duration `json:"vci_create_wallet"`
GetIssuerOIDCConfig time.Duration `json:"vci_get_issuer_oidc_config"`
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"`
Expand Down
118 changes: 6 additions & 112 deletions component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,15 @@ import (
"github.com/samber/lo"
"github.com/trustbloc/did-go/method/jwk"
didkey "github.com/trustbloc/did-go/method/key"
vdrapi "github.com/trustbloc/did-go/vdr/api"
"github.com/trustbloc/kms-go/doc/jose"
"github.com/trustbloc/vc-go/jwt"
"github.com/trustbloc/vc-go/verifiable"
"github.com/valyala/fastjson"
"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"
issuerv1 "github.com/trustbloc/vcs/pkg/restapi/v1/issuer"
"github.com/trustbloc/vcs/pkg/service/oidc4ci"
)

Expand Down Expand Up @@ -92,12 +89,8 @@ func (s *Service) RunOIDC4CI(config *OIDC4CIConfig, hooks *Hooks) error {
}

s.print("Getting issuer OIDC config")
oidcConfig, err := s.getIssuerOIDCConfig(ctx, offerResponse.CredentialIssuer)
if err != nil {
return fmt.Errorf("get issuer OIDC config: %w", err)
}

oidcIssuerCredentialConfig, err := s.getIssuerCredentialsOIDCConfig(
oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration(
offerResponse.CredentialIssuer,
)
if err != nil {
Expand Down Expand Up @@ -129,8 +122,8 @@ func (s *Service) RunOIDC4CI(config *OIDC4CIConfig, hooks *Hooks) error {
RedirectURL: redirectURL.String(),
Scopes: config.Scope,
Endpoint: oauth2.Endpoint{
AuthURL: oidcConfig.AuthorizationEndpoint,
TokenURL: oidcConfig.TokenEndpoint,
AuthURL: oidcIssuerCredentialConfig.AuthorizationEndpoint,
TokenURL: oidcIssuerCredentialConfig.TokenEndpoint,
AuthStyle: oauth2.AuthStyleInHeader,
},
}
Expand Down Expand Up @@ -262,18 +255,13 @@ func (s *Service) RunOIDC4CIWalletInitiated(config *OIDC4CIConfig, hooks *Hooks)
return fmt.Errorf("create wallet: %w", err)
}

oidcIssuerCredentialConfig, err := s.getIssuerCredentialsOIDCConfig(
oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration(
issuerUrl,
)
if err != nil {
return fmt.Errorf("get issuer OIDC issuer config: %w", err)
}

oidcConfig, err := s.getIssuerOIDCConfig(ctx, issuerUrl)
if err != nil {
return fmt.Errorf("get issuer OIDC config: %w", err)
}

redirectURL, err := url.Parse(config.RedirectURI)
if err != nil {
return fmt.Errorf("parse redirect url: %w", err)
Expand All @@ -299,8 +287,8 @@ func (s *Service) RunOIDC4CIWalletInitiated(config *OIDC4CIConfig, hooks *Hooks)
RedirectURL: redirectURL.String(),
Scopes: config.Scope,
Endpoint: oauth2.Endpoint{
AuthURL: oidcConfig.AuthorizationEndpoint,
TokenURL: oidcConfig.TokenEndpoint,
AuthURL: oidcIssuerCredentialConfig.AuthorizationEndpoint,
TokenURL: oidcIssuerCredentialConfig.TokenEndpoint,
AuthStyle: oauth2.AuthStyleInHeader,
},
}
Expand Down Expand Up @@ -408,100 +396,6 @@ func (s *Service) RunOIDC4CIWalletInitiated(config *OIDC4CIConfig, hooks *Hooks)
return nil
}

func (s *Service) getIssuerOIDCConfig(
ctx context.Context,
issuerURL string,
) (*issuerv1.WellKnownOpenIDConfiguration, error) {
// GET /issuer/{profileID}/{profileVersion}/.well-known/openid-configuration
req, err := http.NewRequestWithContext(ctx, "GET", issuerURL+"/.well-known/openid-configuration", nil)
if err != nil {
return nil, err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("get issuer well-known: %w", err)
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("get issuer well-known: status code %d", resp.StatusCode)
}

var oidcConfig issuerv1.WellKnownOpenIDConfiguration

if err = json.NewDecoder(resp.Body).Decode(&oidcConfig); err != nil {
return nil, fmt.Errorf("decode issuer well-known: %w", err)
}

return &oidcConfig, nil
}

func (s *Service) getIssuerCredentialsOIDCConfig(
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 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 =
getWellKnownOpenIDIssuerConfigurationJWTPayload(
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 getWellKnownOpenIDIssuerConfigurationJWTPayload(rawResponse string, vdrRegistry vdrapi.Registry) ([]byte, error) {
jwtVerifier := jwt.NewVerifier(jwt.KeyResolverFunc(
verifiable.NewVDRKeyResolver(vdrRegistry).PublicKeyFetcher()))

_, credentialOfferPayload, err := jwt.Parse(
rawResponse,
jwt.WithSignatureVerifier(jwtVerifier),
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
}

func (s *Service) getAuthCode(
config *OIDC4CIConfig,
authCodeURL string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package walletrunner

import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -29,8 +28,6 @@ import (
func (s *Service) RunOIDC4CIPreAuth(config *OIDC4CIConfig) (*verifiable.Credential, error) {
log.Println("Starting OIDC4VCI pre-authorized code flow")

ctx := context.Background()

startTime := time.Now()
err := s.CreateWallet()
if err != nil {
Expand All @@ -52,26 +49,14 @@ func (s *Service) RunOIDC4CIPreAuth(config *OIDC4CIConfig) (*verifiable.Credenti

s.print("Getting issuer OIDC config")
startTime = time.Now()
oidcConfig, err := s.getIssuerOIDCConfig(ctx, offerResponse.CredentialIssuer)
s.perfInfo.VcsCIFlowDuration += time.Since(startTime) // oidc config
s.perfInfo.GetIssuerOIDCConfig = time.Since(startTime)

if err != nil {
return nil, err
}

startTime = time.Now()
oidcIssuerCredentialConfig, err := s.getIssuerCredentialsOIDCConfig(offerResponse.CredentialIssuer)
oidcIssuerCredentialConfig, err := s.GetWellKnownOpenIDConfiguration(offerResponse.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)
}

tokenEndpoint := oidcConfig.TokenEndpoint
credentialsEndpoint := oidcIssuerCredentialConfig.CredentialEndpoint

tokenValues := url.Values{
"grant_type": []string{"urn:ietf:params:oauth:grant-type:pre-authorized_code"},
"pre-authorized_code": []string{offerResponse.Grants.PreAuthorizationGrant.PreAuthorizedCode},
Expand All @@ -91,7 +76,7 @@ func (s *Service) RunOIDC4CIPreAuth(config *OIDC4CIConfig) (*verifiable.Credenti

s.print("Getting access token")
startTime = time.Now()
tokenResp, tokenErr := s.httpClient.PostForm(tokenEndpoint, tokenValues)
tokenResp, tokenErr := s.httpClient.PostForm(oidcIssuerCredentialConfig.TokenEndpoint, tokenValues)
s.perfInfo.GetAccessToken = time.Since(startTime)
s.perfInfo.VcsCIFlowDuration += time.Since(startTime)
if tokenErr != nil {
Expand All @@ -113,7 +98,7 @@ func (s *Service) RunOIDC4CIPreAuth(config *OIDC4CIConfig) (*verifiable.Credenti
s.oauthClient = &oauth2.Config{
ClientID: "oidc4vc_client",
Endpoint: oauth2.Endpoint{
TokenURL: oidcConfig.TokenEndpoint,
TokenURL: oidcIssuerCredentialConfig.TokenEndpoint,
},
} // todo dynamic client registration
s.token = lo.ToPtr(oauth2.Token{AccessToken: token.AccessToken}).WithExtra(map[string]interface{}{
Expand All @@ -122,7 +107,10 @@ func (s *Service) RunOIDC4CIPreAuth(config *OIDC4CIConfig) (*verifiable.Credenti

s.print("Getting credential")
startTime = time.Now()
vc, vcsDuration, err := s.getCredential(credentialsEndpoint, config.CredentialType, config.CredentialFormat,
vc, vcsDuration, err := s.getCredential(
oidcIssuerCredentialConfig.CredentialEndpoint,
config.CredentialType,
config.CredentialFormat,
offerResponse.CredentialIssuer)
if err != nil {
return nil, fmt.Errorf("get credential: %w", err)
Expand Down
89 changes: 89 additions & 0 deletions component/wallet-cli/pkg/walletrunner/wellknown.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
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/verifiable"
"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 := jwt.NewVerifier(jwt.KeyResolverFunc(
verifiable.NewVDRKeyResolver(vdrRegistry).PublicKeyFetcher()))

_, credentialOfferPayload, err := jwt.Parse(
rawResponse,
jwt.WithSignatureVerifier(jwtVerifier),
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
}
Loading