blob: 890e7ba19099dbe43247f6173c3c2889c6e046c7 [file] [log] [blame]
package cose
import (
"bytes"
"crypto"
"crypto/rand"
"reflect"
"testing"
)
func TestSign1Message_MarshalCBOR(t *testing.T) {
tests := []struct {
name string
m *Sign1Message
want []byte
wantErr bool
}{
{
name: "valid message",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("foo"),
Signature: []byte("bar"),
},
want: []byte{
0xd2, // tag
0x84,
0x43, 0xa1, 0x01, 0x26, // protected
0xa1, 0x04, 0x18, 0x2a, // unprotected
0x43, 0x66, 0x6f, 0x6f, // payload
0x43, 0x62, 0x61, 0x72, // signature
},
},
{
name: "nil message",
m: nil,
wantErr: true,
},
{
name: "nil payload",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: nil,
Signature: []byte("bar"),
},
want: []byte{
0xd2, // tag
0x84,
0x43, 0xa1, 0x01, 0x26, // protected
0xa1, 0x04, 0x18, 0x2a, // unprotected
0xf6, // payload
0x43, 0x62, 0x61, 0x72, // signature
},
},
{
name: "nil signature",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("foo"),
Signature: nil,
},
wantErr: true,
},
{
name: "empty signature",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: nil,
Signature: []byte{},
},
wantErr: true,
},
{
name: "invalid protected header",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: make(chan bool),
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("foo"),
Signature: []byte("bar"),
},
wantErr: true,
},
{
name: "invalid unprotected header",
m: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: make(chan bool),
},
},
Payload: []byte("foo"),
Signature: []byte("bar"),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.m.MarshalCBOR()
if (err != nil) != tt.wantErr {
t.Errorf("Sign1Message.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Sign1Message.MarshalCBOR() = %v, want %v", got, tt.want)
}
})
}
}
func TestSign1Message_UnmarshalCBOR(t *testing.T) {
// test nil pointer
t.Run("nil Sign1Message pointer", func(t *testing.T) {
var msg *Sign1Message
data := []byte{0xd2, 0x84, 0x40, 0xa0, 0xf6, 0x41, 0x00}
if err := msg.UnmarshalCBOR(data); err == nil {
t.Errorf("want error on nil *Sign1Message")
}
})
// test others
tests := []struct {
name string
data []byte
want Sign1Message
wantErr bool
}{
{
name: "valid message",
data: []byte{
0xd2, // tag
0x84,
0x43, 0xa1, 0x01, 0x26, // protected
0xa1, 0x04, 0x18, 0x2a, // unprotected
0x43, 0x66, 0x6f, 0x6f, // payload
0x43, 0x62, 0x61, 0x72, // signature
},
want: Sign1Message{
Headers: Headers{
RawProtected: []byte{0x43, 0xa1, 0x01, 0x26},
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
RawUnprotected: []byte{0xa1, 0x04, 0x18, 0x2a},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: int64(42),
},
},
Payload: []byte("foo"),
Signature: []byte("bar"),
},
},
{
name: "valid message with nil payload",
data: []byte{
0xd2, // tag
0x84,
0x43, 0xa1, 0x01, 0x26, // protected
0xa1, 0x04, 0x18, 0x2a, // unprotected
0xf6, // payload
0x43, 0x62, 0x61, 0x72, // signature
},
want: Sign1Message{
Headers: Headers{
RawProtected: []byte{0x43, 0xa1, 0x01, 0x26},
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
RawUnprotected: []byte{0xa1, 0x04, 0x18, 0x2a},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: int64(42),
},
},
Payload: nil,
Signature: []byte("bar"),
},
},
{
name: "nil CBOR data",
data: nil,
wantErr: true,
},
{
name: "empty CBOR data",
data: []byte{},
wantErr: true,
},
{
name: "invalid message with valid prefix", // issue #29
data: []byte{0xd2, 0x84, 0xf7, 0xf7, 0xf7, 0xf7},
wantErr: true,
},
{
name: "tagged signature", // issue #30
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf6, // nil payload
0xcb, 0xa1, 0x00, // tagged signature
},
wantErr: true,
},
{
name: "nil signature",
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf6, // payload
0xf6, // nil signature
},
wantErr: true,
},
{
name: "empty signature",
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf6, // payload
0x40, // empty signature
},
wantErr: true,
},
{
name: "mismatch tag",
data: []byte{
0xd3, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf6, // payload
0x41, 0x00, // signature
},
wantErr: true,
},
{
name: "mismatch type",
data: []byte{
0xd2, 0x40,
},
wantErr: true,
},
{
name: "smaller array size",
data: []byte{
0xd2, 0x83, // prefix
0x40, 0xa0, // empty headers
0xf6, // payload
},
wantErr: true,
},
{
name: "larger array size",
data: []byte{
0xd2, 0x85, // prefix
0x40, 0xa0, // empty headers
0xf6, // payload
0x41, 0x00, // signature
0x40,
},
wantErr: true,
},
{
name: "undefined payload",
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf7, // undefined payload
0x41, 0x00, // signature
},
wantErr: true,
},
{
name: "payload as a byte array",
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0x80, // payload
0x41, 0x00, // signature
},
wantErr: true,
},
{
name: "signature as a byte array",
data: []byte{
0xd2, 0x84, // prefix
0x40, 0xa0, // empty headers
0xf6, // nil payload
0x81, 0x00, // signature
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got Sign1Message
if err := got.UnmarshalCBOR(tt.data); (err != nil) != tt.wantErr {
t.Errorf("Sign1Message.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Sign1Message.UnmarshalCBOR() = %v, want %v", got, tt.want)
}
})
}
}
func TestSign1Message_Sign(t *testing.T) {
// generate key and set up signer / verifier
alg := AlgorithmES256
key := generateTestECDSAKey(t)
signer, err := NewSigner(alg, key)
if err != nil {
t.Fatalf("NewSigner() error = %v", err)
}
verifier, err := NewVerifier(alg, key.Public())
if err != nil {
t.Fatalf("NewVerifier() error = %v", err)
}
// sign / verify round trip
// see also conformance_test.go for strict tests.
tests := []struct {
name string
msg *Sign1Message
externalOnSign []byte
externalOnVerify []byte
wantErr bool
check func(t *testing.T, m *Sign1Message)
}{
{
name: "valid message",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
externalOnSign: []byte{},
externalOnVerify: []byte{},
},
{
name: "valid message with external",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
externalOnSign: []byte("foo"),
externalOnVerify: []byte("foo"),
},
{
name: "nil external",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
externalOnSign: nil,
externalOnVerify: nil,
},
{
name: "mixed nil / empty external",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
externalOnSign: []byte{},
externalOnVerify: nil,
},
{
name: "nil payload", // payload is detached
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
},
Payload: nil,
},
wantErr: true,
},
{
name: "mismatch algorithm",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES512,
},
},
Payload: []byte("hello world"),
},
wantErr: true,
},
{
name: "missing algorithm",
msg: &Sign1Message{
Payload: []byte("hello world"),
},
check: func(t *testing.T, m *Sign1Message) {
got, err := m.Headers.Protected.Algorithm()
if err != nil {
t.Errorf("Sign1Message.Headers.Protected.Algorithm() error = %v", err)
}
if got != alg {
t.Errorf("Sign1Message.Headers.Protected.Algorithm() = %v, want %v", got, alg)
}
},
},
{
name: "missing algorithm with raw protected",
msg: &Sign1Message{
Headers: Headers{
RawProtected: []byte{0x40},
},
Payload: []byte("hello world"),
},
wantErr: true,
},
{
name: "missing algorithm with externally supplied data",
msg: &Sign1Message{
Payload: []byte("hello world"),
},
externalOnSign: []byte("foo"),
externalOnVerify: []byte("foo"),
check: func(t *testing.T, m *Sign1Message) {
_, err := m.Headers.Protected.Algorithm()
if want := ErrAlgorithmNotFound; err != want {
t.Errorf("Sign1Message.Headers.Protected.Algorithm() error = %v, wantErr %v", err, want)
}
},
},
{
name: "double signing",
msg: &Sign1Message{
Payload: []byte("hello world"),
Signature: []byte("foobar"),
},
wantErr: true,
},
{
name: "nil message",
msg: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.msg.Sign(rand.Reader, tt.externalOnSign, signer)
if (err != nil) != tt.wantErr {
t.Errorf("Sign1Message.Sign() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil {
return
}
if tt.check != nil {
tt.check(t, tt.msg)
}
if err := tt.msg.Verify(tt.externalOnVerify, verifier); err != nil {
t.Errorf("Sign1Message.Verify() error = %v", err)
}
})
}
}
func TestSign1Message_Sign_Internal(t *testing.T) {
tests := []struct {
name string
msg *Sign1Message
external []byte
toBeSigned []byte
}{
{
name: "valid message",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: algorithmMock,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
external: []byte{},
toBeSigned: []byte{
0x84, // array type
0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31, // context
0x47, 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, // protected
0x40, // external
0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload
},
},
{
name: "valid message with external",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: algorithmMock,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
external: []byte("foo"),
toBeSigned: []byte{
0x84, // array type
0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31, // context
0x47, 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, // protected
0x43, 0x66, 0x6f, 0x6f, // external
0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload
},
},
{
name: "nil external",
msg: &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: algorithmMock,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
},
external: nil,
toBeSigned: []byte{
0x84, // array type
0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31, // context
0x47, 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, // protected
0x40, // external
0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload
},
},
{
name: "nil protected header",
msg: &Sign1Message{
Payload: []byte("hello world"),
},
external: []byte("foo"),
toBeSigned: []byte{
0x84, // array type
0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31, // context
0x40, // protected
0x43, 0x66, 0x6f, 0x6f, // external
0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash := crypto.SHA256
RegisterAlgorithm(algorithmMock, "Mock", hash, nil)
defer resetExtendedAlgorithm()
sig := make([]byte, 64)
_, err := rand.Read(sig)
if err != nil {
t.Fatalf("rand.Read() error = %v", err)
}
h := hash.New()
h.Write(tt.toBeSigned)
digest := h.Sum(nil)
signer := newMockSigner(t)
signer.setup(digest, sig)
msg := tt.msg
if err := msg.Sign(rand.Reader, tt.external, signer); err != nil {
t.Errorf("Sign1Message.Sign() error = %v", err)
return
}
if got := msg.Signature; !bytes.Equal(got, sig) {
t.Errorf("Sign1Message.Sign() signature = %v, want %v", got, sig)
}
})
}
}
func TestSign1Message_Verify(t *testing.T) {
// generate key and set up signer / verifier
alg := AlgorithmES256
key := generateTestECDSAKey(t)
signer, err := NewSigner(alg, key)
if err != nil {
t.Fatalf("NewSigner() error = %v", err)
}
verifier, err := NewVerifier(alg, key.Public())
if err != nil {
t.Fatalf("NewVerifier() error = %v", err)
}
// sign / verify round trip
// see also conformance_test.go for strict tests.
tests := []struct {
name string
externalOnSign []byte
externalOnVerify []byte
tamper func(m *Sign1Message) *Sign1Message
wantErr bool
}{
{
name: "round trip on valid message",
},
{
name: "external mismatch",
externalOnSign: []byte("foo"),
externalOnVerify: []byte("bar"),
wantErr: true,
},
{
name: "mixed nil / empty external",
externalOnSign: nil,
externalOnVerify: []byte{},
},
{
name: "nil message",
tamper: func(m *Sign1Message) *Sign1Message {
return nil
},
wantErr: true,
},
{
name: "strip signature",
tamper: func(m *Sign1Message) *Sign1Message {
m.Signature = nil
return m
},
wantErr: true,
},
{
name: "empty signature",
tamper: func(m *Sign1Message) *Sign1Message {
m.Signature = []byte{}
return m
},
wantErr: true,
},
{
name: "tamper protected header",
tamper: func(m *Sign1Message) *Sign1Message {
m.Headers.Protected["foo"] = "bar"
return m
},
wantErr: true,
},
{
name: "tamper unprotected header",
tamper: func(m *Sign1Message) *Sign1Message {
m.Headers.Unprotected["foo"] = "bar"
return m
},
wantErr: false, // allowed
},
{
name: "tamper payload",
tamper: func(m *Sign1Message) *Sign1Message {
m.Payload = []byte("foobar")
return m
},
wantErr: true,
},
{
name: "tamper signature",
tamper: func(m *Sign1Message) *Sign1Message {
m.Signature[0]++
return m
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// generate message and sign
msg := &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
Unprotected: UnprotectedHeader{
HeaderLabelKeyID: 42,
},
},
Payload: []byte("hello world"),
}
if err := msg.Sign(rand.Reader, tt.externalOnSign, signer); err != nil {
t.Errorf("Sign1Message.Sign() error = %v", err)
return
}
// tamper message
if tt.tamper != nil {
msg = tt.tamper(msg)
}
// verify message
if err := msg.Verify(tt.externalOnVerify, verifier); (err != nil) != tt.wantErr {
t.Errorf("Sign1Message.Verify() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
// special cases
t.Run("nil payload", func(t *testing.T) { // payload is detached
msg := &Sign1Message{
Headers: Headers{
Protected: ProtectedHeader{
HeaderLabelAlgorithm: AlgorithmES256,
},
},
Payload: []byte{},
}
if err := msg.Sign(rand.Reader, nil, signer); err != nil {
t.Errorf("Sign1Message.Sign() error = %v", err)
return
}
// make payload nil on verify
msg.Payload = nil
// verify message
if err := msg.Verify(nil, verifier); err == nil {
t.Error("Sign1Message.Verify() error = nil, wantErr true")
}
})
}