Skip to content

Commit

Permalink
Merge pull request #1728 from aholovko/oid4vp_id2
Browse files Browse the repository at this point in the history
feat: OID4VP ID2 updates
  • Loading branch information
fqutishat authored Jun 10, 2024
2 parents f5c97bd + 00bf219 commit b2df12a
Show file tree
Hide file tree
Showing 11 changed files with 736 additions and 471 deletions.
32 changes: 16 additions & 16 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,9 @@ func buildEchoHandler(
}))

oidc4vpv1.RegisterHandlers(e, oidc4vpv1.NewController(&oidc4vpv1.Config{
DefaultHTTPClient: getHTTPClient(metricsProvider.ClientOIDC4PV1),
ExternalHostURL: conf.StartupParameters.hostURLExternal, // use host external as this url will be called internally
Tracer: conf.Tracer,
HTTPClient: getHTTPClient(metricsProvider.ClientOIDC4PV1),
ExternalHostURL: conf.StartupParameters.hostURLExternal, // use host external as this url will be called internally
Tracer: conf.Tracer,
}))

issuerv1.RegisterHandlers(e, issuerv1.NewController(&issuerv1.Config{
Expand Down Expand Up @@ -945,19 +945,19 @@ func buildEchoHandler(
var oidc4vpService oidc4vp.ServiceInterface

oidc4vpService = oidc4vp.NewService(&oidc4vp.Config{
EventSvc: eventSvc,
EventTopic: conf.StartupParameters.verifierEventTopic,
TransactionManager: oidc4vpTxManager,
RequestObjectPublicStore: requestObjectStoreService,
KMSRegistry: kmsRegistry,
VDR: conf.VDR,
DocumentLoader: documentLoader,
ProfileService: verifierProfileSvc,
PresentationVerifier: verifyPresentationSvc,
TrustRegistry: trustRegistryService,
RedirectURL: conf.StartupParameters.apiGatewayURL + oidc4VPCheckEndpoint,
TokenLifetime: 15 * time.Minute,
Metrics: metrics,
EventSvc: eventSvc,
EventTopic: conf.StartupParameters.verifierEventTopic,
TransactionManager: oidc4vpTxManager,
RequestObjectStore: requestObjectStoreService,
KMSRegistry: kmsRegistry,
VDR: conf.VDR,
DocumentLoader: documentLoader,
ProfileService: verifierProfileSvc,
PresentationVerifier: verifyPresentationSvc,
TrustRegistry: trustRegistryService,
RedirectURL: conf.StartupParameters.apiGatewayURL + oidc4VPCheckEndpoint,
TokenLifetime: 15 * time.Minute,
Metrics: metrics,
})

if conf.IsTraceEnabled {
Expand Down
41 changes: 14 additions & 27 deletions component/wallet-cli/pkg/oidc4vp/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,32 @@ import (
)

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"`
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"`
ClientMetadata *ClientMetadata `json:"client_metadata"`
PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition"`
}

type RequestObjectRegistration struct {
type ClientMetadata struct {
ClientName string `json:"client_name"`
ClientPurpose string `json:"client_purpose"`
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 claims retrieved using custom scope.
ScopeAdditionalClaims map[string]Claims `json:"_scope,omitempty"` //custom scope -> additional claims
VPToken IDTokenVPToken `json:"_vp_token"`
AttestationVP string `json:"_attestation_vp"`
Nonce string `json:"nonce"`
Exp int64 `json:"exp"`
Expand Down
41 changes: 21 additions & 20 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,15 @@ func (f *Flow) Run(ctx context.Context) error {

if err = copier.CopyWithOption(
&pd,
requestObject.Claims.VPToken.PresentationDefinition,
requestObject.PresentationDefinition,
copier.Option{IgnoreEmpty: true, DeepCopy: true},
); err != nil {
return fmt.Errorf("copy presentation definition: %w", err)
}

if f.disableSchemaValidation && len(pd.InputDescriptors) > 0 {
pd.InputDescriptors[0].Schema = nil
requestObject.Claims.VPToken.PresentationDefinition.InputDescriptors[0].Schema = nil
requestObject.PresentationDefinition.InputDescriptors[0].Schema = nil
}

vp, err := f.queryWallet(&pd)
Expand Down Expand Up @@ -412,7 +412,7 @@ func (f *Flow) sendAuthorizationResponse(
return fmt.Errorf("missing or invalid presentation_submission")
}

vpFormats := requestObject.Registration.VPFormats
vpFormats := requestObject.ClientMetadata.VPFormats

for i := range presentationSubmission.DescriptorMap {
if vpFormats.JwtVP != nil {
Expand All @@ -429,18 +429,23 @@ func (f *Flow) sendAuthorizationResponse(

idToken, err := f.createIDToken(
ctx,
presentationSubmission,
requestObject.ClientID, requestObject.Nonce, requestObject.Scope,
attestationRequired,
)
if err != nil {
return fmt.Errorf("create id token: %w", err)
}

presentationSubmissionJSON, err := json.Marshal(presentationSubmission)
if err != nil {
return fmt.Errorf("marshal presentation submission: %w", err)
}

v := url.Values{
"id_token": {idToken},
"vp_token": {vpToken},
"state": {requestObject.State},
"id_token": {idToken},
"vp_token": {vpToken},
"presentation_submission": {string(presentationSubmissionJSON)},
"state": {requestObject.State},
}

f.perfInfo.CreateAuthorizedResponse = time.Since(start)
Expand All @@ -459,7 +464,7 @@ func (f *Flow) createVPToken(
return "", fmt.Errorf("get subject did: %w", err)
}

vpFormats := requestObject.Registration.VPFormats
vpFormats := requestObject.ClientMetadata.VPFormats

switch {
case vpFormats.JwtVP != nil:
Expand Down Expand Up @@ -599,7 +604,6 @@ func (f *Flow) signPresentationLDP(

func (f *Flow) createIDToken(
ctx context.Context,
presentationSubmission *presexch.PresentationSubmission,
clientID, nonce, requestObjectScope string,
attestationRequired bool,
) (string, error) {
Expand All @@ -610,17 +614,14 @@ func (f *Flow) createIDToken(

idToken := &IDTokenClaims{
ScopeAdditionalClaims: scopeAdditionalClaims,
VPToken: IDTokenVPToken{
PresentationSubmission: presentationSubmission,
},
Nonce: nonce,
Exp: time.Now().Unix() + tokenLifetimeSeconds,
Iss: "https://self-issued.me/v2/openid-vc",
Aud: clientID,
Sub: f.walletDID.String(),
Nbf: time.Now().Unix(),
Iat: time.Now().Unix(),
Jti: uuid.NewString(),
Nonce: nonce,
Exp: time.Now().Unix() + tokenLifetimeSeconds,
Iss: "https://self-issued.me/v2/openid-vc",
Aud: clientID,
Sub: f.walletDID.String(),
Nbf: time.Now().Unix(),
Iat: time.Now().Unix(),
Jti: uuid.NewString(),
}

if attestationRequired {
Expand Down
24 changes: 16 additions & 8 deletions pkg/internal/testutil/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ func SignedClaimsJWT(t *testing.T, claims interface{}) *SignedClaimsJWTResult {
algName, err := jwsAlgo.Name()
require.NoError(t, err)

token, err := jwt.NewSigned(claims, jwt.SignParameters{
KeyID: didDoc.VerificationMethod[0].ID,
JWTAlg: algName,
}, creator.New(creator.WithJWTAlg(eddsa.New(), fks)))
token, err := jwt.NewSigned(
claims,
jwt.SignParameters{
KeyID: didDoc.VerificationMethod[0].ID,
JWTAlg: algName,
},
creator.New(creator.WithJWTAlg(eddsa.New(), fks)),
)
require.NoError(t, err)

jws, err := token.Serialize(false)
Expand Down Expand Up @@ -85,10 +89,14 @@ func SignedClaimsJWTWithExistingPrivateKey(
algName, err := jwsAlgo.Name()
require.NoError(t, err)

token, err := jwt.NewSigned(claims, jwt.SignParameters{
KeyID: verMethodDIDKeyID,
JWTAlg: algName,
}, creator.New(creator.WithJWTAlg(eddsa.New(), signer)))
token, err := jwt.NewSigned(
claims,
jwt.SignParameters{
KeyID: verMethodDIDKeyID,
JWTAlg: algName,
},
creator.New(creator.WithJWTAlg(eddsa.New(), signer)),
)
require.NoError(t, err)

jws, err := token.Serialize(false)
Expand Down
37 changes: 20 additions & 17 deletions pkg/restapi/v1/oidc4vp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,52 +32,55 @@ type HTTPClient interface {

// Config holds configuration options for Controller.
type Config struct {
DefaultHTTPClient HTTPClient
ExternalHostURL string
Tracer trace.Tracer
HTTPClient HTTPClient
ExternalHostURL string
Tracer trace.Tracer
}

// Controller for OIDC credential issuance API.
type Controller struct {
defaultHTTPClient HTTPClient
internalHostURL string
tracer trace.Tracer
httpClient HTTPClient
internalHostURL string
tracer trace.Tracer
}

// NewController creates a new Controller instance.
func NewController(config *Config) *Controller {
return &Controller{
defaultHTTPClient: config.DefaultHTTPClient,
internalHostURL: config.ExternalHostURL,
tracer: config.Tracer,
httpClient: config.HTTPClient,
internalHostURL: config.ExternalHostURL,
tracer: config.Tracer,
}
}

// PresentAuthorizationResponse (POST /oidc/present).
func (c *Controller) PresentAuthorizationResponse(e echo.Context) error {
req := e.Request()
request := e.Request()

ctx, span := c.tracer.Start(req.Context(), "PresentAuthorizationResponse")
ctx, span := c.tracer.Start(request.Context(), "PresentAuthorizationResponse")
defer span.End()

req, err := http.NewRequestWithContext(ctx, http.MethodPost,
c.internalHostURL+oidc4VPCheckEndpoint, req.Body)
req, err := http.NewRequestWithContext(ctx,
http.MethodPost,
c.internalHostURL+oidc4VPCheckEndpoint,
request.Body,
)
if err != nil {
return err
return fmt.Errorf("failed to create request: %w", err)
}

req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

resp, err := c.defaultHTTPClient.Do(req)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
return fmt.Errorf("failed to send request: %w", err)
}

defer closeResponseBody(e.Request().Context(), resp.Body)

respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
return fmt.Errorf("failed to read response body: %w", err)
}

if resp.StatusCode != http.StatusOK {
Expand Down
Loading

0 comments on commit b2df12a

Please sign in to comment.