Skip to main content

4. Signing Objects

  1. Signing Objects

COSE supports two different signature structures. COSE_Sign allows for one or more signatures to be applied to the same content. COSE_Sign1 is restricted to a single signer. The structures cannot be converted between each other; as the signature computation includes a parameter identifying which structure is being used, the converted structure will fail signature validation.

4.1. Signing with One or More Signers

The COSE_Sign structure allows for one or more signatures to be applied to a message payload. Header parameters relating to the content and header parameters relating to the signature are carried along with the signature itself. These header parameters may be authenticated by the signature, or just be present. An example of a header parameter about the content is the content type header parameter. An example of a header parameter about the signature would be the algorithm and key used to create the signature.

[RFC5652] indicates that:

| When more than one signature is present, the successful validation | of one signature associated with a given signer is usually treated | as a successful signature by that signer. However, there are some | application environments where other rules are needed. An | application that employs a rule other than one valid signature for | each signer must specify those rules. Also, where simple matching | of the signer identifier is not sufficient to determine whether | the signatures were generated by the same signer, the application | specification must describe how to determine which signatures were | generated by the same signer. Support of different communities of | recipients is the primary reason that signers choose to include | more than one signature.

For example, the COSE_Sign structure might include signatures generated with the Edwards-curve Digital Signature Algorithm (EdDSA) [RFC8032] and the Elliptic Curve Digital Signature Algorithm (ECDSA) [DSS]. This allows recipients to verify the signature associated with one algorithm or the other. More detailed information on multiple signature evaluations can be found in [RFC5752].

The signature structure can be encoded as either tagged or untagged, depending on the context it will be used in. A tagged COSE_Sign structure is identified by the CBOR tag 98. The CDDL fragment that represents this is:

COSE_Sign_Tagged = #6.98(COSE_Sign)

A COSE Signed Message is defined in two parts. The CBOR object that carries the body and information about the message is called the COSE_Sign structure. The CBOR object that carries the signature and information about the signature is called the COSE_Signature structure. Examples of COSE Signed Messages can be found in Appendix C.1.

The COSE_Sign structure is a CBOR array. The fields of the array, in order, are:

protected: This is as described in Section 3.

unprotected: This is as described in Section 3.

payload: This field contains the serialized content to be signed. If the payload is not present in the message, the application is required to supply the payload separately. The payload is wrapped in a bstr to ensure that it is transported without changes. If the payload is transported separately ("detached content"), then a nil CBOR object is placed in this location, and it is the responsibility of the application to ensure that it will be transported without changes.

  Note: When a signature with a message recovery algorithm is used
(Section 8.1), the maximum number of bytes that can be recovered
is the length of the original payload. The size of the encoded
payload is reduced by the number of bytes that will be recovered.
If all of the bytes of the original payload are consumed, then the
transmitted payload is encoded as a zero-length byte string rather
than as being absent.

signatures: This field is an array of signatures. Each signature is represented as a COSE_Signature structure.

The CDDL fragment that represents the above text for COSE_Sign follows.

COSE_Sign = [ Headers, payload : bstr / nil, signatures : [+ COSE_Signature] ]

The COSE_Signature structure is a CBOR array. The fields of the array, in order, are:

protected: This is as described in Section 3.

unprotected: This is as described in Section 3.

signature: This field contains the computed signature value. The type of the field is a bstr. Algorithms MUST specify padding if the signature value is not a multiple of 8 bits.

The CDDL fragment that represents the above text for COSE_Signature follows.

COSE_Signature = [ Headers, signature : bstr ]

4.2. Signing with One Signer

The COSE_Sign1 signature structure is used when only one signature is going to be placed on a message. The header parameters dealing with the content and the signature are placed in the same pair of buckets, rather than having the separation of COSE_Sign.

The structure can be encoded as either tagged or untagged depending on the context it will be used in. A tagged COSE_Sign1 structure is identified by the CBOR tag 18. The CDDL fragment that represents this is:

COSE_Sign1_Tagged = #6.18(COSE_Sign1)

The CBOR object that carries the body, the signature, and the information about the body and signature is called the COSE_Sign1 structure. Examples of COSE_Sign1 messages can be found in Appendix C.2.

The COSE_Sign1 structure is a CBOR array. The fields of the array, in order, are:

protected: This is as described in Section 3.

unprotected: This is as described in Section 3.

