Skip to content

Commit

Permalink
Add untagged COSE_Sign1 support
Browse files Browse the repository at this point in the history
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 <setrofim@gmail.com>
  • Loading branch information
setrofim committed May 15, 2023
1 parent 3b32cdb commit d08d663
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 17 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
33 changes: 23 additions & 10 deletions sign1.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Sign1Message struct {
Headers Headers
Payload []byte
Signature []byte
Untagged bool
}

// NewSign1Message returns a Sign1Message with header initialized.
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
Expand All @@ -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
Expand Down
35 changes: 29 additions & 6 deletions sign1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down

0 comments on commit d08d663

Please sign in to comment.