Skip to content

Commit

Permalink
feat: add jsonld type validator (#86)
Browse files Browse the repository at this point in the history
* feat: add jsonld type validator

* fix: holder

* fix: subject

* feat: temp enable

* Revert "feat: temp enable"

This reverts commit 2abb69d.

* feat: add support for related resources

* fix: type validation

* fix: er

* fix: types

* fix: parse

* fix: string

* fix: check

* fix: lint

* feat: update did

* feat: update go

* fix: lint

* feat: validate hash

* fix: lint

* fix: lint
  • Loading branch information
skynet2 authored Dec 13, 2024
1 parent 386efd1 commit 7d341e2
Show file tree
Hide file tree
Showing 12 changed files with 1,124 additions and 54 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/go-jose/go-jose/v3 v3.0.1
github.com/golang/mock v1.4.4
github.com/google/uuid v1.3.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/kawamuray/jsonpath v0.0.0-20201211160320-7483bafabd7e
github.com/mitchellh/mapstructure v1.5.0
github.com/multiformats/go-multibase v0.1.1
Expand All @@ -25,7 +26,7 @@ require (
github.com/tidwall/gjson v1.14.3
github.com/tidwall/sjson v1.1.4
github.com/trustbloc/bbs-signature-go v1.0.2
github.com/trustbloc/did-go v1.3.2-0.20241206132250-6ae560f13021
github.com/trustbloc/did-go v1.3.2-0.20241212234638-3c3fc9094d0f
github.com/trustbloc/kms-go v1.2.0
github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd
github.com/xeipuuv/gojsonschema v1.2.0
Expand Down
18 changes: 16 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2 h1:B1Nt8hKb//KvgGRprk0h1t4lCnwhE9/ryb1WqfZbV+M=
github.com/hyperledger/fabric-amcl v0.0.0-20230602173724-9e02669dceb2/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE=
Expand Down Expand Up @@ -128,8 +130,20 @@ github.com/tidwall/sjson v1.1.4 h1:bTSsPLdAYF5QNLSwYsKfBKKTnlGbIuhqL3CpRsjzGhg=
github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg=
github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o=
github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU=
github.com/trustbloc/did-go v1.3.2-0.20241206132250-6ae560f13021 h1:HCliHtrCijpx2nyhBdgjUjmXGdpFXPVwv/E2h/5TxlI=
github.com/trustbloc/did-go v1.3.2-0.20241206132250-6ae560f13021/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241210152236-b8ce698e17ed h1:QhmN9IUePEGCXcKsC/CdglpZMEfnEEqpcJFnLMMD0/I=
github.com/trustbloc/did-go v1.3.2-0.20241210152236-b8ce698e17ed/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212142633-71a7976010bf h1:fES7dd86c1bCZuI/R9iOMxQfSQFaKWJHdf7ukGJm6+I=
github.com/trustbloc/did-go v1.3.2-0.20241212142633-71a7976010bf/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212143438-9e0e9ad7f4d7 h1:dyw3e9jgwje4yXeaW5oUCjKkSKicw+akagQtcSFcPIg=
github.com/trustbloc/did-go v1.3.2-0.20241212143438-9e0e9ad7f4d7/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212143903-183f9aca86d1 h1:wDJxuMO+6+Bznp/Xx2aZcMIYXtITrbpszwfiSQmX96k=
github.com/trustbloc/did-go v1.3.2-0.20241212143903-183f9aca86d1/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212145009-fd71069d9b40 h1:WI+RD31dqiBHv2cKEgzyR1WLoov5mi2pHtITksuZ8Lg=
github.com/trustbloc/did-go v1.3.2-0.20241212145009-fd71069d9b40/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212202325-9d68788945ff h1:qtCR+M0LH5EMDxXeWUKPmzBKvssL6vZNFBGMUssMbmw=
github.com/trustbloc/did-go v1.3.2-0.20241212202325-9d68788945ff/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/did-go v1.3.2-0.20241212234638-3c3fc9094d0f h1:l7QamFbIY1rjWUAnxNxY0RZY9+oYNDujZqYgEK1l/g8=
github.com/trustbloc/did-go v1.3.2-0.20241212234638-3c3fc9094d0f/go.mod h1:GdmgtrHKWuBIw77t0FBqHO9rUjbG3zrKU/RZWbhUL9g=
github.com/trustbloc/json-gold v0.5.2-0.20241206130328-d2135d9f36a8 h1:DomzdQu7D3CDBsMijT0E9uQl91iFcsIfYq1UKXmI/XQ=
github.com/trustbloc/json-gold v0.5.2-0.20241206130328-d2135d9f36a8/go.mod h1:RVhE35veDX19r5gfUAR+IYHkAUuPwJO8Ie/qVeFaIzw=
github.com/trustbloc/kms-go v1.2.0 h1:kM2mkK4vBT7MN18rE6cTEXtrnEo3Uc83F68UKakqeR4=
Expand Down
181 changes: 136 additions & 45 deletions verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,20 +842,28 @@ func SubjectFromJSON(subjectObj JSONObject) (Subject, error) {

// CredentialContents store credential contents as typed structure.
type CredentialContents struct {
Context []string
CustomContext []interface{}
ID string
Types []string
Subject []Subject
Issuer *Issuer
Issued *util.TimeWrapper
Expired *util.TimeWrapper
Status *TypedID
Schemas []TypedID
Evidence Evidence
TermsOfUse []TypedID
RefreshService *TypedID
SDJWTHashAlg *crypto.Hash
Context []string
CustomContext []interface{}
ID string
Types []string
Subject []Subject
Issuer *Issuer
Issued *util.TimeWrapper
Expired *util.TimeWrapper
Status *TypedID
Schemas []TypedID
Evidence Evidence
TermsOfUse []TypedID
RefreshService *TypedID
SDJWTHashAlg *crypto.Hash
RelatedResources []RelatedResource
}

type RelatedResource struct {
Id string `json:"id,omitempty"`
DigestSRI string `json:"digestSRI,omitempty"`
DigestMultiBase string `json:"digestMultibase,omitempty"`
MediaType string `json:"mediaType,omitempty"`
}

// JSONObject used to store json object.
Expand Down Expand Up @@ -1055,22 +1063,23 @@ func (vc *Credential) CustomField(name string) interface{} {
}

const (
jsonFldContext = "@context"
jsonFldID = "id"
jsonFldType = "type"
jsonFldSubject = "credentialSubject"
jsonFldIssued = "issuanceDate"
jsonFldExpired = "expirationDate"
jsonFldLDProof = "proof"
jsonFldStatus = "credentialStatus"
jsonFldIssuer = "issuer"
jsonFldSchema = "credentialSchema"
jsonFldEvidence = "evidence"
jsonFldTermsOfUse = "termsOfUse"
jsonFldRefreshService = "refreshService"
jsonFldSDJWTHashAlg = "_sd_alg"
jsonFldValidFrom = "validFrom"
jsonFldValidUntil = "validUntil"
jsonFldContext = "@context"
jsonFldID = "id"
jsonFldType = "type"
jsonFldSubject = "credentialSubject"
jsonFldIssued = "issuanceDate"
jsonFldExpired = "expirationDate"
jsonFldLDProof = "proof"
jsonFldStatus = "credentialStatus"
jsonFldIssuer = "issuer"
jsonFldSchema = "credentialSchema"
jsonFldEvidence = "evidence"
jsonFldTermsOfUse = "termsOfUse"
jsonFldRefreshService = "refreshService"
jsonFldSDJWTHashAlg = "_sd_alg"
jsonFldValidFrom = "validFrom"
jsonFldValidUntil = "validUntil"
jsonFldRelatedResource = "relatedResource"
)

// CombinedProofChecker universal proof checker for both LD and JWT proofs.
Expand Down Expand Up @@ -1113,6 +1122,8 @@ type credentialOpts struct {
verifyDataIntegrity *verifyDataIntegrityOpts

jsonldCredentialOpts
disableRelatedResourceCheck bool
enableJsonLDTypesCheck bool
}

// CredentialOpt is the Verifiable Credential decoding option.
Expand All @@ -1125,6 +1136,20 @@ func WithDisabledProofCheck() CredentialOpt {
}
}

// WithEnabledJSONLDTypesCheck option for enabling check of JSON-LD types.
func WithEnabledJSONLDTypesCheck() CredentialOpt {
return func(opts *credentialOpts) {
opts.enableJsonLDTypesCheck = true
}
}

// WithDisabledRelatedResourceCheck option for disabling check of related resources.
func WithDisabledRelatedResourceCheck() CredentialOpt {
return func(opts *credentialOpts) {
opts.disableRelatedResourceCheck = true
}
}

// WithCredDisableValidation options for disabling of JSON-LD and json-schema validation.
func WithCredDisableValidation() CredentialOpt {
return func(opts *credentialOpts) {
Expand Down Expand Up @@ -1526,6 +1551,12 @@ func parseCredential(vcData []byte, parser CredentialParser, opts *credentialOpt
}
}

if !opts.disableRelatedResourceCheck {
if err = DefaultRelatedResourceValidator.Validate([]*Credential{vc}); err != nil {
return nil, err
}
}

return vc, nil
}

Expand Down Expand Up @@ -1658,9 +1689,22 @@ func validateJSONLD(vcJSON JSONObject, vcc *CredentialContents, vcOpts *credenti
)
}

return docjsonld.ValidateJSONLDMap(jsonutil.ShallowCopyObj(vcJSON),
err = docjsonld.ValidateJSONLDMap(jsonutil.ShallowCopyObj(vcJSON),
validateOpts...,
)
if err != nil {
return err
}

if vcOpts.enableJsonLDTypesCheck {
if err = docjsonld.ValidateJSONLDTypes(jsonutil.ShallowCopyObj(vcJSON),
validateOpts...,
); err != nil {
return err
}
}

return nil
}

// nolint: funlen,gocyclo
Expand Down Expand Up @@ -1704,6 +1748,11 @@ func parseCredentialContents(raw JSONObject, isSDJWT bool) (*CredentialContents,
return nil, fmt.Errorf("fill credential refresh service from raw: %w", err)
}

relatedResource, err := parseRelatedResources(raw[jsonFldRelatedResource])
if err != nil {
return nil, fmt.Errorf("fill credential relatedResource from raw: %w", err)
}

subjects, err := parseSubject(raw[jsonFldSubject])
if err != nil {
return nil, fmt.Errorf("fill credential subject from raw: %w", err)
Expand Down Expand Up @@ -1749,20 +1798,21 @@ func parseCredentialContents(raw JSONObject, isSDJWT bool) (*CredentialContents,
}

return &CredentialContents{
Context: context,
CustomContext: customContext,
ID: id,
Types: types,
Subject: subjects,
Issuer: issuer,
Issued: issued,
Expired: expired,
Status: status,
Schemas: schemas,
Evidence: raw[jsonFldEvidence],
TermsOfUse: termsOfUse,
RefreshService: refreshService,
SDJWTHashAlg: sdJWTHashAlgCode,
Context: context,
CustomContext: customContext,
ID: id,
Types: types,
Subject: subjects,
Issuer: issuer,
Issued: issued,
Expired: expired,
Status: status,
Schemas: schemas,
Evidence: raw[jsonFldEvidence],
TermsOfUse: termsOfUse,
RefreshService: refreshService,
RelatedResources: relatedResource,
SDJWTHashAlg: sdJWTHashAlgCode,
}, nil
}

Expand All @@ -1779,6 +1829,43 @@ func parseRefreshService(typeIDRaw interface{}) (*TypedID, error) {
return &typed[0], nil
}

func parseRelatedResources(typeRaw interface{}) ([]RelatedResource, error) {
if typeRaw == nil {
return nil, nil
}

relatedResources, ok := typeRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("related resources of unsupported format, %v", typeRaw)
}

var resources []RelatedResource

for _, rawResource := range relatedResources {
mapVal, mapOk := rawResource.(map[string]interface{})
if !mapOk {
return nil, fmt.Errorf("related resource of unsupported format, %v", rawResource)
}

resources = append(resources, RelatedResource{
Id: stringify(mapVal["id"]),
DigestSRI: stringify(mapVal["digestSRI"]),
MediaType: stringify(mapVal["mediaType"]),
DigestMultiBase: stringify(mapVal["digestMultibase"]),
})
}

return resources, nil
}

