From 7de3c50e12b2b619430ec9428600e16df7619ebf Mon Sep 17 00:00:00 2001 From: Filip Burlacu Date: Sat, 18 Mar 2023 16:08:17 -0400 Subject: [PATCH] feat: credential status validation client (#334) Signed-off-by: Filip Burlacu --- component/vc/status/api/api.go | 27 ++ component/vc/status/go.mod | 44 +++ component/vc/status/go.sum | 143 ++++++++ .../vc/status/internal/bitstring/bitstring.go | 73 ++++ component/vc/status/resolver/resolver.go | 96 ++++++ component/vc/status/status.go | 87 +++++ component/vc/status/status_test.go | 320 ++++++++++++++++++ .../statuslist2021/statuslist2021.go | 87 +++++ component/vc/status/validator/validator.go | 26 ++ 9 files changed, 903 insertions(+) create mode 100644 component/vc/status/api/api.go create mode 100644 component/vc/status/go.mod create mode 100644 component/vc/status/go.sum create mode 100644 component/vc/status/internal/bitstring/bitstring.go create mode 100644 component/vc/status/resolver/resolver.go create mode 100644 component/vc/status/status.go create mode 100644 component/vc/status/status_test.go create mode 100644 component/vc/status/validator/statuslist2021/statuslist2021.go create mode 100644 component/vc/status/validator/validator.go diff --git a/component/vc/status/api/api.go b/component/vc/status/api/api.go new file mode 100644 index 00000000..988c7d7a --- /dev/null +++ b/component/vc/status/api/api.go @@ -0,0 +1,27 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package api contains dependency-injection interfaces for Credential Status validation clients. +package api + +import ( + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" +) + +// Validator holds handlers for validating a particular format of Status(Revocation) List VC. +type Validator interface { + ValidateStatus(vcStatus *verifiable.TypedID) error + GetStatusVCURI(vcStatus *verifiable.TypedID) (string, error) + GetStatusListIndex(vcStatus *verifiable.TypedID) (int, error) +} + +// ValidatorGetter provides the matching Validator for a given credential status type. +type ValidatorGetter func(statusType string) (Validator, error) + +// StatusListVCURIResolver resolves a VC StatusList Credential. +type StatusListVCURIResolver interface { + Resolve(statusListVCURL string) (*verifiable.Credential, error) +} diff --git a/component/vc/status/go.mod b/component/vc/status/go.mod new file mode 100644 index 00000000..d15881fe --- /dev/null +++ b/component/vc/status/go.mod @@ -0,0 +1,44 @@ +// Copyright Avast Software. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +module github.com/hyperledger/aries-framework-go-ext/component/vc/status + +go 1.19 + +require ( + github.com/hyperledger/aries-framework-go v0.1.10-0.20230307184157-877172747719 + github.com/stretchr/testify v1.8.1 +) + +require ( + github.com/VictoriaMetrics/fastcache v1.5.7 // indirect + github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.1-0.20221117193127-916db76e8214 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/tink/go v1.7.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e // indirect + github.com/hyperledger/ursa-wrapper-go v0.3.1 // indirect + github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-multibase v0.1.1 // indirect + github.com/piprate/json-gold v0.4.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pquerna/cachecontrol v0.1.0 // indirect + github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/component/vc/status/go.sum b/component/vc/status/go.sum new file mode 100644 index 00000000..a89d107a --- /dev/null +++ b/component/vc/status/go.sum @@ -0,0 +1,143 @@ +github.com/PaesslerAG/gval v1.1.0 h1:k3RuxeZDO3eejD4cMPSt+74tUSvTnbGvLx0df4mdwFc= +github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= +github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= +github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-jose/go-jose/v3 v3.0.1-0.20221117193127-916db76e8214 h1:w5li6eMV6NCHh1YVbKRM/gMCVtZ2w7mnwq78eNnHXQQ= +github.com/go-jose/go-jose/v3 v3.0.1-0.20221117193127-916db76e8214/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hyperledger/aries-framework-go v0.1.10-0.20230307184157-877172747719 h1:7eXY4+oFhD0keVIV9mf2K9HQS8wykfllg21XW/p3egE= +github.com/hyperledger/aries-framework-go v0.1.10-0.20230307184157-877172747719/go.mod h1:qrOxEGVsu8M2RahaJgM8nz9AcAHjR/dKd1JIJ3ieJhY= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e h1:SxbXlF39661T9w/L9PhVdtbJfJ51Pm4JYEEW6XfZHEQ= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20221025204933-b807371b6f1e/go.mod h1:oryUyWb23l/a3tAP9KW+GBbfcfqp9tZD4y5hSkFrkqI= +github.com/hyperledger/ursa-wrapper-go v0.3.1 h1:Do+QrVNniY77YK2jTIcyWqj9rm/Yb5SScN0bqCjiibA= +github.com/hyperledger/ursa-wrapper-go v0.3.1/go.mod h1:nPSAuMasIzSVciQo22PedBk4Opph6bJ6ia3ms7BH/mk= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69 h1:kMJlf8z8wUcpyI+FQJIdGjAhfTww1y0AbQEv86bpVQI= +github.com/kilic/bls12-381 v0.1.1-0.20210503002446-7b7597926c69/go.mod h1:tlkavyke+Ac7h8R3gZIjI5LKBcvMlSWnXNMgT3vZXo8= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= +github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= +github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= +github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/piprate/json-gold v0.4.2 h1:Rq8V+637HOFcj20KdTqW/g/llCwX2qtau0g5d1pD79o= +github.com/piprate/json-gold v0.4.2/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= +github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 h1:RBkacARv7qY5laaXGlF4wFB/tk5rnthhPb8oIBGoagY= +github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8/go.mod h1:9PdLyPiZIiW3UopXyRnPYyjUXSpiQNHRLu8fOsR3o8M= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/component/vc/status/internal/bitstring/bitstring.go b/component/vc/status/internal/bitstring/bitstring.go new file mode 100644 index 00000000..5593c4b0 --- /dev/null +++ b/component/vc/status/internal/bitstring/bitstring.go @@ -0,0 +1,73 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package bitstring provides functions for operating on byte slices as if they are 0-indexed arrays of bits, +// packed 8 bits to a byte, LSB-first. +package bitstring + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" +) + +const ( + bitsPerByte = 8 + one = 0x1 +) + +// Decode decodes a compressed bitstring from a base64URL-encoded string. +func Decode(src string) ([]byte, error) { + decodedBits, err := base64.RawURLEncoding.DecodeString(src) + if err != nil { + return nil, err + } + + b := bytes.NewReader(decodedBits) + + r, err := gzip.NewReader(b) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(r); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// BitAt returns the bit in the idx'th position (zero-indexed) in the given bitstring. +func BitAt(bitString []byte, idx int) (bool, error) { + nByte := idx / bitsPerByte + nBit := idx % bitsPerByte + + if idx < 0 || nByte >= len(bitString) { + return false, fmt.Errorf("position is invalid") + } + + bitValue := (bitString[nByte] & (one << nBit)) != 0 + + return bitValue, nil +} + +// Encode gzips a bitstring and encodes it as a raw urlsafe base-64 string. +func Encode(bitString []byte) (string, error) { + var buf bytes.Buffer + + w := gzip.NewWriter(&buf) + if _, err := w.Write(bitString); err != nil { + return "", err + } + + if err := w.Close(); err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(buf.Bytes()), nil +} diff --git a/component/vc/status/resolver/resolver.go b/component/vc/status/resolver/resolver.go new file mode 100644 index 00000000..9e6e0e0a --- /dev/null +++ b/component/vc/status/resolver/resolver.go @@ -0,0 +1,96 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package resolver provides a resolver for remotely-stored credential status list VCs. +package resolver + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" +) + +// Resolver resolves credential status list VCs. +type Resolver struct { + client *http.Client + bearerToken string +} + +// NewResolver creates a Resolver. +func NewResolver(client *http.Client, bearerToken string) *Resolver { + return &Resolver{ + client: client, + bearerToken: bearerToken, + } +} + +// Resolve fetches the VC at the given URI. +func (r *Resolver) Resolve(statusListVCURI string) (*verifiable.Credential, error) { + var ( + vcBytes []byte + err error + ) + + if strings.HasPrefix(statusListVCURI, "did:") { + return nil, fmt.Errorf("did-uri status list VC resolution not supported") + } + + req, e := http.NewRequestWithContext(context.Background(), http.MethodGet, statusListVCURI, nil) + if e != nil { + return nil, e + } + + vcBytes, err = r.sendHTTPRequest(req, http.StatusOK, r.bearerToken) + + if err != nil { + return nil, fmt.Errorf("unable to resolve statusListVCURI: %w", err) + } + + // TODO: need to verify proof on vc - consider if validation also needs to be done (json-ld and json schema) + vc, err := verifiable.ParseCredential( + vcBytes, + verifiable.WithDisabledProofCheck(), + verifiable.WithCredDisableValidation(), + ) + if err != nil { + return nil, fmt.Errorf("failed to parse and verify status vc: %w", err) + } + + return vc, nil +} + +func (r *Resolver) sendHTTPRequest(req *http.Request, status int, token string) ([]byte, error) { + if token != "" { + req.Header.Add("Authorization", "Bearer "+token) + } + + resp, err := r.client.Do(req) + if err != nil { + return nil, err + } + + defer func() { + e := resp.Body.Close() + if e != nil { + fmt.Printf("failed to close message body: %v", e) + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("unable to read response, code=%d: %w", resp.StatusCode, err) + } + + if resp.StatusCode != status { + return nil, fmt.Errorf("expected response code %d, got %d", status, resp.StatusCode) + } + + return body, nil +} diff --git a/component/vc/status/status.go b/component/vc/status/status.go new file mode 100644 index 00000000..71bcd5d4 --- /dev/null +++ b/component/vc/status/status.go @@ -0,0 +1,87 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package status implements a Verifiable Credential Status API Client. +package status + +import ( + "fmt" + + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/api" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/internal/bitstring" +) + +const ( + // RevokedMessage is the Client.VerifyStatus error message when the given verifiable.Credential is revoked. + RevokedMessage = "revoked" +) + +// Client verifies revocation status for Verifiable Credentials. +type Client struct { + ValidatorGetter api.ValidatorGetter + Resolver api.StatusListVCURIResolver +} + +// VerifyStatus verifies the revocation status on the given Verifiable Credential, returning the errorstring "revoked" +// if the given credential's status is revoked, nil if the credential is not revoked, and a different error if +// verification fails. +func (c *Client) VerifyStatus(credential *verifiable.Credential) error { //nolint:gocyclo + if credential.Status == nil { + return fmt.Errorf("vc missing status list field") + } + + validator, err := c.ValidatorGetter(credential.Status.Type) + if err != nil { + return err + } + + err = validator.ValidateStatus(credential.Status) + if err != nil { + return err + } + + statusListIndex, err := validator.GetStatusListIndex(credential.Status) + if err != nil { + return err + } + + statusVCURL, err := validator.GetStatusVCURI(credential.Status) + if err != nil { + return err + } + + statusListVC, err := c.Resolver.Resolve(statusVCURL) + if err != nil { + return err + } + + if statusListVC.Issuer.ID != credential.Issuer.ID { + return fmt.Errorf("issuer of the credential does not match status list vc issuer") + } + + credSubject, ok := statusListVC.Subject.([]verifiable.Subject) + if !ok { + return fmt.Errorf("invalid subject field structure") + } + + bitString, err := bitstring.Decode(credSubject[0].CustomFields["encodedList"].(string)) + if err != nil { + return fmt.Errorf("failed to decode bits: %w", err) + } + + bitSet, err := bitstring.BitAt(bitString, statusListIndex) + if err != nil { + return err + } + + if bitSet { + return fmt.Errorf(RevokedMessage) + } + + return nil +} diff --git a/component/vc/status/status_test.go b/component/vc/status/status_test.go new file mode 100644 index 00000000..6ee1ba44 --- /dev/null +++ b/component/vc/status/status_test.go @@ -0,0 +1,320 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package status_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + "github.com/stretchr/testify/require" + + . "github.com/hyperledger/aries-framework-go-ext/component/vc/status" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/api" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/internal/bitstring" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/resolver" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/validator" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/validator/statuslist2021" +) + +const issuerID = "issuer-id" + +func TestClient_VerifyStatus(t *testing.T) { + t.Run("success", func(t *testing.T) { + client := Client{ + ValidatorGetter: validator.GetValidator, + Resolver: resolver.NewResolver(http.DefaultClient, ""), + } + + statusServer := httptest.NewServer(mockStatusResponseHandler(t, mockStatusVC(t, issuerID, isRevoked{false, true}))) + + defer func() { + statusServer.Close() + }() + + // status: not revoked + err := client.VerifyStatus(&verifiable.Credential{ + Issuer: verifiable.Issuer{ + ID: issuerID, + }, + Status: &verifiable.TypedID{ + ID: "foo-bar", + Type: statuslist2021.StatusList2021Type, + CustomFields: map[string]interface{}{ + statuslist2021.StatusPurpose: "foo", + statuslist2021.StatusListCredential: statusServer.URL, + statuslist2021.StatusListIndex: "0", + }, + }, + }) + require.NoError(t, err) + + // status: revoked + err = client.VerifyStatus(&verifiable.Credential{ + Issuer: verifiable.Issuer{ + ID: issuerID, + }, + + Status: &verifiable.TypedID{ + ID: "foo-bar", + Type: statuslist2021.StatusList2021Type, + CustomFields: map[string]interface{}{ + statuslist2021.StatusPurpose: "foo", + statuslist2021.StatusListCredential: statusServer.URL, + statuslist2021.StatusListIndex: "1", + }, + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), RevokedMessage) + }) + + t.Run("fail", func(t *testing.T) { + t.Run("missing status field", func(t *testing.T) { + client := &Client{} + err := client.VerifyStatus(&verifiable.Credential{}) + require.Error(t, err) + require.Contains(t, err.Error(), "vc missing status list field") + }) + + t.Run("no validator found for status type", func(t *testing.T) { + expectErr := errors.New("expected error") + + client := &Client{ + ValidatorGetter: func(_ string) (api.Validator, error) { + return nil, expectErr + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.ErrorIs(t, err, expectErr) + }) + + t.Run("status field validation error", func(t *testing.T) { + expectErr := errors.New("expected error") + + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{ + ValidateStatusErr: expectErr, + }, nil + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.ErrorIs(t, err, expectErr) + }) + + t.Run("getting status list index", func(t *testing.T) { + expectErr := errors.New("expected error") + + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{ + GetStatusListIndexErr: expectErr, + }, nil + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.ErrorIs(t, err, expectErr) + }) + + t.Run("getting status VC URI", func(t *testing.T) { + expectErr := errors.New("expected error") + + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{ + GetStatusVCURIErr: expectErr, + }, nil + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.ErrorIs(t, err, expectErr) + }) + + t.Run("resolving status VC", func(t *testing.T) { + expectErr := errors.New("expected error") + + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{}, nil + }, + Resolver: &mockResolver{ + Err: expectErr, + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.ErrorIs(t, err, expectErr) + }) + + t.Run("issuer fields don't match", func(t *testing.T) { + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{}, nil + }, + Resolver: &mockResolver{ + Cred: &verifiable.Credential{ + Issuer: verifiable.Issuer{ + ID: "bar", + }, + }, + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Issuer: verifiable.Issuer{ + ID: "foo", + }, + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "issuer of the credential does not match status list vc issuer") + }) + + t.Run("subject has unexpected type", func(t *testing.T) { + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{}, nil + }, + Resolver: &mockResolver{ + Cred: &verifiable.Credential{ + Subject: &mockValidator{}, + }, + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid subject field structure") + }) + + t.Run("status bitstring has invalid format", func(t *testing.T) { + client := &Client{ + ValidatorGetter: func(string) (api.Validator, error) { + return &mockValidator{}, nil + }, + Resolver: &mockResolver{ + Cred: &verifiable.Credential{ + Subject: []verifiable.Subject{ + { + CustomFields: map[string]interface{}{ + "encodedList": ":( this is not base-64 data", + }, + }, + }, + }, + }, + } + err := client.VerifyStatus(&verifiable.Credential{ + Status: &verifiable.TypedID{}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to decode bits") + }) + }) +} + +type mockValidator struct { + ValidateStatusErr error + GetStatusVCURIVal string + GetStatusVCURIErr error + GetStatusListIndexVal int + GetStatusListIndexErr error +} + +func (m *mockValidator) ValidateStatus(*verifiable.TypedID) error { + return m.ValidateStatusErr +} + +func (m *mockValidator) GetStatusVCURI(*verifiable.TypedID) (string, error) { + return m.GetStatusVCURIVal, m.GetStatusVCURIErr +} + +func (m *mockValidator) GetStatusListIndex(*verifiable.TypedID) (int, error) { + return m.GetStatusListIndexVal, m.GetStatusListIndexErr +} + +type mockResolver struct { + Cred *verifiable.Credential + Err error +} + +func (m *mockResolver) Resolve(string) (*verifiable.Credential, error) { + return m.Cred, m.Err +} + +type isRevoked []bool + +func bool2bits(data []bool) []byte { + numBytes := len(data) / 8 + + if len(data)%8 != 0 { + numBytes++ + } + + out := make([]byte, numBytes) + + for i, datum := range data { + if datum { + out[i/8] |= 1 << (i % 8) + } + } + + return out +} + +func mockStatusVC(t *testing.T, issuerID string, vcStatus isRevoked) *verifiable.Credential { + t.Helper() + + statusBits := bool2bits(vcStatus) + + statusEncoded, err := bitstring.Encode(statusBits) + require.NoError(t, err) + + return &verifiable.Credential{ + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + Issuer: verifiable.Issuer{ + ID: issuerID, + }, + Subject: []verifiable.Subject{ + { + CustomFields: map[string]interface{}{ + "encodedList": statusEncoded, + }, + }, + }, + } +} + +func mockStatusResponseHandler(t *testing.T, statusVC *verifiable.Credential) http.HandlerFunc { + t.Helper() + + return func(w http.ResponseWriter, req *http.Request) { + vcBytes, err := statusVC.MarshalJSON() + require.NoError(t, err) + + _, err = w.Write(vcBytes) + require.NoError(t, err) + } +} diff --git a/component/vc/status/validator/statuslist2021/statuslist2021.go b/component/vc/status/validator/statuslist2021/statuslist2021.go new file mode 100644 index 00000000..73cc9729 --- /dev/null +++ b/component/vc/status/validator/statuslist2021/statuslist2021.go @@ -0,0 +1,87 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package statuslist2021 handles client-side validation and parsing for +// Credential Status fields of type StatusList2021Type, as per spec: https://w3c-ccg.github.io/vc-status-list-2021/ +package statuslist2021 + +import ( + "fmt" + "strconv" + + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" +) + +const ( + // StatusList2021Type represents the implementation of VC Status List 2021. + // VC.Status.Type + // Doc: https://w3c-ccg.github.io/vc-status-list-2021/ + StatusList2021Type = "StatusList2021Entry" + + // StatusListCredential stores the link to the status list VC. + // VC.Status.CustomFields key. + StatusListCredential = "statusListCredential" + + // StatusListIndex identifies the bit position of the status value of the VC. + // VC.Status.CustomFields key. + StatusListIndex = "statusListIndex" + + // StatusPurpose for StatusList2021. + // VC.Status.CustomFields key. Only "revocation" value is supported. + StatusPurpose = "statusPurpose" +) + +// Validator validates a Verifiable Credential's Status field against the VC Status List 2021 specification, and +// returns fields for status verification. +// Spec: https://w3c.github.io/vc-status-list-2021/#statuslist2021entry +type Validator struct{} + +// ValidateStatus validates that a Verifiable Credential's Status field matches the VC Status List 2021 specification. +func (v *Validator) ValidateStatus(vcStatus *verifiable.TypedID) error { + if vcStatus == nil { + return fmt.Errorf("vc status does not exist") + } + + if vcStatus.Type != StatusList2021Type { + return fmt.Errorf("vc status %s not supported", vcStatus.Type) + } + + for _, field := range []string{StatusListCredential, StatusListIndex, StatusPurpose} { + if err := isMissingField(vcStatus, field); err != nil { + return err + } + } + + return nil +} + +func isMissingField(vcStatus *verifiable.TypedID, field string) error { + if vcStatus.CustomFields[field] == nil { + return fmt.Errorf("%s field does not exist in vc status", field) + } + + return nil +} + +// GetStatusVCURI returns the ID (URL) of status VC. +func (v *Validator) GetStatusVCURI(vcStatus *verifiable.TypedID) (string, error) { + statusListVC, ok := vcStatus.CustomFields[StatusListCredential].(string) + if !ok { + return "", fmt.Errorf("failed to cast URI of statusListCredential") + } + + return statusListVC, nil +} + +// GetStatusListIndex returns the bit position of the status value of the VC. +func (v *Validator) GetStatusListIndex(vcStatus *verifiable.TypedID) (int, error) { + idx, err := strconv.Atoi(vcStatus.CustomFields[StatusListIndex].(string)) + if err != nil { + return -1, fmt.Errorf("unable to get statusListIndex: %w", err) + } + + return idx, nil +} diff --git a/component/vc/status/validator/validator.go b/component/vc/status/validator/validator.go new file mode 100644 index 00000000..4fdf5296 --- /dev/null +++ b/component/vc/status/validator/validator.go @@ -0,0 +1,26 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package validator holds validation handlers for status fields +// for different formats of verifiable credential status list. +package validator + +import ( + "fmt" + + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/api" + "github.com/hyperledger/aries-framework-go-ext/component/vc/status/validator/statuslist2021" +) + +// GetValidator returns the VC status list validator for the given status type. +func GetValidator(statusType string) (api.Validator, error) { + switch statusType { + case statuslist2021.StatusList2021Type: + return &statuslist2021.Validator{}, nil + default: + return nil, fmt.Errorf("unsupported VCStatusListType %s", statusType) + } +}