diff --git a/cmd/wallet-sdk-gomobile/openid4vp/interaction.go b/cmd/wallet-sdk-gomobile/openid4vp/interaction.go index 6b7b3665..412ee460 100644 --- a/cmd/wallet-sdk-gomobile/openid4vp/interaction.go +++ b/cmd/wallet-sdk-gomobile/openid4vp/interaction.go @@ -286,16 +286,6 @@ func toGoAPIOpts(opts *Opts) ([]openid4vp.Opt, error) { goAPIOpts = append(goAPIOpts, openid4vp.WithMetricsLogger(mobileMetricsLoggerWrapper)) } - if opts.kms != nil { - signer, err := opts.kms.GoAPILocalKMS.AriesSuite.KMSCryptoSigner() - if err != nil { - return nil, fmt.Errorf("aries local crypto suite missing support for signing: %w", err) - } - - goAPIOpts = append(goAPIOpts, - openid4vp.WithDIProofs(signer)) - } - return goAPIOpts, nil } diff --git a/cmd/wallet-sdk-gomobile/openid4vp/interaction_test.go b/cmd/wallet-sdk-gomobile/openid4vp/interaction_test.go index 0019c55e..11d2f35d 100644 --- a/cmd/wallet-sdk-gomobile/openid4vp/interaction_test.go +++ b/cmd/wallet-sdk-gomobile/openid4vp/interaction_test.go @@ -17,24 +17,19 @@ import ( "strings" "testing" - "github.com/trustbloc/kms-go/doc/jose/jwk" - wrapperapi "github.com/trustbloc/kms-go/wrapper/api" - - gomobdid "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did" - "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/metricslogger/stderr" - goapilocalkms "github.com/trustbloc/wallet-sdk/pkg/localkms" - - "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/activitylogger/mem" - - "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/localkms" - "github.com/piprate/json-gold/ld" "github.com/stretchr/testify/require" "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/kms-go/doc/jose/jwk" + wrapperapi "github.com/trustbloc/kms-go/wrapper/api" "github.com/trustbloc/vc-go/presexch" afgoverifiable "github.com/trustbloc/vc-go/verifiable" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/activitylogger/mem" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/api" + gomobdid "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/did" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/localkms" + "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/metricslogger/stderr" "github.com/trustbloc/wallet-sdk/cmd/wallet-sdk-gomobile/verifiable" "github.com/trustbloc/wallet-sdk/internal/testutil" "github.com/trustbloc/wallet-sdk/pkg/models" @@ -107,24 +102,6 @@ func TestNewInteraction(t *testing.T) { }) }) - t.Run("Failure - kms crypto suite does not support signing for DI proofs", func(t *testing.T) { - requiredArgs := NewArgs( - requestObjectJWT, - &mockCrypto{}, - &mockDIDResolver{}, - ) - - opts := NewOpts() - - localKMS := &localkms.KMS{GoAPILocalKMS: &goapilocalkms.LocalKMS{AriesSuite: &mockSuite{}}} - - opts.EnableAddingDIProofs(localKMS) - - instance, err := NewInteraction(requiredArgs, opts) - testutil.RequireErrorContains(t, err, "aries local crypto suite missing support for signing") - require.Nil(t, instance) - }) - t.Run("Failure - invalid authorization request", func(t *testing.T) { requiredArgs := NewArgs( requestObjectJWT, @@ -467,7 +444,7 @@ func (o *mockGoAPIInteraction) CustomScope() []string { func (o *mockGoAPIInteraction) PresentCredential( []*afgoverifiable.Credential, openid4vp.CustomClaims, - ...openid4vp.PresentOpt, +...openid4vp.PresentOpt, ) error { return o.PresentCredentialErr } diff --git a/cmd/wallet-sdk-gomobile/openid4vp/opts.go b/cmd/wallet-sdk-gomobile/openid4vp/opts.go index 7be2b549..436ba609 100644 --- a/cmd/wallet-sdk-gomobile/openid4vp/opts.go +++ b/cmd/wallet-sdk-gomobile/openid4vp/opts.go @@ -95,6 +95,7 @@ func (o *Opts) DisableOpenTelemetry() *Opts { // EnableAddingDIProofs enables the adding of data integrity proofs to presentations sent to the verifier. It requires // a KMS to be passed in. +// Deprecated: DI proofs are now enabled by default. Their usage depends on the proof types supported by the verifier. func (o *Opts) EnableAddingDIProofs(kms *localkms.KMS) *Opts { o.kms = kms diff --git a/pkg/ldproof/ldproof.go b/pkg/ldproof/ldproof.go index a980726b..23b2fac3 100644 --- a/pkg/ldproof/ldproof.go +++ b/pkg/ldproof/ldproof.go @@ -7,15 +7,20 @@ SPDX-License-Identifier: Apache-2.0 package ldproof import ( - "crypto" - "encoding/base64" "fmt" + "strings" "github.com/piprate/json-gold/ld" - diddoc "github.com/trustbloc/did-go/doc/did" + "github.com/samber/lo" + "github.com/trustbloc/did-go/doc/did" "github.com/trustbloc/did-go/doc/ld/processor" + vdrapi "github.com/trustbloc/did-go/vdr/api" + "github.com/trustbloc/kms-go/doc/util/jwkkid" "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/vc-go/presexch" + "github.com/trustbloc/vc-go/dataintegrity" + "github.com/trustbloc/vc-go/dataintegrity/suite" + "github.com/trustbloc/vc-go/dataintegrity/suite/ecdsa2019" + "github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022" "github.com/trustbloc/vc-go/proof" "github.com/trustbloc/vc-go/proof/creator" "github.com/trustbloc/vc-go/proof/ldproofs/ecdsasecp256k1signature2019" @@ -38,43 +43,107 @@ var supportedLDProofTypes = map[string]proof.LDProofDescriptor{ jsonwebsignature2020.ProofType: jsonwebsignature2020.New(), } +var supportedDIKeyTypes = map[string][]kms.KeyType{ + ecdsa2019.SuiteTypeNew: {kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363}, + eddsa2022.SuiteType: {kms.ED25519Type}, +} + // LDProof implements functionality of adding linked data proof to the verifiable presentation. type LDProof struct { - crypto api.Crypto - documentLoader ld.DocumentLoader - ldProofDescriptor proof.LDProofDescriptor - keyType kms.KeyType + crypto api.Crypto + documentLoader ld.DocumentLoader + didResolver api.DIDResolver } // New returns a new instance of LDProof. func New( crypto api.Crypto, documentLoader ld.DocumentLoader, - ldpVPFormat *presexch.LdpType, - keyType kms.KeyType, -) (*LDProof, error) { - var ( - proofDesc proof.LDProofDescriptor - found bool - ) + didResolver api.DIDResolver, +) *LDProof { + return &LDProof{ + crypto: crypto, + documentLoader: documentLoader, + didResolver: didResolver, + } +} + +// Add adds linked data proof to the verifiable presentation. +func (p *LDProof) Add(vp *verifiable.Presentation, opts ...Opt) error { + o := &options{} + + for _, opt := range opts { + opt(o) + } + + keyID, keyType, err := getKeyIDAndType(o.verificationMethod) + if err != nil { + return fmt.Errorf("get key ID and type: %w", err) + } + + for _, proofType := range o.ldpType.ProofType { + // data integrity proof + if proofType == ecdsa2019.SuiteTypeNew || proofType == eddsa2022.SuiteType { + if keyTypes, found := supportedDIKeyTypes[proofType]; found && lo.Contains(keyTypes, keyType) { + return p.addDataIntegrityProof(vp, proofType, keyID, o) + } + } - for _, proofType := range ldpVPFormat.ProofType { - if proofDesc, found = supportedLDProofTypes[proofType]; found && isKeyTypeSupported(proofDesc, keyType) { - break + // linked data proof + if proofDesc, found := supportedLDProofTypes[proofType]; found && isKeyTypeSupported(proofDesc, keyType) { + return p.addLinkedDataProof(vp, proofDesc, keyID, keyType, o) } - found = false } - if !found { - return nil, fmt.Errorf("no supported linked data proof found") + return fmt.Errorf("no supported ldp types found") +} + +func getKeyIDAndType(vm *did.VerificationMethod) (string, kms.KeyType, error) { + if vm == nil { + return "", "", fmt.Errorf("missing verification method") } - return &LDProof{ - crypto: crypto, - documentLoader: documentLoader, - ldProofDescriptor: proofDesc, - keyType: keyType, - }, nil + if jwk := vm.JSONWebKey(); jwk != nil { + keyType, err := jwk.KeyType() + if err != nil { + return "", "", fmt.Errorf("get key type from jwk: %w", err) + } + + return jwk.KeyID, keyType, nil + } + + if len(vm.Value) == 0 { + return "", "", fmt.Errorf("missing key value for %s verification method", vm.ID) + } + + switch vm.Type { + case ed25519signature2018.VerificationMethodType, + ed25519signature2020.VerificationMethodType: + kid, err := jwkkid.CreateKID(vm.Value, kms.ED25519Type) + if err != nil { + return "", "", fmt.Errorf("failed to generate key ID for ed25519 key: %w", err) + } + + return kid, kms.ED25519Type, nil + } + + return "", "", fmt.Errorf("unsupported verification method type: %s", vm.Type) +} + +func fullVMID(did, vmID string) string { + if vmID == "" { + return did + } + + if vmID[0] == '#' { + return did + vmID + } + + if strings.HasPrefix(vmID, "did:") { + return vmID + } + + return did + "#" + vmID } func isKeyTypeSupported(ldProof proof.LDProofDescriptor, keyType kms.KeyType) bool { @@ -87,66 +156,68 @@ func isKeyTypeSupported(ldProof proof.LDProofDescriptor, keyType kms.KeyType) bo return false } -// Add adds linked data proof to the verifiable presentation. -func (p *LDProof) Add(vp *verifiable.Presentation, opts ...Opt) error { - o := &options{} - - for _, opt := range opts { - opt(o) +func (p *LDProof) addDataIntegrityProof(vp *verifiable.Presentation, dataIntegritySuite string, keyID string, o *options) error { + var initializer suite.SignerInitializer + + switch dataIntegritySuite { + case ecdsa2019.SuiteTypeNew: + initializer = ecdsa2019.NewSignerInitializer( + &ecdsa2019.SignerInitializerOptions{ + LDDocumentLoader: p.documentLoader, + SignerGetter: ecdsa2019.WithStaticSigner(p.createSigner(keyID)), + }, + ) + case eddsa2022.SuiteType: + initializer = eddsa2022.NewSignerInitializer( + &eddsa2022.SignerInitializerOptions{ + LDDocumentLoader: p.documentLoader, + SignerGetter: eddsa2022.WithStaticSigner(p.createSigner(keyID)), + }, + ) + default: + return fmt.Errorf("unsupported data integrity suite: %s", dataIntegritySuite) } - if err := p.validateSigningVerificationMethod(o); err != nil { + dataIntegritySigner, err := dataintegrity.NewSigner( + &dataintegrity.Options{ + DIDResolver: &didResolverWrapper{ + didResolver: p.didResolver, + }, + }, + initializer, + ) + if err != nil { return err } - signer, err := p.createSigner(o) - if err != nil { - return err + proofContext := &verifiable.DataIntegrityProofContext{ + SigningKeyID: fullVMID(o.did, o.verificationMethod.ID), + CryptoSuite: dataIntegritySuite, + ProofPurpose: proofPurpose, + Challenge: o.challenge, + Domain: o.domain, } - p.updatePresentationContext(vp) + return vp.AddDataIntegrityProof(proofContext, dataIntegritySigner) +} +func (p *LDProof) addLinkedDataProof(vp *verifiable.Presentation, + proofDesc proof.LDProofDescriptor, keyID string, keyType kms.KeyType, o *options) error { proofContext := &verifiable.LinkedDataProofContext{ - SignatureType: p.ldProofDescriptor.ProofType(), - ProofCreator: creator.New(creator.WithLDProofType(p.ldProofDescriptor, signer)), - KeyType: p.keyType, + SignatureType: proofDesc.ProofType(), + ProofCreator: creator.New(creator.WithLDProofType(proofDesc, p.createSigner(keyID))), + KeyType: keyType, SignatureRepresentation: verifiable.SignatureProofValue, - VerificationMethod: o.signingVM.ID, - Challenge: o.nonce, + VerificationMethod: o.verificationMethod.ID, + Challenge: o.challenge, Domain: o.domain, Purpose: proofPurpose, } - return vp.AddLinkedDataProof( - proofContext, - processor.WithDocumentLoader(p.documentLoader), - ) -} - -func (p *LDProof) validateSigningVerificationMethod(opts *options) error { - if opts.signingVM == nil { - return fmt.Errorf("missing signing verification method") - } - - if jwk := opts.signingVM.JSONWebKey(); jwk == nil { - return fmt.Errorf("missing jwk for %s verification method", opts.signingVM.ID) - } - - return nil -} - -func (p *LDProof) createSigner(opts *options) (*cryptoSigner, error) { - jwk := opts.signingVM.JSONWebKey() - - tb, err := jwk.Thumbprint(crypto.SHA256) - if err != nil { - return nil, fmt.Errorf("create crypto thumbprint for jwk: %w", err) - } + p.updatePresentationContext(vp) - return &cryptoSigner{ - crypto: p.crypto, - keyID: base64.RawURLEncoding.EncodeToString(tb), - }, nil + return vp.AddLinkedDataProof(proofContext, + processor.WithDocumentLoader(p.documentLoader)) } func (p *LDProof) updatePresentationContext(vp *verifiable.Presentation) { @@ -156,41 +227,26 @@ func (p *LDProof) updatePresentationContext(vp *verifiable.Presentation) { ) } -type options struct { - signingVM *diddoc.VerificationMethod - nonce string - domain string -} - -// Opt is an option for adding linked data proof. -type Opt func(opts *options) - -// WithSigningVM sets signing verification method. -func WithSigningVM(vm *diddoc.VerificationMethod) Opt { - return func(opts *options) { - opts.signingVM = vm - } +type signer struct { + crypto api.Crypto + keyID string } -// WithNonce sets nonce. -func WithNonce(nonce string) Opt { - return func(opts *options) { - opts.nonce = nonce - } +func (s *signer) Sign(msg []byte) ([]byte, error) { + return s.crypto.Sign(msg, s.keyID) } -// WithDomain sets domain. -func WithDomain(domain string) Opt { - return func(opts *options) { - opts.domain = domain +func (p *LDProof) createSigner(keyID string) *signer { + return &signer{ + crypto: p.crypto, + keyID: keyID, } } -type cryptoSigner struct { - crypto api.Crypto - keyID string +type didResolverWrapper struct { + didResolver api.DIDResolver } -func (s *cryptoSigner) Sign(data []byte) ([]byte, error) { - return s.crypto.Sign(data, s.keyID) +func (d *didResolverWrapper) Resolve(did string, _ ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { + return d.didResolver.Resolve(did) } diff --git a/pkg/ldproof/ldproof_opts.go b/pkg/ldproof/ldproof_opts.go new file mode 100644 index 00000000..c7942438 --- /dev/null +++ b/pkg/ldproof/ldproof_opts.go @@ -0,0 +1,58 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ldproof + +import ( + diddoc "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/vc-go/presexch" +) + +type options struct { + ldpType *presexch.LdpType + verificationMethod *diddoc.VerificationMethod + did string + challenge string + domain string +} + +// Opt is an option for adding linked data proof. +type Opt func(opts *options) + +// WithLdpType sets the supported JSON-LD proof type. +func WithLdpType(ldpType *presexch.LdpType) Opt { + return func(opts *options) { + opts.ldpType = ldpType + } +} + +// WithVerificationMethod sets the verification method to get the associated signing key. +func WithVerificationMethod(vm *diddoc.VerificationMethod) Opt { + return func(opts *options) { + opts.verificationMethod = vm + } +} + +// WithDID sets did. +func WithDID(did string) Opt { + return func(opts *options) { + opts.did = did + } +} + +// WithChallenge sets challenge. +func WithChallenge(challenge string) Opt { + return func(opts *options) { + opts.challenge = challenge + } +} + +// WithDomain sets domain. +func WithDomain(domain string) Opt { + return func(opts *options) { + opts.domain = domain + } +} diff --git a/pkg/ldproof/ldproof_test.go b/pkg/ldproof/ldproof_test.go index 939efb8f..44e717a4 100644 --- a/pkg/ldproof/ldproof_test.go +++ b/pkg/ldproof/ldproof_test.go @@ -7,159 +7,194 @@ SPDX-License-Identifier: Apache-2.0 package ldproof_test import ( + "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "testing" - "github.com/piprate/json-gold/ld" + "github.com/btcsuite/btcd/btcec/v2" "github.com/stretchr/testify/require" diddoc "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" "github.com/trustbloc/kms-go/doc/util/jwkkid" "github.com/trustbloc/kms-go/spi/kms" + "github.com/trustbloc/vc-go/dataintegrity/suite/ecdsa2019" + "github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022" "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/verifiable" "github.com/trustbloc/wallet-sdk/internal/testutil" - "github.com/trustbloc/wallet-sdk/pkg/api" "github.com/trustbloc/wallet-sdk/pkg/ldproof" ) -func TestNew(t *testing.T) { +func TestLDProof_Add(t *testing.T) { var ( - crypto api.Crypto - documentLoader ld.DocumentLoader - ldpVPFormat *presexch.LdpType - keyType kms.KeyType + ldpType *presexch.LdpType + vm *diddoc.VerificationMethod ) tests := []struct { name string setup func() - check func(t *testing.T, ldProof *ldproof.LDProof, err error) + check func(t *testing.T, err error) }{ { - name: "success", + name: "success EcdsaSecp256k1Signature2019", setup: func() { - crypto = &cryptoMock{} - documentLoader = testutil.DocumentLoader(t) - ldpVPFormat = &presexch.LdpType{ - ProofType: []string{"EcdsaSecp256k1Signature2019", "Ed25519Signature2018", "Ed25519Signature2020", - "JsonWebSignature2020", - }, + ldpType = &presexch.LdpType{ + ProofType: []string{"EcdsaSecp256k1Signature2019"}, } - keyType = kms.ECDSAP384IEEEP1363 + + privateKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) + + jwk, err := jwksupport.JWKFromKey(&privateKey.PublicKey) + require.NoError(t, err) + + vm, err = diddoc.NewVerificationMethodFromJWK("vmID", "EcdsaSecp256k1Signature2019", "", jwk) + require.NoError(t, err) }, - check: func(t *testing.T, ldProof *ldproof.LDProof, err error) { - require.NotNil(t, ldProof) + check: func(t *testing.T, err error) { require.NoError(t, err) }, }, { - name: "no supported linked data proof found", + name: "success Ed25519Signature2018", setup: func() { - documentLoader = testutil.DocumentLoader(t) - ldpVPFormat = &presexch.LdpType{ - ProofType: []string{"Ed25519Signature2018", "Ed25519Signature2020"}, + ldpType = &presexch.LdpType{ + ProofType: []string{"Ed25519Signature2018"}, } - keyType = kms.ECDSAP384IEEEP1363 + + verKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + vm = diddoc.NewVerificationMethodFromBytes("vmID", "Ed25519VerificationKey2018", "", verKey) }, - check: func(t *testing.T, ldProof *ldproof.LDProof, err error) { - require.Nil(t, ldProof) - require.ErrorContains(t, err, "no supported linked data proof found") + check: func(t *testing.T, err error) { + require.NoError(t, err) }, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setup() + { + name: "success Ed25519Signature2020", + setup: func() { + ldpType = &presexch.LdpType{ + ProofType: []string{"Ed25519Signature2020"}, + } - ldProof, err := ldproof.New(crypto, documentLoader, ldpVPFormat, keyType) - tt.check(t, ldProof, err) - }) - } -} + verKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) -func TestLDProof_Add(t *testing.T) { - ldProof, err := ldproof.New( - &cryptoMock{Signature: []byte("signature")}, - testutil.DocumentLoader(t), - &presexch.LdpType{ - ProofType: []string{"EcdsaSecp256k1Signature2019", "Ed25519Signature2018", "Ed25519Signature2020", - "JsonWebSignature2020", + vm = diddoc.NewVerificationMethodFromBytes("vmID", "Ed25519VerificationKey2020", "", verKey) + }, + check: func(t *testing.T, err error) { + require.NoError(t, err) }, }, - kms.ED25519Type, - ) - require.NoError(t, err) - - verKey, _, err := ed25519.GenerateKey(rand.Reader) - require.NoError(t, err) + { + name: "success JsonWebSignature2020", + setup: func() { + ldpType = &presexch.LdpType{ + ProofType: []string{"JsonWebSignature2020"}, + } - jwk, err := jwkkid.BuildJWK(verKey, kms.ED25519Type) - require.NoError(t, err) + privateKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) - var ( - vp *verifiable.Presentation - opts []ldproof.Opt - ) + jwk, err := jwksupport.JWKFromKey(&privateKey.PublicKey) + require.NoError(t, err) - tests := []struct { - name string - setup func() - check func(t *testing.T, err error) - }{ + vm, err = diddoc.NewVerificationMethodFromJWK("vmID", "JsonWebKey2020", "", jwk) + require.NoError(t, err) + }, + check: func(t *testing.T, err error) { + require.NoError(t, err) + }, + }, { - name: "success", + name: "success ecdsa-rdfc-2019", setup: func() { - vp = &verifiable.Presentation{} + ldpType = &presexch.LdpType{ + ProofType: []string{ecdsa2019.SuiteTypeNew}, + } - var signingVM *diddoc.VerificationMethod + privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(t, err) - signingVM, err = diddoc.NewVerificationMethodFromJWK("vmID", "Ed25519VerificationKey2018", "", jwk) + jwk, err := jwksupport.JWKFromKey(&privateKey.PublicKey) require.NoError(t, err) - opts = []ldproof.Opt{ - ldproof.WithSigningVM(signingVM), - ldproof.WithNonce("nonce"), - ldproof.WithDomain("example.com"), - } + vm, err = diddoc.NewVerificationMethodFromJWK("vmID", "", "", jwk) + require.NoError(t, err) }, check: func(t *testing.T, err error) { require.NoError(t, err) }, }, { - name: "missing signing verification method", + name: "success eddsa-rdfc-2022", setup: func() { - vp = &verifiable.Presentation{} - - opts = []ldproof.Opt{ - ldproof.WithNonce("nonce"), - ldproof.WithDomain("example.com"), + ldpType = &presexch.LdpType{ + ProofType: []string{eddsa2022.SuiteType}, } + + verKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + jwk, err := jwkkid.BuildJWK(verKey, kms.ED25519Type) + require.NoError(t, err) + + vm, err = diddoc.NewVerificationMethodFromJWK("vmID", "", "", jwk) + require.NoError(t, err) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "missing signing verification method") + require.NoError(t, err) }, }, { - name: "missing jwk for verification method", + name: "missing key value for verification method", setup: func() { - vp = &verifiable.Presentation{} + ldpType = &presexch.LdpType{ + ProofType: []string{"Ed25519Signature2020"}, + } - signingVM := &diddoc.VerificationMethod{ - ID: "vmID", - Type: "Ed25519VerificationKey2018", + vm = diddoc.NewVerificationMethodFromBytes("vmID", "Ed25519VerificationKey2020", "", nil) + }, + check: func(t *testing.T, err error) { + require.ErrorContains(t, err, "missing key value for vmID verification method") + }, + }, + { + name: "unsupported verification method type", + setup: func() { + ldpType = &presexch.LdpType{ + ProofType: []string{"Ed25519VerificationKey2020"}, } - opts = []ldproof.Opt{ - ldproof.WithSigningVM(signingVM), - ldproof.WithNonce("nonce"), - ldproof.WithDomain("example.com"), + vm = diddoc.NewVerificationMethodFromBytes("vmID", "unsupported", "", []byte("key value")) + }, + check: func(t *testing.T, err error) { + require.ErrorContains(t, err, "unsupported verification method type") + }, + }, + { + name: "unsupported data integrity key type", + setup: func() { + ldpType = &presexch.LdpType{ + ProofType: []string{ecdsa2019.SuiteTypeNew}, } + + privateKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) + + jwk, err := jwksupport.JWKFromKey(&privateKey.PublicKey) + require.NoError(t, err) + + vm, err = diddoc.NewVerificationMethodFromJWK("vmID", "", "", jwk) + require.NoError(t, err) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "missing jwk for vmID verification method") + require.ErrorContains(t, err, "no supported ldp types found") }, }, } @@ -167,7 +202,38 @@ func TestLDProof_Add(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tt.setup() - err = ldProof.Add(vp, opts...) + resolverMock := &didResolverMock{ + DocResolution: &diddoc.DocResolution{ + DIDDocument: &diddoc.Doc{ + AssertionMethod: []diddoc.Verification{ + { + VerificationMethod: *vm, + }, + }, + Authentication: []diddoc.Verification{ + { + VerificationMethod: *vm, + }, + }, + VerificationMethod: []diddoc.VerificationMethod{ + *vm, + }, + }, + }, + } + + ldProof := ldproof.New( + &cryptoMock{Signature: []byte("signature")}, + testutil.DocumentLoader(t), + resolverMock, + ) + + err := ldProof.Add(&verifiable.Presentation{}, + ldproof.WithLdpType(ldpType), + ldproof.WithVerificationMethod(vm), + ldproof.WithChallenge("nonce"), + ldproof.WithDomain("example.com"), + ) tt.check(t, err) }) } @@ -178,6 +244,15 @@ type cryptoMock struct { Err error } -func (c *cryptoMock) Sign([]byte, string) ([]byte, error) { - return c.Signature, c.Err +func (m *cryptoMock) Sign([]byte, string) ([]byte, error) { + return m.Signature, m.Err +} + +type didResolverMock struct { + DocResolution *diddoc.DocResolution + Err error +} + +func (m *didResolverMock) Resolve(did string) (*diddoc.DocResolution, error) { + return m.DocResolution, m.Err } diff --git a/pkg/openid4vp/openid4vp.go b/pkg/openid4vp/openid4vp.go index 234e7e39..3946985b 100644 --- a/pkg/openid4vp/openid4vp.go +++ b/pkg/openid4vp/openid4vp.go @@ -26,12 +26,6 @@ import ( "github.com/trustbloc/bbs-signature-go/bbs12381g2pub" diddoc "github.com/trustbloc/did-go/doc/did" vdrapi "github.com/trustbloc/did-go/vdr/api" - "github.com/trustbloc/kms-go/spi/kms" - wrapperapi "github.com/trustbloc/kms-go/wrapper/api" - "github.com/trustbloc/vc-go/dataintegrity" - "github.com/trustbloc/vc-go/dataintegrity/suite" - "github.com/trustbloc/vc-go/dataintegrity/suite/ecdsa2019" - "github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022" "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/proof/defaults" @@ -57,14 +51,6 @@ const ( sendAuthorizedResponseEventText = "Send authorized response via an HTTP POST request to %s" ) -type didResolverWrapper struct { - didResolver api.DIDResolver -} - -func (d *didResolverWrapper) Resolve(did string, _ ...vdrapi.DIDMethodOption) (*diddoc.DocResolution, error) { - return d.didResolver.Resolve(did) -} - type httpClient interface { Do(req *http.Request) (*http.Response, error) } @@ -85,7 +71,6 @@ type Interaction struct { didResolver api.DIDResolver crypto api.Crypto documentLoader ld.DocumentLoader - signer wrapperapi.KMSCryptoSigner } type authorizedResponse struct { @@ -105,7 +90,7 @@ func NewInteraction( documentLoader ld.DocumentLoader, opts ...Opt, ) (*Interaction, error) { - client, activityLogger, metricsLogger, signer := processOpts(opts) + client, activityLogger, metricsLogger := processOpts(opts) var ( authorizationRequestClientID string @@ -155,7 +140,6 @@ func NewInteraction( didResolver: didResolver, crypto: crypto, documentLoader: documentLoader, - signer: signer, }, nil } @@ -227,7 +211,6 @@ func (o *Interaction) Acknowledgment() *Acknowledgment { type presentOpts struct { ignoreConstraints bool - signer wrapperapi.KMSCryptoSigner attestationVPSigner api.JWTSigner attestationVC string @@ -264,7 +247,7 @@ func (o *Interaction) PresentCredential( customClaims CustomClaims, opts ...PresentOpt, ) error { - resolveOpts := &presentOpts{signer: o.signer} + resolveOpts := &presentOpts{} for _, opt := range opts { if opt != nil { @@ -515,8 +498,7 @@ func createAuthorizedResponse( didResolver, crypto, documentLoader, opts) default: return createAuthorizedResponseMultiCred(credentials, requestObject, customClaims, - didResolver, crypto, documentLoader, - opts.signer, opts) + didResolver, crypto, documentLoader, opts) } } @@ -574,12 +556,12 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom return nil, fmt.Errorf("presentation VC does not have a subject ID") } - signingVM, err := getSigningVM(did, didResolver) + assertionVM, err := getAssertionVM(did, didResolver) if err != nil { return nil, err } - jwtSigner, err := getHolderSigner(signingVM, crypto) + jwtSigner, err := getHolderSigner(assertionVM, crypto) if err != nil { return nil, err } @@ -613,51 +595,9 @@ func createAuthorizedResponseOneCred( //nolint:funlen,gocyclo // Unable to decom return nil, fmt.Errorf("sign vp token: %w", err) } case presexch.FormatLDPVP: - var proofAdded bool - - vpFormats := requestObject.ClientMetadata.VPFormats - - if vpFormats != nil && vpFormats.LdpVP != nil { - for _, proofType := range vpFormats.LdpVP.ProofType { - if proofType == ecdsa2019.SuiteTypeNew || proofType == eddsa2022.SuiteType { - if opts == nil || opts.signer == nil { - return nil, errors.New("signer is required for ldp_vp data integrity proof") - } - - err = addDataIntegrityProof( - fullVMID(did, signingVM.ID), - didResolver, - documentLoader, - opts.signer, - presentation, - proofType, - requestObject.Nonce, - requestObject.ClientID, - ) - if err != nil { - return nil, fmt.Errorf("failed to add data integrity proof to vp: %w", err) - } - - proofAdded = true - break - } - } - } - - if !proofAdded { - vpToken, err = createLdpVPToken(crypto, documentLoader, signingVM, requestObject, presentation) - if err != nil { - return nil, fmt.Errorf("create ldp vp token: %w", err) - } - } else { - var vpBytes []byte - - vpBytes, err = presentation.MarshalJSON() - if err != nil { - return nil, fmt.Errorf("marshal vp into vp token: %w", err) - } - - vpToken = string(vpBytes) + vpToken, err = createLdpVPToken(crypto, documentLoader, didResolver, did, assertionVM, requestObject, presentation) + if err != nil { + return nil, fmt.Errorf("create ldp vp token: %w", err) } default: return nil, fmt.Errorf("unsupported presentation exchange format: %s", vpFormat) @@ -697,7 +637,6 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec didResolver api.DIDResolver, crypto api.Crypto, documentLoader ld.DocumentLoader, - signer wrapperapi.KMSCryptoSigner, opts *presentOpts, ) (*authorizedResponse, error) { pd := requestObject.PresentationDefinition @@ -747,29 +686,12 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec return nil, fmt.Errorf("presentation VC does not have a subject ID: %w", e) } - signingVM, e := getSigningVM(holderDID, didResolver) + assertionVM, e := getAssertionVM(holderDID, didResolver) if e != nil { return nil, e } - // TODO: Refactor data integrity proof implementation - //if signer != nil { - // e = addDataIntegrityProof( - // fullVMID(holderDID, signingVM.ID), - // didResolver, - // documentLoader, - // signer, - // presentation, - // ecdsa2019.SuiteTypeNew, - // requestObject.Nonce, - // requestObject.ClientID, - // ) - // if e != nil { - // return nil, fmt.Errorf("failed to add data integrity proof to VP: %w", e) - // } - //} - - jwtSigner, e := getHolderSigner(signingVM, crypto) + jwtSigner, e := getHolderSigner(assertionVM, crypto) if e != nil { return nil, e } @@ -797,7 +719,7 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec return nil, fmt.Errorf("sign vp token: %w", err) } case presexch.FormatLDPVP: - vpToken, err = createLdpVPToken(crypto, documentLoader, signingVM, requestObject, presentation) + vpToken, err = createLdpVPToken(crypto, documentLoader, didResolver, holderDID, assertionVM, requestObject, presentation) if err != nil { return nil, fmt.Errorf("create ldp vp token: %w", err) } @@ -852,79 +774,33 @@ func createAuthorizedResponseMultiCred( //nolint:funlen,gocyclo // Unable to dec }, nil } -func addDataIntegrityProof(did string, didResolver api.DIDResolver, documentLoader ld.DocumentLoader, - signer wrapperapi.KMSCryptoSigner, presentation *verifiable.Presentation, - dataIntegritySuite, challenge, domain string, -) error { - var signerInitializer suite.SignerInitializer - - switch dataIntegritySuite { - case ecdsa2019.SuiteTypeNew: - signerInitializer = ecdsa2019.NewSignerInitializer( - &ecdsa2019.SignerInitializerOptions{ - LDDocumentLoader: documentLoader, - SignerGetter: ecdsa2019.WithKMSCryptoWrapper(signer), - }, - ) - case eddsa2022.SuiteType: - signerInitializer = eddsa2022.NewSignerInitializer( - &eddsa2022.SignerInitializerOptions{ - LDDocumentLoader: documentLoader, - SignerGetter: eddsa2022.WithKMSCryptoWrapper(signer), - }, - ) - default: - return fmt.Errorf("unsupported data integrity suite: %s", dataIntegritySuite) - } - - dataIntegritySigner, err := dataintegrity.NewSigner( - &dataintegrity.Options{ - DIDResolver: &didResolverWrapper{ - didResolver: didResolver, - }, - }, - signerInitializer) - if err != nil { - return err - } - - proofContext := &verifiable.DataIntegrityProofContext{ - SigningKeyID: did, - CryptoSuite: dataIntegritySuite, - ProofPurpose: "authentication", - Challenge: challenge, - Domain: domain, - } - - err = presentation.AddDataIntegrityProof(proofContext, dataIntegritySigner) - if err != nil { - return err - } - - return nil -} - func createLdpVPToken( crypto api.Crypto, documentLoader ld.DocumentLoader, - signingVM *diddoc.VerificationMethod, + didResolver api.DIDResolver, + did string, + assertionVM *diddoc.VerificationMethod, requestObject *requestObject, presentation *verifiable.Presentation, ) (string, error) { - ldProof, err := ldproof.New( + vpFormats := requestObject.ClientMetadata.VPFormats + + if vpFormats == nil || vpFormats.LdpVP == nil { + return "", errors.New("client does not support ldp_vp format") + } + + ldProof := ldproof.New( crypto, documentLoader, - requestObject.ClientMetadata.VPFormats.LdpVP, - kms.ECDSAP384IEEEP1363, // TODO: Use the key type passed from the caller + didResolver, ) - if err != nil { - return "", fmt.Errorf("create ld proof: %w", err) - } - err = ldProof.Add( + err := ldProof.Add( presentation, - ldproof.WithSigningVM(signingVM), - ldproof.WithNonce(requestObject.Nonce), + ldproof.WithLdpType(vpFormats.LdpVP), + ldproof.WithVerificationMethod(assertionVM), + ldproof.WithDID(did), + ldproof.WithChallenge(requestObject.Nonce), ldproof.WithDomain(requestObject.ClientID), ) if err != nil { @@ -1043,10 +919,10 @@ func signToken(claims interface{}, signer api.JWTSigner) (string, error) { return tokenBytes, nil } -func getSigningVM(holderDID string, didResolver api.DIDResolver) (*diddoc.VerificationMethod, error) { +func getAssertionVM(holderDID string, didResolver api.DIDResolver) (*diddoc.VerificationMethod, error) { docRes, err := didResolver.Resolve(holderDID) if err != nil { - return nil, fmt.Errorf("resolve holder DID for signing: %w", err) + return nil, fmt.Errorf("resolve holder DID for assertion method: %w", err) } verificationMethods := docRes.DIDDocument.VerificationMethods(diddoc.AssertionMethod) @@ -1055,13 +931,13 @@ func getSigningVM(holderDID string, didResolver api.DIDResolver) (*diddoc.Verifi return nil, fmt.Errorf("holder DID has no assertion method for signing") } - signingVM := verificationMethods[diddoc.AssertionMethod][0].VerificationMethod + assertionVM := verificationMethods[diddoc.AssertionMethod][0].VerificationMethod - return &signingVM, nil + return &assertionVM, nil } -func getHolderSigner(signingVM *diddoc.VerificationMethod, crypto api.Crypto) (api.JWTSigner, error) { - return common.NewJWSSigner(models.VerificationMethodFromDoc(signingVM), crypto) +func getHolderSigner(vm *diddoc.VerificationMethod, crypto api.Crypto) (api.JWTSigner, error) { + return common.NewJWSSigner(models.VerificationMethodFromDoc(vm), crypto) } func getSubjectID(vc *verifiable.Credential) (string, error) { @@ -1087,22 +963,6 @@ func pickRandomElement(list []string) (string, error) { return list[idx.Int64()], nil } -func fullVMID(did, vmID string) string { - if vmID == "" { - return did - } - - if vmID[0] == '#' { - return did + vmID - } - - if strings.HasPrefix(vmID, "did:") { - return vmID - } - - return did + "#" + vmID -} - type resolverAdapter struct { didResolver api.DIDResolver } diff --git a/pkg/openid4vp/openid4vp_test.go b/pkg/openid4vp/openid4vp_test.go index b31a3a03..611eb754 100644 --- a/pkg/openid4vp/openid4vp_test.go +++ b/pkg/openid4vp/openid4vp_test.go @@ -35,7 +35,6 @@ import ( "github.com/trustbloc/wallet-sdk/pkg/api" "github.com/trustbloc/wallet-sdk/pkg/common" "github.com/trustbloc/wallet-sdk/pkg/internal/mock" - "github.com/trustbloc/wallet-sdk/pkg/localkms" "github.com/trustbloc/wallet-sdk/pkg/models" "github.com/trustbloc/wallet-sdk/pkg/walleterror" ) @@ -519,6 +518,52 @@ func TestOpenID4VP_PresentCredential(t *testing.T) { require.NotEmpty(t, data["presentation_submission"]) }) + t.Run("Success - with ldp_vp, multi cred", func(t *testing.T) { + mockHTTPClient := &mock.HTTPClientMock{ + StatusCode: 200, + } + + crypto := &cryptoMock{SignVal: []byte(testSignature)} + + var ldpCredentials []*verifiable.Credential + + for _, cred := range credentials { + if cred.IsJWT() { + continue + } + + ldpCredentials = append(ldpCredentials, cred) + } + + interaction, err := NewInteraction( + requestObjectJWTLdpVP, + &jwtSignatureVerifierMock{}, + &didResolverMock{ResolveValue: mockResolution(t, mockDID, true)}, + crypto, + lddl, + WithHTTPClient(mockHTTPClient), + ) + require.NoError(t, err) + + query := interaction.GetQuery() + require.NotNil(t, query) + + err = interaction.PresentCredential(ldpCredentials, CustomClaims{}) + require.NoError(t, err) + + data, err := url.ParseQuery(string(mockHTTPClient.SentBody)) + require.NoError(t, err) + + require.Contains(t, data, "id_token") + require.NotEmpty(t, data["id_token"]) + + require.Contains(t, data, "vp_token") + require.NotEmpty(t, data["vp_token"]) + + require.Contains(t, data, "presentation_submission") + require.NotEmpty(t, data["presentation_submission"]) + }) + t.Run("Check custom claims", func(t *testing.T) { response, err := createAuthorizedResponse( singleCred, @@ -745,45 +790,17 @@ func TestOpenID4VP_PresentCredential(t *testing.T) { } t.Run("single credential", func(t *testing.T) { - localKMS, err := localkms.NewLocalKMS(localkms.Config{ - Storage: localkms.NewMemKMSStore(), - }) - require.NoError(t, err) - - signer, err := localKMS.AriesSuite.KMSCryptoSigner() - require.NoError(t, err) - - _, err = createAuthorizedResponse( + _, err := createAuthorizedResponse( singleCred, reqObject, CustomClaims{}, &didResolverMock{ResolveValue: mockDoc}, &cryptoMock{}, lddl, - &presentOpts{signer: signer}, + &presentOpts{}, ) - require.ErrorContains(t, err, "no supported linked data proof found") + require.ErrorContains(t, err, "no supported ldp types found") }) - //t.Run("multiple credentials", func(t *testing.T) { - // localKMS, err := localkms.NewLocalKMS(localkms.Config{ - // Storage: localkms.NewMemKMSStore(), - // }) - // require.NoError(t, err) - // - // signer, err := localKMS.AriesSuite.KMSCryptoSigner() - // require.NoError(t, err) - // - // _, err = createAuthorizedResponse( - // credentials, - // reqObject, - // CustomClaims{}, - // &didResolverMock{ResolveValue: mockDoc}, - // &cryptoMock{}, - // lddl, - // &presentOpts{signer: signer}, - // ) - // require.ErrorContains(t, err, "failed to add data integrity proof to VP") - //}) }) t.Run("fail to send authorized response", func(t *testing.T) { diff --git a/pkg/openid4vp/opts.go b/pkg/openid4vp/opts.go index 3e60dbb7..086b7ea0 100644 --- a/pkg/openid4vp/opts.go +++ b/pkg/openid4vp/opts.go @@ -54,19 +54,10 @@ func WithMetricsLogger(metricsLogger api.MetricsLogger) Opt { } } -// WithDIProofs enables the adding of data integrity proofs to presentations sent to the verifier. It requires -// a signer and a KMS to be passed in. -func WithDIProofs(signer wrapperapi.KMSCryptoSigner) Opt { - return func(opts *opts) { - opts.signer = signer - } -} - func processOpts(options []Opt) ( httpClient, api.ActivityLogger, api.MetricsLogger, - wrapperapi.KMSCryptoSigner, ) { opts := mergeOpts(options) @@ -82,7 +73,7 @@ func processOpts(options []Opt) ( opts.metricsLogger = noopmetricslogger.NewMetricsLogger() } - return opts.httpClient, opts.activityLogger, opts.metricsLogger, opts.signer + return opts.httpClient, opts.activityLogger, opts.metricsLogger } func mergeOpts(options []Opt) *opts { diff --git a/pkg/openid4vp/request_object.go b/pkg/openid4vp/request_object.go index 1f8c0af9..84cdec7b 100644 --- a/pkg/openid4vp/request_object.go +++ b/pkg/openid4vp/request_object.go @@ -31,11 +31,11 @@ type requestObject struct { ClientMetadata clientMetadata `json:"client_metadata"` PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition"` - // Deprecated: Use response_uri instead. + // Deprecated: Deprecated in OID4VP-ID2. Use response_uri instead. RedirectURI string `json:"redirect_uri"` - // Deprecated: Use client_metadata instead. + // Deprecated: Deprecated in OID4VP-ID2. Use client_metadata instead. Registration requestObjectRegistration `json:"registration"` - // Deprecated: Use top-level "presentation_definition" instead. + // Deprecated: Deprecated in OID4VP-ID2. Use top-level "presentation_definition" instead. Claims requestObjectClaims `json:"claims"` } diff --git a/test/integration/fixtures/.env b/test/integration/fixtures/.env index 2249ee52..02a62096 100644 --- a/test/integration/fixtures/.env +++ b/test/integration/fixtures/.env @@ -6,7 +6,7 @@ # vc services VC_REST_IMAGE=ghcr.io/trustbloc-cicd/vc-server -VC_REST_IMAGE_TAG=v1.11.1-snapshot-4106ae8 +VC_REST_IMAGE_TAG=v1.12.1-snapshot-8d5b3bf # Remote JSON-LD context provider CONTEXT_PROVIDER_URL=https://file-server.trustbloc.local:10096/ld-contexts.json