Skip to content

Commit

Permalink
feat(sdk): id token custom claims (#655)
Browse files Browse the repository at this point in the history
feat(sdk): id_token custom claims.

Signed-off-by: Volodymyr Kubiv <[email protected]>
  • Loading branch information
vkubiv authored Nov 3, 2023
1 parent 4c25d64 commit fa733d4
Show file tree
Hide file tree
Showing 17 changed files with 336 additions and 71 deletions.
24 changes: 24 additions & 0 deletions cmd/wallet-sdk-gomobile/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,18 @@ val preferredVC = savedCredentials.atIndex(0)
interaction.presentCredentialUnsafe(preferredVC)
```

###### Read scope and add custom scope claims

```kotlin

val scope = interaction.scope()

interaction.presentCredentialWithOpts(selectedCredentials, PresentCredentialOpts()
.addScopeClaim("registration", """{"email", "[email protected]"}"""))

```


#### Swift (iOS)

```swift
Expand Down Expand Up @@ -1514,6 +1526,18 @@ let preferredVC = savedCredentials.atIndex(0)
interaction.presentCredentialUnsafe(preferredVC)
```

###### Read scope and add custom scope claims

```swift

val scope = interaction.scope()

let opts = Openid4vpNewPresentCredentialOpts()?.addScopeClaim("registration", #"{"email", "[email protected]"}"#)

try interaction.presentCredentialWithOpts(selectedCredentials, opts)

```

### Error Codes & Troubleshooting Tips

| Error | Possible Reasons |
Expand Down
6 changes: 3 additions & 3 deletions cmd/wallet-sdk-gomobile/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/net v0.16.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
12 changes: 6 additions & 6 deletions cmd/wallet-sdk-gomobile/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,24 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
56 changes: 52 additions & 4 deletions cmd/wallet-sdk-gomobile/openid4vp/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import (

type goAPIOpenID4VP interface {
GetQuery() *presexch.PresentationDefinition
PresentCredential(credentials []*afgoverifiable.Credential) error
PresentCredentialUnsafe(credential *afgoverifiable.Credential) error
Scope() []string
PresentCredential(credentials []*afgoverifiable.Credential, customClaims openid4vp.CustomClaims) error
PresentCredentialUnsafe(credential *afgoverifiable.Credential, customClaims openid4vp.CustomClaims) error
VerifierDisplayData() *openid4vp.VerifierDisplayData
}

Expand Down Expand Up @@ -134,6 +135,11 @@ func (o *Interaction) GetQuery() ([]byte, error) {
return pdBytes, nil
}

// Scope returns vp integration scope.
func (o *Interaction) Scope() *Scope {
return NewScope(o.goAPIOpenID4VP.Scope())
}

// VerifierDisplayData returns display information about verifier.
func (o *Interaction) VerifierDisplayData() *VerifierDisplayData {
displayData := o.goAPIOpenID4VP.VerifierDisplayData()
Expand All @@ -148,7 +154,25 @@ func (o *Interaction) PresentCredential(credentials *verifiable.CredentialsArray
return wrapper.ToMobileErrorWithTrace(err, o.oTel)
}

return wrapper.ToMobileErrorWithTrace(o.goAPIOpenID4VP.PresentCredential(vcs), o.oTel)
return wrapper.ToMobileErrorWithTrace(o.goAPIOpenID4VP.PresentCredential(vcs, openid4vp.CustomClaims{}), o.oTel)
}

// PresentCredentialWithOpts presents credentials to redirect uri from request object.
func (o *Interaction) PresentCredentialWithOpts(
credentials *verifiable.CredentialsArray,
opts *PresentCredentialOpts,
) error {
vcs, err := unwrapVCs(credentials)
if err != nil {
return wrapper.ToMobileErrorWithTrace(err, o.oTel)
}

claims, err := getCustomClaims(opts)
if err != nil {
return err
}

return wrapper.ToMobileErrorWithTrace(o.goAPIOpenID4VP.PresentCredential(vcs, claims), o.oTel)
}

// PresentCredentialUnsafe presents a single credential to redirect uri from
Expand All @@ -159,7 +183,8 @@ func (o *Interaction) PresentCredential(credentials *verifiable.CredentialsArray
// provided credential, at least in terms of issuer fields, and subject data
// fields.
func (o *Interaction) PresentCredentialUnsafe(credential *verifiable.Credential) error {
return wrapper.ToMobileErrorWithTrace(o.goAPIOpenID4VP.PresentCredentialUnsafe(credential.VC), o.oTel)
return wrapper.ToMobileErrorWithTrace(o.goAPIOpenID4VP.PresentCredentialUnsafe(credential.VC,
openid4vp.CustomClaims{}), o.oTel)
}

// OTelTraceID returns open telemetry trace id.
Expand Down Expand Up @@ -222,3 +247,26 @@ func unwrapVCs(vcs *verifiable.CredentialsArray) ([]*afgoverifiable.Credential,

return credentials, nil
}

func getCustomClaims(opts *PresentCredentialOpts) (openid4vp.CustomClaims, error) {
if opts == nil {
return openid4vp.CustomClaims{}, nil
}

claims := openid4vp.CustomClaims{
ScopeClaims: map[string]interface{}{},
}

for key, value := range opts.scopeClaims {
var jsonValue interface{}

err := json.Unmarshal([]byte(value), &jsonValue)
if err != nil {
return openid4vp.CustomClaims{}, fmt.Errorf("fail to parse %q claim json: %w", key, err)
}

claims.ScopeClaims[key] = jsonValue
}

return claims, nil
}
60 changes: 58 additions & 2 deletions cmd/wallet-sdk-gomobile/openid4vp/interaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func TestNewInteraction(t *testing.T) {
instance, err := NewInteraction(requiredArgs, opts)
require.NoError(t, err)
require.NotNil(t, instance)
require.Equal(t, 1, instance.Scope().Length())
require.Equal(t, "openid", instance.Scope().AtIndex(0))
})
t.Run("All other options invoked", func(t *testing.T) {
resolver, err := gomobdid.NewResolver(gomobdid.NewResolverOpts())
Expand Down Expand Up @@ -182,6 +184,21 @@ func TestOpenID4VP_PresentCredential(t *testing.T) {
require.NoError(t, err)
})

t.Run("Success With Opts", func(t *testing.T) {
instance := makeInteraction()

err := instance.PresentCredentialWithOpts(credentials, NewPresentCredentialOpts().
AddScopeClaim("claim1", `{"key" : "val"}`))
require.NoError(t, err)
})

t.Run("Success With nil Opts", func(t *testing.T) {
instance := makeInteraction()

err := instance.PresentCredentialWithOpts(credentials, nil)
require.NoError(t, err)
})

t.Run("Success Unsafe", func(t *testing.T) {
instance := makeInteraction()

Expand All @@ -200,6 +217,26 @@ func TestOpenID4VP_PresentCredential(t *testing.T) {
require.Contains(t, err.Error(), "present credentials failed")
})

t.Run("Present credentials with opts failed", func(t *testing.T) {
instance := makeInteraction()

instance.goAPIOpenID4VP = &mockGoAPIInteraction{
PresentCredentialErr: errors.New("present credentials failed"),
}

err := instance.PresentCredentialWithOpts(credentials, NewPresentCredentialOpts().
AddScopeClaim("claim1", `{"key" : "val"}`))
require.Contains(t, err.Error(), "present credentials failed")
})

t.Run("Present credentials with invalid scope value", func(t *testing.T) {
instance := makeInteraction()

err := instance.PresentCredentialWithOpts(credentials, NewPresentCredentialOpts().
AddScopeClaim("claim1", `"key" : "val"`))
require.ErrorContains(t, err, `fail to parse "claim1" claim json`)
})

t.Run("Present credentials unsafe failed", func(t *testing.T) {
instance := makeInteraction()

Expand Down Expand Up @@ -229,6 +266,20 @@ func TestOpenID4VP_PresentCredential(t *testing.T) {
})
}

func TestGetCustomClaims(t *testing.T) {
t.Run("Success", func(t *testing.T) {
claims, err := getCustomClaims(NewPresentCredentialOpts().
AddScopeClaim("claim1", `{"key" : "val"}`))
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"claim1": map[string]interface{}{
"key": "val",
},
},
claims.ScopeClaims)
})
}

func TestInteraction_VerifierDisplayData(t *testing.T) {
t.Run("Success", func(t *testing.T) {
instance := &Interaction{
Expand Down Expand Up @@ -289,6 +340,7 @@ func (c *mockCrypto) Verify([]byte, []byte, string) error {

type mockGoAPIInteraction struct {
GetQueryResult *presexch.PresentationDefinition
ScopeResult []string
PresentCredentialErr error
PresentCredentialUnsafeErr error
VerifierDisplayDataRes *openid4vp.VerifierDisplayData
Expand All @@ -298,11 +350,15 @@ func (o *mockGoAPIInteraction) GetQuery() *presexch.PresentationDefinition {
return o.GetQueryResult
}

func (o *mockGoAPIInteraction) PresentCredential([]*afgoverifiable.Credential) error {
func (o *mockGoAPIInteraction) Scope() []string {
return o.ScopeResult
}

func (o *mockGoAPIInteraction) PresentCredential([]*afgoverifiable.Credential, openid4vp.CustomClaims) error {
return o.PresentCredentialErr
}

func (o *mockGoAPIInteraction) PresentCredentialUnsafe(*afgoverifiable.Credential) error {
func (o *mockGoAPIInteraction) PresentCredentialUnsafe(*afgoverifiable.Credential, openid4vp.CustomClaims) error {
return o.PresentCredentialUnsafeErr
}

Expand Down
21 changes: 21 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4vp/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,24 @@ func (o *Opts) EnableAddingDIProofs(kms *localkms.KMS) *Opts {

return o
}

// NewPresentCredentialOpts returns a new PresentCredentialOpts object.
func NewPresentCredentialOpts() *PresentCredentialOpts {
return &PresentCredentialOpts{}
}

// PresentCredentialOpts contains options for present credential operation.
type PresentCredentialOpts struct {
scopeClaims map[string]string
}

// AddScopeClaim adds scope claim with given name.
func (o *PresentCredentialOpts) AddScopeClaim(claimName, claimJSON string) *PresentCredentialOpts {
if o.scopeClaims == nil {
o.scopeClaims = map[string]string{}
}

o.scopeClaims[claimName] = claimJSON

return o
}
28 changes: 28 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4vp/scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package openid4vp

// Scope represents an array of scope strings.
// Since arrays and slices are not compatible with gomobile, this type acts as a wrapper around a Go array of strings.
type Scope struct {
scope []string
}

// NewScope creates Scope object from array of scopes.
func NewScope(scope []string) *Scope {
return &Scope{scope: scope}
}

// Length returns the number scopes.
func (s *Scope) Length() int {
return len(s.scope)
}

// AtIndex returns scope by index.
func (s *Scope) AtIndex(index int) string {
return s.scope[index]
}
23 changes: 23 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4vp/scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package openid4vp_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4vp"
)

func TestNewScope(t *testing.T) {
scope := openid4vp.NewScope([]string{"scope1", "scope2"})

require.Equal(t, 2, scope.Length())
require.Equal(t, "scope1", scope.AtIndex(0))
require.Equal(t, "scope2", scope.AtIndex(1))
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package walletsdk.openid4vp

import dev.trustbloc.wallet.sdk.api.*
import dev.trustbloc.wallet.sdk.openid4vp.Interaction
import dev.trustbloc.wallet.sdk.credential.*
import dev.trustbloc.wallet.sdk.openid4vp.Opts
import dev.trustbloc.wallet.sdk.openid4vp.Args
import dev.trustbloc.wallet.sdk.openid4vp.VerifierDisplayData
import dev.trustbloc.wallet.sdk.openid4vp.*
import dev.trustbloc.wallet.sdk.otel.Otel
import dev.trustbloc.wallet.sdk.verifiable.CredentialsArray
import dev.trustbloc.wallet.sdk.stderr.MetricsLogger
Expand Down
4 changes: 2 additions & 2 deletions demo/app/ios/Runner/OpenID4VP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class OpenID4VP {
opts!.add(trace!.traceHeader())

let interaction = Openid4vpNewInteraction(args, opts, nil)

vpQueryContent = try interaction!.getQuery()
initiatedInteraction = interaction
}
Expand All @@ -67,7 +67,7 @@ public class OpenID4VP {
}

// let verifiablePresentation = try CredentialNewInquirer(documentLoader)!.query(vpQueryContent, credentials: selectedCredentials)

try initiatedInteraction.presentCredential(selectedCredentials)
}

Expand Down
Loading

0 comments on commit fa733d4

Please sign in to comment.