payload: This is as described in Section 4.1.

signature: This field contains the computed signature value. The type of the field is a bstr.

The CDDL fragment that represents the above text for COSE_Sign1 follows.

COSE_Sign1 = [ Headers, payload : bstr / nil, signature : bstr ]

4.3. Externally Supplied Data

One of the features offered in COSE is the ability for applications to provide additional data that is to be authenticated but is not carried as part of the COSE object. The primary reason for supporting this can be seen by looking at the CoAP message structure [RFC7252], where the facility exists for options to be carried before the payload. Examples of data that can be placed in this location would be the CoAP code or CoAP options. If the data is in the headers of the CoAP message, then it is available for proxies to help in performing proxying operations. For example, the Accept option can be used by a proxy to determine if an appropriate value is in the proxy's cache. The sender can use the additional-data functionality to enable detection of any changes to the set of Accept values made by a proxy or an attacker. By including the field in the externally supplied data, any subsequent modification will cause the server processing of the message to result in failure.

This document describes the process for using a byte array of externally supplied authenticated data; the method of constructing the byte array is a function of the application. Applications that use this feature need to define how the externally supplied authenticated data is to be constructed. Such a construction needs to take into account the following issues:

  • If multiple items are included, applications need to ensure that the same byte string cannot be produced if there are different inputs. An example of how the problematic scenario could arise would be by concatenating the text strings "AB" and "CDE" or by concatenating the text strings "ABC" and "DE". This is usually addressed by making fields a fixed width and/or encoding the length of the field as part of the output. Using options from CoAP [RFC7252] as an example, these fields use a TLV structure so they can be concatenated without any problems.

  • If multiple items are included, an order for the items needs to be defined. Using options from CoAP as an example, an application could state that the fields are to be ordered by the option number.

  • Applications need to ensure that the byte string is going to be the same on both sides. Using options from CoAP might give a problem if the same relative numbering is kept. An intermediate node could insert or remove an option, changing how the relative numbering is done. An application would need to specify that the relative number must be re-encoded to be relative only to the options that are in the external data.

4.4. Signing and Verification Process

In order to create a signature, a well-defined byte string is needed. The Sig_structure is used to create the canonical form. This signing and verification process takes in the body information (COSE_Sign or COSE_Sign1), the signer information (COSE_Signature), and the application data (external source). A Sig_structure is a CBOR array. The fields of the Sig_structure, in order, are:

  1. A context text string identifying the context of the signature. The context text string is:

    "Signature" for signatures using the COSE_Signature structure.

    "Signature1" for signatures using the COSE_Sign1 structure.

  2. The protected attributes from the body structure, encoded in a bstr type. If there are no protected attributes, a zero-length byte string is used.

  3. The protected attributes from the signer structure, encoded in a bstr type. If there are no protected attributes, a zero-length byte string is used. This field is omitted for the COSE_Sign1 signature structure.

  4. The externally supplied data from the application, encoded in a bstr type. If this field is not supplied, it defaults to a zero- length byte string. (See Section 4.3 for application guidance on constructing this field.)

  5. The payload to be signed, encoded in a bstr type. The full payload is used here, independent of how it is transported.

The CDDL fragment that describes the above text is:

Sig_structure = [ context : "Signature" / "Signature1", body_protected : empty_or_serialized_map, ? sign_protected : empty_or_serialized_map, external_aad : bstr, payload : bstr ]

How to compute a signature:

  1. Create a Sig_structure and populate it with the appropriate fields.

  2. Create the value ToBeSigned by encoding the Sig_structure to a byte string, using the encoding described in Section 9.

  3. Call the signature creation algorithm, passing in K (the key to sign with), alg (the algorithm to sign with), and ToBeSigned (the value to sign).

  4. Place the resulting signature value in the correct location. This is the "signature" field of the COSE_Signature or COSE_Sign1 structure.

The steps for verifying a signature are:

  1. Create a Sig_structure and populate it with the appropriate fields.

  2. Create the value ToBeSigned by encoding the Sig_structure to a byte string, using the encoding described in Section 9.

  3. Call the signature verification algorithm, passing in K (the key to verify with), alg (the algorithm used to sign with), ToBeSigned (the value to sign), and sig (the signature to be verified).

In addition to performing the signature verification, the application performs the appropriate checks to ensure that the key is correctly paired with the signing identity and that the signing identity is authorized before performing actions.