diff --git a/.gitignore b/.gitignore index f9a314b..08d1b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ dist build umd +# Auto-generated mock files +*_mocks_test.go +gomocks_test.go \ No newline at end of file diff --git a/Makefile b/Makefile index bc3d478..5668cdf 100644 --- a/Makefile +++ b/Makefile @@ -9,15 +9,23 @@ DOCKER_CMD ?= docker GOBIN_PATH=$(abspath .)/build/bin MOCKGEN=$(GOBIN_PATH)/mockgen GOMOCKS=pkg/internal/gomocks +MOCK_VERSION ?=v1.7.0-rc.1 + +OS := $(shell uname) +ifeq ($(OS),$(filter $(OS),Darwin Linux)) + PATH:=$(PATH):$(GOBIN_PATH) +else + PATH:=$(PATH);$(subst /,\\,$(GOBIN_PATH)) +endif .PHONY: all all: clean checks unit-test .PHONY: checks -checks: license lint +checks: generate license lint .PHONY: lint -lint: +lint: generate @scripts/check_lint.sh .PHONY: license @@ -25,10 +33,23 @@ license: @scripts/check_license.sh .PHONY: unit-test -unit-test: +unit-test: generate @scripts/check_unit.sh .PHONY: clean clean: @rm -rf ./.build - @rm -rf coverage*.out \ No newline at end of file + @rm -rf coverage*.out + +.PHONY: generate +generate: + @GOBIN=$(GOBIN_PATH) go install github.com/golang/mock/mockgen@$(MOCK_VERSION) + @go generate ./... + +.PHONY: tidy-modules +tidy-modules: + @find . -type d \( -name build -prune \) -o -name go.mod -print | while read -r gomod_path; do \ + dir_path=$$(dirname "$$gomod_path"); \ + echo "Executing 'go mod tidy' in directory: $$dir_path"; \ + (cd "$$dir_path" && GOPROXY=$(GOPROXY) go mod tidy) || exit 1; \ + done \ No newline at end of file diff --git a/cwt/cwt.go b/cwt/cwt.go new file mode 100644 index 0000000..5938959 --- /dev/null +++ b/cwt/cwt.go @@ -0,0 +1,100 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt + +import ( + "errors" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/go-cose" +) + +const ( + issuerPayloadIndex = 1 + keyIDHeaderIndex = int64(4) +) + +// SignParameters contains parameters of signing for cwt vc. +type SignParameters struct { + KeyID string + CWTAlg cose.Algorithm +} + +// ParseAndCheckProof parses input JWT in serialized form into JSON Web Token and check signature proof. +// if checkIssuer set to true, will check if issuer set by "iss" own key set by "kid" header. +func ParseAndCheckProof( + cwtSerialized []byte, + proofChecker ProofChecker, + checkIssuer bool, +) (*cose.Sign1Message, []byte, error) { + cwtParsed, err := Parse(cwtSerialized) + if err != nil { + return nil, nil, err + } + + var expectedProofIssuer *string + + if checkIssuer { + payload := map[int]interface{}{} + if err = cbor.Unmarshal(cwtParsed.Payload, &payload); err != nil { + return nil, nil, err + } + + iss, ok := payload[issuerPayloadIndex] + if !ok { + return nil, nil, errors.New("check cwt failure: iss claim is required") + } + + issStr, ok := iss.(string) + if !ok { + return nil, nil, errors.New("check cwt failure: iss claim is not a string") + } + + expectedProofIssuer = &issStr + } + + err = CheckProof(cwtParsed, proofChecker, expectedProofIssuer) + if err != nil { + return nil, nil, err + } + + return cwtParsed, cwtParsed.Payload, nil +} + +// Parse parses input CWT in serialized form into JSON Web Token. +func Parse(cwtSerialized []byte) (*cose.Sign1Message, error) { + var message cose.Sign1Message + if err := message.UnmarshalCBOR(cwtSerialized); err != nil { + return nil, err + } + + return &message, nil +} + +// CheckProof checks that jwt have correct signature. +func CheckProof( + message *cose.Sign1Message, + proofChecker ProofChecker, + expectedProofIssuer *string, +) error { + alg, err := message.Headers.Protected.Algorithm() + if err != nil { + return err + } + + keyIDBytes, ok := message.Headers.Unprotected[keyIDHeaderIndex].([]byte) + if !ok { + return errors.New("check cwt failure: kid header is required") + } + + checker := Verifier{ + ProofChecker: proofChecker, + expectedProofIssuer: expectedProofIssuer, + } + + return checker.Verify(message, string(keyIDBytes), alg) +} diff --git a/cwt/cwt_test.go b/cwt/cwt_test.go new file mode 100644 index 0000000..05119f3 --- /dev/null +++ b/cwt/cwt_test.go @@ -0,0 +1,157 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + _ "crypto/sha256" + "encoding/hex" + "errors" + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/veraison/go-cose" + + "github.com/trustbloc/vc-go/cwt" + "github.com/trustbloc/vc-go/proof/checker" +) + +const ( + exampleCWT = "d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30" //nolint:lll +) + +func TestParse(t *testing.T) { + t.Run("success", func(t *testing.T) { + input, decodeErr := hex.DecodeString(exampleCWT) + assert.NoError(t, decodeErr) + + proofChecker := NewMockProofChecker(gomock.NewController(t)) + proofChecker.EXPECT().CheckCWTProof(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(request checker.CheckCWTProofRequest, message *cose.Sign1Message, expectedIssuer string) error { + assert.Equal(t, "AsymmetricECDSA256", request.KeyID) + assert.Equal(t, cose.AlgorithmES256, request.Algo) + assert.NotNil(t, message) + assert.Equal(t, "coap://as.example.com", expectedIssuer) + return nil + }) + + resp, _, err := cwt.ParseAndCheckProof(input, proofChecker, true) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("invalid proof", func(t *testing.T) { + input, decodeErr := hex.DecodeString(exampleCWT) + assert.NoError(t, decodeErr) + + proofChecker := NewMockProofChecker(gomock.NewController(t)) + proofChecker.EXPECT().CheckCWTProof(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(request checker.CheckCWTProofRequest, message *cose.Sign1Message, expectedIssuer string) error { + return errors.New("invalid proof") + }) + + resp, _, err := cwt.ParseAndCheckProof(input, proofChecker, true) + assert.ErrorContains(t, err, "invalid proof") + assert.Nil(t, resp) + }) + + t.Run("invalid cwt", func(t *testing.T) { + resp, _, err := cwt.ParseAndCheckProof([]byte(exampleCWT), nil, true) + assert.ErrorContains(t, err, "invalid COSE_Sign1_Tagged object") + assert.Nil(t, resp) + }) + + t.Run("missing issuer", func(t *testing.T) { + data := map[int]interface{}{ + 100500: "1234567890", + } + + encoded, err := cbor.Marshal(data) + assert.NoError(t, err) + + signature, err := SignP256(encoded) + assert.NoError(t, err) + + resp, _, err := cwt.ParseAndCheckProof(signature, nil, true) + assert.ErrorContains(t, err, "check cwt failure: iss claim is required") + assert.Nil(t, resp) + }) + + t.Run("issuer invalid type", func(t *testing.T) { + data := map[int]interface{}{ + 1: 100500, + } + + encoded, err := cbor.Marshal(data) + assert.NoError(t, err) + + signature, err := SignP256(encoded) + assert.NoError(t, err) + + resp, _, err := cwt.ParseAndCheckProof(signature, nil, true) + assert.ErrorContains(t, err, "check cwt failure: iss claim is not a string") + assert.Nil(t, resp) + }) + + t.Run("invalid data type", func(t *testing.T) { + data := map[string]interface{}{ + "100500": "1234567890", + } + + encoded, err := cbor.Marshal(data) + assert.NoError(t, err) + + signature, err := SignP256(encoded) + assert.NoError(t, err) + + resp, _, err := cwt.ParseAndCheckProof(signature, nil, true) + assert.ErrorContains(t, err, "cbor: cannot unmarshal UTF-8 text string into Go value of type int") + assert.Nil(t, resp) + }) + + t.Run("no algo", func(t *testing.T) { + assert.ErrorContains(t, cwt.CheckProof(&cose.Sign1Message{}, nil, nil), + "algorithm not found") + }) + + t.Run("no key", func(t *testing.T) { + assert.ErrorContains(t, cwt.CheckProof(&cose.Sign1Message{ + Headers: cose.Headers{ + Protected: cose.ProtectedHeader{ + cose.HeaderLabelAlgorithm: cose.AlgorithmES256, + }, + }, + }, nil, nil), + "check cwt failure: kid header is required") + }) +} + +func SignP256(data []byte) ([]byte, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + + signer, err := cose.NewSigner(cose.AlgorithmES256, privateKey) + if err != nil { + return nil, err + } + + // create message header + headers := cose.Headers{ + Protected: cose.ProtectedHeader{ + cose.HeaderLabelAlgorithm: cose.AlgorithmES256, + }, + } + + // sign and marshal message + return cose.Sign1(rand.Reader, signer, headers, data, nil) +} diff --git a/cwt/interfaces.go b/cwt/interfaces.go new file mode 100644 index 0000000..c46b7a6 --- /dev/null +++ b/cwt/interfaces.go @@ -0,0 +1,23 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt + +//go:generate mockgen -destination interfaces_mocks_test.go -package cwt_test -source=interfaces.go +import ( + "github.com/veraison/go-cose" + + "github.com/trustbloc/vc-go/proof/checker" +) + +// ProofChecker used to check proof of jwt vc. +type ProofChecker interface { + CheckCWTProof( + checkCWTRequest checker.CheckCWTProofRequest, + msg *cose.Sign1Message, + expectedProofIssuer string, + ) error +} diff --git a/cwt/wrappers.go b/cwt/wrappers.go new file mode 100644 index 0000000..64c50bc --- /dev/null +++ b/cwt/wrappers.go @@ -0,0 +1,42 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt + +import ( + "strings" + + "github.com/veraison/go-cose" + + "github.com/trustbloc/vc-go/proof/checker" +) + +// Verifier verifies CWT proof. +type Verifier struct { + ProofChecker ProofChecker + expectedProofIssuer *string +} + +// Verify verifies CWT proof. +func (v *Verifier) Verify( + proof *cose.Sign1Message, + keyID string, + algo cose.Algorithm, +) error { + var expectedProofIssuer string + + if v.expectedProofIssuer != nil { + expectedProofIssuer = *v.expectedProofIssuer + } else { + // if expectedProofIssuer not set, we get issuer DID from first part of key id. + expectedProofIssuer = strings.Split(keyID, "#")[0] + } + + return v.ProofChecker.CheckCWTProof(checker.CheckCWTProofRequest{ + KeyID: keyID, + Algo: algo, + }, proof, expectedProofIssuer) +} diff --git a/cwt/wrappers_test.go b/cwt/wrappers_test.go new file mode 100644 index 0000000..7ca733e --- /dev/null +++ b/cwt/wrappers_test.go @@ -0,0 +1,41 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt_test + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/veraison/go-cose" + + "github.com/trustbloc/vc-go/cwt" + "github.com/trustbloc/vc-go/proof/checker" +) + +func TestWrapper(t *testing.T) { + t.Run("extract issuer", func(t *testing.T) { + mockVerifier := NewMockProofChecker(gomock.NewController(t)) + verifier := cwt.Verifier{ + ProofChecker: mockVerifier, + } + + mockVerifier.EXPECT().CheckCWTProof(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func( + request checker.CheckCWTProofRequest, + message *cose.Sign1Message, + expectedProofIssuer string, + ) error { + assert.Equal(t, "coap://as.example.com", expectedProofIssuer) + + return nil + }) + + assert.NoError(t, verifier.Verify(&cose.Sign1Message{}, + "coap://as.example.com#AsymmetricECDSA256#321232131", 0)) + }) +} diff --git a/go.mod b/go.mod index c7dc094..9ed1ecb 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,9 @@ require ( github.com/VictoriaMetrics/fastcache v1.5.7 github.com/btcsuite/btcd/btcec/v2 v2.1.3 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce + github.com/fxamacker/cbor/v2 v2.5.0 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/kawamuray/jsonpath v0.0.0-20201211160320-7483bafabd7e github.com/mitchellh/mapstructure v1.5.0 @@ -24,6 +26,7 @@ require ( github.com/trustbloc/bbs-signature-go v1.0.2-0.20240117165819-e99610e107f4 github.com/trustbloc/did-go v1.1.1-0.20240117181910-cb9c77016955 github.com/trustbloc/kms-go v1.1.1-0.20240117181216-c38a74431167 + github.com/veraison/go-cose v1.1.0 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) @@ -52,6 +55,7 @@ require ( github.com/teserakt-io/golang-ed25519 v0.0.0-20210104091850-3888c087a4c8 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect golang.org/x/crypto v0.17.0 // indirect diff --git a/go.sum b/go.sum index be90536..a1f7004 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= @@ -132,6 +134,10 @@ github.com/trustbloc/did-go v1.1.1-0.20240117181910-cb9c77016955 h1:pVe5+rmEzDuu github.com/trustbloc/did-go v1.1.1-0.20240117181910-cb9c77016955/go.mod h1:PXsxEF78QvuExNO3WkumlYvbrjLH+AN8P74D8IOMABo= github.com/trustbloc/kms-go v1.1.1-0.20240117181216-c38a74431167 h1:tgzKqzUJRLg0sY/BmHEDpMQ3TZOtC7u1RDYEMPeqnCI= github.com/trustbloc/kms-go v1.1.1-0.20240117181216-c38a74431167/go.mod h1:23chV97aYVcT3ihO8xO9+T0KZn9lFGbk2KXhJNaHbXE= +github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= +github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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= @@ -149,8 +155,10 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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/sync v0.0.0-20190423024810-112230192c58/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= @@ -158,6 +166,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= diff --git a/proof/checker/checker.go b/proof/checker/checker.go index 5f3134c..ad9253f 100644 --- a/proof/checker/checker.go +++ b/proof/checker/checker.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package checker import ( + "crypto" "fmt" "github.com/tidwall/gjson" @@ -15,6 +16,7 @@ import ( "github.com/trustbloc/kms-go/doc/jose" "github.com/trustbloc/kms-go/doc/jose/jwk" "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/crypto-ext/pubkey" proofdesc "github.com/trustbloc/vc-go/proof" @@ -46,6 +48,10 @@ type jwtCheckDescriptor struct { proofDescriptor proofdesc.JWTProofDescriptor } +type cwtCheckDescriptor struct { + proofDescriptor proofdesc.JWTProofDescriptor +} + // nolint: gochecknoglobals var possibleIssuerPath = []string{ "vc.issuer.id", @@ -59,6 +65,7 @@ var possibleIssuerPath = []string{ type ProofCheckerBase struct { supportedLDProofs []ldCheckDescriptor supportedJWTProofs []jwtCheckDescriptor + supportedCWTProofs []cwtCheckDescriptor signatureVerifiers []signatureVerifier } @@ -104,6 +111,17 @@ func WithJWTAlg(proofDescs ...proofdesc.JWTProofDescriptor) Opt { } } +// WithCWTAlg option to set supported jwt algs. +func WithCWTAlg(proofDescs ...proofdesc.JWTProofDescriptor) Opt { + return func(c *ProofCheckerBase) { + for _, proofDesc := range proofDescs { + c.supportedCWTProofs = append(c.supportedCWTProofs, cwtCheckDescriptor{ + proofDescriptor: proofDesc, + }) + } + } +} + // WithSignatureVerifiers option to set signature verifiers. func WithSignatureVerifiers(verifiers ...signatureVerifier) Opt { return func(c *ProofCheckerBase) { @@ -214,6 +232,49 @@ func (c *ProofChecker) CheckJWTProof(headers jose.Headers, expectedProofIssuer s return verifier.Verify(signature, msg, pubKey) } +// CheckCWTProof check cwt proof. +func (c *ProofChecker) CheckCWTProof( + checkCWTRequest CheckCWTProofRequest, + msg *cose.Sign1Message, + expectedProofIssuer string, +) error { + if checkCWTRequest.KeyID == "" { + return fmt.Errorf("missed kid in cwt header") + } + + if checkCWTRequest.Algo == 0 { + return fmt.Errorf("missed alg in cwt header") + } + + vm, err := c.verificationMethodResolver.ResolveVerificationMethod(checkCWTRequest.KeyID, expectedProofIssuer) + if err != nil { + return fmt.Errorf("invalid public key id: %w", err) + } + + supportedProof, err := c.getSupportedCWTProofByAlg(checkCWTRequest.Algo) + if err != nil { + return err + } + + pubKey, err := convertToPublicKey(supportedProof.proofDescriptor.SupportedVerificationMethods(), vm) + if err != nil { + return fmt.Errorf("cwt with alg %s check: %w", checkCWTRequest.Algo, err) + } + + finalPubKey := crypto.PublicKey(pubKey) + + if pubKey.JWK != nil { + finalPubKey = pubKey.JWK.Key + } + + verifier, err := cose.NewVerifier(checkCWTRequest.Algo, finalPubKey) + if err != nil { + return err + } + + return msg.Verify(nil, verifier) +} + // FindIssuer finds issuer in payload. func (c *ProofChecker) FindIssuer(payload []byte) string { parsed := gjson.ParseBytes(payload) @@ -229,7 +290,8 @@ func (c *ProofChecker) FindIssuer(payload []byte) string { func convertToPublicKey( supportedMethods []proofdesc.SupportedVerificationMethod, - vm *vermethod.VerificationMethod) (*pubkey.PublicKey, error) { + vm *vermethod.VerificationMethod, +) (*pubkey.PublicKey, error) { for _, supported := range supportedMethods { if supported.VerificationMethodType != vm.Type { continue @@ -286,6 +348,16 @@ func (c *ProofCheckerBase) getSupportedProofByAlg(jwtAlg string) (jwtCheckDescri return jwtCheckDescriptor{}, fmt.Errorf("unsupported jwt alg: %s", jwtAlg) } +func (c *ProofCheckerBase) getSupportedCWTProofByAlg(cwtAlg cose.Algorithm) (cwtCheckDescriptor, error) { + for _, supported := range c.supportedCWTProofs { + if supported.proofDescriptor.CWTAlgorithm() == cwtAlg { + return supported, nil + } + } + + return cwtCheckDescriptor{}, fmt.Errorf("unsupported cwt alg: %s", cwtAlg) +} + func (c *ProofCheckerBase) getSignatureVerifier(keyType kms.KeyType) (signatureVerifier, error) { for _, verifier := range c.signatureVerifiers { if verifier.SupportedKeyType(keyType) { diff --git a/proof/checker/checker_test.go b/proof/checker/checker_test.go index 37a8ef9..5042785 100644 --- a/proof/checker/checker_test.go +++ b/proof/checker/checker_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/trustbloc/did-go/doc/ld/proof" "github.com/trustbloc/kms-go/doc/jose" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof/checker" "github.com/trustbloc/vc-go/proof/jwtproofs/eddsa" @@ -33,6 +34,10 @@ func TestProofChecker_AllLD(t *testing.T) { t.Run("Test With all jwt proofs", func(t *testing.T) { commontest.TestEmbeddedProofChecker(t) }) + + t.Run("Test With all cwt proofs", func(t *testing.T) { + commontest.TestAllCWTSignersVerifiers(t) + }) } func TestProofChecker_CheckLDProof(t *testing.T) { @@ -95,6 +100,56 @@ func TestProofCheckerIssuer(t *testing.T) { require.ErrorContains(t, err, `invalid public key id: invalid issuer. expected "awesome" got "abcd"`) } +func TestProofChecker_CheckCWTProof(t *testing.T) { + testable := checker.New( + testsupport.NewSingleKeyResolver("lookupId", []byte{}, "test", "issuerID"), + checker.WithCWTAlg(eddsa.New())) + + err := testable.CheckCWTProof(checker.CheckCWTProofRequest{ + Algo: cose.AlgorithmEd25519, + }, &cose.Sign1Message{}, "issuerID") + require.ErrorContains(t, err, "missed kid in cwt header") + + err = testable.CheckCWTProof(checker.CheckCWTProofRequest{ + KeyID: "tid", + }, &cose.Sign1Message{}, "issuerID") + require.ErrorContains(t, err, "missed alg in cwt header") + + err = testable.CheckCWTProof(checker.CheckCWTProofRequest{ + KeyID: "tid", + Algo: 1, + }, &cose.Sign1Message{}, "issuerID") + require.ErrorContains(t, err, "invalid public key id") + + err = testable.CheckCWTProof(checker.CheckCWTProofRequest{ + KeyID: "lookupId", + Algo: 1, + }, &cose.Sign1Message{}, "issuerID") + require.ErrorContains(t, err, "unsupported cwt alg:") + + err = testable.CheckCWTProof(checker.CheckCWTProofRequest{ + KeyID: "lookupId", + Algo: cose.AlgorithmEd25519, + }, &cose.Sign1Message{}, "issuerID") + require.ErrorContains(t, err, "can't verifiy with \"test\" verification method") +} + +func TestProofCheckerIssuerCwt(t *testing.T) { + testable := checker.New( + testsupport.NewSingleKeyResolver("lookupId", []byte{}, "test", "awesome"), + checker.WithCWTAlg(eddsa.New())) + + err := testable.CheckCWTProof( + checker.CheckCWTProofRequest{ + KeyID: "tid", + Algo: cose.AlgorithmEd25519, + }, + &cose.Sign1Message{}, + "abcd") + + require.ErrorContains(t, err, `invalid public key id: invalid issuer. expected "awesome" got "abcd"`) +} + func TestEmbeddedVMProofChecker_CheckJWTProof(t *testing.T) { testable := checker.NewEmbeddedVMProofChecker( &vermethod.VerificationMethod{Type: "test"}, diff --git a/proof/checker/types.go b/proof/checker/types.go new file mode 100644 index 0000000..3df9b2f --- /dev/null +++ b/proof/checker/types.go @@ -0,0 +1,15 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package checker + +import "github.com/veraison/go-cose" + +// CheckCWTProofRequest is the request for checking a CWT proof. +type CheckCWTProofRequest struct { + KeyID string + Algo cose.Algorithm +} diff --git a/proof/creator/creator.go b/proof/creator/creator.go index db4f147..02ef155 100644 --- a/proof/creator/creator.go +++ b/proof/creator/creator.go @@ -13,7 +13,9 @@ import ( "github.com/trustbloc/did-go/doc/ld/proof" "github.com/trustbloc/kms-go/doc/jose" "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" + "github.com/trustbloc/vc-go/cwt" "github.com/trustbloc/vc-go/jwt" proofdesc "github.com/trustbloc/vc-go/proof" ) @@ -168,6 +170,16 @@ func (c *ProofCreator) SignJWT(params jwt.SignParameters, data []byte) ([]byte, return supportedProof.cryptographicSigner.Sign(data) } +// SignCWT will sign document and return signature. +func (c *ProofCreator) SignCWT(params cwt.SignParameters, cborData []byte) ([]byte, error) { + supportedProof, err := c.getSupportedProofByCwtAlg(params.CWTAlg) + if err != nil { + return nil, err + } + + return supportedProof.cryptographicSigner.Sign(cborData) +} + // CreateJWTHeaders creates correct jwt headers. func (c *ProofCreator) CreateJWTHeaders(params jwt.SignParameters) (jose.Headers, error) { headers := map[string]interface{}{ @@ -191,6 +203,16 @@ func (c *ProofCreator) getSupportedProof(proofType string) (ldProofCreateDescrip return ldProofCreateDescriptor{}, fmt.Errorf("unsupported proof type: %s", proofType) } +func (c *ProofCreator) getSupportedProofByCwtAlg(cwtAlg cose.Algorithm) (jwtProofCreateDescriptor, error) { + for _, supported := range c.supportedJWTAlgs { + if supported.proofDescriptor.CWTAlgorithm() == cwtAlg { + return supported, nil + } + } + + return jwtProofCreateDescriptor{}, fmt.Errorf("unsupported cwt alg: %s", cwtAlg.String()) +} + func (c *ProofCreator) getSupportedProofByAlg(jwtAlg string) (jwtProofCreateDescriptor, error) { for _, supported := range c.supportedJWTAlgs { if supported.proofDescriptor.JWTAlgorithm() == jwtAlg { diff --git a/proof/creator/creator_test.go b/proof/creator/creator_test.go index 1283094..895e518 100644 --- a/proof/creator/creator_test.go +++ b/proof/creator/creator_test.go @@ -20,4 +20,8 @@ func TestProofCreator_Common(t *testing.T) { t.Run("Test With all jwt proofs", func(t *testing.T) { commontest.TestAllJWTSignersVerifiers(t) }) + + t.Run("Test With all cwt proofs", func(t *testing.T) { + commontest.TestAllCWTSignersVerifiers(t) + }) } diff --git a/proof/defaults/all.go b/proof/defaults/all.go index 2f56009..e544a2e 100644 --- a/proof/defaults/all.go +++ b/proof/defaults/all.go @@ -11,6 +11,7 @@ import ( "github.com/trustbloc/vc-go/crypto-ext/verifiers/ecdsa" "github.com/trustbloc/vc-go/crypto-ext/verifiers/ed25519" "github.com/trustbloc/vc-go/crypto-ext/verifiers/rsa" + proofdesc "github.com/trustbloc/vc-go/proof" "github.com/trustbloc/vc-go/proof/checker" "github.com/trustbloc/vc-go/proof/jwtproofs/eddsa" "github.com/trustbloc/vc-go/proof/jwtproofs/es256" @@ -34,6 +35,9 @@ type verificationMethodResolver interface { // NewDefaultProofChecker creates proof checker with all available validation algorithms. func NewDefaultProofChecker(verificationMethodResolver verificationMethodResolver) *checker.ProofChecker { + jwtCheckers := []proofdesc.JWTProofDescriptor{ + eddsa.New(), es256.New(), es256k.New(), es384.New(), es521.New(), rs256.New(), ps256.New(), + } return checker.New(verificationMethodResolver, checker.WithSignatureVerifiers(ed25519.New(), bbs.NewBBSG2SignatureVerifier(), rsa.NewPS256(), rsa.NewRS256(), @@ -46,6 +50,7 @@ func NewDefaultProofChecker(verificationMethodResolver verificationMethodResolve jsonwebsignature2020.New(), ), checker.WithLDProofTypeEx(bbsblssignatureproof2020.New(), bbs.NewBBSG2SignatureProofVerifier()), - checker.WithJWTAlg(eddsa.New(), es256.New(), es256k.New(), es384.New(), es521.New(), rs256.New(), ps256.New()), + checker.WithJWTAlg(jwtCheckers...), + checker.WithCWTAlg(jwtCheckers...), ) } diff --git a/proof/descriptor.go b/proof/descriptor.go index 79e44bb..4052100 100644 --- a/proof/descriptor.go +++ b/proof/descriptor.go @@ -9,6 +9,7 @@ package proof import ( "github.com/trustbloc/did-go/doc/ld/processor" "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" ) // SupportedVerificationMethod describes verification methods that supported by proof checker. @@ -37,6 +38,7 @@ type LDProofDescriptor interface { // JWTProofDescriptor describes jwt proof. type JWTProofDescriptor interface { JWTAlgorithm() string + CWTAlgorithm() cose.Algorithm SupportedVerificationMethods() []SupportedVerificationMethod } diff --git a/proof/jwtproofs/eddsa/proof.go b/proof/jwtproofs/eddsa/proof.go index f9ce243..9cf84e7 100644 --- a/proof/jwtproofs/eddsa/proof.go +++ b/proof/jwtproofs/eddsa/proof.go @@ -8,6 +8,7 @@ package eddsa import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -59,3 +60,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return cose.AlgorithmEd25519 +} diff --git a/proof/jwtproofs/es256/proof.go b/proof/jwtproofs/es256/proof.go index 0718db7..65f2103 100644 --- a/proof/jwtproofs/es256/proof.go +++ b/proof/jwtproofs/es256/proof.go @@ -8,6 +8,7 @@ package es256 import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -58,3 +59,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return cose.AlgorithmES256 +} diff --git a/proof/jwtproofs/es256k/proof.go b/proof/jwtproofs/es256k/proof.go index 4f9a5a1..bb799ae 100644 --- a/proof/jwtproofs/es256k/proof.go +++ b/proof/jwtproofs/es256k/proof.go @@ -8,6 +8,7 @@ package es256k import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -70,3 +71,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return 0 +} diff --git a/proof/jwtproofs/es384/proof.go b/proof/jwtproofs/es384/proof.go index 2a8a090..63eea84 100644 --- a/proof/jwtproofs/es384/proof.go +++ b/proof/jwtproofs/es384/proof.go @@ -8,6 +8,7 @@ package es384 import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -58,3 +59,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return cose.AlgorithmES384 +} diff --git a/proof/jwtproofs/es521/proof.go b/proof/jwtproofs/es521/proof.go index 3d6abb8..79fa166 100644 --- a/proof/jwtproofs/es521/proof.go +++ b/proof/jwtproofs/es521/proof.go @@ -8,6 +8,7 @@ package es521 import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -58,3 +59,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return 0 +} diff --git a/proof/jwtproofs/ps256/proof.go b/proof/jwtproofs/ps256/proof.go index 6420ef7..1e71c7c 100644 --- a/proof/jwtproofs/ps256/proof.go +++ b/proof/jwtproofs/ps256/proof.go @@ -8,6 +8,7 @@ package ps256 import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -53,3 +54,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return cose.AlgorithmPS256 +} diff --git a/proof/jwtproofs/rs256/proof.go b/proof/jwtproofs/rs256/proof.go index 160656a..b37dea8 100644 --- a/proof/jwtproofs/rs256/proof.go +++ b/proof/jwtproofs/rs256/proof.go @@ -8,6 +8,7 @@ package rs256 import ( "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/proof" ) @@ -53,3 +54,8 @@ func (s *Proof) SupportedVerificationMethods() []proof.SupportedVerificationMeth func (s *Proof) JWTAlgorithm() string { return JWTAlg } + +// CWTAlgorithm return cwt algorithm that corresponds to VerificationMethod. +func (s *Proof) CWTAlgorithm() cose.Algorithm { + return 0 +} diff --git a/proof/testsupport/commontest/commontest.go b/proof/testsupport/commontest/commontest.go index 5befc31..d6f7b16 100644 --- a/proof/testsupport/commontest/commontest.go +++ b/proof/testsupport/commontest/commontest.go @@ -9,18 +9,24 @@ package commontest import ( "crypto/ed25519" "crypto/rand" + "fmt" "testing" + "github.com/fxamacker/cbor/v2" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" jsonld "github.com/trustbloc/did-go/doc/ld/processor" ldtestutil "github.com/trustbloc/did-go/doc/ld/testutil" "github.com/trustbloc/kms-go/spi/kms" + "github.com/veraison/go-cose" "github.com/trustbloc/vc-go/crypto-ext/testutil" + "github.com/trustbloc/vc-go/cwt" "github.com/trustbloc/vc-go/proof/creator" "github.com/trustbloc/vc-go/proof/jwtproofs/eddsa" "github.com/trustbloc/vc-go/proof/testsupport" "github.com/trustbloc/vc-go/verifiable" + cwt2 "github.com/trustbloc/vc-go/verifiable/cwt" ) const testCredential = ` @@ -66,6 +72,15 @@ type jwtTestCase struct { verFail bool } +type cwtTestCase struct { + Alg verifiable.JWSAlgorithm + CborAlg cose.Algorithm + proofCreator *creator.ProofCreator + signingKey testsupport.SigningKey + fail bool + verFail bool +} + // TestAllLDSignersVerifiers tests all supported ld proof types. func TestAllLDSignersVerifiers(t *testing.T) { docLoader, dlErr := ldtestutil.DocumentLoader() @@ -184,6 +199,65 @@ func TestAllJWTSignersVerifiers(t *testing.T) { } } +// TestAllCWTSignersVerifiers tests all supported jwt proof types. +func TestAllCWTSignersVerifiers(t *testing.T) { + _, ldErr := ldtestutil.DocumentLoader() + require.NoError(t, ldErr) + + allKeyTypes := []testsupport.SigningKey{ + {Type: kms.ED25519Type, PublicKeyID: "did:example:12345#key-1"}, + {Type: kms.ECDSAP256TypeIEEEP1363, PublicKeyID: "did:example:12345#key-2"}, + {Type: kms.ECDSASecp256k1TypeIEEEP1363, PublicKeyID: "did:example:12345#key-3"}, + {Type: kms.ECDSAP384TypeIEEEP1363, PublicKeyID: "did:example:12345#key-4"}, + {Type: kms.ECDSAP521TypeIEEEP1363, PublicKeyID: "did:example:12345#key-5"}, + {Type: kms.RSARS256Type, PublicKeyID: "did:example:12345#key-6"}, + } + + proofCreators, proofChecker := testsupport.NewKMSSignersAndVerifier(t, allKeyTypes) + + testCases := []cwtTestCase{ + {Alg: verifiable.EdDSA, proofCreator: proofCreators[0], signingKey: allKeyTypes[0], CborAlg: cose.AlgorithmEd25519}, + {Alg: verifiable.ECDSASecp256r1, proofCreator: proofCreators[1], signingKey: allKeyTypes[1], CborAlg: cose.AlgorithmES256}, + {Alg: verifiable.ECDSASecp384r1, proofCreator: proofCreators[3], signingKey: allKeyTypes[3], CborAlg: cose.AlgorithmES384}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("key id %v and algo %s", + testCase.signingKey.PublicKeyID, + testCase.CborAlg.String(), + ), func(t *testing.T) { + data := "1234567890" + encoded, err := cbor.Marshal(data) + assert.NoError(t, err) + msg := &cose.Sign1Message{ + Headers: cose.Headers{ + Protected: cose.ProtectedHeader{ + cose.HeaderLabelAlgorithm: testCase.CborAlg, + }, + Unprotected: map[interface{}]interface{}{ + int64(4): []byte(testCase.signingKey.PublicKeyID), + }, + }, + Payload: encoded, + } + + signData, err := cwt2.GetProofValue(msg) + assert.NoError(t, err) + + signed, err := testCase.proofCreator.SignCWT(cwt.SignParameters{ + KeyID: testCase.signingKey.PublicKeyID, + CWTAlg: testCase.CborAlg, + }, signData) + assert.NoError(t, err) + + msg.Signature = signed + + assert.NotNil(t, signed) + assert.NoError(t, cwt.CheckProof(msg, proofChecker, nil)) + }) + } +} + // TestEmbeddedProofChecker tests embedded proof case. func TestEmbeddedProofChecker(t *testing.T) { docLoader, ldErr := ldtestutil.DocumentLoader() diff --git a/verifiable/cwt/cbor.go b/verifiable/cwt/cbor.go new file mode 100644 index 0000000..58a3d0e --- /dev/null +++ b/verifiable/cwt/cbor.go @@ -0,0 +1,101 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt + +import ( + "errors" + "io" + + "github.com/fxamacker/cbor/v2" +) + +// Pre-configured modes for CBOR encoding and decoding. +var ( + encMode cbor.EncMode //nolint:gochecknoglobals + decModeWithTagsForbidden cbor.DecMode //nolint:gochecknoglobals +) + +func init() { // nolint:gochecknoinits + var err error + + // init encode mode + encOpts := cbor.EncOptions{ + Sort: cbor.SortCoreDeterministic, // sort map keys + IndefLength: cbor.IndefLengthForbidden, // no streaming + } + + encMode, err = encOpts.EncMode() + if err != nil { + panic(err) + } + + // init decode mode + decOpts := cbor.DecOptions{ + DupMapKey: cbor.DupMapKeyEnforcedAPF, // duplicated key not allowed + IndefLength: cbor.IndefLengthForbidden, // no streaming + IntDec: cbor.IntDecConvertSigned, // decode CBOR uint/int to Go int64 + } + decOpts.TagsMd = cbor.TagsForbidden + decModeWithTagsForbidden, err = decOpts.DecMode() + + if err != nil { + panic(err) + } +} + +// deterministicBinaryString converts a bstr into the deterministic encoding. +// +// Reference: https://www.rfc-editor.org/rfc/rfc9052.html#section-9 +func deterministicBinaryString(data cbor.RawMessage) (cbor.RawMessage, error) { //nolint:gocyclo + if len(data) == 0 { + return nil, io.EOF + } + + if data[0]>>5 != 2 { // major type 2: bstr + return nil, errors.New("cbor: require bstr type") + } + + // fast path: return immediately if bstr is already deterministic + if err := decModeWithTagsForbidden.Valid(data); err != nil { + return nil, err + } + + ai := data[0] & 0x1f + if ai < 24 { + return data, nil + } + + switch ai { + case 24: + if data[1] >= 24 { + return data, nil + } + case 25: + if data[1] != 0 { + return data, nil + } + case 26: + if data[1] != 0 || data[2] != 0 { + return data, nil + } + case 27: + if data[1] != 0 || data[2] != 0 || data[3] != 0 || data[4] != 0 { + return data, nil + } + } + + // slow path: convert by re-encoding + // error checking is not required since `data` has been validataed + var s []byte + + err := decModeWithTagsForbidden.Unmarshal(data, &s) + if err != nil { + return nil, err + } + + return encMode.Marshal(s) +} diff --git a/verifiable/cwt/cwt.go b/verifiable/cwt/cwt.go new file mode 100644 index 0000000..509d80f --- /dev/null +++ b/verifiable/cwt/cwt.go @@ -0,0 +1,41 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package cwt + +import ( + "github.com/fxamacker/cbor/v2" + "github.com/veraison/go-cose" +) + +// GetProofValue returns the proof value for the given COSE_Sign1 message. +func GetProofValue(message *cose.Sign1Message) ([]byte, error) { + var protected cbor.RawMessage + protected, err := message.Headers.MarshalProtected() + + if err != nil { + return nil, err + } + + cborProtectedData, err := deterministicBinaryString(protected) + if err != nil { + return nil, err + } + + sigStructure := []interface{}{ + "Signature1", // context + cborProtectedData, // body_protected + []byte{}, // external_aad + message.Payload, // payload + } + + cborData, err := cbor.Marshal(sigStructure) + if err != nil { + return nil, err + } + + return cborData, nil +}