From 6d35c7091706f35644f784aa8ce9439547b4ecf8 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 24 Nov 2020 13:15:16 +0000 Subject: [PATCH 1/4] Check all preconditions are met on Sign1 messages sent for signature and verification --- sign1_verify.go | 26 ++++++++-- sign1_verify_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/sign1_verify.go b/sign1_verify.go index d826b13..d0a69b6 100644 --- a/sign1_verify.go +++ b/sign1_verify.go @@ -36,8 +36,18 @@ func NewSign1Message() *Sign1Message { // Verify verifies the signature on the Sign1Message returning nil on // success or a suitable error if verification fails. -func (m *Sign1Message) Verify(external []byte, verifier Verifier) (err error) { - // TODO(tho) check preconditions +func (m Sign1Message) Verify(external []byte, verifier Verifier) (err error) { + if m.Signature == nil || len(m.Signature) == 0 { + return errors.New("Sign1Message has no signature to verify") + } + + if m.Headers == nil { + return errors.New("Sign1Message has no headers") // TODO(tho) make error + } + + if m.Headers.Protected == nil { + return errors.New("Sign1Message has no protected headers") + } alg, err := getAlg(m.Headers) if err != nil { @@ -62,7 +72,17 @@ func (m *Sign1Message) Verify(external []byte, verifier Verifier) (err error) { // Sign signs a Sign1Message using the provided Signer func (m *Sign1Message) Sign(rand io.Reader, external []byte, signer Signer) (err error) { - // TODO(tho) check preconditions + if m.Signature != nil || len(m.Signature) > 0 { + return errors.New("Sign1Message signature already has signature bytes") + } + + if m.Headers == nil { + return errors.New("Sign1Message has no headers") // TODO(tho) make error + } + + if m.Headers.Protected == nil { + return errors.New("Sign1Message has no protected headers") // TODO(tho) make error + } alg, err := getAlg(m.Headers) if err != nil { diff --git a/sign1_verify_test.go b/sign1_verify_test.go index 531d34a..f4be721 100644 --- a/sign1_verify_test.go +++ b/sign1_verify_test.go @@ -5,16 +5,18 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestSign1Roundtrip(t *testing.T) { +func TestSign1_Roundtrip(t *testing.T) { assert := assert.New(t) + require := require.New(t) msg := NewSign1Message() msg.Payload = []byte("EAT token claims") signer, err := NewSigner(ES256, nil) - assert.Nil(err, "signer creation failed") + require.Nil(err, "signer creation failed") msg.Headers.Protected[algTag] = -7 // ECDSA w/ SHA-256 @@ -35,3 +37,113 @@ func TestSign1Roundtrip(t *testing.T) { err = msg.Verify(external, *verifier) assert.Nil(err, "signature verification failed") } + +func TestSign1_SignErrors(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + signer, err := NewSigner(ES256, nil) + require.Nil(err, "signer creation failed") + + external := []byte("") + + // empty Sign1 structure has nil Headers + invalid := &Sign1Message{} + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Sign1Message has no headers") + + // empty Headers structure has nil ProtectedHeaders + invalid.Headers = &Headers{} + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Sign1Message has no protected headers") + + // signature should be empty before signature is applied + invalid.Signature = []byte("fake signature") + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Sign1Message signature already has signature bytes") + + // empty protected headers don't carry any signature alg + invalid.Signature = nil + invalid.Headers.Protected = map[interface{}]interface{}{} + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Error fetching alg") + + // an inconsistent algorithm + invalid.Headers.Protected[algTag] = 1 // should be -7, i.e.: ECDSA w/ SHA-256 + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Invalid algorithm") + + // an inconsistent signing key + invalid.Headers.Protected[algTag] = -7 // ECDSA w/ SHA-256 + signer.PrivateKey = dsaPrivateKey + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Unrecognized private key type") + + // an inconsistent signer + signer.alg = PS256 + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Signer of type PS256 cannot generate a signature of type ES256") + + // unknown algorithm id + invalid.Headers.Protected[algTag] = -9000 + + err = invalid.Sign(rand.Reader, external, *signer) + assert.EqualError(err, "Algorithm with value -9000 not found") +} + +func TestSign1_VerifyErrors(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + invalid := &Sign1Message{} + + signer, err := NewSigner(ES256, nil) + require.Nil(err) + + verifier := signer.Verifier() + + external := []byte("") + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "Sign1Message has no signature to verify") + + invalid.Signature = []byte("fake signature") + invalid.Headers = nil + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "Sign1Message has no headers") + + invalid.Headers = &Headers{} + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "Sign1Message has no protected headers") + + invalid.Headers.Protected = map[interface{}]interface{}{} + + invalid.Headers.Protected[algTag] = -9000 + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "Algorithm with value -9000 not found") + + invalid.Headers.Protected[algTag] = -41 + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "hash function is not available") + + invalid.Headers.Protected[algTag] = 1 + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "Invalid algorithm") + + invalid.Headers.Protected[algTag] = -7 + + err = invalid.Verify(external, *verifier) + assert.EqualError(err, "invalid signature length: 14") +} From aa3cf054b60b43357c007e4b0590bd448ebebdba Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 24 Nov 2020 14:11:30 +0000 Subject: [PATCH 2/4] Added Sign1 example --- Makefile | 1 + example/sign.go | 1 + example/sign1.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++ example/verify.go | 1 + 4 files changed, 68 insertions(+) create mode 100644 example/sign1.go diff --git a/Makefile b/Makefile index bab6174..53aa809 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ goveralls: smoketest-examples: go run example/sign.go go run example/verify.go + go run example/sign1.go ci: install-golint goveralls install coverage lint vet goveralls -coverprofile=coverage.out -service=circle-ci -repotoken=$(COVERALLS_TOKEN) diff --git a/example/sign.go b/example/sign.go index 30cee3d..9ff82b4 100644 --- a/example/sign.go +++ b/example/sign.go @@ -2,6 +2,7 @@ package main import ( "crypto/rand" + _ "crypto/sha256" "fmt" cose "github.com/veraison/go-cose" diff --git a/example/sign1.go b/example/sign1.go new file mode 100644 index 0000000..69559db --- /dev/null +++ b/example/sign1.go @@ -0,0 +1,65 @@ +package main + +import ( + "crypto/rand" + _ "crypto/sha256" + "log" + + cose "github.com/veraison/go-cose" +) + +func main() { + msg := cose.NewSign1Message() + + // EAT token from Appendix A.2 of draft-ietf-rats-eat + msg.Payload = []byte{ + 0xa7, 0x0a, 0x49, 0x94, 0x8f, 0x88, 0x60, 0xd1, 0x3a, 0x46, + 0x3e, 0x8e, 0x0b, 0x53, 0x01, 0x98, 0xf5, 0x0a, 0x4f, 0xf6, + 0xc0, 0x58, 0x61, 0xc8, 0x86, 0x0d, 0x13, 0xa6, 0x38, 0xea, + 0x4f, 0xe2, 0xfa, 0x0f, 0xf5, 0x10, 0x03, 0x06, 0xc1, 0x1a, + 0x5a, 0xfd, 0x32, 0x2e, 0x0e, 0x03, 0x14, 0xa3, 0x6f, 0x41, + 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x41, 0x70, 0x70, + 0x20, 0x46, 0x6f, 0x6f, 0xa1, 0x0e, 0x01, 0x72, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x20, 0x45, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x20, 0x45, 0x61, 0x74, 0xd8, 0x3d, 0xd2, 0x42, + 0x01, 0x23, 0x6d, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x41, + 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0xa1, 0x0e, 0x01, + } + + // create a signer with a new private key + // ES256 (algId: -7), i.e.: ECDSA w/ SHA-256 from RFC8152 + signer, err := cose.NewSigner(cose.ES256, nil) + if err != nil { + log.Fatalf("signer creation failed: %s", err) + } + + msg.Headers.Protected[1] = -7 // ECDSA w/ SHA-256 + + msg.Headers.Unprotected["kid"] = 1 + + // no external data + err = msg.Sign(rand.Reader, nil, *signer) + if err != nil { + log.Fatalf("signature creation failed: %s\n", err) + } + + log.Printf("COSE Sign1 signature bytes: %x\n", msg.Signature) + + coseSig, err := cose.Marshal(msg) + if err != nil { + log.Fatalf("COSE marshaling failed: %s", err) + } + + log.Printf("COSE Sign1 message: %x\n", coseSig) + + // derive a verifier using the signer's public key and COSE algorithm + verifier := signer.Verifier() + + // Verify + err = msg.Verify(nil, *verifier) + if err != nil { + log.Fatalf("Error verifying the message %+v", err) + } + + log.Println("COSE Sign1 signature verified") +} diff --git a/example/verify.go b/example/verify.go index fa7ba3e..5b42c45 100644 --- a/example/verify.go +++ b/example/verify.go @@ -2,6 +2,7 @@ package main import ( "crypto/rand" + _ "crypto/sha256" "fmt" cose "github.com/veraison/go-cose" From f3a6cfe3509a1aa767fea94aec58bbcbebc5eb68 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 24 Nov 2020 14:24:39 +0000 Subject: [PATCH 3/4] Update README: explain fork and provide a pointer to the Sign1 example --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 98c90c4..0656344 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# Why forking Mozilla go-cose? + +This fork of [Mozilla go-cose](https://github.com/mozilla-services/go-cose) adds [Sign1](https://tools.ietf.org/html/rfc8152#section-4.2) capabilities to the parent package. We hope we can reconcile our fork quite quickly. In the meantime, you can inspect the delta [here](https://github.com/mozilla-services/go-cose/compare/master...veraison:master). + # go-cose [![GitHub CI](https://github.com/veraison/go-cose/workflows/ci/badge.svg)](https://github.com/veraison/go-cose/actions?query=workflow%3Aci) @@ -6,7 +10,7 @@ A [COSE](https://tools.ietf.org/html/rfc8152) library for go. -It currently supports signing and verifying the SignMessage type with the ES{256,384,512} and PS256 algorithms. +It currently supports signing and verifying the SignMessage and Sign1Message types with the ES{256,384,512} and PS256 algorithms. [API docs](https://pkg.go.dev/github.com/veraison/go-cose) @@ -39,6 +43,17 @@ Message signature (ES256): 9411dc5200c1cb67ccd76424ade09ce89c4a8d8d2b66f2bbf70ed Message signature verified ``` +### Signing a message with one signer (Sign1) + +See [example/sign1.go](example/sign1.go) and run it with: + +```console +$ go run example/sign1.go +2020/11/24 14:13:48 COSE Sign1 signature bytes: 424285def70f75909cc763640d7ace555a6dafa576e0ed1b50964b80efbd92746e268ee6e5fa58258ae873bc1a510b1cc964b5c3905100ea0e625253a10150df +2020/11/24 14:13:48 COSE Sign1 message: d28443a10126a10401586da70a49948f8860d13a463e8e0b530198f50a4ff6c05861c8860d13a638ea4fe2fa0ff5100306c11a5afd322e0e0314a36f416e64726f69642041707020466f6fa10e017253656375726520456c656d656e7420456174d83dd24201236d4c696e757820416e64726f6964a10e015840424285def70f75909cc763640d7ace555a6dafa576e0ed1b50964b80efbd92746e268ee6e5fa58258ae873bc1a510b1cc964b5c3905100ea0e625253a10150df +2020/11/24 14:13:48 COSE Sign1 signature verified +``` + ## Development Running tests: From f67e4d89a1b98e5d7ecedc3f5e9e6712ceb421ce Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 24 Nov 2020 14:54:11 +0000 Subject: [PATCH 4/4] Add Sign1-specific error codes --- errors.go | 32 +++++++++++++++++--------------- sign1_verify.go | 8 ++++---- sign1_verify_test.go | 8 ++++---- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/errors.go b/errors.go index 614d497..d2ddbb5 100644 --- a/errors.go +++ b/errors.go @@ -5,19 +5,21 @@ import ( ) var ( - ErrInvalidAlg = errors.New("Invalid algorithm") - ErrAlgNotFound = errors.New("Error fetching alg") - ErrECDSAVerification = errors.New("verification failed ecdsa.Verify") - ErrRSAPSSVerification = errors.New("verification failed rsa.VerifyPSS err crypto/rsa: verification error") - ErrMissingCOSETagForLabel = errors.New("No common COSE tag for label") - ErrMissingCOSETagForTag = errors.New("No common COSE label for tag") - ErrNilSigHeader = errors.New("Signature.headers is nil") - ErrNilSigProtectedHeaders = errors.New("Signature.headers.protected is nil") - ErrNilSignatures = errors.New("SignMessage.signatures is nil. Use AddSignature to add one") - ErrNoSignatures = errors.New("No signatures to sign the message. Use AddSignature to add them") - ErrNoSignerFound = errors.New("No signer found") - ErrNoVerifierFound = errors.New("No verifier found") - ErrUnavailableHashFunc = errors.New("hash function is not available") - ErrUnknownPrivateKeyType = errors.New("Unrecognized private key type") - ErrUnknownPublicKeyType = errors.New("Unrecognized public key type") + ErrInvalidAlg = errors.New("Invalid algorithm") + ErrAlgNotFound = errors.New("Error fetching alg") + ErrECDSAVerification = errors.New("verification failed ecdsa.Verify") + ErrRSAPSSVerification = errors.New("verification failed rsa.VerifyPSS err crypto/rsa: verification error") + ErrMissingCOSETagForLabel = errors.New("No common COSE tag for label") + ErrMissingCOSETagForTag = errors.New("No common COSE label for tag") + ErrNilSigHeader = errors.New("Signature.headers is nil") + ErrNilSigProtectedHeaders = errors.New("Signature.headers.protected is nil") + ErrNilSignatures = errors.New("SignMessage.signatures is nil. Use AddSignature to add one") + ErrNoSignatures = errors.New("No signatures to sign the message. Use AddSignature to add them") + ErrNoSignerFound = errors.New("No signer found") + ErrNoVerifierFound = errors.New("No verifier found") + ErrUnavailableHashFunc = errors.New("hash function is not available") + ErrUnknownPrivateKeyType = errors.New("Unrecognized private key type") + ErrUnknownPublicKeyType = errors.New("Unrecognized public key type") + ErrNilSign1Headers = errors.New("Sign1Message.headers is nil") + ErrNilSign1ProtectedHeaders = errors.New("Sign1Message.headers.protected is nil") ) diff --git a/sign1_verify.go b/sign1_verify.go index d0a69b6..572d2b5 100644 --- a/sign1_verify.go +++ b/sign1_verify.go @@ -42,11 +42,11 @@ func (m Sign1Message) Verify(external []byte, verifier Verifier) (err error) { } if m.Headers == nil { - return errors.New("Sign1Message has no headers") // TODO(tho) make error + return ErrNilSign1Headers } if m.Headers.Protected == nil { - return errors.New("Sign1Message has no protected headers") + return ErrNilSign1ProtectedHeaders } alg, err := getAlg(m.Headers) @@ -77,11 +77,11 @@ func (m *Sign1Message) Sign(rand io.Reader, external []byte, signer Signer) (err } if m.Headers == nil { - return errors.New("Sign1Message has no headers") // TODO(tho) make error + return ErrNilSign1Headers } if m.Headers.Protected == nil { - return errors.New("Sign1Message has no protected headers") // TODO(tho) make error + return ErrNilSign1ProtectedHeaders } alg, err := getAlg(m.Headers) diff --git a/sign1_verify_test.go b/sign1_verify_test.go index f4be721..0b7dc1d 100644 --- a/sign1_verify_test.go +++ b/sign1_verify_test.go @@ -51,13 +51,13 @@ func TestSign1_SignErrors(t *testing.T) { invalid := &Sign1Message{} err = invalid.Sign(rand.Reader, external, *signer) - assert.EqualError(err, "Sign1Message has no headers") + assert.Equal(err, ErrNilSign1Headers) // empty Headers structure has nil ProtectedHeaders invalid.Headers = &Headers{} err = invalid.Sign(rand.Reader, external, *signer) - assert.EqualError(err, "Sign1Message has no protected headers") + assert.Equal(err, ErrNilSign1ProtectedHeaders) // signature should be empty before signature is applied invalid.Signature = []byte("fake signature") @@ -118,12 +118,12 @@ func TestSign1_VerifyErrors(t *testing.T) { invalid.Headers = nil err = invalid.Verify(external, *verifier) - assert.EqualError(err, "Sign1Message has no headers") + assert.Equal(err, ErrNilSign1Headers) invalid.Headers = &Headers{} err = invalid.Verify(external, *verifier) - assert.EqualError(err, "Sign1Message has no protected headers") + assert.Equal(err, ErrNilSign1ProtectedHeaders) invalid.Headers.Protected = map[interface{}]interface{}{}