A countersignature is normally defined as a second signature that confirms a primary signature. A normal example of a countersignature is the signature that a notary public places on a document as witnessing that you have signed the document. A notary typically includes a timestamp to indicate when notarization occurs; however, such a timestamp has not yet been defined for COSE. A timestamp, once defined in a future document, might be included as a protected header parameter. Thus, applying a countersignature to either the COSE_Signature or COSE_Sign1 objects matches this traditional definition. This document extends the context of a countersignature to allow it to be applied to all of the security structures defined. The countersignature needs to be treated as a separate operation from the initial operation even if it is applied by the same user, as is done in [
GROUP-OSCORE].
COSE supports two different forms for countersignatures. Full countersignatures use the structure COSE_Countersignature, which has the same structure as COSE_Signature. Thus, full countersignatures can have protected and unprotected attributes, including chained countersignatures. Abbreviated countersignatures use the structure COSE_Countersignature0. This structure only contains the signature value and nothing else. 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.
The version 2 countersignature changes the algorithm used for computing the signature from the original version that is specified in
Section 4.5 of
RFC 8152. The new version now includes the cryptographic material generated for all of the structures rather than just for a subset.
COSE was designed for uniformity in how the data structures are specified. One result of this is that for COSE one can expand the concept of countersignatures beyond just the idea of signing a signature to being able to sign most of the structures without having to create a new signing layer. When creating a countersignature, one needs to be clear about the security properties that result. When done on a COSE_Signature or COSE_Sign1, the normal countersignature semantics are preserved. That is, the countersignature makes a statement about the existence of a signature and, when used with a yet-to-be-specified timestamp, a point in time at which the signature exists. When done on a COSE_Mac or COSE_Mac0, the payload is included as well as the MAC value. When done on a COSE_Encrypt or COSE_Encrypt0, the existence of the encrypted data is attested to. It should be noted that there is a distinction between attesting to the encrypted data as opposed to attesting to the unencrypted data. If the latter is what is desired, then one needs to apply a signature to the data and then encrypt that. It is always possible to construct cases where the use of two different keys results in successful decryption, where the tag check succeeds, but two completely different plaintexts are produced. This situation is not detectable by a countersignature on the encrypted data.
The COSE_Countersignature structure allows for the same set of capabilities as a COSE_Signature. This means that all of the capabilities of a signature are duplicated with this structure. Specifically, the countersigner does not need to be related to the producer of what is being countersigned, as key and algorithm identification can be placed in the countersignature attributes. This also means that the countersignature can itself be countersigned. This is a feature required by protocols such as long-term archiving services. More information on how countersignatures are used can be found in the evidence record syntax described in [
RFC 4998].
The full countersignature structure can be encoded as either tagged or untagged, depending on the context. A tagged COSE_Countersignature structure is identified by the CBOR tag 19. The countersignature structure is the same as that used for a signer on a signed object. The CDDL fragment for full countersignatures is:
COSE_Countersignature_Tagged = #6.19(COSE_Countersignature)
COSE_Countersignature = COSE_Signature
The details of the fields of a countersignature can be found in
Section 4.1 of
RFC 9052.
An example of a countersignature on a signature can be found in
Appendix A.1.1. An example of a countersignature in an encryption object can be found in
Appendix A.3.1.
It should be noted that only a signature algorithm with appendix (see
Section 8.1 of
RFC 9052) can be used for countersignatures. This is because the body should be able to be processed without having to evaluate the countersignature, and this is not possible for signature schemes with message recovery.
Abbreviated countersignatures support encrypted group messaging where identification of the message originator is required but there is a desire to keep the countersignature as small as possible. For abbreviated countersignatures, there is no provision for any protected attributes related to the signing operation. That is, the parameters for computing or verifying the abbreviated countersignature are provided by the same context used to describe the encryption, signature, or MAC processing.
The CDDL fragment for the abbreviated countersignatures is:
COSE_Countersignature0 = bstr
The byte string representing the signature value is placed in the Countersignature0 attribute. This attribute is then encoded as an unprotected header parameter.
In order to create a signature, a well-defined byte string is needed. The Countersign_structure is used to create the canonical form. This signing and verification process takes in the countersignature target structure (COSE_Signature, COSE_Sign1, COSE_Sign, COSE_Mac, COSE_Mac0, COSE_Encrypt, or COSE_Encrypt0), the signer information (COSE_Signature), and the application data (external source). A Countersign_structure is a CBOR array. The target structure of the countersignature needs to have all of its cryptographic functions finalized before computing the signature. The fields of the Countersign_structure, in order, are:
-
context:
-
A context text string identifying the context of the signature. The context text string is one of the following:
-
"CounterSignature" for countersignatures using the COSE_Countersignature structure when other_fields is absent.
-
"CounterSignature0" for countersignatures using the COSE_Countersignature0 structure when other_fields is absent.
-
"CounterSignatureV2" for countersignatures using the COSE_Countersignature structure when other_fields is present.
-
"CounterSignature0V2" for countersignatures using the COSE_Countersignature0 structure when other_fields is present.
-
body_protected:
-
The serialized protected attributes from the target structure, encoded in a bstr type. If there are no protected attributes, a zero-length byte string is used.
-
sign_protected:
-
The serialized 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 Countersignature0V2 attribute.
-
external_aad:
-
The externally supplied additional authenticated data (AAD) 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.4 of RFC 9052 for application guidance on constructing this field.)
-
payload:
-
The payload to be signed, encoded in a bstr type. The payload is placed here independently of how it is transported.
-
other_fields:
-
Omitted if there are only two bstr fields in the target structure. This field is an array of all bstr fields after the second. As an example, this would be an array of one element for the COSE_Sign1 structure containing the signature value.
The CDDL fragment that describes the above text is:
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 ]
]
How to compute a countersignature:
-
Create a Countersign_structure and populate it with the appropriate fields.
-
Create the value ToBeSigned by encoding the Countersign_structure to a byte string, using the encoding described in Section 4.
-
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).
-
Place the resulting signature value in the correct location. This is the "signature" field of the COSE_Countersignature structure for full countersignatures (see Section 3.1). This is the value of the Countersignature0 attribute for abbreviated countersignatures (see Section 3.2).
The steps for verifying a countersignature:
-
Create a Countersign_structure and populate it with the appropriate fields.
-
Create the value ToBeSigned by encoding the Countersign_structure to a byte string, using the encoding described in Section 4.
-
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.