Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign1 bells and whistles #2

Merged
merged 4 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand Down
32 changes: 17 additions & 15 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
1 change: 1 addition & 0 deletions example/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto/rand"
_ "crypto/sha256"
"fmt"

cose "github.com/veraison/go-cose"
Expand Down
65 changes: 65 additions & 0 deletions example/sign1.go
Original file line number Diff line number Diff line change
@@ -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")
}
1 change: 1 addition & 0 deletions example/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto/rand"
_ "crypto/sha256"
"fmt"

cose "github.com/veraison/go-cose"
Expand Down
26 changes: 23 additions & 3 deletions sign1_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ErrNilSign1Headers
}

if m.Headers.Protected == nil {
return ErrNilSign1ProtectedHeaders
}

alg, err := getAlg(m.Headers)
if err != nil {
Expand All @@ -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 ErrNilSign1Headers
}

if m.Headers.Protected == nil {
return ErrNilSign1ProtectedHeaders
}

alg, err := getAlg(m.Headers)
if err != nil {
Expand Down
116 changes: 114 additions & 2 deletions sign1_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.Equal(err, ErrNilSign1Headers)

// empty Headers structure has nil ProtectedHeaders
invalid.Headers = &Headers{}

err = invalid.Sign(rand.Reader, external, *signer)
assert.Equal(err, ErrNilSign1ProtectedHeaders)

// 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.Equal(err, ErrNilSign1Headers)

invalid.Headers = &Headers{}

err = invalid.Verify(external, *verifier)
assert.Equal(err, ErrNilSign1ProtectedHeaders)

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")
}