Skip to content

Commit

Permalink
feat(sdk): credential display API v2 (#824)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko authored Nov 15, 2024
1 parent 5e59f60 commit 9d8ea67
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 164 deletions.
1 change: 1 addition & 0 deletions cmd/wallet-sdk-gomobile/display/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Opts struct {
maskingString *string
didResolver api.DIDResolver
skipNonClaimData bool
credentialConfigIDs []string
}

// NewOpts returns a new Opts object.
Expand Down
35 changes: 27 additions & 8 deletions cmd/wallet-sdk-gomobile/display/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@ package display

import (
"errors"

"github.com/trustbloc/vc-go/proof/defaults"
afgoverifiable "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/common"

afgoverifiable "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/wrapper"
"github.com/trustbloc/wallet-sdk/pkg/common"
goapicredentialschema "github.com/trustbloc/wallet-sdk/pkg/credentialschema"
)

Expand All @@ -44,8 +41,30 @@ func Resolve(vcs *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*D
return &Data{resolvedDisplayData: resolvedDisplayData}, nil
}

func ResolveCredential(vcs *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*Resolved, error) {
goAPIOpts, err := generateGoAPIOpts(vcs, issuerURI, opts)
func ResolveCredential(credentialsArray *verifiable.CredentialsArray, issuerURI string, opts *Opts) (*Resolved, error) {
goAPIOpts, err := generateGoAPIOpts(credentialsArray, issuerURI, opts)
if err != nil {
return nil, err
}

resolvedDisplayData, err := goapicredentialschema.ResolveCredential(goAPIOpts...)
if err != nil {
return nil, err
}

return &Resolved{resolvedDisplayData: resolvedDisplayData}, nil
}

func ResolveCredentialV2(credentialsArray *verifiable.CredentialsArrayV2, issuerURI string, opts *Opts) (*Resolved, error) {
credentials := &verifiable.CredentialsArray{}
opts.credentialConfigIDs = make([]string, credentialsArray.Length())

for i := 0; i < credentialsArray.Length(); i++ {
credentials.Add(credentialsArray.AtIndex(i))
opts.credentialConfigIDs[i] = credentialsArray.ConfigIDAtIndex(i)
}

goAPIOpts, err := generateGoAPIOpts(credentials, issuerURI, opts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -89,7 +108,7 @@ func generateGoAPIOpts(vcs *verifiable.CredentialsArray, issuerURI string,
httpClient := wrapper.NewHTTPClient(opts.httpTimeout, opts.additionalHeaders, opts.disableHTTPClientTLSVerification)

goAPIOpts := []goapicredentialschema.ResolveOpt{
goapicredentialschema.WithCredentials(mobileVCsArrayToGoAPIVCsArray(vcs)),
goapicredentialschema.WithCredentials(mobileVCsArrayToGoAPIVCsArray(vcs), opts.credentialConfigIDs...),
goapicredentialschema.WithIssuerURI(issuerURI),
goapicredentialschema.WithPreferredLocale(opts.preferredLocale),
goapicredentialschema.WithHTTPClient(httpClient),
Expand Down
77 changes: 54 additions & 23 deletions cmd/wallet-sdk-gomobile/display/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import (
"strconv"
"testing"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/models/issuer"

"github.com/stretchr/testify/require"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/display"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/openid4ci"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/pkg/models/issuer"
)

const (
Expand Down Expand Up @@ -215,33 +214,65 @@ func TestResolveCredential(t *testing.T) {
parseVCOptionalArgs := verifiable.NewOpts()
parseVCOptionalArgs.DisableProofCheck()

vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)
t.Run("Success: CredentialArray v1", func(t *testing.T) {
vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)

vcs := verifiable.NewCredentialsArray()
vcs.Add(vc)
vcs := verifiable.NewCredentialsArray()
vcs.Add(vc)

opts := display.NewOpts().SetMaskingString("*")
opts := display.NewOpts().SetMaskingString("*")

resolvedDisplayData, err := display.ResolveCredential(vcs, server.URL, opts)
require.NoError(t, err)
resolvedDisplayData, err := display.ResolveCredential(vcs, server.URL, opts)
require.NoError(t, err)

require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)
require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)

credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)
credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)

for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)
for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)

if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
}
}
}
})

t.Run("Success: CredentialArray v2", func(t *testing.T) {
vc, err := verifiable.ParseCredential(credentialUniversityDegree, parseVCOptionalArgs)
require.NoError(t, err)

vcs := verifiable.NewCredentialsArrayV2()
vcs.Add(vc, "UniversityDegreeCredential_jwt_vc_json_v1")

opts := display.NewOpts().SetMaskingString("*")

resolvedDisplayData, err := display.ResolveCredentialV2(vcs, server.URL, opts)
require.NoError(t, err)

require.Equal(t, resolvedDisplayData.LocalizedIssuersLength(), 2)
require.Equal(t, resolvedDisplayData.CredentialsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).LocalizedOverviewsLength(), 1)
require.Equal(t, resolvedDisplayData.CredentialAtIndex(0).SubjectsLength(), 6)

credentialDisplay := resolvedDisplayData.CredentialAtIndex(0)

for i := 0; i < credentialDisplay.SubjectsLength(); i++ {
claim := credentialDisplay.SubjectAtIndex(i)

if claim.LocalizedLabelAtIndex(0).Name() == sensitiveIDLabel {
require.Equal(t, "*****6789", claim.Value())
} else if claim.LocalizedLabelAtIndex(0).Name() == reallySensitiveIDLabel {
require.Equal(t, "*******", claim.Value())
}
}
})
}

