From 944667791f12cedad06e2a76f82d7b3f66374b99 Mon Sep 17 00:00:00 2001 From: Dmitriy Kinoshenko Date: Thu, 24 Dec 2020 00:05:56 +0200 Subject: [PATCH] test: BBS+ selective disclosure extended examples with a more constrained set of claims in the derived credential closes #2410 Signed-off-by: Dmitriy Kinoshenko --- pkg/doc/presexch/definition.go | 12 +- pkg/doc/presexch/definition_test.go | 372 +++++++++++++++--- pkg/doc/verifiable/credential.go | 17 +- pkg/doc/verifiable/credential_bbs.go | 2 +- pkg/doc/verifiable/credential_bbs_test.go | 38 +- pkg/doc/verifiable/credential_test.go | 18 + pkg/doc/verifiable/example_credential_test.go | 10 +- test/bdd/features/issue_credential.feature | 2 + 8 files changed, 396 insertions(+), 75 deletions(-) diff --git a/pkg/doc/presexch/definition.go b/pkg/doc/presexch/definition.go index c311f2d49..c63bd1586 100644 --- a/pkg/doc/presexch/definition.go +++ b/pkg/doc/presexch/definition.go @@ -530,11 +530,13 @@ func filterConstraints(constraints *Constraints, creds []*verifiable.Credential) if constraints.LimitDisclosure { template, err = json.Marshal(map[string]interface{}{ - "id": credential.ID, - "credentialSchema": credential.Schemas, - "type": credential.Types, - "@context": credential.Context, - "issuer": "", + "id": credential.ID, + "credentialSchema": credential.Schemas, + "type": credential.Types, + "@context": credential.Context, + "issuer": credential.Issuer, + "credentialSubject": credential.Subject, + "issuanceDate": credential.Issued, }) if err != nil { return nil, err diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index 0d95ef3b8..f1ffb5aab 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -10,9 +10,12 @@ import ( "context" "encoding/json" "io/ioutil" + "net/http" + "net/http/httptest" "os" "strings" "testing" + "time" "github.com/PaesslerAG/gval" "github.com/PaesslerAG/jsonpath" @@ -20,6 +23,7 @@ import ( "github.com/stretchr/testify/require" . "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" + "github.com/hyperledger/aries-framework-go/pkg/doc/util" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" ) @@ -32,6 +36,211 @@ var ( intFilterType = "integer" ) +const defaultVCSchema = `{ + "required": [ + "@context", + "type", + "credentialSubject", + "issuer", + "issuanceDate" + ], + "properties": { + "@context": { + "oneOf": [ + { + "type": "string", + "const": "https://www.w3.org/2018/credentials/v1" + }, + { + "type": "array", + "items": [ + { + "type": "string", + "const": "https://www.w3.org/2018/credentials/v1" + } + ], + "uniqueItems": true, + "additionalItems": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "string" + } + ] + } + } + ] + }, + "id": { + "type": "string", + "format": "uri" + }, + "type": { + "oneOf": [ + { + "type": "array", + "minItems": 1, + "contains": { + "type": "string", + "pattern": "^VerifiableCredential$" + } + }, + { + "type": "string", + "pattern": "^VerifiableCredential$" + } + ] + }, + "credentialSubject": { + "anyOf": [ + { + "type": "array" + }, + { + "type": "object" + }, + { + "type": "string" + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "format": "uri" + } + } + } + ] + }, + "issuanceDate": { + "type": "string", + "format": "date-time" + }, + "proof": { + "anyOf": [ + { + "$ref": "#/definitions/proof" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/proof" + } + }, + { + "type": "null" + } + ] + }, + "expirationDate": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "credentialStatus": { + "$ref": "#/definitions/typedID" + }, + "credentialSchema": { + "$ref": "#/definitions/typedIDs" + }, + "evidence": { + "$ref": "#/definitions/typedIDs" + }, + "refreshService": { + "$ref": "#/definitions/typedID" + } + }, + "definitions": { + "typedID": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + } + ] + }, + "typedIDs": { + "anyOf": [ + { + "$ref": "#/definitions/typedID" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/typedID" + } + }, + { + "type": "null" + } + ] + }, + "proof": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string" + } + } + } + } +} +` + +var testServer *httptest.Server // nolint: gochecknoglobals + +// nolint: gochecknoinits +func init() { + testServer = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusOK) + _, _ = res.Write([]byte(defaultVCSchema)) //nolint:errcheck + })) +} + func TestPresentationDefinition_IsValid(t *testing.T) { samples := []string{"sample_1.json", "sample_2.json", "sample_3.json"} @@ -99,7 +308,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"A"}, Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -111,7 +320,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"child"}, Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -128,7 +337,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"teenager"}, Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -145,7 +354,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"adult"}, Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -164,7 +373,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -174,7 +384,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -217,7 +427,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Group: []string{"A"}, Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -231,7 +441,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -241,7 +452,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -262,7 +473,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -275,12 +486,20 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { } vp, err := pd.CreateVP(&verifiable.Credential{ - ID: uuid.New().String(), + ID: "http://example.edu/credentials/1872", Context: []string{"https://www.w3.org/2018/credentials/v1"}, Types: []string{"VerifiableCredential"}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, + Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Now(), + }, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, CustomFields: map[string]interface{}{ "first_name": "First name", "last_name": "Last name", @@ -311,7 +530,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ LimitDisclosure: true, @@ -325,12 +544,20 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { } vp, err := pd.CreateVP(&verifiable.Credential{ - ID: uuid.New().String(), + ID: "http://example.edu/credentials/1872", Context: []string{"https://www.w3.org/2018/credentials/v1"}, Types: []string{"VerifiableCredential"}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, + Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Now(), + }, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, CustomFields: map[string]interface{}{ "first_name": "First name", "last_name": "Last name", @@ -361,7 +588,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -375,7 +602,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": make(chan struct{}), @@ -393,7 +621,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -406,7 +634,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -414,7 +643,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "last_name": "Travis", @@ -431,7 +660,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -446,7 +675,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -454,7 +684,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "last_name": "Travis", @@ -474,7 +704,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -493,7 +723,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Subject: map[string]interface{}{}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -503,7 +734,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: verifiable.Subject{ID: issuerID}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -521,14 +752,14 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { t.Run("Matches one credentials (three fields - disclosure)", func(t *testing.T) { subjectIsIssuer := Required - issuerID := uuid.New().String() + issuerID := "did:example:76e12ec712ebc6f1c221ebfeb1f" pd := &PresentationDefinition{ ID: uuid.New().String(), InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -556,20 +787,25 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []map[string]interface{}{{}}, Issuer: verifiable.Issuer{ID: uuid.New().String()}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "last_name": "Travis", }, }, &verifiable.Credential{ - ID: uuid.New().String(), + ID: "http://example.edu/credentials/1872", Context: []string{"https://www.w3.org/2018/credentials/v1"}, Types: []string{"VerifiableCredential"}, Subject: []map[string]interface{}{{"id": issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, + Issued: &util.TimeWithTrailingZeroMsec{ + Time: time.Now(), + }, CustomFields: map[string]interface{}{ "first_name": "Jesse", "ssn": "000-00-000", @@ -637,7 +873,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ LimitDisclosure: true, @@ -656,7 +892,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Issuer: verifiable.Issuer{CustomFields: map[string]interface{}{"k": "v"}}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -677,7 +914,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -697,7 +934,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: map[string]interface{}{"id": issuerID}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -706,7 +944,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { ID: uuid.New().String(), Subject: map[string]interface{}{"id": 123}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Travis", @@ -731,7 +969,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -745,7 +983,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -755,7 +994,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: []verifiable.Subject{{ID: issuerID}}, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -780,7 +1019,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ SubjectIsIssuer: &subjectIsIssuer, @@ -791,7 +1030,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, { ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -806,7 +1045,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: issuerID, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -816,7 +1056,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { Subject: issuerID, Issuer: verifiable.Issuer{ID: issuerID}, Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -838,7 +1078,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, Constraints: &Constraints{ Fields: []*Field{{ @@ -851,7 +1091,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -859,7 +1100,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, CustomFields: map[string]interface{}{ "first_name": "Jesse", @@ -881,7 +1122,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, }}, } @@ -889,12 +1130,13 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, }}, }) @@ -912,7 +1154,7 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { InputDescriptors: []*InputDescriptor{{ ID: uuid.New().String(), Schema: []*Schema{{ - URI: "https://www.w3.org/TR/vc-data-model/#types", + URI: testServer.URL, }}, }}, } @@ -920,7 +1162,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/#types", + ID: testServer.URL, + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), @@ -951,12 +1194,14 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/2.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/2.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/3.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/3.0/#types", + Type: "JsonSchemaValidator2018", }}, }) @@ -983,12 +1228,14 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/2.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/2.0/#types", + Type: "JsonSchemaValidator2018", }}, }) @@ -1019,12 +1266,14 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/3.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/3.0/#types", + Type: "JsonSchemaValidator2018", }}, }) @@ -1053,7 +1302,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), @@ -1087,7 +1337,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), @@ -1126,7 +1377,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { vp, err := pd.CreateVP(&verifiable.Credential{ ID: uuid.New().String(), Schemas: []verifiable.TypedID{{ - ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + ID: "https://www.w3.org/TR/vc-data-model/1.0/#types", + Type: "JsonSchemaValidator2018", }}, }, &verifiable.Credential{ ID: uuid.New().String(), diff --git a/pkg/doc/verifiable/credential.go b/pkg/doc/verifiable/credential.go index 7ae3539ff..fb24c9b16 100644 --- a/pkg/doc/verifiable/credential.go +++ b/pkg/doc/verifiable/credential.go @@ -699,6 +699,10 @@ func WithEmbeddedSignatureSuites(suites ...verifier.SignatureSuite) CredentialOp // // - object with mandatory "id" field and optional "name" field. func parseIssuer(issuerBytes json.RawMessage) (Issuer, error) { + if len(issuerBytes) == 0 { + return Issuer{}, nil + } + var issuer Issuer err := json.Unmarshal(issuerBytes, &issuer) @@ -837,6 +841,11 @@ func ParseUnverifiedCredential(vcBytes []byte, opts ...CredentialOpt) (*Credenti return nil, fmt.Errorf("build new credential: %w", err) } + err = validateCredential(vc, vcDataDecoded, vcOpts) + if err != nil { + return nil, err + } + return vc, nil } @@ -859,17 +868,17 @@ func validateCredential(vc *Credential, vcBytes []byte, vcOpts *credentialOpts) return vc.validateJSONLD(vcBytes, vcOpts) case baseContextValidation: - return validateBaseContext(vc, vcBytes, vcOpts) + return vc.validateBaseContext(vcBytes, vcOpts) case baseContextExtendedValidation: - return validateBaseContextWithExtendedValidation(vc, vcOpts, vcBytes) + return vc.validateBaseContextWithExtendedValidation(vcOpts, vcBytes) default: return fmt.Errorf("unsupported vcModelValidationMode: %v", vcOpts.modelValidationMode) } } -func validateBaseContext(vc *Credential, vcBytes []byte, vcOpts *credentialOpts) error { +func (vc *Credential) validateBaseContext(vcBytes []byte, vcOpts *credentialOpts) error { if len(vc.Types) > 1 || vc.Types[0] != vcType { return errors.New("violated type constraint: not base only type defined") } @@ -881,7 +890,7 @@ func validateBaseContext(vc *Credential, vcBytes []byte, vcOpts *credentialOpts) return vc.validateJSONSchema(vcBytes, vcOpts) } -func validateBaseContextWithExtendedValidation(vc *Credential, vcOpts *credentialOpts, vcBytes []byte) error { +func (vc *Credential) validateBaseContextWithExtendedValidation(vcOpts *credentialOpts, vcBytes []byte) error { for _, vcContext := range vc.Context { if _, ok := vcOpts.allowedCustomContexts[vcContext]; !ok { return fmt.Errorf("not allowed @context: %s", vcContext) diff --git a/pkg/doc/verifiable/credential_bbs.go b/pkg/doc/verifiable/credential_bbs.go index aada21b8b..c6ed0442e 100644 --- a/pkg/doc/verifiable/credential_bbs.go +++ b/pkg/doc/verifiable/credential_bbs.go @@ -47,5 +47,5 @@ func (vc *Credential) GenerateBBSSelectiveDisclosure(revealDoc map[string]interf return nil, err } - return ParseUnverifiedCredential(vcWithSelectiveDisclosureBytes) + return ParseUnverifiedCredential(vcWithSelectiveDisclosureBytes, opts...) } diff --git a/pkg/doc/verifiable/credential_bbs_test.go b/pkg/doc/verifiable/credential_bbs_test.go index 0b2db4758..716c9c265 100644 --- a/pkg/doc/verifiable/credential_bbs_test.go +++ b/pkg/doc/verifiable/credential_bbs_test.go @@ -82,6 +82,10 @@ func TestCredential_GenerateBBSSelectiveDisclosure(t *testing.T) { "https://w3c-ccg.github.io/ldp-bbs2020/context/v1" ], "type": ["VerifiableCredential", "PermanentResidentCard"], + "@explicit": true, + "identifier": {}, + "issuer": {}, + "issuanceDate": {}, "credentialSubject": { "@explicit": true, "type": ["PermanentResident", "Person"], @@ -92,8 +96,7 @@ func TestCredential_GenerateBBSSelectiveDisclosure(t *testing.T) { } ` - var revealDoc map[string]interface{} - err = json.Unmarshal([]byte(revealJSON), &revealDoc) + revealDoc, err := toMap(revealJSON) r.NoError(err) nonce := []byte("nonce") @@ -147,6 +150,37 @@ func TestCredential_GenerateBBSSelectiveDisclosure(t *testing.T) { r.Empty(vcWithSelectiveDisclosure) }) + t.Run("Reveal document with hidden VC mandatory field", func(t *testing.T) { + revealJSONWithMissingIssuer := ` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3c-ccg.github.io/ldp-bbs2020/context/v1" + ], + "type": ["VerifiableCredential", "PermanentResidentCard"], + "@explicit": true, + "identifier": {}, + "issuanceDate": {}, + "credentialSubject": { + "@explicit": true, + "type": ["PermanentResident", "Person"], + "givenName": {}, + "familyName": {}, + "gender": {} + } +} +` + + revealDoc, err = toMap(revealJSONWithMissingIssuer) + r.NoError(err) + + vcWithSelectiveDisclosure, err = signedVC.GenerateBBSSelectiveDisclosure(revealDoc, nonce, vcOptions...) + r.Error(err) + r.Contains(err.Error(), "issuer is required") + r.Nil(vcWithSelectiveDisclosure) + }) + t.Run("VC with no embedded proof", func(t *testing.T) { signedVC.Proofs = nil vcWithSelectiveDisclosure, err = signedVC.GenerateBBSSelectiveDisclosure(revealDoc, nonce, vcOptions...) diff --git a/pkg/doc/verifiable/credential_test.go b/pkg/doc/verifiable/credential_test.go index 46fa5f983..479e2d318 100644 --- a/pkg/doc/verifiable/credential_test.go +++ b/pkg/doc/verifiable/credential_test.go @@ -1327,6 +1327,12 @@ func TestParseIssuer(t *testing.T) { require.Contains(t, err.Error(), "unmarshal Issuer") require.Empty(t, issuer.ID) }) + + t.Run("Parse undefined Issuer", func(t *testing.T) { + issuer, err := parseIssuer(nil) + require.NoError(t, err) + require.Equal(t, Issuer{}, issuer) + }) } func TestParseSubject(t *testing.T) { @@ -1923,6 +1929,18 @@ func TestParseUnverifiedCredential(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "build new credential") require.Nil(t, vc) + + require.NoError(t, json.Unmarshal([]byte(validCredential), &rawVCMap)) + delete(rawVCMap, "issuer") + + rawVCMapBytes, err = json.Marshal(rawVCMap) + require.NoError(t, err) + + vc, err = ParseUnverifiedCredential(rawVCMapBytes) + require.Error(t, err) + require.Contains(t, err.Error(), "verifiable credential is not valid") + require.Contains(t, err.Error(), "issuer is required") + require.Nil(t, vc) }) } diff --git a/pkg/doc/verifiable/example_credential_test.go b/pkg/doc/verifiable/example_credential_test.go index 36c2519b5..b5d8e49e3 100644 --- a/pkg/doc/verifiable/example_credential_test.go +++ b/pkg/doc/verifiable/example_credential_test.go @@ -565,6 +565,9 @@ func ExampleCredential_GenerateBBSSelectiveDisclosure() { // Create BBS+ selective disclosure. We explicitly state the fields we want to reveal in the output document. // For example, "credentialSubject.birthDate" is not mentioned and thus will be hidden. + // To hide top-level VC fields, "@explicit": true is used on top level of reveal doc. + // For example, we can reveal "identifier" top-level VC field only. "issuer" and "issuanceDate" are mandatory + // and thus must be defined in reveal doc in case of hiding top-level VC fields. revealDoc := ` { "@context": [ @@ -573,6 +576,10 @@ func ExampleCredential_GenerateBBSSelectiveDisclosure() { "https://w3c-ccg.github.io/ldp-bbs2020/context/v1" ], "type": ["VerifiableCredential", "PermanentResidentCard"], + "@explicit": true, + "identifier": {}, + "issuer": {}, + "issuanceDate": {}, "credentialSubject": { "@explicit": true, "type": ["PermanentResident", "Person"], @@ -679,13 +686,10 @@ func ExampleCredential_GenerateBBSSelectiveDisclosure() { // "PermanentResident" // ] // }, - // "description": "Government of Example Permanent Resident Card.", - // "expirationDate": "2029-12-03T12:19:52Z", // "id": "https://issuer.oidp.uscis.gov/credentials/83627465", // "identifier": "83627465", // "issuanceDate": "2019-12-03T12:19:52Z", // "issuer": "did:example:489398593", - // "name": "Permanent Resident Card", // "proof": [ // { // "created": "2010-01-01T19:23:24Z", diff --git a/test/bdd/features/issue_credential.feature b/test/bdd/features/issue_credential.feature index 754b615a6..2f6839488 100644 --- a/test/bdd/features/issue_credential.feature +++ b/test/bdd/features/issue_credential.feature @@ -43,6 +43,8 @@ Feature: Issue credential protocol And "Stanford University" accepts request and sends credential to the Holder And "Graduate" accepts credential with name "bachelors degree" Then "Graduate" checks that credential is being stored under "bachelors degree" name + And "Graduate" waits for state "done" + And "Stanford University" waits for state "done" @decline_request Scenario: The Holder begins with a request and the Issuer declines it Given "Alice" exchange DIDs with "Bank"