From 0b9d50bb14825a0fa9b36926ca569d8d2bf71733 Mon Sep 17 00:00:00 2001 From: gabe Date: Tue, 29 Aug 2023 09:23:48 -0400 Subject: [PATCH 1/4] include latest spec updates --- credential/model.go | 5 +- credential/schema/model.go | 5 ++ .../jsonschemacredential-schema-1.json | 5 ++ credential/schema/vcjsonschema.go | 56 +++++++++++++------ credential/schema/vcjsonschema_test.go | 52 +++++++++++++++++ 5 files changed, 105 insertions(+), 18 deletions(-) diff --git a/credential/model.go b/credential/model.go index 6b5dba16..f2cb350a 100644 --- a/credential/model.go +++ b/credential/model.go @@ -68,8 +68,9 @@ func (cs CredentialSubject) GetJSONSchema() map[string]any { } type CredentialSchema struct { - ID string `json:"id" validate:"required"` - Type string `json:"type" validate:"required"` + ID string `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` + DigestSRI string `json:"digestSRI,omitempty"` } type RefreshService struct { diff --git a/credential/schema/model.go b/credential/schema/model.go index fb64557a..5886a590 100644 --- a/credential/schema/model.go +++ b/credential/schema/model.go @@ -13,6 +13,11 @@ const ( JSONSchemaType VCJSONSchemaType = "JsonSchema" TypeProperty string = "type" + // Known CredentialSchema property values for JsonSchemaCredential + + JSONSchemaCredentialSchemaID = "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json" + JSONSchemaCredentialDigestSRI = "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW" + Draft202012 JSONSchemaVersion = "https://json-schema.org/draft/2020-12/schema" Draft201909 JSONSchemaVersion = "https://json-schema.org/draft/2019-09/schema" Draft7 JSONSchemaVersion = "https://json-schema.org/draft-07/schema#" diff --git a/credential/schema/testdata/jsonschemacredential-schema-1.json b/credential/schema/testdata/jsonschemacredential-schema-1.json index e4fe4cff..6d4f447d 100644 --- a/credential/schema/testdata/jsonschemacredential-schema-1.json +++ b/credential/schema/testdata/jsonschemacredential-schema-1.json @@ -7,6 +7,11 @@ "type": ["VerifiableCredential", "JsonSchemaCredential"], "issuer": "https://example.com/issuers/14", "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSchema": { + "id": "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json", + "type": "JsonSchema", + "digestSRI": "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW" + }, "credentialSubject": { "id": "https://example.com/schemas/email-credential-schema.json", "type": "JsonSchema", diff --git a/credential/schema/vcjsonschema.go b/credential/schema/vcjsonschema.go index d3cbc0bc..1c98b339 100644 --- a/credential/schema/vcjsonschema.go +++ b/credential/schema/vcjsonschema.go @@ -48,23 +48,11 @@ func IsCredentialValidForJSONSchema(cred credential.VerifiableCredential, vcs VC s = JSONSchema(vcs) schemaID = s.ID() case JSONSchemaCredentialType: - var vc credential.VerifiableCredential - schemaString := vcs.String() - if err := json.Unmarshal([]byte(schemaString), &vc); err != nil { - return errors.Wrap(err, "unmarshalling schema") + var err error + s, schemaID, err = parseJSONSchemaCredential(vcs) + if err != nil { + return errors.Wrap(err, "parsing credential schema") } - schemaType, ok := vc.CredentialSubject[TypeProperty] - if !ok { - return errors.New("credential schema's credential subject does not contain a `type`") - } - if schemaType != JSONSchemaType.String() { - return fmt.Errorf("credential schema's credential subject type<%s> does not match schema type<%s>", schemaType, JSONSchemaType) - } - s = vc.CredentialSubject.GetJSONSchema() - if len(s) == 0 { - return errors.New("credential schema's credential subject does not contain a valid `jsonSchema`") - } - schemaID = vc.ID } // check the ID is a valid URI @@ -100,6 +88,42 @@ func IsCredentialValidForJSONSchema(cred credential.VerifiableCredential, vcs VC return nil } +// JsonSchemaCredential helper for IsCredentialValidForJSONSchema +func parseJSONSchemaCredential(vcs VCJSONSchema) (JSONSchema, string, error) { + var vc credential.VerifiableCredential + schemaString := vcs.String() + if err := json.Unmarshal([]byte(schemaString), &vc); err != nil { + return nil, "", errors.Wrap(err, "unmarshalling schema") + } + schemaType, ok := vc.CredentialSubject[TypeProperty] + if !ok { + return nil, "", errors.New("credential schema's credential subject does not contain a `type`") + } + if schemaType != JSONSchemaType.String() { + return nil, "", fmt.Errorf("credential schema's credential subject type<%s> does not match schema type<%s>", schemaType, JSONSchemaType) + } + if vc.CredentialSchema == nil { + return nil, "", errors.New("credential schema's credential subject does not contain a `credentialSchema`") + } else { + credSchema := vc.CredentialSchema + if credSchema.ID != JSONSchemaCredentialSchemaID { + return nil, "", fmt.Errorf("credential schema's credential schema id<%s> does not match known id<%s>", credSchema.ID, JSONSchemaCredentialSchemaID) + } + if credSchema.Type != JSONSchemaType.String() { + return nil, "", fmt.Errorf("credential schema's credential schema type<%s> does not match known type<%s>", credSchema.Type, JSONSchemaType) + } + if credSchema.DigestSRI != JSONSchemaCredentialDigestSRI { + return nil, "", fmt.Errorf("credential schema's credential schema digest sri<%s> does not match known sri<%s>", credSchema.DigestSRI, JSONSchemaCredentialDigestSRI) + } + } + s := vc.CredentialSubject.GetJSONSchema() + if len(s) == 0 { + return nil, "", errors.New("credential schema's credential subject does not contain a valid `jsonSchema`") + } + schemaID := vc.ID + return s, schemaID, nil +} + // GetCredentialSchemaFromCredential returns the credential schema and type for a given credential given // a credential schema access, which is used to retrieve the schema func GetCredentialSchemaFromCredential(access VCJSONSchemaAccess, cred credential.VerifiableCredential) (VCJSONSchema, VCJSONSchemaType, error) { diff --git a/credential/schema/vcjsonschema_test.go b/credential/schema/vcjsonschema_test.go index 34a7551a..237d2748 100644 --- a/credential/schema/vcjsonschema_test.go +++ b/credential/schema/vcjsonschema_test.go @@ -254,6 +254,53 @@ func TestIsCredentialValidForJSONSchema_JsonSchemaCredential(t *testing.T) { }) }) + t.Run("2.2 The value of the credentialSchema property MUST always be set to [known json schema]", func(t *testing.T) { + t.Run("valid credentialSchema", func(t *testing.T) { + cred := getTestVCJSONSchemaCredential() + schema := getTestVCJSONSchemaSchema() + err := IsCredentialValidForJSONSchema(cred, schema, JSONSchemaCredentialType) + assert.NoError(t, err) + }) + + t.Run("credentialSchema wrong id", func(t *testing.T) { + cred := getTestVCJSONSchemaCredential() + schema := getTestVCJSONSchemaSchema() + schema["credentialSchema"] = map[string]any{ + "id": "bad", + "type": JSONSchemaType, + "digestSRI": JSONSchemaCredentialDigestSRI, + } + err := IsCredentialValidForJSONSchema(cred, schema, JSONSchemaCredentialType) + assert.Error(t, err) + assert.ErrorContains(t, err, "credential schema's credential schema id does not match known id") + }) + + t.Run("credentialSchema wrong type", func(t *testing.T) { + cred := getTestVCJSONSchemaCredential() + schema := getTestVCJSONSchemaSchema() + schema["credentialSchema"] = map[string]any{ + "id": JSONSchemaCredentialSchemaID, + "type": "NotJsonSchema", + "digestSRI": JSONSchemaCredentialDigestSRI, + } + err := IsCredentialValidForJSONSchema(cred, schema, JSONSchemaCredentialType) + assert.Error(t, err) + assert.ErrorContains(t, err, "credential schema's credential schema type does not match known type") + }) + + t.Run("credentialSchema missing digestSRI", func(t *testing.T) { + cred := getTestVCJSONSchemaCredential() + schema := getTestVCJSONSchemaSchema() + schema["credentialSchema"] = map[string]any{ + "id": JSONSchemaCredentialSchemaID, + "type": JSONSchemaType, + } + err := IsCredentialValidForJSONSchema(cred, schema, JSONSchemaCredentialType) + assert.Error(t, err) + assert.ErrorContains(t, err, "credential schema's credential schema digest sri<> does not match known sri") + }) + }) + t.Run("the $id property MUST be present and its value MUST represent a valid URI", func(t *testing.T) { t.Run("$id is a valid URI", func(t *testing.T) { cred := getTestVCJSONSchemaCredential() @@ -385,6 +432,11 @@ func getTestVCJSONSchemaSchema() VCJSONSchema { "required": []string{"credentialSubject"}, }, }, + "credentialSchema": map[string]any{ + "id": JSONSchemaCredentialSchemaID, + "type": "JsonSchema", + "digestSRI": JSONSchemaCredentialDigestSRI, + }, } } From 56929a7d2271c3e586bc74e336729afe3adeeba3 Mon Sep 17 00:00:00 2001 From: gabe Date: Tue, 29 Aug 2023 09:27:28 -0400 Subject: [PATCH 2/4] lint --- credential/schema/vcjsonschema.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/credential/schema/vcjsonschema.go b/credential/schema/vcjsonschema.go index 1c98b339..afc1f3fc 100644 --- a/credential/schema/vcjsonschema.go +++ b/credential/schema/vcjsonschema.go @@ -102,20 +102,20 @@ func parseJSONSchemaCredential(vcs VCJSONSchema) (JSONSchema, string, error) { if schemaType != JSONSchemaType.String() { return nil, "", fmt.Errorf("credential schema's credential subject type<%s> does not match schema type<%s>", schemaType, JSONSchemaType) } - if vc.CredentialSchema == nil { + if vc.CredentialSchema != nil { return nil, "", errors.New("credential schema's credential subject does not contain a `credentialSchema`") - } else { - credSchema := vc.CredentialSchema - if credSchema.ID != JSONSchemaCredentialSchemaID { - return nil, "", fmt.Errorf("credential schema's credential schema id<%s> does not match known id<%s>", credSchema.ID, JSONSchemaCredentialSchemaID) - } - if credSchema.Type != JSONSchemaType.String() { - return nil, "", fmt.Errorf("credential schema's credential schema type<%s> does not match known type<%s>", credSchema.Type, JSONSchemaType) - } - if credSchema.DigestSRI != JSONSchemaCredentialDigestSRI { - return nil, "", fmt.Errorf("credential schema's credential schema digest sri<%s> does not match known sri<%s>", credSchema.DigestSRI, JSONSchemaCredentialDigestSRI) - } } + credSchema := vc.CredentialSchema + if credSchema.ID != JSONSchemaCredentialSchemaID { + return nil, "", fmt.Errorf("credential schema's credential schema id<%s> does not match known id<%s>", credSchema.ID, JSONSchemaCredentialSchemaID) + } + if credSchema.Type != JSONSchemaType.String() { + return nil, "", fmt.Errorf("credential schema's credential schema type<%s> does not match known type<%s>", credSchema.Type, JSONSchemaType) + } + if credSchema.DigestSRI != JSONSchemaCredentialDigestSRI { + return nil, "", fmt.Errorf("credential schema's credential schema digest sri<%s> does not match known sri<%s>", credSchema.DigestSRI, JSONSchemaCredentialDigestSRI) + } + s := vc.CredentialSubject.GetJSONSchema() if len(s) == 0 { return nil, "", errors.New("credential schema's credential subject does not contain a valid `jsonSchema`") From 76703b273fda672d66e7c4bd90656331a1cf38b6 Mon Sep 17 00:00:00 2001 From: gabe Date: Tue, 29 Aug 2023 09:54:20 -0400 Subject: [PATCH 3/4] fix test --- credential/schema/vcjsonschema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credential/schema/vcjsonschema.go b/credential/schema/vcjsonschema.go index afc1f3fc..614754ce 100644 --- a/credential/schema/vcjsonschema.go +++ b/credential/schema/vcjsonschema.go @@ -102,7 +102,7 @@ func parseJSONSchemaCredential(vcs VCJSONSchema) (JSONSchema, string, error) { if schemaType != JSONSchemaType.String() { return nil, "", fmt.Errorf("credential schema's credential subject type<%s> does not match schema type<%s>", schemaType, JSONSchemaType) } - if vc.CredentialSchema != nil { + if vc.CredentialSchema == nil { return nil, "", errors.New("credential schema's credential subject does not contain a `credentialSchema`") } credSchema := vc.CredentialSchema From ac6067d1ca48a659af987a6351fe9cb12fb0dad4 Mon Sep 17 00:00:00 2001 From: gabe Date: Tue, 29 Aug 2023 10:09:34 -0400 Subject: [PATCH 4/4] pr comments --- credential/schema/vcjsonschema_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/credential/schema/vcjsonschema_test.go b/credential/schema/vcjsonschema_test.go index 237d2748..2061436d 100644 --- a/credential/schema/vcjsonschema_test.go +++ b/credential/schema/vcjsonschema_test.go @@ -254,7 +254,7 @@ func TestIsCredentialValidForJSONSchema_JsonSchemaCredential(t *testing.T) { }) }) - t.Run("2.2 The value of the credentialSchema property MUST always be set to [known json schema]", func(t *testing.T) { + t.Run("The value of the credentialSchema property MUST always be set to [known json schema]", func(t *testing.T) { t.Run("valid credentialSchema", func(t *testing.T) { cred := getTestVCJSONSchemaCredential() schema := getTestVCJSONSchemaSchema() @@ -288,6 +288,19 @@ func TestIsCredentialValidForJSONSchema_JsonSchemaCredential(t *testing.T) { assert.ErrorContains(t, err, "credential schema's credential schema type does not match known type") }) + t.Run("credentialSchema wrong digestSRI", func(t *testing.T) { + cred := getTestVCJSONSchemaCredential() + schema := getTestVCJSONSchemaSchema() + schema["credentialSchema"] = map[string]any{ + "id": JSONSchemaCredentialSchemaID, + "type": JSONSchemaType, + "digestSRI": "bad", + } + err := IsCredentialValidForJSONSchema(cred, schema, JSONSchemaCredentialType) + assert.Error(t, err) + assert.ErrorContains(t, err, "credential schema's credential schema digest sri does not match known sri") + }) + t.Run("credentialSchema missing digestSRI", func(t *testing.T) { cred := getTestVCJSONSchemaCredential() schema := getTestVCJSONSchemaSchema()