Skip to content

Commit

Permalink
feat: OID4VCI combining metadata file (#1438)
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Sizov <mykhailo.sizov@securekey.com>
  • Loading branch information
mishasizov-SK authored Sep 27, 2023
1 parent 14a101b commit 9376d3a
Show file tree
Hide file tree
Showing 24 changed files with 460 additions and 1,091 deletions.
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

0 comments on commit 9376d3a

Please sign in to comment.