Skip to content

Commit

Permalink
feat: Self Attested Claims (VCs)
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Sizov <[email protected]>
  • Loading branch information
mishasizov-SK committed Oct 31, 2023
1 parent 31c00a5 commit c3b6801
Show file tree
Hide file tree
Showing 27 changed files with 897 additions and 379 deletions.
293 changes: 147 additions & 146 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ import (
oidc4vpclaimsstoremongo "github.com/trustbloc/vcs/pkg/storage/mongodb/oidc4vpclaimsstore"
oidc4vpnoncestoremongo "github.com/trustbloc/vcs/pkg/storage/mongodb/oidc4vpnoncestore"
oidc4vptxstoremongo "github.com/trustbloc/vcs/pkg/storage/mongodb/oidc4vptxstore"
"github.com/trustbloc/vcs/pkg/storage/mongodb/requestobjectstore"
requestobjectstoremongo "github.com/trustbloc/vcs/pkg/storage/mongodb/requestobjectstore"
"github.com/trustbloc/vcs/pkg/storage/mongodb/vcissuancehistorystore"
"github.com/trustbloc/vcs/pkg/storage/mongodb/vcstatusstore"
"github.com/trustbloc/vcs/pkg/storage/redis"
Expand All @@ -119,7 +119,7 @@ import (
oidc4vptxstoreredis "github.com/trustbloc/vcs/pkg/storage/redis/oidc4vptxstore"
"github.com/trustbloc/vcs/pkg/storage/s3/credentialoffer"
cslstores3 "github.com/trustbloc/vcs/pkg/storage/s3/cslvcstore"
requestobjectstore2 "github.com/trustbloc/vcs/pkg/storage/s3/requestobjectstore"
requestobjectstores3 "github.com/trustbloc/vcs/pkg/storage/s3/requestobjectstore"
)

const (
Expand Down Expand Up @@ -1151,9 +1151,9 @@ func createRequestObjectStore(
otelaws.AppendMiddlewares(&cfg.APIOptions, otelaws.WithTracerProvider(otel.GetTracerProvider()))
}

return requestobjectstore2.NewStore(s3.NewFromConfig(cfg), s3Bucket, s3Region, s3HostName), nil
return requestobjectstores3.NewStore(s3.NewFromConfig(cfg), s3Bucket, s3Region, s3HostName), nil
default:
return requestobjectstore.NewStore(mongoDbClient), nil
return requestobjectstoremongo.NewStore(mongoDbClient), nil
}
}

Expand Down
22 changes: 13 additions & 9 deletions component/wallet-cli/pkg/oidc4vp/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,20 @@ type IDTokenVPToken struct {
PresentationSubmission *presexch.PresentationSubmission `json:"presentation_submission"`
}

type Claims = map[string]interface{}

