Skip to content

Commit 354ac99

Browse files
authored
Add untagged COSE_Sign1 support (#143)
Signed-off-by: setrofim <[email protected]>
1 parent 38be1cb commit 354ac99

File tree

4 files changed

+298
-38
lines changed

4 files changed

+298
-38
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ go get github.com/veraison/go-cose@main
6565
import "github.com/veraison/go-cose"
6666
```
6767

68-
Construct a new COSE_Sign1 message, then sign it using ECDSA w/ SHA-256 and finally marshal it. For example:
68+
Construct a new COSE_Sign1_Tagged message, then sign it using ECDSA w/ SHA-256 and finally marshal it. For example:
6969

7070
```go
7171
package main
@@ -102,7 +102,7 @@ func SignP256(data []byte) ([]byte, error) {
102102
}
103103
```
104104

105-
Verify a raw COSE_Sign1 message. For example:
105+
Verify a raw COSE_Sign1_Tagged message. For example:
106106

107107
```go
108108
package main
@@ -132,6 +132,11 @@ func VerifyP256(publicKey crypto.PublicKey, sig []byte) error {
132132

133133
See [example_test.go](./example_test.go) for more examples.
134134

135+
#### Untagged Signing and Verification
136+
137+
Untagged COSE_Sign1 messages can be signed and verified as above, using
138+
`cose.UntaggedSign1Message` instead of `cose.Sign1Message`.
139+
135140
### About hashing
136141

137142
`go-cose` does not import any hash package by its own to avoid linking unnecessary algorithms to the final binary.

example_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func ExampleSign1Message() {
139139
// verification error as expected
140140
}
141141

142-
// This example demonstrates signing COSE_Sign1 signatures using Sign1().
142+
// This example demonstrates signing COSE_Sign1_Tagged signatures using Sign1().
143143
func ExampleSign1() {
144144
// create a signer
145145
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
@@ -170,3 +170,35 @@ func ExampleSign1() {
170170
// Output:
171171
// message signed
172172
}
173+
174+
// This example demonstrates signing COSE_Sign1 signatures using Sign1Untagged().
175+
func ExampleSign1Untagged() {
176+
// create a signer
177+
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
178+
if err != nil {
179+
panic(err)
180+
}
181+
signer, err := cose.NewSigner(cose.AlgorithmES512, privateKey)
182+
if err != nil {
183+
panic(err)
184+
}
185+
186+
// sign message
187+
headers := cose.Headers{
188+
Protected: cose.ProtectedHeader{
189+
cose.HeaderLabelAlgorithm: cose.AlgorithmES512,
190+
},
191+
Unprotected: cose.UnprotectedHeader{
192+
cose.HeaderLabelKeyID: []byte("1"),
193+
},
194+
}
195+
sig, err := cose.Sign1Untagged(rand.Reader, signer, headers, []byte("hello world"), nil)
196+
if err != nil {
197+
panic(err)
198+
}
199+
200+
fmt.Println("message signed")
201+
_ = sig // further process on sig
202+
// Output:
203+
// message signed
204+
}

sign1.go

Lines changed: 118 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,11 @@ func NewSign1Message() *Sign1Message {
5252

5353
// MarshalCBOR encodes Sign1Message into a COSE_Sign1_Tagged object.
5454
func (m *Sign1Message) MarshalCBOR() ([]byte, error) {
55-
if m == nil {
56-
return nil, errors.New("cbor: MarshalCBOR on nil Sign1Message pointer")
57-
}
58-
if len(m.Signature) == 0 {
59-
return nil, ErrEmptySignature
60-
}
61-
protected, unprotected, err := m.Headers.marshal()
55+
content, err := m.getContent()
6256
if err != nil {
6357
return nil, err
6458
}
65-
content := sign1Message{
66-
Protected: protected,
67-
Unprotected: unprotected,
68-
Payload: m.Payload,
69-
Signature: m.Signature,
70-
}
59+
7160
return encMode.Marshal(cbor.Tag{
7261
Number: CBORTagSign1Message,
7362
Content: content,
@@ -85,28 +74,7 @@ func (m *Sign1Message) UnmarshalCBOR(data []byte) error {
8574
return errors.New("cbor: invalid COSE_Sign1_Tagged object")
8675
}
8776

88-
// decode to sign1Message and parse
89-
var raw sign1Message
90-
if err := decModeWithTagsForbidden.Unmarshal(data[1:], &raw); err != nil {
91-
return err
92-
}
93-
if len(raw.Signature) == 0 {
94-
return ErrEmptySignature
95-
}
96-
msg := Sign1Message{
97-
Headers: Headers{
98-
RawProtected: raw.Protected,
99-
RawUnprotected: raw.Unprotected,
100-
},
101-
Payload: raw.Payload,
102-
Signature: raw.Signature,
103-
}
104-
if err := msg.Headers.UnmarshalFromRaw(); err != nil {
105-
return err
106-
}
107-
108-
*m = msg
109-
return nil
77+
return m.doUnmarshal(data[1:])
11078
}
11179

11280
// Sign signs a Sign1Message using the provided Signer.
@@ -218,6 +186,53 @@ func (m *Sign1Message) toBeSigned(external []byte) ([]byte, error) {
218186
return encMode.Marshal(sigStructure)
219187
}
220188

189+
func (m *Sign1Message) getContent() (sign1Message, error) {
190+
if m == nil {
191+
return sign1Message{}, errors.New("cbor: MarshalCBOR on nil Sign1Message pointer")
192+
}
193+
if len(m.Signature) == 0 {
194+
return sign1Message{}, ErrEmptySignature
195+
}
196+
protected, unprotected, err := m.Headers.marshal()
197+
if err != nil {
198+
return sign1Message{}, err
199+
}
200+
201+
content := sign1Message{
202+
Protected: protected,
203+
Unprotected: unprotected,
204+
Payload: m.Payload,
205+
Signature: m.Signature,
206+
}
207+
208+
return content, nil
209+
}
210+
211+
func (m *Sign1Message) doUnmarshal(data []byte) error {
212+
// decode to sign1Message and parse
213+
var raw sign1Message
214+
if err := decModeWithTagsForbidden.Unmarshal(data, &raw); err != nil {
215+
return err
216+
}
217+
if len(raw.Signature) == 0 {
218+
return ErrEmptySignature
219+
}
220+
msg := Sign1Message{
221+
Headers: Headers{
222+
RawProtected: raw.Protected,
223+
RawUnprotected: raw.Unprotected,
224+
},
225+
Payload: raw.Payload,
226+
Signature: raw.Signature,
227+
}
228+
if err := msg.Headers.UnmarshalFromRaw(); err != nil {
229+
return err
230+
}
231+
232+
*m = msg
233+
return nil
234+
}
235+
221236
// Sign1 signs a Sign1Message using the provided Signer.
222237
//
223238
// This method is a wrapper of `Sign1Message.Sign()`.
@@ -234,3 +249,71 @@ func Sign1(rand io.Reader, signer Signer, headers Headers, payload []byte, exter
234249
}
235250
return msg.MarshalCBOR()
236251
}
252+
253+
type UntaggedSign1Message Sign1Message
254+
255+
// MarshalCBOR encodes UntaggedSign1Message into a COSE_Sign1 object.
256+
func (m *UntaggedSign1Message) MarshalCBOR() ([]byte, error) {
257+
content, err := (*Sign1Message)(m).getContent()
258+
if err != nil {
259+
return nil, err
260+
}
261+
262+
return encMode.Marshal(content)
263+
}
264+
265+
// UnmarshalCBOR decodes a COSE_Sign1 object into an UnataggedSign1Message.
266+
func (m *UntaggedSign1Message) UnmarshalCBOR(data []byte) error {
267+
if m == nil {
268+
return errors.New("cbor: UnmarshalCBOR on nil UntaggedSign1Message pointer")
269+
}
270+
271+
if len(data) == 0 {
272+
return errors.New("cbor: zero length data")
273+
}
274+
275+
// fast message check - ensure the frist byte indicates a four-element array
276+
if data[0] != sign1MessagePrefix[1] {
277+
return errors.New("cbor: invalid COSE_Sign1 object")
278+
}
279+
280+
return (*Sign1Message)(m).doUnmarshal(data)
281+
}
282+
283+
// Sign signs an UnttaggedSign1Message using the provided Signer.
284+
// The signature is stored in m.Signature.
285+
//
286+
// Note that m.Signature is only valid as long as m.Headers.Protected and
287+
// m.Payload remain unchanged after calling this method.
288+
// It is possible to modify m.Headers.Unprotected after signing,
289+
// i.e., add counter signatures or timestamps.
290+
//
291+
// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-4.4
292+
func (m *UntaggedSign1Message) Sign(rand io.Reader, external []byte, signer Signer) error {
293+
return (*Sign1Message)(m).Sign(rand, external, signer)
294+
}
295+
296+
// Verify verifies the signature on the UntaggedSign1Message returning nil on success or
297+
// a suitable error if verification fails.
298+
//
299+
// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-4.4
300+
func (m *UntaggedSign1Message) Verify(external []byte, verifier Verifier) error {
301+
return (*Sign1Message)(m).Verify(external, verifier)
302+
}
303+
304+
// Sign1Untagged signs an UntaggedSign1Message using the provided Signer.
305+
//
306+
// This method is a wrapper of `UntaggedSign1Message.Sign()`.
307+
//
308+
// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-4.4
309+
func Sign1Untagged(rand io.Reader, signer Signer, headers Headers, payload []byte, external []byte) ([]byte, error) {
310+
msg := UntaggedSign1Message{
311+
Headers: headers,
312+
Payload: payload,
313+
}
314+
err := msg.Sign(rand, external, signer)
315+
if err != nil {
316+
return nil, err
317+
}
318+
return msg.MarshalCBOR()
319+
}

sign1_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,3 +978,143 @@ func TestSign1Message_toBeSigned(t *testing.T) {
978978
})
979979
}
980980
}
981+
982+
func TestUntaggedSign1Message_MarshalCBOR(t *testing.T) {
983+
tests := []struct {
984+
name string
985+
m *UntaggedSign1Message
986+
want []byte
987+
wantErr string
988+
}{
989+
{
990+
name: "valid message",
991+
m: &UntaggedSign1Message{
992+
Headers: Headers{
993+
Protected: ProtectedHeader{
994+
HeaderLabelAlgorithm: AlgorithmES256,
995+
},
996+
Unprotected: UnprotectedHeader{
997+
HeaderLabelContentType: 42,
998+
},
999+
},
1000+
Payload: []byte("foo"),
1001+
Signature: []byte("bar"),
1002+
},
1003+
want: []byte{
1004+
0x84,
1005+
0x43, 0xa1, 0x01, 0x26, // protected
1006+
0xa1, 0x03, 0x18, 0x2a, // unprotected
1007+
0x43, 0x66, 0x6f, 0x6f, // payload
1008+
0x43, 0x62, 0x61, 0x72, // signature
1009+
},
1010+
},
1011+
}
1012+
for _, tt := range tests {
1013+
t.Run(tt.name, func(t *testing.T) {
1014+
got, err := tt.m.MarshalCBOR()
1015+
1016+
if err != nil && (err.Error() != tt.wantErr) {
1017+
t.Errorf("UntaggedSign1Message.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
1018+
return
1019+
} else if err == nil && (tt.wantErr != "") {
1020+
t.Errorf("UntaggedSign1Message.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
1021+
return
1022+
}
1023+
if !reflect.DeepEqual(got, tt.want) {
1024+
t.Errorf("UntaggedSign1Message.MarshalCBOR() = %v, want %v", got, tt.want)
1025+
}
1026+
})
1027+
}
1028+
}
1029+
1030+
func TestUntaggedSign1Message_UnmarshalCBOR(t *testing.T) {
1031+
// test others
1032+
tests := []struct {
1033+
name string
1034+
data []byte
1035+
want UntaggedSign1Message
1036+
wantErr string
1037+
}{
1038+
{
1039+
name: "valid message",
1040+
data: []byte{
1041+
0x84,
1042+
0x43, 0xa1, 0x01, 0x26, // protected
1043+
0xa1, 0x03, 0x18, 0x2a, // unprotected
1044+
0x43, 0x66, 0x6f, 0x6f, // payload
1045+
0x43, 0x62, 0x61, 0x72, // signature
1046+
},
1047+
want: UntaggedSign1Message{
1048+
Headers: Headers{
1049+
RawProtected: []byte{0x43, 0xa1, 0x01, 0x26},
1050+
Protected: ProtectedHeader{
1051+
HeaderLabelAlgorithm: AlgorithmES256,
1052+
},
1053+
RawUnprotected: []byte{0xa1, 0x03, 0x18, 0x2a},
1054+
Unprotected: UnprotectedHeader{
1055+
HeaderLabelContentType: int64(42),
1056+
},
1057+
},
1058+
Payload: []byte("foo"),
1059+
Signature: []byte("bar"),
1060+
},
1061+
},
1062+
{
1063+
name: "tagged message",
1064+
data: []byte{
1065+
0xd2, // tag
1066+
0x84,
1067+
0x43, 0xa1, 0x01, 0x26, // protected
1068+
0xa1, 0x03, 0x18, 0x2a, // unprotected
1069+
0x43, 0x66, 0x6f, 0x6f, // payload
1070+
0x43, 0x62, 0x61, 0x72, // signature
1071+
},
1072+
wantErr: "cbor: invalid COSE_Sign1 object",
1073+
},
1074+
{
1075+
name: "empty data",
1076+
data: []byte{},
1077+
wantErr: "cbor: zero length data",
1078+
},
1079+
}
1080+
for _, tt := range tests {
1081+
t.Run(tt.name, func(t *testing.T) {
1082+
var got UntaggedSign1Message
1083+
err := got.UnmarshalCBOR(tt.data)
1084+
if (err != nil) && (err.Error() != tt.wantErr) {
1085+
t.Errorf("Sign1Message.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
1086+
return
1087+
} else if err == nil && (tt.wantErr != "") {
1088+
t.Errorf("Sign1Message.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr)
1089+
return
1090+
}
1091+
if !reflect.DeepEqual(got, tt.want) {
1092+
t.Errorf("Sign1Message.UnmarshalCBOR() = %v, want %v", got, tt.want)
1093+
}
1094+
})
1095+
}
1096+
}
1097+
1098+
func TestUntaggedSign1Message_nil(t *testing.T) {
1099+
var m *UntaggedSign1Message
1100+
1101+
_, err := m.MarshalCBOR()
1102+
if err.Error() != "cbor: MarshalCBOR on nil Sign1Message pointer" {
1103+
t.Errorf("UntaggedSign1Message.MarshalCBOR unexpected err: %v", err)
1104+
}
1105+
1106+
err = m.UnmarshalCBOR([]byte{})
1107+
if err.Error() != "cbor: UnmarshalCBOR on nil UntaggedSign1Message pointer" {
1108+
t.Errorf("UntaggedSign1Message.UnmarshalCBOR unexpected err: %v", err)
1109+
}
1110+
1111+
err = m.Sign(nil, []byte{}, nil)
1112+
if err.Error() != "signing nil Sign1Message" {
1113+
t.Errorf("UntaggedSign1Message.Sign unexpected err: %v", err)
1114+
}
1115+
1116+
err = m.Verify([]byte{}, nil)
1117+
if err.Error() != "verifying nil Sign1Message" {
1118+
t.Errorf("UntaggedSign1Message.Sign unexpected err: %v", err)
1119+
}
1120+
}

0 commit comments

Comments
 (0)