func TestResolveCredentialOffer(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions cmd/wallet-sdk-gomobile/openid4ci/interaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,13 @@ func toGomobileCredentials(credentials []*afgoverifiable.Credential) *verifiable

return gomobileCredentials
}

func toGomobileCredentialsV2(credentials []*afgoverifiable.Credential, configIDs []string) *verifiable.CredentialsArrayV2 {
credentialArray := verifiable.NewCredentialsArrayV2()

for i := range credentials {
credentialArray.Add(verifiable.NewCredential(credentials[i]), configIDs[i])
}

return credentialArray
}
44 changes: 39 additions & 5 deletions cmd/wallet-sdk-gomobile/openid4ci/issuerinitiatedinteraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ package openid4ci

import (
"errors"
"fmt"

"github.com/trustbloc/wallet-sdk/pkg/walleterror"
verifiableapi "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/otel"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable"
"github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/wrapper"
openid4cigoapi "github.com/trustbloc/wallet-sdk/pkg/openid4ci"
"github.com/trustbloc/wallet-sdk/pkg/walleterror"
)

// IssuerInitiatedInteraction represents a single issuer-instantiated OpenID4CI interaction between a wallet and an
Expand Down Expand Up @@ -102,32 +104,64 @@ func (i *IssuerInitiatedInteraction) CreateAuthorizationURL(clientID, redirectUR
func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuth(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) (*verifiable.CredentialsArray, error) {
credentials, _, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentials(credentials), nil
}

// RequestCredentialWithPreAuthV2 requests credentials using a pre-authorized code flow.
// Returns an array of credentials with config IDs, which map to CredentialConfigurationSupported in the
// issuer's metadata.
func (i *IssuerInitiatedInteraction) RequestCredentialWithPreAuthV2(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) (*verifiable.CredentialsArrayV2, error) {
credentials, configIDs, err := i.requestCredentialWithPreAuth(vm, opts)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentialsV2(credentials, configIDs), nil
}

func (i *IssuerInitiatedInteraction) requestCredentialWithPreAuth(
vm *api.VerificationMethod, opts *RequestCredentialWithPreAuthOpts,
) ([]*verifiableapi.Credential, []string, error) {
if opts == nil {
opts = NewRequestCredentialWithPreAuthOpts()
}

signer, err := createSigner(vm, i.crypto)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

goOpts := []openid4cigoapi.RequestCredentialWithPreAuthOpt{openid4cigoapi.WithPIN(opts.pin)}

if opts.attestationVM != nil {
attestationSigner, attErr := createSigner(opts.attestationVM, i.crypto)
if attErr != nil {
return nil, wrapper.ToMobileErrorWithTrace(attErr, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(attErr, i.oTel)
}

goOpts = append(goOpts, openid4cigoapi.WithAttestationVC(attestationSigner, opts.attestationVC))
}

credentials, err := i.goAPIInteraction.RequestCredentialWithPreAuth(signer, goOpts...)
if err != nil {
return nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
return nil, nil, wrapper.ToMobileErrorWithTrace(err, i.oTel)
}

return toGomobileCredentials(credentials), nil
configIDs := i.goAPIInteraction.CredentialConfigIDs()

if len(credentials) != len(configIDs) {
return nil, nil, fmt.Errorf("mismatch in the number of credentials and configuration IDs: "+
"expected %d but got %d", len(credentials), len(configIDs))
}

return credentials, configIDs, nil
}

// RequestCredentialWithAuth requests credential(s) from the issuer. This method can only be used for the
Expand Down
43 changes: 43 additions & 0 deletions cmd/wallet-sdk-gomobile/verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,46 @@ func (a *CredentialsArray) AtIndex(index int) *Credential {

return a.credentials[index]
}

// CredentialsArrayV2 represents an array of Credentials with associated array of config IDs.
// Each config ID maps to CredentialConfigurationSupported in the issuer's metadata.
type CredentialsArrayV2 struct {
credentials []*Credential
configIDs []string
}

// NewCredentialsArrayV2 creates a new CredentialsArrayV2.
func NewCredentialsArrayV2() *CredentialsArrayV2 {
return &CredentialsArrayV2{}
}

// Add adds a new Credential with associated credential config ID.
func (a *CredentialsArrayV2) Add(credential *Credential, configID string) {
a.credentials = append(a.credentials, credential)
a.configIDs = append(a.configIDs, configID)
}

// Length returns the number of Credentials contained within this CredentialsArrayV2.
func (a *CredentialsArrayV2) Length() int {
return len(a.credentials)
}

// AtIndex returns the Credential at the given index.
// If the index passed in is out of bounds, then nil is returned.
func (a *CredentialsArrayV2) AtIndex(index int) *Credential {
if index < 0 || index >= len(a.credentials) {
return nil
}

return a.credentials[index]
}

// ConfigIDAtIndex returns the config ID for the Credential at the given index.
// If the index is out of bounds, it returns an empty string.
func (a *CredentialsArrayV2) ConfigIDAtIndex(index int) string {
if index < 0 || index >= len(a.credentials) {
return ""
}

return a.configIDs[index]
}
Loading

0 comments on commit 9d8ea67

Please sign in to comment.