type IDTokenClaims struct {
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"`
// ScopeAdditionalClaims stores claims retrieved using additional scope.
ScopeAdditionalClaims map[string]Claims `json:"_scope,omitempty"` //additional scope -> 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 {
Expand Down
20 changes: 18 additions & 2 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ func (f *Flow) sendAuthorizationResponse(
return fmt.Errorf("no matching credentials found")
}

idToken, err := f.createIDToken(presentationSubmission, requestObject.ClientID, requestObject.Nonce)
idToken, err := f.createIDToken(presentationSubmission, requestObject.ClientID, requestObject.Nonce, requestObject.Scope)
if err != nil {
return fmt.Errorf("create id token: %w", err)
}
Expand Down Expand Up @@ -569,9 +569,10 @@ func (f *Flow) signPresentationLDP(

func (f *Flow) createIDToken(
presentationSubmission *presexch.PresentationSubmission,
clientID, nonce string,
clientID, nonce, requestObjectScope string,
) (string, error) {
idToken := &IDTokenClaims{
ScopeAdditionalClaims: f.extractAdditionalClaims(requestObjectScope),
VPToken: IDTokenVPToken{
PresentationSubmission: presentationSubmission,
},
Expand Down Expand Up @@ -602,6 +603,21 @@ func (f *Flow) createIDToken(
return idTokenJSON, nil
}

func (f *Flow) extractAdditionalClaims(requestObjectScope string) map[string]Claims {
chunks := strings.Split(requestObjectScope, "+")
if len(chunks) == 1 {
return nil
}

return map[string]Claims{
chunks[1]: {
// For now random claims are used.
"timestamp": time.Now().Format(time.RFC3339),
"uuid": uuid.NewString(),
},
}
}

func (f *Flow) postAuthorizationResponse(ctx context.Context, redirectURI string, body []byte) error {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, redirectURI, bytes.NewBuffer(body))
if err != nil {
Expand Down
22 changes: 13 additions & 9 deletions component/wallet-cli/pkg/walletrunner/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,20 @@ type IDTokenVPToken struct {
PresentationSubmission *presexch.PresentationSubmission `json:"presentation_submission"`
}

type Claims = map[string]interface{}

type IDTokenClaims struct {
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"`
// ScopeAdditionalClaims stores claims retrieved using additional scope.
ScopeAdditionalClaims map[string]Claims `json:"_scope,omitempty"` //additional scope -> 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 {
Expand Down
20 changes: 18 additions & 2 deletions component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,9 @@ func (e *VPFlowExecutor) QueryCredentialFromWalletMultiVP() error {
return nil
}

func (e *VPFlowExecutor) getIDTokenClaims(requestPresentationSubmission *presexch.PresentationSubmission) *IDTokenClaims {
func (e *VPFlowExecutor) getIDTokenClaims(requestPresentationSubmission *presexch.PresentationSubmission, requestObjectScope string) *IDTokenClaims {
return &IDTokenClaims{
ScopeAdditionalClaims: e.extractAdditionalClaims(requestObjectScope),
VPToken: IDTokenVPToken{
PresentationSubmission: requestPresentationSubmission,
},
Expand All @@ -374,6 +375,21 @@ func (e *VPFlowExecutor) signIDTokenJWT(idToken *IDTokenClaims, signatureType vc
return idTokenJWS, nil
}

func (e *VPFlowExecutor) extractAdditionalClaims(requestObjectScope string) map[string]Claims {
chunks := strings.Split(requestObjectScope, "+")
if len(chunks) == 1 {
return nil
}

return map[string]Claims{
chunks[1]: {
// For now random claims are used.
"timestamp": time.Now().Format(time.RFC3339),
"uuid": uuid.NewString(),
},
}
}

func (e *VPFlowExecutor) CreateAuthorizedResponse(o ...RPConfigOverride) (string, error) {
configRP, err := e.getRPConfig()
if err != nil {
Expand Down Expand Up @@ -431,7 +447,7 @@ func (e *VPFlowExecutor) CreateAuthorizedResponse(o ...RPConfigOverride) (string

var signedIDToken string

signedIDToken, err = e.signIDTokenJWT(e.getIDTokenClaims(e.requestPresentationSubmission), e.walletSignType)
signedIDToken, err = e.signIDTokenJWT(e.getIDTokenClaims(e.requestPresentationSubmission, e.requestObject.Scope), e.walletSignType)
if err != nil {
return "", err
}
Expand Down
3 changes: 3 additions & 0 deletions docs/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,9 @@ components:
type: string
purpose:
type: string
scope:
type: string
description: Additional scope that defines custom claims requested from Holder to Verifier.
presentationDefinitionFilters:
$ref: '#/components/schemas/PresentationDefinitionFilters'
PresentationDefinitionFilters:
Expand Down
16 changes: 11 additions & 5 deletions pkg/observability/tracing/wrappers/oidc4vp/oidc4vp_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,36 @@ func Wrap(svc Service, tracer trace.Tracer) *Wrapper {
return &Wrapper{svc: svc, tracer: tracer}
}

func (w *Wrapper) InitiateOidcInteraction(ctx context.Context, presentationDefinition *presexch.PresentationDefinition, purpose string, profile *profileapi.Verifier) (*oidc4vp.InteractionInfo, error) {
func (w *Wrapper) InitiateOidcInteraction(
ctx context.Context,
presentationDefinition *presexch.PresentationDefinition,
purpose string,
customScope string,
profile *profileapi.Verifier) (*oidc4vp.InteractionInfo, error) {
ctx, span := w.tracer.Start(ctx, "oidc4vp.InitiateOidcInteraction")
defer span.End()

span.SetAttributes(attribute.String("profile_id", profile.ID))
span.SetAttributes(attribute.String("purpose", purpose))
span.SetAttributes(attribute.String("custom_cope", customScope))
span.SetAttributes(attributeutil.JSON("presentation_definition", presentationDefinition))

resp, err := w.svc.InitiateOidcInteraction(ctx, presentationDefinition, purpose, profile)
resp, err := w.svc.InitiateOidcInteraction(ctx, presentationDefinition, purpose, customScope, profile)
if err != nil {
return nil, err
}

return resp, nil
}

func (w *Wrapper) VerifyOIDCVerifiablePresentation(ctx context.Context, txID oidc4vp.TxID, token []*oidc4vp.ProcessedVPToken) error {
func (w *Wrapper) VerifyOIDCVerifiablePresentation(ctx context.Context, txID oidc4vp.TxID, authResponse *oidc4vp.AuthorizationResponseParsed) error {
ctx, span := w.tracer.Start(ctx, "oidc4vp.VerifyOIDCVerifiablePresentation")
defer span.End()

span.SetAttributes(attribute.String("tx_id", string(txID)))
span.SetAttributes(attributeutil.JSON("token", token, attributeutil.WithRedacted("#.Presentation")))
span.SetAttributes(attributeutil.JSON("token", authResponse.VPTokens, attributeutil.WithRedacted("#.Presentation")))

return w.svc.VerifyOIDCVerifiablePresentation(ctx, txID, token)
return w.svc.VerifyOIDCVerifiablePresentation(ctx, txID, authResponse)
}

func (w *Wrapper) GetTx(ctx context.Context, id oidc4vp.TxID) (*oidc4vp.Transaction, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ func TestWrapper_InitiateOidcInteraction(t *testing.T) {
ctrl := gomock.NewController(t)

svc := NewMockService(ctrl)
svc.EXPECT().InitiateOidcInteraction(gomock.Any(), &presexch.PresentationDefinition{}, "purpose", &profileapi.Verifier{}).Times(1)
svc.EXPECT().InitiateOidcInteraction(gomock.Any(), &presexch.PresentationDefinition{}, "purpose", "additionalScope", &profileapi.Verifier{}).Times(1)

w := Wrap(svc, trace.NewNoopTracerProvider().Tracer(""))

_, err := w.InitiateOidcInteraction(context.Background(), &presexch.PresentationDefinition{}, "purpose", &profileapi.Verifier{})
_, err := w.InitiateOidcInteraction(context.Background(), &presexch.PresentationDefinition{}, "purpose", "additionalScope", &profileapi.Verifier{})
require.NoError(t, err)
}

func TestWrapper_VerifyOIDCVerifiablePresentation(t *testing.T) {
ctrl := gomock.NewController(t)

svc := NewMockService(ctrl)
svc.EXPECT().VerifyOIDCVerifiablePresentation(gomock.Any(), oidc4vp.TxID("txID"), []*oidc4vp.ProcessedVPToken{}).Times(1)
svc.EXPECT().VerifyOIDCVerifiablePresentation(gomock.Any(), oidc4vp.TxID("txID"), &oidc4vp.AuthorizationResponseParsed{VPTokens: []*oidc4vp.ProcessedVPToken{}}).Times(1)

w := Wrap(svc, trace.NewNoopTracerProvider().Tracer(""))

err := w.VerifyOIDCVerifiablePresentation(context.Background(), "txID", []*oidc4vp.ProcessedVPToken{})
err := w.VerifyOIDCVerifiablePresentation(context.Background(), "txID", &oidc4vp.AuthorizationResponseParsed{VPTokens: []*oidc4vp.ProcessedVPToken{}})
require.NoError(t, err)
}

Expand Down
58 changes: 34 additions & 24 deletions pkg/restapi/v1/verifier/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ var (
errMissedField = errors.New("missed field")
)

type authorizationResponse struct {
type rawAuthorizationResponse struct {
IDToken string
VPToken []string
State string
Expand All @@ -72,10 +72,12 @@ type IDTokenVPToken struct {
}

type IDTokenClaims struct {
VPToken IDTokenVPToken `json:"_vp_token"`
Nonce string `json:"nonce"`
Aud string `json:"aud"`
Exp int64 `json:"exp"`
// CustomScopeClaims stores claims retrieved using custom scope.
CustomScopeClaims map[string]oidc4vp.Claims `json:"_scope,omitempty"`
VPToken IDTokenVPToken `json:"_vp_token"`
Nonce string `json:"nonce"`
Aud string `json:"aud"`
Exp int64 `json:"exp"`
}

type VPTokenClaims struct {
Expand Down Expand Up @@ -346,7 +348,7 @@ func (c *Controller) initiateOidcInteraction(
errors.New("OIDC not configured"))
}

pd, err := findPresentationDefinition(profile, strPtrToStr(data.PresentationDefinitionId))
pd, err := findPresentationDefinition(profile, lo.FromPtr(data.PresentationDefinitionId))
if err != nil {
return nil, resterr.NewValidationError(resterr.InvalidValue, "presentationDefinitionID", err)
}
Expand All @@ -362,7 +364,8 @@ func (c *Controller) initiateOidcInteraction(
logger.Debugc(ctx, "InitiateOidcInteraction applied filters to pd", logfields.WithPresDefID(pd.ID))
}

result, err := c.oidc4VPService.InitiateOidcInteraction(ctx, pd, strPtrToStr(data.Purpose), profile)
result, err := c.oidc4VPService.InitiateOidcInteraction(
ctx, pd, lo.FromPtr(data.Purpose), lo.FromPtr(data.Scope), profile)
if err != nil {
return nil, resterr.NewSystemError(resterr.VerifierOIDC4vpSvcComponent, "InitiateOidcInteraction", err)
}
Expand Down Expand Up @@ -460,21 +463,22 @@ func (c *Controller) CheckAuthorizationResponse(e echo.Context) error {
log.WithDuration(time.Since(startTime)))
}()

authResp, err := validateAuthorizationResponse(e)
rawAuthResp, err := validateAuthorizationResponse(e)
if err != nil {
return err
}

processedTokens, err := c.verifyAuthorizationResponseTokens(ctx, authResp)
authorisationResponseParsed, err := c.verifyAuthorizationResponseTokens(ctx, rawAuthResp)
if err != nil {
if tenantID, e := util.GetTenantIDFromRequest(e); e == nil {
c.sendFailedEvent(ctx, authResp.State, tenantID, "", "", err)
c.sendFailedEvent(ctx, rawAuthResp.State, tenantID, "", "", err)
}

return err
}

err = c.oidc4VPService.VerifyOIDCVerifiablePresentation(ctx, oidc4vp.TxID(authResp.State), processedTokens)
err = c.oidc4VPService.VerifyOIDCVerifiablePresentation(
ctx, oidc4vp.TxID(rawAuthResp.State), authorisationResponseParsed)
if err != nil {
return err
}
Expand Down Expand Up @@ -562,8 +566,8 @@ func (c *Controller) accessOIDC4VPTx(ctx context.Context, txID string) (*oidc4vp

func (c *Controller) verifyAuthorizationResponseTokens(
ctx context.Context,
authResp *authorizationResponse,
) ([]*oidc4vp.ProcessedVPToken, error) {
authResp *rawAuthorizationResponse,
) (*oidc4vp.AuthorizationResponseParsed, error) {
startTime := time.Now()
defer func() {
logger.Debugc(ctx, "validateResponseAuthTokens", log.WithDuration(time.Since(startTime)))
Expand Down Expand Up @@ -618,7 +622,10 @@ func (c *Controller) verifyAuthorizationResponseTokens(
})
}

return processedVPTokens, nil
return &oidc4vp.AuthorizationResponseParsed{
CustomScopeClaims: idTokenClaims.CustomScopeClaims,
VPTokens: processedVPTokens,
}, nil
}

func validateIDToken(idToken string, verifier jwt.ProofChecker) (*IDTokenClaims, error) {
Expand All @@ -644,7 +651,18 @@ func validateIDToken(idToken string, verifier jwt.ProofChecker) (*IDTokenClaims,
}
}

var customScopeClaims map[string]oidc4vp.Claims
if val := v.Get("_scope"); val != nil {
sb, err = val.Object()
if err == nil {
if err = json.Unmarshal(sb.MarshalTo([]byte{}), &customScopeClaims); err != nil {
return nil, fmt.Errorf("decode _scope: %w", err)
}
}
}

idTokenClaims := &IDTokenClaims{
CustomScopeClaims: customScopeClaims,
VPToken: IDTokenVPToken{
PresentationSubmission: presentationSubmission,
},
Expand Down Expand Up @@ -752,7 +770,7 @@ func (c *Controller) validateVPTokenLDP(vpToken string) (*VPTokenClaims, error)
}, nil
}

func validateAuthorizationResponse(ctx echo.Context) (*authorizationResponse, error) {
func validateAuthorizationResponse(ctx echo.Context) (*rawAuthorizationResponse, error) {
startTime := time.Now().UTC()
defer func() {
logger.Debugc(ctx.Request().Context(),
Expand All @@ -770,7 +788,7 @@ func validateAuthorizationResponse(ctx echo.Context) (*authorizationResponse, er
return nil, resterr.NewValidationError(resterr.InvalidValue, "body", err)
}

res := &authorizationResponse{}
res := &rawAuthorizationResponse{}

err = decodeFormValue(&res.IDToken, "id_token", req.PostForm)
if err != nil {
Expand Down Expand Up @@ -995,11 +1013,3 @@ func getVerifyPresentationOptions(options *VerifyPresentationOptions) *verifypre

return result
}

func strPtrToStr(str *string) string {
if str == nil {
return ""
}

return *str
}
Loading

0 comments on commit c3b6801

Please sign in to comment.