diff --git a/pkg/doc/presexch/definition.go b/pkg/doc/presexch/definition.go index 524ef793d..485459d9d 100644 --- a/pkg/doc/presexch/definition.go +++ b/pkg/doc/presexch/definition.go @@ -23,6 +23,7 @@ import ( "github.com/xeipuuv/gojsonschema" "github.com/hyperledger/aries-framework-go/pkg/common/log" + "github.com/hyperledger/aries-framework-go/pkg/doc/jose" "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" ) @@ -1012,7 +1013,7 @@ func filterFormat(format *Format, credentials []*verifiable.Credential) (string, ) if credential.JWT != "" { - pJWT, err := jwt.Parse(credential.JWT) + pJWT, err := jwt.Parse(credential.JWT, jwt.WithSignatureVerifier(&noVerifier{})) if err != nil { logger.Warnf("unmarshal credential error: %w", err) @@ -1062,6 +1063,14 @@ func filterFormat(format *Format, credentials []*verifiable.Credential) (string, return "", nil } +// noVerifier is used when no JWT signature verification is needed. +// To be used with precaution. +type noVerifier struct{} + +func (v noVerifier) Verify(_ jose.Headers, _, _, _ []byte) error { + return nil +} + func algMatch(credAlg string, jwtType *JwtType) bool { if jwtType == nil { return false diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index 77438da9c..4f79cbde0 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -23,14 +23,21 @@ import ( "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" "github.com/hyperledger/aries-framework-go/pkg/doc/ld" . "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignature2020" "github.com/hyperledger/aries-framework-go/pkg/doc/util" + "github.com/hyperledger/aries-framework-go/pkg/doc/util/signature" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" "github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil" + "github.com/hyperledger/aries-framework-go/pkg/kms" + "github.com/hyperledger/aries-framework-go/pkg/kms/localkms" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" + "github.com/hyperledger/aries-framework-go/pkg/mock/storage" + "github.com/hyperledger/aries-framework-go/pkg/secretlock/noop" ) const errMsgSchema = "credentials do not satisfy requirements" @@ -79,7 +86,66 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) t.Run("Checks submission requirements", func(t *testing.T) { - issuerID := uuid.New().String() + issuerID := "did:example:76e12ec712ebc6f1c221ebfeb1f" + + vc1JWT := &verifiable.Credential{ + Issued: util.NewTime(time.Now()), + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: "http://example.edu/credentials/1872", + Subject: []verifiable.Subject{{ID: issuerID}}, + Issuer: verifiable.Issuer{ID: issuerID}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Travis", + "age": 17, + }, + // vc as jwt does not use proof, do not set it here. + } + + ed25519Signer, err := newCryptoSigner(kms.ED25519Type) + require.NoError(t, err) + + vc1JWT.JWT = createEdDSAJWS(t, vc1JWT, ed25519Signer, "76e12ec712ebc6f1c221ebfeb1f", true) + + candidateVCs := []*verifiable.Credential{ + vc1JWT, + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: "http://example.edu/credentials/1872", + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + }, + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: "http://example.edu/credentials/1872", + Subject: []verifiable.Subject{{ID: issuerID}}, + Issuer: verifiable.Issuer{ID: issuerID}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Travis", + "age": 17, + }, + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: "http://example.edu/credentials/1872", + Subject: []verifiable.Subject{{ID: issuerID}}, + Issuer: verifiable.Issuer{ID: issuerID}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Travis", + "age": 2, + }, + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + } tests := []struct { name string @@ -107,6 +173,27 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { LdpVC: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, }, }, + { + name: "test JWT format", + format: FormatJWT, + vFormat: &Format{ + Jwt: &JwtType{Alg: []string{"EdDSA"}}, + }, + }, + { + name: "test JWTVC format", + format: FormatJWTVC, + vFormat: &Format{ + JwtVC: &JwtType{Alg: []string{"EdDSA"}}, + }, + }, + { + name: "test JWTVP format", + format: FormatJWTVP, + vFormat: &Format{ + JwtVP: &JwtType{Alg: []string{"EdDSA"}}, + }, + }, } for _, tc := range tests { @@ -205,46 +292,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Format: tc.vFormat, } - vp, err := pd.CreateVP([]*verifiable.Credential{ - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - }, - // since Format in InputDescriptor works only with proofs, need to add it in the vc. - Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, - }, - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - Subject: []verifiable.Subject{{ID: issuerID}}, - Issuer: verifiable.Issuer{ID: issuerID}, - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - "last_name": "Travis", - "age": 17, - }, - // since Format in InputDescriptor works only with proofs, need to add it in the vc. - Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, - }, - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - Subject: []verifiable.Subject{{ID: issuerID}}, - Issuer: verifiable.Issuer{ID: issuerID}, - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - "last_name": "Travis", - "age": 2, - }, - // since Format in InputDescriptor works only with proofs, need to add it in the vc. - Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, - }, - }, lddl) + vp, err := pd.CreateVP(candidateVCs, lddl) require.NoError(t, err) require.NotNil(t, vp) @@ -1473,6 +1521,41 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) } +func createEdDSAJWS(t *testing.T, cred *verifiable.Credential, signer verifiable.Signer, + keyID string, minimize bool) string { + t.Helper() + + jwtClaims, err := cred.JWTClaims(minimize) + require.NoError(t, err) + vcJWT, err := jwtClaims.MarshalJWS(verifiable.EdDSA, signer, cred.Issuer.ID+"#keys-"+keyID) + require.NoError(t, err) + + return vcJWT +} + +func createKMS() (*localkms.LocalKMS, error) { + p, err := mockkms.NewProviderForKMS(storage.NewMockStoreProvider(), &noop.NoLock{}) + if err != nil { + return nil, err + } + + return localkms.New("local-lock://custom/master/key/", p) +} + +func newCryptoSigner(keyType kms.KeyType) (signature.Signer, error) { + localKMS, err := createKMS() + if err != nil { + return nil, err + } + + tinkCrypto, err := tinkcrypto.New() + if err != nil { + return nil, err + } + + return signature.NewCryptoSigner(tinkCrypto, localKMS, keyType) +} + func checkSubmission(t *testing.T, vp *verifiable.Presentation, pd *PresentationDefinition) { t.Helper() diff --git a/pkg/doc/verifiable/presentation.go b/pkg/doc/verifiable/presentation.go index 5c06dac7e..440b11f8c 100644 --- a/pkg/doc/verifiable/presentation.go +++ b/pkg/doc/verifiable/presentation.go @@ -500,7 +500,11 @@ func decodeCredentials(rawCred interface{}, opts *presentationOpts) ([]interface return nil, fmt.Errorf("decode credential of presentation: %w", err) } - return credDecoded, nil + vc, err := ParseCredential(credDecoded, + WithJSONLDDocumentLoader(opts.jsonldCredentialOpts.jsonldDocumentLoader), + WithDisabledProofCheck()) + + return vc, err } // return credential in a structure format as is diff --git a/pkg/doc/verifiable/presentation_test.go b/pkg/doc/verifiable/presentation_test.go index 28211d560..17043a083 100644 --- a/pkg/doc/verifiable/presentation_test.go +++ b/pkg/doc/verifiable/presentation_test.go @@ -514,6 +514,7 @@ func TestPresentation_decodeCredentials(t *testing.T) { // single credential - JWS opts := defaultPresentationOpts() + opts.jsonldCredentialOpts.jsonldDocumentLoader = createTestDocumentLoader(t) opts.publicKeyFetcher = SingleKey(signer.PublicKeyBytes(), kms.ED25519) dCreds, err := decodeCredentials(jws, opts) r.NoError(err) diff --git a/scripts/check_go_integration.sh b/scripts/check_go_integration.sh index 406fd47a3..7b5904cad 100755 --- a/scripts/check_go_integration.sh +++ b/scripts/check_go_integration.sh @@ -10,7 +10,8 @@ echo "Running aries-framework-go integration tests..." PWD=`pwd` cd test/bdd go test -count=1 -v -cover . -p 1 -timeout=45m -race -go test -count=1 -v -cover . -p 1 -timeout=30m -race -run presentproof,present_proof_controller,issue_credential,issue_credential_controller,webkms,waci_issuance,verifiable,verifiable_jwt +go test -count=1 -v -cover . -p 1 -timeout=30m -race -run present_proof_controller,issue_credential,issue_credential_controller,webkms,waci_issuance,verifiable,verifiable_jwt +go test -count=1 -v -cover . -p 1 -timeout=30m -race -run presentproof go test -count=1 -v -cover . -p 1 -timeout=30m -race -run didcomm_remote_crypto,outofbandv2 go test -count=1 -v -cover . -p 1 -timeout=45m -race -run outofband DEFAULT_KEY_TYPE="ecdsap256ieee1363" DEFAULT_KEY_AGREEMENT_TYPE="p256kw" go test -count=1 -v -cover . -p 1 -timeout=10m -race -run didcommv2 diff --git a/test/bdd/features/presentproof.feature b/test/bdd/features/presentproof.feature index 23d88430e..95632e301 100644 --- a/test/bdd/features/presentproof.feature +++ b/test/bdd/features/presentproof.feature @@ -7,21 +7,39 @@ @presentproof Feature: Present Proof protocol @begin_with_request_presentation - Scenario: The Verifier begins with a request presentation - Given "Alice" exchange DIDs with "Bob" - Then "Alice" sends a request presentation to the "Bob" - And "Bob" accepts a request and sends a presentation to the "Alice" - And "Alice" accepts a presentation with name "license" - And "Alice" checks that presentation is being stored under "license" name - Then "Bob" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Verifier begins with a request presentation + Given "Alice-" exchange DIDs with "Bob-" + Then "Alice-" sends a request presentation to the "Bob-" + And "Bob-" accepts a request and sends a presentation to the "Alice-" with format "" + And "Alice-" accepts a presentation with name "license" + And "Alice-" checks that presentation is being stored under "license" name + Then "Bob-" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_request_presentation_v3 - Scenario: The Verifier begins with a request presentation v3 - Given "Jonny" exchange DIDs V2 with "Robert" - Then "Jonny" sends a request presentation v3 to the "Robert" - And "Robert" accepts a request and sends a presentation v3 to the "Jonny" - And "Jonny" accepts a presentation with name "license" - And "Jonny" checks that presentation is being stored under "license" name - Then "Robert" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Verifier begins with a request presentation v3 + Given "Jonny-" exchange DIDs V2 with "Robert-" + Then "Jonny-" sends a request presentation v3 to the "Robert-" + And "Robert-" accepts a request and sends a presentation v3 to the "Jonny-" with format "" + And "Jonny-" accepts a presentation with name "license" + And "Jonny-" checks that presentation is being stored under "license" name + Then "Robert-" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_request_presentation_bbs Scenario: The Verifier begins with a request presentation (BBS+) Given "Julia" exchange DIDs with "Max" @@ -55,23 +73,41 @@ Feature: Present Proof protocol And "Scott" checks that presentation is being stored under "bbs-license" name and has "BbsBlsSignature2020" proof Then "Patrick" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" @decline_presentation - Scenario: The Verifier declines presentation - Given "Thomas" exchange DIDs with "Paul" - Then "Thomas" sends a request presentation to the "Paul" - And "Paul" accepts a request and sends a presentation to the "Thomas" - And "Thomas" declines presentation - Then "Paul" receives problem report message (Present Proof) - Then "Paul" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,abandoned,abandoned" - And "Thomas" checks the history of events "request-sent,request-sent,abandoned,abandoned" + Scenario Outline: The Verifier declines presentation + Given "Thomas-" exchange DIDs with "Paul-" + Then "Thomas-" sends a request presentation to the "Paul-" + And "Paul-" accepts a request and sends a presentation to the "Thomas-" with format "" + And "Thomas-" declines presentation + Then "Paul-" receives problem report message (Present Proof) + Then "Paul-" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,abandoned,abandoned" + And "Thomas-" checks the history of events "request-sent,request-sent,abandoned,abandoned" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @decline_presentation_v3 - Scenario: The Verifier declines presentation v3 - Given "Dennis" exchange DIDs V2 with "Nathan" - Then "Dennis" sends a request presentation v3 to the "Nathan" - And "Nathan" accepts a request and sends a presentation v3 to the "Dennis" - And "Dennis" declines presentation - Then "Nathan" receives problem report message (Present Proof) - Then "Nathan" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,abandoned,abandoned" - And "Dennis" checks the history of events "request-sent,request-sent,abandoned,abandoned" + Scenario Outline: The Verifier declines presentation v3 + Given "Dennis-" exchange DIDs V2 with "Nathan-" + Then "Dennis-" sends a request presentation v3 to the "Nathan-" + And "Nathan-" accepts a request and sends a presentation v3 to the "Dennis-" with format "" + And "Dennis-" declines presentation + Then "Nathan-" receives problem report message (Present Proof) + Then "Nathan-" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,abandoned,abandoned" + And "Dennis-" checks the history of events "request-sent,request-sent,abandoned,abandoned" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @decline_request_presentation Scenario: The Prover declines a request presentation Given "Liam" exchange DIDs with "Samuel" @@ -89,23 +125,41 @@ Feature: Present Proof protocol Then "Adam" checks the history of events "abandoned,abandoned" And "Tyler" checks the history of events "request-sent,request-sent,abandoned,abandoned" @begin_with_propose_presentation - Scenario: The Prover begins with a proposal - Given "Carol" exchange DIDs with "Andrew" - Then "Carol" sends a propose presentation to the "Andrew" - And "Andrew" accepts a proposal and sends a request to the Prover - And "Carol" accepts a request and sends a presentation to the "Andrew" - And "Andrew" accepts a presentation with name "passport" - And "Andrew" checks that presentation is being stored under "passport" name - Then "Carol" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Prover begins with a proposal + Given "Carol-" exchange DIDs with "Andrew-" + Then "Carol-" sends a propose presentation to the "Andrew-" + And "Andrew-" accepts a proposal and sends a request to the Prover + And "Carol-" accepts a request and sends a presentation to the "Andrew-" with format "" + And "Andrew-" accepts a presentation with name "passport" + And "Andrew-" checks that presentation is being stored under "passport" name + Then "Carol-" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_propose_presentation_v3 - Scenario: The Prover begins with a proposal v3 - Given "Douglas" exchange DIDs V2 with "Peter" - Then "Douglas" sends a propose presentation v3 to the "Peter" - And "Peter" accepts a proposal and sends a request v3 to the Prover - And "Douglas" accepts a request and sends a presentation v3 to the "Peter" - And "Peter" accepts a presentation with name "passport" - And "Peter" checks that presentation is being stored under "passport" name - Then "Douglas" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Prover begins with a proposal v3 + Given "Douglas-" exchange DIDs V2 with "Peter-" + Then "Douglas-" sends a propose presentation v3 to the "Peter-" + And "Peter-" accepts a proposal and sends a request v3 to the Prover + And "Douglas-" accepts a request and sends a presentation v3 to the "Peter-" with format "" + And "Peter-" accepts a presentation with name "passport" + And "Peter-" checks that presentation is being stored under "passport" name + Then "Douglas-" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @decline_propose_presentation Scenario: The Verifier declines a propose presentation Given "Michael" exchange DIDs with "David" @@ -123,63 +177,117 @@ Feature: Present Proof protocol Then "Harold" checks the history of events "proposal-sent,proposal-sent,abandoned,abandoned" And "Roger" checks the history of events "abandoned,abandoned" @begin_with_request_presentation_negotiation - Scenario: The Verifier begins with a request presentation (negotiation) - Given "William" exchange DIDs with "Felix" - Then "William" sends a request presentation to the "Felix" - Then "Felix" negotiates about the request presentation with a proposal - And "William" accepts a proposal and sends a request to the Prover - And "Felix" accepts a request and sends a presentation to the "William" - And "William" accepts a presentation with name "passport" - And "William" checks that presentation is being stored under "passport" name - Then "Felix" checks the history of events "request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Verifier begins with a request presentation (negotiation) + Given "William-" exchange DIDs with "Felix-" + Then "William-" sends a request presentation to the "Felix-" + Then "Felix-" negotiates about the request presentation with a proposal + And "William-" accepts a proposal and sends a request to the Prover + And "Felix-" accepts a request and sends a presentation to the "William-" with format "" + And "William-" accepts a presentation with name "passport" + And "William-" checks that presentation is being stored under "passport" name + Then "Felix-" checks the history of events "request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_request_presentation_negotiation_v3 - Scenario: The Verifier begins with a request presentation v3 (negotiation) - Given "Sean" exchange DIDs V2 with "Joe" - Then "Sean" sends a request presentation v3 to the "Joe" - Then "Joe" negotiates about the request presentation v3 with a proposal - And "Sean" accepts a proposal and sends a request v3 to the Prover - And "Joe" accepts a request and sends a presentation v3 to the "Sean" - And "Sean" accepts a presentation with name "passport" - And "Sean" checks that presentation is being stored under "passport" name - Then "Joe" checks the history of events "request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Verifier begins with a request presentation v3 (negotiation) + Given "Sean-" exchange DIDs V2 with "Joe-" + Then "Sean-" sends a request presentation v3 to the "Joe-" + Then "Joe-" negotiates about the request presentation v3 with a proposal + And "Sean-" accepts a proposal and sends a request v3 to the Prover + And "Joe-" accepts a request and sends a presentation v3 to the "Sean-" with format "" + And "Sean-" accepts a presentation with name "passport" + And "Sean-" checks that presentation is being stored under "passport" name + Then "Joe-" checks the history of events "request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_propose_presentation_negotiation - Scenario: The Prover begins with a proposal (negotiation) - Given "Jason" exchange DIDs with "Jesse" - Then "Jason" sends a propose presentation to the "Jesse" - And "Jesse" accepts a proposal and sends a request to the Prover - Then "Jason" negotiates about the request presentation with a proposal - And "Jesse" accepts a proposal and sends a request to the Prover - And "Jason" accepts a request and sends a presentation to the "Jesse" - And "Jesse" accepts a presentation with name "bachelors degree" - And "Jesse" checks that presentation is being stored under "bachelors degree" name - Then "Jason" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Prover begins with a proposal (negotiation) + Given "Jason-" exchange DIDs with "Jesse-" + Then "Jason-" sends a propose presentation to the "Jesse-" + And "Jesse-" accepts a proposal and sends a request to the Prover + Then "Jason-" negotiates about the request presentation with a proposal + And "Jesse-" accepts a proposal and sends a request to the Prover + And "Jason-" accepts a request and sends a presentation to the "Jesse-" with format "" + And "Jesse-" accepts a presentation with name "bachelors degree" + And "Jesse-" checks that presentation is being stored under "bachelors degree" name + Then "Jason-" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @begin_with_propose_presentation_negotiation_v3 - Scenario: The Prover begins with a proposal v3 (negotiation) - Given "Arthur" exchange DIDs V2 with "Logan" - Then "Arthur" sends a propose presentation v3 to the "Logan" - And "Logan" accepts a proposal and sends a request v3 to the Prover - Then "Arthur" negotiates about the request presentation v3 with a proposal - And "Logan" accepts a proposal and sends a request v3 to the Prover - And "Arthur" accepts a request and sends a presentation v3 to the "Logan" - And "Logan" accepts a presentation with name "bachelors degree" - And "Logan" checks that presentation is being stored under "bachelors degree" name - Then "Arthur" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Prover begins with a proposal v3 (negotiation) + Given "Arthur-" exchange DIDs V2 with "Logan-" + Then "Arthur-" sends a propose presentation v3 to the "Logan-" + And "Logan-" accepts a proposal and sends a request v3 to the Prover + Then "Arthur-" negotiates about the request presentation v3 with a proposal + And "Logan-" accepts a proposal and sends a request v3 to the Prover + And "Arthur-" accepts a request and sends a presentation v3 to the "Logan-" with format "" + And "Logan-" accepts a presentation with name "bachelors degree" + And "Logan-" checks that presentation is being stored under "bachelors degree" name + Then "Arthur-" checks the history of events "proposal-sent,proposal-sent,request-received,request-received,proposal-sent,proposal-sent,request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @share_redirect_presentation_ack - Scenario: The Verifier begins with a request presentation (Web Redirect) - Given "Dan" exchange DIDs with "Tracy" - Then "Dan" sends a request presentation to the "Tracy" - And "Tracy" accepts a request and sends a presentation to the "Dan" - And "Dan" accepts a presentation with name "license" and requests redirect to "http://example.com/success" - And "Dan" checks that presentation is being stored under "license" name - Then "Tracy" receives present proof event "done" with status "OK" and redirect "http://example.com/success" + Scenario Outline: The Verifier begins with a request presentation (Web Redirect) + Given "Dan-" exchange DIDs with "Tracy-" + Then "Dan-" sends a request presentation to the "Tracy-" + And "Tracy-" accepts a request and sends a presentation to the "Dan-" with format "" + And "Dan-" accepts a presentation with name "license" and requests redirect to "http://example.com/success" + And "Dan-" checks that presentation is being stored under "license" name + Then "Tracy-" receives present proof event "done" with status "OK" and redirect "http://example.com/success" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @share_redirect_presentation_problem_report_1 - Scenario: The Verifier declines presentation (Web Redirect) - Given "Tim" exchange DIDs with "Wendy" - Then "Tim" sends a request presentation to the "Wendy" - And "Wendy" accepts a request and sends a presentation to the "Tim" - And "Tim" declines presentation and requests redirect to "http://example.com/error" - Then "Wendy" receives problem report message (Present Proof) - Then "Wendy" receives present proof event "abandoned" with status "FAIL" and redirect "http://example.com/error" + Scenario Outline: The Verifier declines presentation (Web Redirect) + Given "Tim-" exchange DIDs with "Wendy-" + Then "Tim-" sends a request presentation to the "Wendy-" + And "Wendy-" accepts a request and sends a presentation to the "Tim-" with format "" + And "Tim-" declines presentation and requests redirect to "http://example.com/error" + Then "Wendy-" receives problem report message (Present Proof) + Then "Wendy-" receives present proof event "abandoned" with status "FAIL" and redirect "http://example.com/error" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | + @share_redirect_presentation_problem_report_2 Scenario: The Verifier declines presentation (Web Redirect) Given "Tiana" exchange DIDs with "Gracia" @@ -188,16 +296,24 @@ Feature: Present Proof protocol Then "Gracia" receives problem report message (Present Proof) Then "Gracia" receives present proof event "abandoned" with status "FAIL" and redirect "http://example.com/error" @ppv3_no_didex - Scenario: The Verifier begins with a request presentation v3 without didexchange - Given "Clarence" agent is running on "localhost" port "random" with "http" as the transport provider and "sidetree=${SIDETREE_URL},DIDCommV2" flags - And "Florence" agent is running on "localhost" port "random" with "http" as the transport provider and "sidetree=${SIDETREE_URL},DIDCommV2" flags - Given "Clarence" creates public DID for did method "sidetree" - And "Florence" creates public DID for did method "sidetree" - Then "Clarence" waits for public did to become available in sidetree for up to 10 seconds - And "Florence" waits for public did to become available in sidetree for up to 10 seconds - And "Clarence" and "Florence" have a DIDComm v2 connection - Then "Clarence" sends a request presentation v3 to the "Florence" - And "Florence" accepts a request and sends a presentation v3 to the "Clarence" - And "Clarence" accepts a presentation with name "license" - And "Clarence" checks that presentation is being stored under "license" name - Then "Florence" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Scenario Outline: The Verifier begins with a request presentation v3 without didexchange + Given "Clarence-" agent is running on "localhost" port "random" with "http" as the transport provider and "sidetree=${SIDETREE_URL},DIDCommV2" flags + And "Florence-" agent is running on "localhost" port "random" with "http" as the transport provider and "sidetree=${SIDETREE_URL},DIDCommV2" flags + Given "Clarence-" creates public DID for did method "sidetree" + And "Florence-" creates public DID for did method "sidetree" + Then "Clarence-" waits for public did to become available in sidetree for up to 10 seconds + And "Florence-" waits for public did to become available in sidetree for up to 10 seconds + And "Clarence-" and "Florence-" have a DIDComm v2 connection + Then "Clarence-" sends a request presentation v3 to the "Florence-" + And "Florence-" accepts a request and sends a presentation v3 to the "Clarence-" with format "" + And "Clarence-" accepts a presentation with name "license" + And "Clarence-" checks that presentation is being stored under "license" name + Then "Florence-" checks the history of events "request-received,request-received,presentation-sent,presentation-sent,done,done" + Examples: + | suffix | format | + | 1 | jwt | + | 2 | jwt_vc | + | 3 | jwt_vp | + | 4 | ldp | + | 5 | ldp_vc | + | 6 | ldp_vp | diff --git a/test/bdd/pkg/presentproof/presentproof_sdk_steps.go b/test/bdd/pkg/presentproof/presentproof_sdk_steps.go index 34c29016d..ed8dddf3f 100644 --- a/test/bdd/pkg/presentproof/presentproof_sdk_steps.go +++ b/test/bdd/pkg/presentproof/presentproof_sdk_steps.go @@ -56,7 +56,14 @@ const vpStrFromWallet = ` "UniversityDegreeCredential" ], "verifiableCredential": [ - { + %s + ], + "holder": "%s" +} +` + +const vcStrFromWallet = ` +{ "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" @@ -84,9 +91,6 @@ const vpStrFromWallet = ` "UniversityDegreeCredential" ] } - ], - "holder": "%s" -} ` const ( @@ -128,8 +132,10 @@ func (a *SDKSteps) RegisterSteps(s *godog.Suite) { s.Step(`^"([^"]*)" sends a propose presentation v3 to the "([^"]*)"$`, a.sendProposePresentationV3) s.Step(`^"([^"]*)" negotiates about the request presentation with a proposal$`, a.negotiateRequestPresentation) s.Step(`^"([^"]*)" negotiates about the request presentation v3 with a proposal$`, a.negotiateRequestPresentationV3) - s.Step(`^"([^"]*)" accepts a request and sends a presentation to the "([^"]*)"$`, a.acceptRequestPresentation) - s.Step(`^"([^"]*)" accepts a request and sends a presentation v3 to the "([^"]*)"$`, a.acceptRequestPresentationV3) + s.Step(`^"([^"]*)" accepts a request and sends a presentation to the "([^"]*)" with format "([^"]*)"$`, + a.acceptRequestPresentation) + s.Step(`^"([^"]*)" accepts a request and sends a presentation v3 to the "([^"]*)" with format "([^"]*)"$`, + a.acceptRequestPresentationV3) s.Step(`^"([^"]*)" accepts a request and sends credentials with BBS to the "([^"]*)" and proof "([^"]*)"$`, a.acceptRequestPresentationBBS) s.Step(`^"([^"]*)" accepts a request v3 and sends credentials with BBS to the "([^"]*)" and proof "([^"]*)"$`, @@ -383,7 +389,8 @@ func (a *SDKSteps) getActionID(agent string) (string, error) { } } -func (a *SDKSteps) acceptRequestPresentation(prover, verifier string) error { +//nolint:funlen,gocyclo +func (a *SDKSteps) acceptRequestPresentation(prover, verifier, format string) error { PIID, err := a.getActionID(prover) if err != nil { return err @@ -399,19 +406,6 @@ func (a *SDKSteps) acceptRequestPresentation(prover, verifier string) error { return err } - vp, err := verifiable.ParsePresentation( - []byte(fmt.Sprintf(vpStrFromWallet, conn.MyDID, conn.MyDID)), - verifiable.WithPresJSONLDDocumentLoader(loader), - verifiable.WithPresDisabledProofCheck()) - if err != nil { - return fmt.Errorf("failed to decode VP JSON: %w", err) - } - - jwtClaims, err := vp.JWTClaims([]string{conn.MyDID}, true) - if err != nil { - return fmt.Errorf("failed to create JWT claims of VP: %w", err) - } - doc, err := a.bddContext.AgentCtx[prover].VDRegistry().Resolve(conn.MyDID) if err != nil { return err @@ -426,7 +420,67 @@ func (a *SDKSteps) acceptRequestPresentation(prover, verifier string) error { return fmt.Errorf("failed to key kid for kms: %w", err) } - vpJWS, err := jwtClaims.MarshalJWS(verifiable.EdDSA, newSigner(km, cr, kid), pubKey.ID) + ed25519Signer := newSigner(km, cr, kid) + + vc := fmt.Sprintf(vcStrFromWallet, conn.MyDID) + + switch format { + case presexch.FormatJWTVC, presexch.FormatJWTVP, presexch.FormatJWT: + jwtVC := &verifiable.Credential{ + Issued: util.NewTime(time.Date(2010, 01, 01, 19, 23, 24, 0, time.UTC)), //nolint:gocritic + Context: []string{verifiable.ContextURI, "https://www.w3.org/2018/credentials/examples/v1"}, + Types: []string{verifiable.VCType, "UniversityDegreeCredential"}, + ID: "http://example.edu/credentials/1872", + Subject: []verifiable.Subject{{ + ID: conn.MyDID, + CustomFields: verifiable.CustomFields{ + "degree": map[string]string{ + "type": "BachelorDegree", + "university": "MIT", + }, + "name": "Jayden Doe", + "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1", + }, + }}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + CustomFields: verifiable.CustomFields{ + "name": "Example University", + }, + }, + Expired: util.NewTime(time.Date(2020, 01, 01, 19, 23, 24, 0, time.UTC)), //nolint:gocritic + CustomFields: map[string]interface{}{ + "referenceNumber": 83294847, //nolint:gomnd + }, + } + + jwtVC.JWT, err = createEdDSAJWS(jwtVC, ed25519Signer, pubKey.ID, true) + if err != nil { + return fmt.Errorf("failed to create EdDSA JWT: %w", err) + } + + jwtVCMarshalled, e := jwtVC.MarshalJSON() + if e != nil { + return fmt.Errorf("failed to marshal JWT vc: %w", e) + } + + vc = string(jwtVCMarshalled) + } + + vp, err := verifiable.ParsePresentation( + []byte(fmt.Sprintf(vpStrFromWallet, vc, conn.MyDID)), + verifiable.WithPresJSONLDDocumentLoader(loader), + verifiable.WithPresDisabledProofCheck()) + if err != nil { + return fmt.Errorf("failed to decode VP JSON: %w", err) + } + + jwtClaims, err := vp.JWTClaims([]string{conn.MyDID}, true) + if err != nil { + return fmt.Errorf("failed to create JWT claims of VP: %w", err) + } + + vpJWS, err := jwtClaims.MarshalJWS(verifiable.EdDSA, ed25519Signer, pubKey.ID) if err != nil { return fmt.Errorf("failed to sign VP inside JWT: %w", err) } @@ -440,7 +494,8 @@ func (a *SDKSteps) acceptRequestPresentation(prover, verifier string) error { }, nil) } -func (a *SDKSteps) acceptRequestPresentationV3(prover, verifier string) error { +//nolint:funlen,gocyclo +func (a *SDKSteps) acceptRequestPresentationV3(prover, verifier, format string) error { PIID, err := a.getActionID(prover) if err != nil { return err @@ -456,19 +511,6 @@ func (a *SDKSteps) acceptRequestPresentationV3(prover, verifier string) error { return err } - vp, err := verifiable.ParsePresentation( - []byte(fmt.Sprintf(vpStrFromWallet, proverDID, proverDID)), - verifiable.WithPresJSONLDDocumentLoader(loader), - verifiable.WithPresDisabledProofCheck()) - if err != nil { - return fmt.Errorf("failed to decode VP JSON: %w", err) - } - - jwtClaims, err := vp.JWTClaims([]string{proverDID}, true) - if err != nil { - return fmt.Errorf("failed to create JWT claims of VP: %w", err) - } - doc, err := a.bddContext.AgentCtx[prover].VDRegistry().Resolve(proverDID) if err != nil { return err @@ -483,7 +525,67 @@ func (a *SDKSteps) acceptRequestPresentationV3(prover, verifier string) error { return fmt.Errorf("failed to key kid for kms: %w", err) } - vpJWS, err := jwtClaims.MarshalJWS(verifiable.EdDSA, newSigner(km, cr, kid), pubKey.ID) + ed25519Signer := newSigner(km, cr, kid) + + vc := fmt.Sprintf(vcStrFromWallet, proverDID) + + switch format { + case presexch.FormatJWTVC, presexch.FormatJWTVP, presexch.FormatJWT: + jwtVC := &verifiable.Credential{ + Issued: util.NewTime(time.Date(2010, 01, 01, 19, 23, 24, 0, time.UTC)), //nolint:gocritic + Context: []string{verifiable.ContextURI, "https://www.w3.org/2018/credentials/examples/v1"}, + Types: []string{verifiable.VCType, "UniversityDegreeCredential"}, + ID: "http://example.edu/credentials/1872", + Subject: []verifiable.Subject{{ + ID: proverDID, + CustomFields: verifiable.CustomFields{ + "degree": map[string]string{ + "type": "BachelorDegree", + "university": "MIT", + }, + "name": "Jayden Doe", + "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1", + }, + }}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + CustomFields: verifiable.CustomFields{ + "name": "Example University", + }, + }, + Expired: util.NewTime(time.Date(2020, 01, 01, 19, 23, 24, 0, time.UTC)), //nolint:gocritic + CustomFields: map[string]interface{}{ + "referenceNumber": 83294847, //nolint:gomnd + }, + } + + jwtVC.JWT, err = createEdDSAJWS(jwtVC, ed25519Signer, pubKey.ID, true) + if err != nil { + return fmt.Errorf("failed to create EdDSA JWT: %w", err) + } + + jwtVCMarshalled, e := jwtVC.MarshalJSON() + if e != nil { + return fmt.Errorf("failed to marshal JWT vc: %w", e) + } + + vc = string(jwtVCMarshalled) + } + + vp, err := verifiable.ParsePresentation( + []byte(fmt.Sprintf(vpStrFromWallet, vc, proverDID)), + verifiable.WithPresJSONLDDocumentLoader(loader), + verifiable.WithPresDisabledProofCheck()) + if err != nil { + return fmt.Errorf("failed to decode VP JSON: %w", err) + } + + jwtClaims, err := vp.JWTClaims([]string{proverDID}, true) + if err != nil { + return fmt.Errorf("failed to create JWT claims of VP: %w", err) + } + + vpJWS, err := jwtClaims.MarshalJWS(verifiable.EdDSA, ed25519Signer, pubKey.ID) if err != nil { return fmt.Errorf("failed to sign VP inside JWT: %w", err) } @@ -497,6 +599,20 @@ func (a *SDKSteps) acceptRequestPresentationV3(prover, verifier string) error { }, nil) } +func createEdDSAJWS(vc *verifiable.Credential, signer verifiable.Signer, keyID string, minimize bool) (string, error) { + jwtClaims, err := vc.JWTClaims(minimize) + if err != nil { + return "", err + } + + vcJWT, err := jwtClaims.MarshalJWS(verifiable.EdDSA, signer, vc.Issuer.ID+"#keys-"+keyID) + if err != nil { + return "", err + } + + return vcJWT, nil +} + func (a *SDKSteps) acceptRequestPresentationBBS(prover, _, proof string) error { // nolint: funlen PIID, err := a.getActionID(prover) if err != nil {