func stringify(val any) string {
if val == nil {
return ""
}

return fmt.Sprint(val)
}

func parseTypedID(typeIDRaw interface{}) ([]TypedID, error) {
if typeIDRaw == nil {
return nil, nil
Expand Down Expand Up @@ -2411,6 +2498,10 @@ func serializeCredentialContents(vcc *CredentialContents, proofs []Proof) (JSONO
vcJSON[jsonFldRefreshService] = serializeTypedIDObj(*vcc.RefreshService)
}

if len(vcc.RelatedResources) > 0 {
vcJSON[jsonFldRelatedResource] = vcc.RelatedResources
}

if len(vcc.TermsOfUse) > 0 {
vcJSON[jsonFldTermsOfUse] = typedIDsToRaw(vcc.TermsOfUse)
}
Expand Down
2 changes: 2 additions & 0 deletions verifiable/credential_ldp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ func TestExtraContextWithLDP(t *testing.T) {
// Use extra context.
vcWithLdp, err = parseTestCredential(t, vcBytes,
WithProofChecker(proofChecker),
WithEnabledJSONLDTypesCheck(),
WithExternalJSONLDContext("https://trustbloc.github.io/context/vc/examples-v1.jsonld"),
WithStrictValidation())
r.NoError(err)
Expand All @@ -462,6 +463,7 @@ func TestExtraContextWithLDP(t *testing.T) {
// Use extra context.
vcWithLdp, err = parseTestCredential(t, vcBytes,
WithProofChecker(proofChecker),
WithEnabledJSONLDTypesCheck(),
WithExternalJSONLDContext("https://trustbloc.github.io/context/vc/examples-v1.jsonld"),
WithStrictValidation())
r.NoError(err)
Expand Down
2 changes: 1 addition & 1 deletion verifiable/data_integrity_proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func Test_DataIntegrity_SignVerify(t *testing.T) {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://w3id.org/security/data-integrity/v1"
"https://w3id.org/security/data-integrity/v2"
],
"id": "https://example.com/credentials/1872",
"type": [
Expand Down
15 changes: 15 additions & 0 deletions verifiable/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package verifiable

//go:generate mockgen -destination interfaces_mocks_test.go -package verifiable_test -source=interfaces.go

import "net/http"

// nolint
type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}
Loading

0 comments on commit 7d341e2

Please sign in to comment.