Skip to content

Commit

Permalink
Added support to countersignatures.
Browse files Browse the repository at this point in the history
Signed-off-by: Guilherme Balena Versiani <guibv@mailbox.org>
  • Loading branch information
balena committed Sep 11, 2023
1 parent da0f9a6 commit 61d8f4a
Show file tree
Hide file tree
Showing 3 changed files with 719 additions and 26 deletions.
286 changes: 286 additions & 0 deletions countersign.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 61d8f4a

Please sign in to comment.