From b9d189e9f8d2f49e49ff2d47088e3e05171f544d 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 | 24 ++++++++++++++++++++++++ sign1.go | 33 +++++++++++++++++++++++---------- sign1_test.go | 35 +++++++++++++++++++++++++++++------ 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index feaf8c4..66cb91a 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,30 @@ 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 tagged CBOR object. 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",