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 Nov 1, 2023
1 parent 31c00a5 commit b97098f
Show file tree
Hide file tree
Showing 29 changed files with 918 additions and 383 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
41 changes: 39 additions & 2 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import (
const (
linkedDomainsService = "LinkedDomains"
tokenLifetimeSeconds = 600

customScopeTimeDetails = "timedetails"
customScopeWalletDetails = "walletdetails"
)

type Flow struct {
Expand Down Expand Up @@ -409,7 +412,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 +572,15 @@ func (f *Flow) signPresentationLDP(

func (f *Flow) createIDToken(
presentationSubmission *presexch.PresentationSubmission,
clientID, nonce string,
clientID, nonce, requestObjectScope string,
) (string, error) {
scopeAdditionalClaims, err := ExtractCustomScopeClaims(requestObjectScope)
if err != nil {
return "", fmt.Errorf("extractAdditionalClaims: %w", err)
}

idToken := &IDTokenClaims{
ScopeAdditionalClaims: scopeAdditionalClaims,
VPToken: IDTokenVPToken{
PresentationSubmission: presentationSubmission,
},
Expand Down Expand Up @@ -672,3 +681,31 @@ func WithDomainMatchingDisabled() Opt {
opts.disableDomainMatching = true
}
}

// 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
}

switch chunks[1] {
case customScopeTimeDetails:
return map[string]Claims{
chunks[1]: {
"timestamp": time.Now().Format(time.RFC3339),
"uuid": uuid.NewString(),
},
}, nil
case customScopeWalletDetails:
return map[string]Claims{
chunks[1]: {
"wallet_version": "1.0",
"uuid": uuid.NewString(),
},
}, nil

default:
return nil, fmt.Errorf("unexpected custom scope \"%s\" supplied", chunks[1])
}
}
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
18 changes: 15 additions & 3 deletions component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/trustbloc/vcs/component/wallet-cli/pkg/oidc4vp"
"io"
"log"
"net/http"
Expand Down Expand Up @@ -349,8 +350,14 @@ 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, error) {
scopeAdditionalClaims, err := oidc4vp.ExtractCustomScopeClaims(requestObjectScope)
if err != nil {
return nil, fmt.Errorf("ExtractCustomScopeClaims: %w", err)
}

return &IDTokenClaims{
ScopeAdditionalClaims: scopeAdditionalClaims,
VPToken: IDTokenVPToken{
PresentationSubmission: requestPresentationSubmission,
},
Expand All @@ -362,7 +369,7 @@ func (e *VPFlowExecutor) getIDTokenClaims(requestPresentationSubmission *presexc
Nbf: time.Now().Unix(),
Iat: time.Now().Unix(),
Jti: uuid.NewString(),
}
}, nil
}

func (e *VPFlowExecutor) signIDTokenJWT(idToken *IDTokenClaims, signatureType vcs.SignatureType) (string, error) {
Expand Down Expand Up @@ -431,7 +438,12 @@ func (e *VPFlowExecutor) CreateAuthorizedResponse(o ...RPConfigOverride) (string

var signedIDToken string

signedIDToken, err = e.signIDTokenJWT(e.getIDTokenClaims(e.requestPresentationSubmission), e.walletSignType)
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
}
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
2 changes: 1 addition & 1 deletion pkg/kms/aws/service_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/kms/mocks/kms_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
Loading

0 comments on commit b97098f

Please sign in to comment.