From 61d8f4a11349da6e340795e86f8c4e13ae7ae2f8 Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Mon, 11 Sep 2023 21:17:23 +0000 Subject: [PATCH] Added support to countersignatures. Signed-off-by: Guilherme Balena Versiani --- countersign.go | 286 +++++++++++++++++++++++++++++++++++++++ headers.go | 112 ++++++++++++++-- headers_test.go | 347 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 719 insertions(+), 26 deletions(-) create mode 100644 countersign.go diff --git a/countersign.go b/countersign.go new file mode 100644 index 0000000..fa6deeb --- /dev/null +++ b/countersign.go @@ -0,0 +1,286 @@ +package cose + +import ( + "errors" + "fmt" + "io" + + "github.com/fxamacker/cbor/v2" +) + +// countersignature represents a COSE_Countersignature CBOR object: +// +// COSE_Countersignature = COSE_Signature +// +// Reference: https://tools.ietf.org/html/rfc9338#section-3.1 +type countersignature signature + +// countersignaturePrefix represents the fixed prefix of COSE_Countersignature_Tagged. +var countersignaturePrefix = []byte{ + 0xd3, // #6.19 + 0x83, // Array of length 4 +} + +// Countersignature represents a decoded COSE_Countersignature. +// +// Reference: https://tools.ietf.org/html/rfc9338#section-3.1 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +type Countersignature Signature + +// NewCountersignature returns a Countersignature with header initialized. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func NewCountersignature() *Countersignature { + return (*Countersignature)(NewSignature()) +} + +// MarshalCBOR encodes Countersignature into a COSE_Countersignature object. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) MarshalCBOR() ([]byte, error) { + if s == nil { + return nil, errors.New("cbor: MarshalCBOR on nil Countersignature pointer") + } + // COSE_Countersignature share the exact same format as COSE_Signature + return (*Signature)(s).MarshalCBOR() +} + +// UnmarshalCBOR decodes a COSE_Countersignature object into Countersignature. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) UnmarshalCBOR(data []byte) error { + if s == nil { + return errors.New("cbor: UnmarshalCBOR on nil Countersignature pointer") + } + // COSE_Countersignature share the exact same format as COSE_Signature + return (*Signature)(s).UnmarshalCBOR(data) +} + +// Sign signs a Countersignature using the provided Signer. +// Signing a COSE_Countersignature requires the parent message to be completely +// fulfilled. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) Sign(rand io.Reader, signer Signer, parent any, external []byte) error { + if s == nil { + return errors.New("signing nil Countersignature") + } + if len(s.Signature) > 0 { + return errors.New("Countersignature already has signature bytes") + } + + // check algorithm if present. + // `alg` header MUST present if there is no externally supplied data. + alg := signer.Algorithm() + if err := s.Headers.ensureSigningAlgorithm(alg, external); err != nil { + return err + } + + // sign the message + toBeSigned, err := s.toBeSigned(parent, external) + if err != nil { + return err + } + sig, err := signer.Sign(rand, toBeSigned) + if err != nil { + return err + } + + s.Signature = sig + return nil +} + +// Verify verifies the countersignature, returning nil on success or a suitable error +// if verification fails. +// Verifying a COSE_Countersignature requires the parent message. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-4.4 +// +// # Experimental +// +// Notice: The COSE Sign API is EXPERIMENTAL and may be changed or removed in a +// later release. +func (s *Countersignature) Verify(verifier Verifier, parent any, external []byte) error { + if s == nil { + return errors.New("verifying nil Countersignature") + } + if len(s.Signature) == 0 { + return ErrEmptySignature + } + + // check algorithm if present. + // `alg` header MUST present if there is no externally supplied data. + alg := verifier.Algorithm() + err := s.Headers.ensureVerificationAlgorithm(alg, external) + if err != nil { + return err + } + + // verify the message + toBeSigned, err := s.toBeSigned(parent, external) + if err != nil { + return err + } + return verifier.Verify(toBeSigned, s.Signature) +} + +// toBeSigned returns ToBeSigned from COSE_Countersignature object. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +func (s *Countersignature) toBeSigned(target any, external []byte) ([]byte, error) { + var signProtected cbor.RawMessage + signProtected, err := s.Headers.MarshalProtected() + if err != nil { + return nil, err + } + return countersignToBeSigned(false, target, signProtected, external) +} + +// countersignToBeSigned constructs Countersign_structure, computes and returns ToBeSigned. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +func countersignToBeSigned(abbreviated bool, target any, signProtected cbor.RawMessage, external []byte) ([]byte, error) { + // create a Countersign_structure and populate it with the appropriate fields. + // + // Countersign_structure = [ + // context : "CounterSignature" / "CounterSignature0" / + // "CounterSignatureV2" / "CounterSignature0V2" /, + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr, + // ? other_fields : [+ bstr ] + // ] + + var err error + var bodyProtected cbor.RawMessage + var otherFields []cbor.RawMessage + var payload []byte + + switch t := target.(type) { + case SignMessage: + bodyProtected, err = t.Headers.MarshalProtected() + if err != nil { + return nil, err + } + if len(t.Signatures) == 0 { + return nil, errors.New("target has no signatures yet") + } + signatures, err := encMode.Marshal(t.Signatures) + if err != nil { + return nil, err + } + otherFields, payload = append(otherFields, signatures), t.Payload + case Sign1Message: + if len(t.Signature) == 0 { + return nil, errors.New("target was not signed yet") + } + otherFields, payload = append(otherFields, t.Signature), t.Payload + default: + return nil, fmt.Errorf("unsupported target %T", target) + } + + if payload == nil { + return nil, ErrMissingPayload + } + + var context string + switch { + case len(otherFields) == 0 && abbreviated == false: + context = "CounterSignature" + case len(otherFields) == 0 && abbreviated == true: + context = "CounterSignature0" + case len(otherFields) > 0 && abbreviated == false: + context = "CounterSignatureV2" + case len(otherFields) > 0 && abbreviated == true: + context = "CounterSignature0V2" + } + + bodyProtected, err = deterministicBinaryString(bodyProtected) + if err != nil { + return nil, err + } + signProtected, err = deterministicBinaryString(signProtected) + if err != nil { + return nil, err + } + if external == nil { + external = []byte{} + } + if signProtected == nil { + signProtected = []byte{0x40} + } + countersigStructure := []any{ + context, // context + bodyProtected, // body_protected + signProtected, // sign_protected + external, // external_aad + payload, // payload + } + if len(otherFields) > 0 { + countersigStructure = append(countersigStructure, otherFields) + } + + // create the value ToBeSigned by encoding the Countersign_structure to a byte + // string. + return encMode.Marshal(countersigStructure) +} + +// Countersign0 performs an abbreviated signature over a parent message using +// the provided Signer. +// +// The parent message must be completely fulfilled prior signing. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.2 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func Countersign0(rand io.Reader, signer Signer, parent any, external []byte) ([]byte, error) { + toBeSigned, err := countersignToBeSigned(true, parent, nil, external) + if err != nil { + return nil, err + } + sig, err := signer.Sign(rand, toBeSigned) + if err != nil { + return nil, err + } + + return sig, nil +} + +// VerifyCountersign0 verifies an abbreviated signature over a parent message +// using the provided Verifier. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.2 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func VerifyCountersign0(verifier Verifier, parent any, external, signature []byte) error { + toBeSigned, err := countersignToBeSigned(true, parent, nil, external) + if err != nil { + return err + } + return verifier.Verify(toBeSigned, signature) +} diff --git a/headers.go b/headers.go index 2da2356..3bdcb1d 100644 --- a/headers.go +++ b/headers.go @@ -12,18 +12,20 @@ import ( // // Reference: https://www.iana.org/assignments/cose/cose.xhtml#header-parameters const ( - HeaderLabelAlgorithm int64 = 1 - HeaderLabelCritical int64 = 2 - HeaderLabelContentType int64 = 3 - HeaderLabelKeyID int64 = 4 - HeaderLabelIV int64 = 5 - HeaderLabelPartialIV int64 = 6 - HeaderLabelCounterSignature int64 = 7 - HeaderLabelCounterSignature0 int64 = 9 - HeaderLabelX5Bag int64 = 32 - HeaderLabelX5Chain int64 = 33 - HeaderLabelX5T int64 = 34 - HeaderLabelX5U int64 = 35 + HeaderLabelAlgorithm int64 = 1 + HeaderLabelCritical int64 = 2 + HeaderLabelContentType int64 = 3 + HeaderLabelKeyID int64 = 4 + HeaderLabelIV int64 = 5 + HeaderLabelPartialIV int64 = 6 + HeaderLabelCounterSignature int64 = 7 + HeaderLabelCounterSignature0 int64 = 9 + HeaderLabelCounterSignatureV2 int64 = 11 + HeaderLabelCounterSignature0V2 int64 = 12 + HeaderLabelX5Bag int64 = 32 + HeaderLabelX5Chain int64 = 33 + HeaderLabelX5T int64 = 34 + HeaderLabelX5U int64 = 35 ) // ProtectedHeader contains parameters that are to be cryptographically @@ -197,10 +199,22 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { if err := validateHeaderLabelCBOR(data); err != nil { return err } - var header map[any]any - if err := decMode.Unmarshal(data, &header); err != nil { + + // In order to unmarshal Countersignature structs, it is required to make it + // in two steps instead of one. + var partialHeader map[any]cbor.RawMessage + if err := decMode.Unmarshal(data, &partialHeader); err != nil { return err } + header := make(map[any]any, len(partialHeader)) + for k, v := range partialHeader { + v, err := unmarshalUnprotected(k, v) + if err != nil { + return err + } + header[k] = v + } + if err := validateHeaderParameters(header, false); err != nil { return fmt.Errorf("unprotected header: %w", err) } @@ -208,6 +222,44 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { return nil } +// unmarshalUnprotected produces known structs such as counter signature +// headers, otherwise it defaults to regular unmarshaling to simple types. +func unmarshalUnprotected(key any, value cbor.RawMessage) (any, error) { + label, ok := normalizeLabel(key) + if ok { + switch label { + case HeaderLabelCounterSignature, HeaderLabelCounterSignatureV2: + return unmarshalAsCountersignature(value) + default: + } + } + + return unmarshalAsAny(value) +} + +// unmarshalAsCountersignature produces a Countersignature struct or a list of +// Countersignatures. +func unmarshalAsCountersignature(value cbor.RawMessage) (any, error) { + var result1 Countersignature + err := decMode.Unmarshal(value, &result1) + if err == nil { + return result1, nil + } + var result2 []Countersignature + err = decMode.Unmarshal(value, &result2) + if err == nil { + return result2, nil + } + return nil, errors.New("invalid Countersignature object / list of objects") +} + +// unmarshalAsAny produces simple types. +func unmarshalAsAny(value cbor.RawMessage) (any, error) { + var result any + err := decMode.Unmarshal(value, &result) + return result, err +} + // Headers represents "two buckets of information that are not // considered to be part of the payload itself, but are used for // holding information about content, algorithms, keys, or evaluation @@ -437,6 +489,38 @@ func validateHeaderParameters(h map[any]any, protected bool) error { if hasLabel(h, HeaderLabelIV) { return errors.New("header parameter: IV and PartialIV: parameters must not both be present") } + case HeaderLabelCounterSignature: + if protected { + return errors.New("header parameter: counter signature: not allowed") + } + if _, ok := value.(Countersignature); !ok { + if _, ok := value.([]Countersignature); !ok { + return errors.New("header parameter: counter signature is not a Countersignature or a list") + } + } + case HeaderLabelCounterSignature0: + if protected { + return errors.New("header parameter: countersignature0: not allowed") + } + if !canBstr(value) { + return errors.New("header parameter: countersignature0: require bstr type") + } + case HeaderLabelCounterSignatureV2: + if protected { + return errors.New("header parameter: Countersignature version 2: not allowed") + } + if _, ok := value.(Countersignature); !ok { + if _, ok := value.([]Countersignature); !ok { + return errors.New("header parameter: Countersignature version 2 is not a Countersignature or a list") + } + } + case HeaderLabelCounterSignature0V2: + if protected { + return errors.New("header parameter: Countersignature0 version 2: not allowed") + } + if !canBstr(value) { + return errors.New("header parameter: Countersignature0 version 2: require bstr type") + } } } return nil diff --git a/headers_test.go b/headers_test.go index 2dedd44..aa0bf4d 100644 --- a/headers_test.go +++ b/headers_test.go @@ -47,10 +47,10 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { name: "various types of integer label", h: ProtectedHeader{ uint(10): 0, - uint8(11): 0, - uint16(12): 0, - uint32(13): 0, - uint64(14): 0, + uint8(13): 0, + uint16(14): 0, + uint32(15): 0, + uint64(16): 0, int(-1): 0, int8(-2): 0, int16(-3): 0, @@ -61,10 +61,10 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { 0x55, // bstr 0xaa, // map 0x0a, 0x00, - 0x0b, 0x00, - 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, @@ -162,6 +162,20 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { }, wantErr: "protected header: header parameter: content type: require tstr / uint type", }, + { + name: "invalid counter signature", + h: ProtectedHeader{ + HeaderLabelCounterSignature: Countersignature{}, + }, + wantErr: "protected header: header parameter: counter signature: not allowed", + }, + { + name: "invalid counter signature version 2", + h: ProtectedHeader{ + HeaderLabelCounterSignatureV2: Countersignature{}, + }, + wantErr: "protected header: header parameter: Countersignature version 2: not allowed", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -333,6 +347,24 @@ func TestProtectedHeader_UnmarshalCBOR(t *testing.T) { }, wantErr: "protected header: header parameter: Partial IV: require bstr type", }, + { + name: "countersignature0 is not allowed", + data: []byte{ + 0x54, 0xa1, 0x09, 0x58, 0x10, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + }, + wantErr: "protected header: header parameter: countersignature0: not allowed", + }, + { + name: "Countersignature0V2 is not allowed", + data: []byte{ + 0x54, 0xa1, 0x0c, 0x58, 0x10, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + }, + wantErr: "protected header: header parameter: Countersignature0 version 2: not allowed", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -551,10 +583,10 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { name: "various types of integer label", h: UnprotectedHeader{ uint(10): 0, - uint8(11): 0, - uint16(12): 0, - uint32(13): 0, - uint64(14): 0, + uint8(13): 0, + uint16(14): 0, + uint32(15): 0, + uint64(16): 0, int(-1): 0, int8(-2): 0, int16(-3): 0, @@ -564,10 +596,10 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { want: []byte{ 0xaa, // map 0x0a, 0x00, - 0x0b, 0x00, - 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, @@ -614,6 +646,194 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { }, wantErr: "unprotected header: header parameter: crit: not allowed", }, + { + name: "malformed counter signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: "", + }, + wantErr: "unprotected header: header parameter: counter signature is not a Countersignature or a list", + }, + { + name: "counter signature without signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: []Countersignature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + }, + }, + wantErr: "empty signature", + }, + { + name: "complete counter signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: []Countersignature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + want: []byte{ + 0xa1, 0x07, 0x81, 0x83, 0x43, 0xa1, 0x01, 0x27, 0xa1, + 0x04, 0x42, 0x31, 0x31, 0x58, 0x40, 0xb7, 0xca, 0xcb, + 0xa2, 0x85, 0xc4, 0xcd, 0x3e, 0xd2, 0xf0, 0x14, 0x6f, + 0x41, 0x98, 0x86, 0x14, 0x4c, 0xa6, 0x38, 0xd0, 0x87, + 0xde, 0x12, 0x3d, 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, + 0xab, 0xc4, 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, + 0xb7, 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, 0xf2, + 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "malformed Countersignature version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: "", + }, + wantErr: "unprotected header: header parameter: Countersignature version 2 is not a Countersignature or a list", + }, + { + name: "Countersignature version 2 without signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + }, + wantErr: "empty signature", + }, + { + name: "complete Countersignature version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + want: []byte{ + 0xa1, 0x0b, 0x83, 0x43, 0xa1, 0x01, 0x27, 0xa1, 0x04, + 0x42, 0x31, 0x31, 0x58, 0x40, 0xb7, 0xca, 0xcb, 0xa2, + 0x85, 0xc4, 0xcd, 0x3e, 0xd2, 0xf0, 0x14, 0x6f, 0x41, + 0x98, 0x86, 0x14, 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, + 0x12, 0x3d, 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, + 0xc4, 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, 0xfe, + 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, 0xf2, 0x43, + 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "complete countersignature0", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + want: []byte{ + 0xa1, 0x09, 0x58, 0x40, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "invalid countersignature0", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0: "11", + }, + wantErr: "unprotected header: header parameter: countersignature0: require bstr type", + }, + { + name: "complete Countersignature0 version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0V2: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + want: []byte{ + 0xa1, 0x0c, 0x58, 0x40, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "invalid Countersignature0 version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0V2: "11", + }, + wantErr: "unprotected header: header parameter: Countersignature0 version 2: require bstr type", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -739,7 +959,110 @@ func TestUnprotectedHeader_UnmarshalCBOR(t *testing.T) { }, wantErr: "unprotected header: header parameter: crit: not allowed", }, + { + name: "single counter signature present", + data: []byte{ + 0xa1, // { + 0x07, 0x83, // / counter signature / 7: [ + 0x43, 0xa1, 0x01, 0x27, // / protected h'a10127' / << { / alg / 1:-8 / EdDSA / } >>, + 0xa1, 0x04, 0x42, 0x31, 0x31, // / unprotected / { / kid / 4: '11' }, + 0x58, 0x40, // bytes(64) + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + want: UnprotectedHeader{ + HeaderLabelCounterSignature: Countersignature{ + Headers: Headers{ + RawProtected: []byte{0x43, 0xa1, 0x01, 0x27}, + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + RawUnprotected: []byte{0xa1, 0x04, 0x42, 0x31, 0x31}, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + { + name: "CountersignatureV2 in a list", + data: []byte{ + 0xa1, // { + 0x0b, 0x81, 0x83, // / counter signature / 7: [ [ + 0x43, 0xa1, 0x01, 0x27, // / protected h'a10127' / << { / alg / 1:-8 / EdDSA / } >>, + 0xa1, 0x04, 0x42, 0x31, 0x31, // / unprotected / { / kid / 4: '11' }, + 0x58, 0x40, // bytes(64) + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + want: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: []Countersignature{ + { + Headers: Headers{ + RawProtected: []byte{0x43, 0xa1, 0x01, 0x27}, + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + RawUnprotected: []byte{0xa1, 0x04, 0x42, 0x31, 0x31}, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + }, + { + name: "counter signature should be object or list", + data: []byte{ + 0xa1, // { + 0x07, 0x42, 0xf0, 0x0d, // / counter signature / 7: h'f00d' + }, + wantErr: "invalid Countersignature object / list of objects", + }, + { + name: "CountersignatureV2 should be object or list", + data: []byte{ + 0xa1, // { + 0x0b, 0x42, 0xf0, 0x0d, // / counter signature / 11: h'f00d' + }, + wantErr: "invalid Countersignature object / list of objects", + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got UnprotectedHeader