From d08d663e665a9db2adaf3379ab2b38253ddf80ac Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 11 May 2023 17:37:45 +0100 Subject: [PATCH] Add untagged COSE_Sign1 support RFC8152 specifies that a single-signer token can be either tagged (COSE_Sign1_Tagged) or untagged (COSE_Sign1). Add support for parsing, and optionally generating, the untagged version. The default behaviour is to still generated COSE_Sign1_Tagged. Signed-off-by: setrofim --- README.md | 28 +++++++++++++++++++++++++++- sign1.go | 33 +++++++++++++++++++++++---------- sign1_test.go | 35 +++++++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index feaf8c4..60d3ecd 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ go get github.com/veraison/go-cose@main import "github.com/veraison/go-cose" ``` -Construct a new COSE_Sign1 message, then sign it using ECDSA w/ SHA-256 and finally marshal it. For example: +Construct a new COSE_Sign1_Tagged message, then sign it using ECDSA w/ SHA-256 +and finally marshal it. For example: ```go package main @@ -132,6 +133,31 @@ func VerifyP256(publicKey crypto.PublicKey, sig []byte) error { See [example_test.go](./example_test.go) for more examples. +#### Untagged Signing + +`cose.Sign1` (above) generated a COSE_Sign1_Tagged object. To serialize a +COSE_Sign1 instead (i.e., without the outer tag), you need to create a +`Sign1Message` by hand, and set its `Untagged` flag before signing: + +```go +func SignP256(data []byte) ([]byte, error) { + // ... + // as above + // ... + + msg := Sign1Message{ + Headers: headers, + Payload: payload, + Untagged: true, + } + + err := msg.Sign(rand, external, signer) + if err != nil { + return nil, err + } + return msg.MarshalCBOR() +``` + ### About hashing `go-cose` does not import any hash package by its own to avoid linking unnecessary algorithms to the final binary. diff --git a/sign1.go b/sign1.go index 56e884c..e8ee44f 100644 --- a/sign1.go +++ b/sign1.go @@ -38,6 +38,7 @@ type Sign1Message struct { Headers Headers Payload []byte Signature []byte + Untagged bool } // NewSign1Message returns a Sign1Message with header initialized. @@ -68,10 +69,15 @@ func (m *Sign1Message) MarshalCBOR() ([]byte, error) { Payload: m.Payload, Signature: m.Signature, } - return encMode.Marshal(cbor.Tag{ - Number: CBORTagSign1Message, - Content: content, - }) + + if m.Untagged { + return encMode.Marshal(content) + } else { + return encMode.Marshal(cbor.Tag{ + Number: CBORTagSign1Message, + Content: content, + }) + } } // UnmarshalCBOR decodes a COSE_Sign1_Tagged object into Sign1Message. @@ -80,16 +86,22 @@ func (m *Sign1Message) UnmarshalCBOR(data []byte) error { return errors.New("cbor: UnmarshalCBOR on nil Sign1Message pointer") } - // fast message check - if !bytes.HasPrefix(data, sign1MessagePrefix) { - return errors.New("cbor: invalid COSE_Sign1_Tagged object") - } - // decode to sign1Message and parse var raw sign1Message - if err := decModeWithTagsForbidden.Unmarshal(data[1:], &raw); err != nil { + var err error + var isUntagged bool + + if bytes.HasPrefix(data, sign1MessagePrefix) { + err = decModeWithTagsForbidden.Unmarshal(data[1:], &raw) + isUntagged = false + } else { + err = decModeWithTagsForbidden.Unmarshal(data, &raw) + isUntagged = true + } + if err != nil { return err } + if len(raw.Signature) == 0 { return ErrEmptySignature } @@ -100,6 +112,7 @@ func (m *Sign1Message) UnmarshalCBOR(data []byte) error { }, Payload: raw.Payload, Signature: raw.Signature, + Untagged: isUntagged, } if err := msg.Headers.UnmarshalFromRaw(); err != nil { return err diff --git a/sign1_test.go b/sign1_test.go index c876f49..6e247d0 100644 --- a/sign1_test.go +++ b/sign1_test.go @@ -39,6 +39,29 @@ func TestSign1Message_MarshalCBOR(t *testing.T) { 0x43, 0x62, 0x61, 0x72, // signature }, }, + { + name: "valid message (untagged)", + m: &Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: 42, + }, + }, + Payload: []byte("foo"), + Signature: []byte("bar"), + Untagged: true, + }, + want: []byte{ + 0x84, + 0x43, 0xa1, 0x01, 0x26, // protected + 0xa1, 0x03, 0x18, 0x2a, // unprotected + 0x43, 0x66, 0x6f, 0x6f, // payload + 0x43, 0x62, 0x61, 0x72, // signature + }, + }, { name: "nil message", m: nil, @@ -254,12 +277,12 @@ func TestSign1Message_UnmarshalCBOR(t *testing.T) { { name: "nil CBOR data", data: nil, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "EOF", }, { name: "empty CBOR data", data: []byte{}, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "EOF", }, { name: "invalid message with valid prefix", // issue #29 @@ -304,14 +327,14 @@ func TestSign1Message_UnmarshalCBOR(t *testing.T) { 0xf6, // payload 0x41, 0x00, // signature }, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "cbor: CBOR tag isn't allowed", }, { name: "mismatch type", data: []byte{ 0xd2, 0x40, }, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "cbor: CBOR tag isn't allowed", }, { name: "smaller array size", @@ -320,7 +343,7 @@ func TestSign1Message_UnmarshalCBOR(t *testing.T) { 0x40, 0xa0, // empty headers 0xf6, // payload }, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "cbor: CBOR tag isn't allowed", }, { name: "larger array size", @@ -331,7 +354,7 @@ func TestSign1Message_UnmarshalCBOR(t *testing.T) { 0x41, 0x00, // signature 0x40, }, - wantErr: "cbor: invalid COSE_Sign1_Tagged object", + wantErr: "cbor: CBOR tag isn't allowed", }, { name: "undefined payload",