RFC 9052

CBOR Object Signing and Encryption (COSE): Structures and Process

๐Ÿ“… published August 2022, from ietf

Part 1 of 15 ๐Ÿ’–

RFC 9052: CBOR Object Signing and Encryption (COSE): Structures and Process ๐ŸŽ€

Okay so this is RFC 9052 ๐Ÿ’… It's a Standards Track doc from the IETF, filed under STD 96, and the author is J. Schaad of August Cellars. It was published in August 2022. It's an ISSN 2070-1721 thing, and it obsoletes RFC 8152, which is the old COSE spec it's replacing. It doesn't update anything. Main character energy, honestly ๐Ÿ‘‘

Abstract โœจ

Okay, so CBOR (Concise Binary Object Representation) is a data format built for tiny code size and tiny messages ๐Ÿงš We need basic security services for it, and this doc is the answer: the CBOR Object Signing and Encryption (COSE) protocol ๐Ÿ” It explains how to make and process signatures, message authentication codes, and encryption, all serialized with CBOR. It also shows how to represent cryptographic keys in CBOR ๐Ÿ—๏ธ And yep, this doc, together with RFC 9053, obsoletes RFC 8152 ๐Ÿ’œ

Status of This Memo โœจ

This is an Internet Standards Track document ๐Ÿฆ‹

It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG) ๐ŸŽ‰ If you want more on Internet Standards, check out Section 2 of RFC 7841.

For the current status, any errata, or to give feedback, head to https://www.rfc-editor.org/info/rfc9052 ๐ŸŒธ

Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved ๐Ÿ’…

This doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the publication date. Please read these carefully, because they spell out your rights and restrictions for this document ๐Ÿ“œ Any Code Components pulled out of this document need to include Revised BSD License text, as described in Section 4.e of the Trust Legal Provisions. They come without warranty, as described in the Revised BSD License ๐Ÿซถ


Table of Contents ๐Ÿงš

   1.  Introduction
     1.1.  Requirements Terminology
     1.2.  Changes from RFC 8152
     1.3.  Design Changes from JOSE
     1.4.  CDDL Grammar for CBOR Data Structures
     1.5.  CBOR-Related Terminology
     1.6.  Document Terminology
   2.  Basic COSE Structure
   3.  Header Parameters
     3.1.  Common COSE Header Parameters
   4.  Signing Objects
     4.1.  Signing with One or More Signers
     4.2.  Signing with One Signer
     4.3.  Externally Supplied Data
     4.4.  Signing and Verification Process
   5.  Encryption Objects
     5.1.  Enveloped COSE Structure
       5.1.1.  Content Key Distribution Methods
     5.2.  Single Recipient Encrypted
     5.3.  How to Encrypt and Decrypt for AEAD Algorithms
     5.4.  How to Encrypt and Decrypt for AE Algorithms
   6.  MAC Objects
     6.1.  MACed Message with Recipients
     6.2.  MACed Messages with Implicit Key
     6.3.  How to Compute and Verify a MAC
   7.  Key Objects
     7.1.  COSE Key Common Parameters
   8.  Taxonomy of Algorithms Used by COSE
     8.1.  Signature Algorithms
     8.2.  Message Authentication Code (MAC) Algorithms
     8.3.  Content Encryption Algorithms
     8.4.  Key Derivation Functions (KDFs)
     8.5.  Content Key Distribution Methods
       8.5.1.  Direct Encryption
       8.5.2.  Key Wrap
       8.5.3.  Key Transport
       8.5.4.  Direct Key Agreement
       8.5.5.  Key Agreement with Key Wrap
   9.  CBOR Encoding Restrictions
   10. Application Profiling Considerations
   11. IANA Considerations
     11.1.  COSE Header Parameters Registry
     11.2.  COSE Key Common Parameters Registry
     11.3.  Media Type Registrations
       11.3.1.  COSE Security Message
       11.3.2.  COSE Key Media Type
     11.4.  CoAP Content-Formats Registry
     11.5.  CBOR Tags Registry
     11.6.  Expert Review Instructions
   12. Security Considerations
   13. References
     13.1.  Normative References
     13.2.  Informative References
   Appendix A.  Guidelines for External Data Authentication of
           Algorithms
   Appendix B.  Two Layers of Recipient Information
   Appendix C.  Examples
     C.1.  Examples of Signed Messages
       C.1.1.  Single Signature
       C.1.2.  Multiple Signers
       C.1.3.  Signature with Criticality
     C.2.  Single Signer Examples
       C.2.1.  Single ECDSA Signature
     C.3.  Examples of Enveloped Messages
       C.3.1.  Direct ECDH
       C.3.2.  Direct Plus Key Derivation
       C.3.3.  Encrypted Content with External Data
     C.4.  Examples of Encrypted Messages
       C.4.1.  Simple Encrypted Message
       C.4.2.  Encrypted Message with a Partial IV
     C.5.  Examples of MACed Messages
       C.5.1.  Shared Secret Direct MAC
       C.5.2.  ECDH Direct MAC
       C.5.3.  Wrapped MAC
       C.5.4.  Multi-Recipient MACed Message
     C.6.  Examples of MAC0 Messages
       C.6.1.  Shared-Secret Direct MAC
     C.7.  COSE Keys
       C.7.1.  Public Keys
       C.7.2.  Private Keys
   Acknowledgments
   Author's Address

1. Introduction โœจ

There's been a big push toward small, constrained devices, the stuff that makes up the Internet of Things (IoT) ๐ŸŒˆ One standard that came out of all that is "Concise Binary Object Representation (CBOR)" [STD94]. CBOR extended the data model of JavaScript Object Notation (JSON) [STD90] by allowing binary data, among other changes. Lots of IETF working groups in the IoT space have adopted CBOR to encode their data structures ๐Ÿ’– CBOR was built to be small, both in the messages it carries and in implementation size, and it has a schema-free decoder. We need message security services for IoT, so using CBOR as the message-encoding format just makes sense ๐Ÿ“

The JOSE Working Group made a set of documents [RFC7515] [RFC7516] [RFC7517] [RFC7518] that explain how to process encryption, signatures, and Message Authentication Code (MAC) operations, plus how to encode keys using JSON. This document defines the CBOR Object Signing and Encryption (COSE) standard, which does the same thing for the CBOR encoding format ๐Ÿฆ‹ It's paired with [RFC9053], which gives an initial set of algorithms. COSE keeps the flavor of the original JSON Object Signing and Encryption (JOSE) documents, but two things were taken into account:

  • CBOR can do things JSON can't, and it's worth using them. For example, CBOR can encode binary data directly, so you don't have to convert it into a base64-encoded text string first ๐Ÿ™ˆ
  • COSE isn't a straight copy of JOSE. While making COSE, the team re-examined decisions that had been made for JOSE. In many cases they landed on different results, since the criteria weren't always the same โœจ

This document contains:

  • The description of the structure for the CBOR objects that get sent over the wire. Two objects each are defined for encryption, signing, and message authentication. One object is defined for transporting keys, and one for transporting groups of keys ๐ŸŽ
  • The procedures for building the inputs to the cryptographic functions that each structure needs ๐Ÿ”ง
  • A set of attributes that apply to the different security objects ๐Ÿท๏ธ

This document doesn't cover the rules and procedures for specific cryptographic algorithms. You'll find details on those in [RFC9053] and [RFC8230]. Details for additional algorithms are expected in future documents ๐Ÿ”ฎ

COSE was originally designed as part of a solution for security in Constrained RESTful Environments (CoRE), and that's done using [RFC8613] and [CORE-GROUPCOMM]. But COSE isn't limited to those cases. It can be used anywhere you'd consider JOSE or Cryptographic Message Syntax (CMS) [RFC5652] for providing security services ๐Ÿ‘‘ COSE, like JOSE and CMS, is only for store-and-forward or offline protocols. Using COSE for encryption in online protocols requires an online key establishment process to happen before objects get sent back and forth. Any application using COSE for security services first has to figure out which security services it needs, then pick the matching COSE structures and cryptographic algorithms. Section 10 has more on what applications need to specify when using COSE ๐Ÿ’…

One feature CMS has that this standard doesn't is a digest structure, and that's on purpose ๐Ÿ™ˆ It's better for each protocol to define its own, since different protocols want different sets of fields in it. The algorithm identifier and the digest value will be common to every application, but the two might not always sit next to each other, because the algorithm could be defined once and used with multiple values. Applications may also want extra data fields in the structure. One example is a URI or other pointer to where the hashed data can be found ๐Ÿ”— [RFC9054] has one possible structure like that, and it defines a set of digest algorithms ๐ŸŒธ

While COSE was moving up to Internet Standard, people noticed that the description of countersignature security properties was wrong for the COSE_Sign1 structure. The security properties described were those of a true countersignature, and those were what the working group wanted. So the team removed all of the countersignature text from this document and made a new one, [COSE-COUNTERSIGN]. That new doc deprecates the old countersignature algorithm and header parameters, and defines a new algorithm and header parameters with the properties everyone wanted ๐Ÿงš

1.1. Requirements Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here ๐Ÿ“š

1.2. Changes from RFC 8152

  • This document was split out of the original, with [RFC9053] taking the rest ๐Ÿช„
  • Added some text explaining why COSE doesn't define a digest structure ๐Ÿ’ญ
  • Made some text clarifications and terminology changes โœ๏ธ
  • Removed all of the countersignature details and put them in [COSE-COUNTERSIGN] ๐Ÿซถ

1.3. Design Changes from JOSE โœจ

okay so COSE took a bunch of lessons from JOSE and glowed up ๐Ÿ’… here's what changed:

  • A single overall message structure is defined, so encrypted, signed, and MACed messages are easy to spot and still share the same look. ๐ŸŽ€
  • Signed messages split their protected and unprotected header parameters into two groups: ones about the content and ones about the signature. ๐Ÿ’–
  • MACed messages get their own lane, separate from signed messages. ๐Ÿฆ‹
  • MACed messages can use the same set of recipient algorithms as enveloped messages to get the MAC authentication key. ๐Ÿซถ
  • Binary encodings replace base64url for binary data. no more base64 detours ๐Ÿ™ˆ
  • The authentication tag for encryption algorithms is now combined with the ciphertext. โœจ
  • The set of cryptographic algorithms got expanded in some directions and trimmed in others. ๐ŸŒˆ

1.4. CDDL Grammar for CBOR Data Structures ๐Ÿงš

okay story time ๐Ÿ“– when COSE was first written, the Concise Data Definition Language (CDDL) [RFC8610] wasn't published as an RFC yet, so it couldn't be used to normatively describe the CBOR data structures COSE uses. so the CBOR data objects here are described in prose. the CDDL subset below is extra, non-normative stuff on top. ๐Ÿ’…

the grammar was developed first, and then the prose got written to match it. which means the prose uses the primitive-type strings defined by CDDL [RFC8610]. these are the ones this spec uses:

  • any: a nonspecific value that lets any CBOR value go here. ๐ŸŒธ
  • bool: a boolean value (true: major type 7, value 21; false: major type 7, value 20). โœ…
  • bstr: byte string (major type 2). ๐Ÿ“
  • int: an unsigned integer or a negative integer. ๐Ÿ”ข
  • nil: a null value (major type 7, value 22). ๐Ÿซฅ
  • nint: a negative integer (major type 1). โž–
  • tstr: a UTF-8 text string (major type 3). ๐Ÿ’ฌ
  • uint: an unsigned integer (major type 0). โž•

there are three CDDL shorthand syntaxes in here too, lowkey essential:

  • FOO / BAR: either FOO or BAR can show up here. ๐Ÿ‘ฏ
  • [+ FOO]: the type FOO shows up one or more times in an array. ๐Ÿงบ
  • * FOO: the type FOO shows up zero or more times. โ™พ๏ธ

and two CDDL constraints are used as well:

  • type1 .cbor type2: the contents of type1 (usually a bstr) contain a value of type2. ๐Ÿ“ฆ
  • type1 .size integer: the contents of type1 is integer bytes long. ๐Ÿ“

so the prose is the normative part, and the grammar is informational. ngl the grammar is still super helpful to read alongside it ๐Ÿค“

you can pull all the collected CDDL out of the XML version of this document with the XPath expression below. (depending on which XPath evaluator you use, you might need to handle > as an entity.)

//sourcecode[@type='cddl']/text()

CDDL expects the initial nonterminal symbol to be the first symbol in the file, so the first fragment is shown here:

start = COSE_Messages / COSE_Key / COSE_KeySet / Internal_Types

; This is defined to make the tool quieter:
Internal_Types = Sig_structure / Enc_structure / MAC_structure

the nonterminal Internal_Types exists for the automated validation tools used while this document was being written. it points at the nonterminals that are used for security computations but never sent over the wire. ๐Ÿ”


in JSON, maps are called objects and only have one kind of map key: a text string. in COSE, map keys can be text strings, negative integers, or unsigned integers. the integers make encodings compact and comparisons easy, and text strings unlock an extra range of short encoded values too. ๐Ÿง 

the word "key" is already busy meaning a cryptographic key, so for a map key we use the term "label" instead. โœจ

in a CBOR map defined by this specification, a label that is neither a text string nor an integer is an error. applications can either fail processing or process messages by ignoring incorrect labels, however they MUST NOT create messages with incorrect labels. ๐Ÿšจ

the paragraph above describes the nonterminal "label" along with "values", which allows any value at all:

label = int / tstr
values = any

1.6. Document Terminology ๐Ÿ“š

here's the terminology we're using in this document:

  • Byte: a synonym for octet. ๐Ÿซง
  • Constrained Application Protocol (CoAP): a specialized web transfer protocol for use in constrained systems. it's defined in [RFC7252]. ๐ŸŒ
  • Authenticated Encryption (AE) algorithms [RFC5116]: encryption algorithms that give an authentication check of the contents along with the encryption service. an AE algorithm used in COSE is AES Key Wrap [RFC3394]. these are used for key encryption, but Authenticated Encryption with Associated Data (AEAD) algorithms would be preferred. ๐Ÿ”‘
  • AEAD algorithms [RFC5116]: encryption algorithms that provide the same authentication service for the content as AE algorithms do, and also let associated data that is not part of the encrypted body be included in the authentication service. an AEAD algorithm used in COSE is AES-GCM [RFC5116]. these are used for content encryption and can be used for key encryption too. ๐Ÿ†

"context" shows up all over this document to mean information that is not part of the COSE message. context can come from several places: protocol interactions, associated key structures, and program configuration. the context to use can be implicit, identified using the "kid context" header parameter defined in [RFC8613], or identified by a protocol-specific identifier. context should generally be included in the cryptographic construction; for more details, see Section 4.3. ๐Ÿงฉ

last but not least: "byte string" is used for sequences of bytes, and "text string" is used for sequences of characters. ๐Ÿ’œ

2. Basic COSE Structure โœจ

Okay so bestie, COSE objects are designed so that you can reuse a ton of the same code when you're parsing and processing the different kinds of security messages ๐Ÿง ๐Ÿ’–. Every single message structure is built on the CBOR array type ๐ŸŽ€. The first three elements of that array always hold the same stuff, no cap:

  1. The protected header parameters, encoded and wrapped in a bstr ๐Ÿ”’.
  2. The unprotected header parameters as a map ๐Ÿ”“.
  3. The content of the message ๐Ÿ“ฆ. This is either the plaintext or the ciphertext, whichever is appropriate. The content can be detached, which means it gets transported separately from the COSE structure, but the location is still used ๐Ÿฆ‹. When the content is present, it's wrapped in a bstr. When it's detached, it's a nil value ๐ŸŒธ.

Anything after this point depends on the specific message type ๐Ÿ’….

COSE messages use the concept of layers to keep different cryptographic concepts separated ๐Ÿง. Let's look at COSE_Encrypt (Section 5.1) as an example. It's split into two layers: the content layer and the recipient layer ๐Ÿ’œ. The content layer holds the encrypted plaintext plus info about the encrypted message ๐ŸŽ. The recipient layer holds the encrypted content encryption key (CEK) plus info about how it was encrypted, once for each recipient ๐Ÿ‘‘. There's also a single-layer version called COSE_Encrypt0 (Section 5.2) for when the CEK is preshared ๐Ÿซถ.

Now, how do you figure out which type of message you're looking at? There are four methods, and they're iconic ๐Ÿ˜ญ:

  1. The specific message type is known from the context. This might be defined by a marker in the containing structure, or by restrictions the application protocol puts in place โœจ.
  2. The message type is identified by a CBOR tag. Messages with a CBOR tag are called tagged messages in this spec, and the ones without one are untagged messages ๐ŸŒˆ. This document defines a CBOR tag for each of the message structures, and you can find them in Table 1 ๐Ÿ“.
  3. When a COSE object is carried in a media type of "application/cose", the optional parameter "cose-type" can be used to identify the embedded object ๐ŸŽ€. The parameter is OPTIONAL if you're using the tagged version of the structure. It's REQUIRED if you're using the untagged version. You can find the value for each structure in Table 1 ๐Ÿ’–.
  4. When a COSE object is carried as a CoAP payload, the CoAP Content-Format Option can be used to identify the message content ๐Ÿงš. The CoAP Content-Format values are in Table 2. The CBOR tag isn't required here, because each security message is uniquely identified ๐ŸŒธ.
+==========+===============+===============+=======================+
| CBOR Tag | cose-type     | Data Item     | Semantics             |
+==========+===============+===============+=======================+
| 98       | cose-sign     | COSE_Sign     | COSE Signed Data      |
|          |               |               | Object                |
+----------+---------------+---------------+-----------------------+
| 18       | cose-sign1    | COSE_Sign1    | COSE Single Signer    |
|          |               |               | Data Object           |
+----------+---------------+---------------+-----------------------+
| 96       | cose-encrypt  | COSE_Encrypt  | COSE Encrypted Data   |
|          |               |               | Object                |
+----------+---------------+---------------+-----------------------+
| 16       | cose-encrypt0 | COSE_Encrypt0 | COSE Single Recipient |
|          |               |               | Encrypted Data Object |
+----------+---------------+---------------+-----------------------+
| 97       | cose-mac      | COSE_Mac      | COSE MACed Data       |
|          |               |               | Object                |
+----------+---------------+---------------+-----------------------+
| 17       | cose-mac0     | COSE_Mac0     | COSE Mac w/o          |
|          |               |               | Recipients Object     |
+----------+---------------+---------------+-----------------------+

                   Table 1: COSE Message Identification
        +===========================+==========+=====+===========+
        | Media Type                | Encoding | ID  | Reference |
        +===========================+==========+=====+===========+
        | application/cose; cose-   |          | 98  | RFC 9052  |
        | type="cose-sign"          |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose; cose-   |          | 18  | RFC 9052  |
        | type="cose-sign1"         |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose; cose-   |          | 96  | RFC 9052  |
        | type="cose-encrypt"       |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose; cose-   |          | 16  | RFC 9052  |
        | type="cose-encrypt0"      |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose; cose-   |          | 97  | RFC 9052  |
        | type="cose-mac"           |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose; cose-   |          | 17  | RFC 9052  |
        | type="cose-mac0"          |          |     |           |
        +---------------------------+----------+-----+-----------+
        | application/cose-key      |          | 101 | RFC 9052  |
        +---------------------------+----------+-----+-----------+
        | application/cose-key-set  |          | 102 | RFC 9052  |
        +---------------------------+----------+-----+-----------+

                  Table 2: CoAP Content-Formats for COSE

The following CDDL fragment identifies all of the top messages defined in this document ๐Ÿ“œ. Separate nonterminals are defined for the tagged and untagged versions of the messages ๐ŸŽ€.

COSE_Messages = COSE_Untagged_Message / COSE_Tagged_Message

COSE_Untagged_Message = COSE_Sign / COSE_Sign1 /
    COSE_Encrypt / COSE_Encrypt0 /
    COSE_Mac / COSE_Mac0

COSE_Tagged_Message = COSE_Sign_Tagged / COSE_Sign1_Tagged /
    COSE_Encrypt_Tagged / COSE_Encrypt0_Tagged /
    COSE_Mac_Tagged / COSE_Mac0_Tagged

3. Header Parameters ๐ŸŽ€

COSE has two buckets of information that aren't considered part of the payload itself ๐Ÿง. They hold stuff about content, algorithms, keys, or evaluation hints for processing the layer ๐Ÿ’…. Both buckets are available in all the structures except keys ๐Ÿ”‘. Just because the buckets exist doesn't mean they're always usable, though ๐Ÿ˜ญ. For example, the protected bucket is defined as part of the recipient structure, but some of the algorithms used for recipient structures don't support authenticated data. If that's the case, the protected bucket is left empty ๐Ÿซถ.

Both buckets are implemented as CBOR maps ๐Ÿ—บ๏ธ. The map key is a "label" (Section 1.5), and the value depends on the definition for that label ๐Ÿ’œ. Both maps use the same set of label/value pairs ๐ŸŒˆ. The integer and text-string values for labels are split into several sections: a standard range, a private use range, and a range that depends on the algorithm selected โœจ. You can find the defined labels in the "COSE Header Parameters" IANA registry (Section 11.1) ๐Ÿ“.

The two buckets are:

protected: Contains parameters about the current layer that are cryptographically protected. This bucket MUST be empty if it is not going to be included in a cryptographic computation ๐Ÿ”’. This bucket is encoded in the message as a binary object. Its value is obtained by CBOR encoding the protected map and wrapping it in a bstr object ๐ŸŽ. Senders SHOULD encode a zero-length map as a zero-length byte string rather than as a zero-length map (encoded as h'a0') ๐Ÿ’–. The zero-length byte string encoding is preferred, because it's both shorter and the version used in the serialization structures for cryptographic computation ๐Ÿฆ‹. Recipients MUST accept both a zero-length byte string and a zero-length map encoded in a byte string ๐Ÿ™ˆ.

Wrapping the encoding in a byte string gives the protected map a better chance of arriving unaltered in transit ๐Ÿ›ก๏ธ. (Badly behaved intermediates could decode and re-encode it, but that will cause a failure to verify unless the re-encoded byte string is identical to the decoded byte string ๐Ÿ’€.) This means all parties don't need to agree on a common canonical encoding of the map for cryptographic operations ๐Ÿ‘‘.

unprotected: Contains parameters about the current layer that are not cryptographically protected ๐ŸŒธ.

Only header parameters that deal with the current layer should be placed at that layer ๐Ÿงš. For example, the header parameter "content type" describes the content of the message being carried, so it goes only in the content layer, not in the recipient or signature layers ๐Ÿ’…. In principle, you should be able to process any layer without referring to any other layer ๐Ÿซง. Apart from the COSE_Sign structure, the only data that needs to cross layers is the cryptographic key ๐Ÿ”‘.

The buckets are present in all of the security objects defined in this document ๐ŸŽ€. The fields, in order, are the "protected" bucket (as a CBOR "bstr" type) and then the "unprotected" bucket (as a CBOR "map" type) โœจ. Both buckets are required to be present ๐Ÿ’œ. The header parameters that go into the buckets come from the IANA "COSE Header Parameters" registry (Section 11.1) ๐Ÿ“. Some header parameters are defined in the next section ๐ŸŒˆ.

Labels in each of the maps MUST be unique ๐Ÿ™…โ€โ™€๏ธ. When processing messages, if a label appears multiple times, the message MUST be rejected as malformed ๐Ÿ’€. Applications SHOULD verify that the same label does not occur in both the protected and unprotected header parameters ๐Ÿ”. If the message isn't rejected as malformed, attributes MUST be obtained from the protected bucket, and only if an attribute isn't found there can it be obtained from the unprotected bucket ๐Ÿ‘‘.

The following CDDL fragment represents the two header-parameter buckets ๐Ÿ“œ. A group "Headers" is defined in CDDL that represents the two buckets where attributes are placed ๐ŸŽ€. This group is used to provide these two fields consistently in all locations ๐Ÿซถ. A type is also defined that represents the map of common header parameters ๐Ÿฆ‹.

Headers = (
    protected : empty_or_serialized_map,
    unprotected : header_map
)

header_map = {
    Generic_Headers,
    * label => values
}

empty_or_serialized_map = bstr .cbor header_map / bstr .size 0

3.1. Common COSE Header Parameters โœจ

okay bestie, this is where we meet the common header parameters ๐Ÿ’– Table 3 is your cheat sheet for each label and its value type, so keep it open while you read ๐Ÿ“‹

This section defines a set of common header parameters, and here's the full lineup:

  • alg ๐Ÿ”ฅ: This one says which algorithm is used for the security processing. It MUST be authenticated wherever that's possible. AEAD algorithms or constructions (like COSE_Sign and COSE_Mac0) can do this, and the authentication can happen either by putting the parameter in the protected-header-parameters bucket or as part of the externally supplied data (Section 4.3). The value comes from the "COSE Algorithms" registry (see [COSE.Algorithms]). Main character energy ๐Ÿ‘‘
  • crit ๐Ÿšจ: This one tells the app which protected header parameters it's required to understand. Header parameters defined in this document don't need to be listed, since every implementation should already get them. There's also the "counter signature" header parameter (label 7) from [RFC8152]. New implementations must understand it so they stay compatible with senders who follow that document and assume everyone will understand it. When "crit" is present, it MUST be placed in the protected-header-parameters bucket, and the array MUST have at least one value in it ๐ŸŽ€

Not every header-parameter label has to go in "crit." Here's how to decide what goes in the array:

  • Integer labels in the range of 0 to 7 SHOULD be omitted.
  • Integer labels in the range -1 to -128 can be omitted. Algorithms can assign labels in this range when being able to process the label's content is core to the algorithm. Algorithms can also assign labels outside this range and include them in "crit" when processing the content isn't core functionality but still needs to be understood to correctly process this instance. Integer labels in the range -129 to -65536 SHOULD be included, since these are less common header parameters that might not be generally supported ๐Ÿงš
  • Labels for header parameters required for an application MAY be omitted. Applications should have a statement declaring whether or not the label can be omitted ๐ŸŒธ

The header parameters indicated by "crit" can be processed by either the security-library code or an application using a security library. The only requirement is that the header parameter gets processed. If the "crit" value list includes a label whose header parameter isn't in the protected-header-parameters bucket, that's a fatal error in processing the message ๐Ÿ’€

  • content type ๐Ÿฆ‹: This one says what kind of data is in the "payload" or "ciphertext" field. Integers come from the "CoAP Content-Formats" IANA registry table [COAP.Formats]. Text values follow the syntax of "<type-name>/<subtype-name>", where <type-name> and <subtype-name> are defined in Section 4.2 of [RFC6838]. Leading and trailing whitespace is not permitted. Textual content type values, along with parameters and subparameters, can be found in the IANA "Media Types" registry. Applications SHOULD provide this header parameter if the content structure is potentially ambiguous ๐Ÿ“
  • kid ๐Ÿ”‘: This one is a piece of data that can be used as input to find the cryptographic key you need. Its value can be matched against the "kid" member in a COSE_Key structure. Other key distribution methods can define an equivalent field to match. Applications MUST NOT assume that "kid" values are unique! There may be more than one key with the same "kid" value, so all of the keys associated with that "kid" may need checking ๐Ÿ˜ญ The internal structure of "kid" values isn't defined and can't be relied on by applications. Key identifiers are just hints about which key to use. This isn't a security-critical field, so it can be placed in the unprotected-header-parameters bucket ๐Ÿ’œ
  • IV ๐ŸŒˆ: This holds the Initialization Vector (IV) value. For some symmetric encryption algorithms, it may be called a nonce. The IV can go in the unprotected bucket, because for AE and AEAD algorithms, changing the IV will make decryption fail anyway โœจ
  • Partial IV ๐Ÿง: This holds a part of the IV value. When you use the COSE_Encrypt0 structure, one portion of the IV can be part of the context associated with the key (Context IV), while another portion gets changed with each message (Partial IV). This field carries the value that makes the IV change for each message. The Partial IV can go in the unprotected bucket, since changing it makes the decrypted plaintext come out readably garbled. The "Initialization Vector" and "Partial Initialization Vector" header parameters MUST NOT both be present in the same security layer ๐Ÿ™…โ€โ™€๏ธ

The message IV is generated by these steps:

  1. Left-pad the Partial IV with zeros to the length of IV (determined by the algorithm).
  2. XOR the padded Partial IV with the Context IV.
   +=========+=======+========+=====================+==================+
   | Name    | Label | Value  | Value Registry      | Description      |
   |         |       | Type   |                     |                  |
   +=========+=======+========+=====================+==================+
   | alg     | 1     | int /  | COSE Algorithms     | Cryptographic    |
   |         |       | tstr   | registry            | algorithm to use |
   +---------+-------+--------+---------------------+------------------+
   | crit    | 2     | [+     | COSE Header         | Critical header  |
   |         |       | label] | Parameters          | parameters to be |
   |         |       |        | registry            | understood       |
   +---------+-------+--------+---------------------+------------------+
   | content | 3     | tstr / | CoAP Content-       | Content type of  |
   | type    |       | uint   | Formats or Media    | the payload      |
   |         |       |        | Types registries    |                  |
   +---------+-------+--------+---------------------+------------------+
   | kid     | 4     | bstr   |                     | Key identifier   |
   +---------+-------+--------+---------------------+------------------+
   | IV      | 5     | bstr   |                     | Full             |
   |         |       |        |                     | Initialization   |
   |         |       |        |                     | Vector           |
   +---------+-------+--------+---------------------+------------------+
   | Partial | 6     | bstr   |                     | Partial          |
   | IV      |       |        |                     | Initialization   |
   |         |       |        |                     | Vector           |
   +---------+-------+--------+---------------------+------------------+

                     Table 3: Common Header Parameters

Every header parameter in this section is tagged as optional in the CDDL below, since they don't all have to show up in every map. The specific maps that require certain header parameters are covered above ๐Ÿซถ

   Generic_Headers = (
       ? 1 => int / tstr,  ; algorithm identifier
       ? 2 => [+label],    ; criticality
       ? 3 => tstr / int,  ; content type
       ? 4 => bstr,        ; key identifier
       ? ( 5 => bstr //    ; IV
           6 => bstr )     ; Partial IV
   )

4. Signing Objects ๐Ÿ–‹๏ธ

okay so COSE gives you two signature structures ๐Ÿ‘‘ COSE_Sign lets one or more signatures be applied to the same content, while COSE_Sign1 is restricted to a single signer. You can't convert between them, though, because the signature computation includes a parameter that identifies which structure is being used. If you try to convert one into the other, the converted structure will fail signature validation ๐Ÿ’… bestie, don't even try it ๐Ÿ’€

4.1. Signing with One or More Signers ๐Ÿ’–

okay so COSE_Sign is the one that lets you put one or more signatures on a single message payload ๐ŸŽ€ The header parameters about the content and the ones about each signature all ride along with the signature itself. Some of those headers can be covered by the signature, and some are just sitting there. A content header would be something like the content type. A signature header would be the algorithm and key used to make the signature ๐Ÿฆ‹

[RFC5652] says this, and I'm quoting it exactly because it's important:

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, a COSE_Sign could carry signatures made with the Edwards-curve Digital Signature Algorithm (EdDSA) [RFC8032] and the Elliptic Curve Digital Signature Algorithm (ECDSA) [DSS]. That way recipients can verify whichever one they support ๐ŸŒˆ More detail on evaluating multiple signatures is in [RFC5752] โœจ

The signature structure can be encoded as tagged or untagged, depending on where it's going ๐Ÿ’… A tagged COSE_Sign is identified by CBOR tag 98. The CDDL fragment for that is:

COSE_Sign_Tagged = #6.98(COSE_Sign)

A COSE Signed Message is split into two parts ๐Ÿ‘‘ The CBOR object that holds the body and info about the message is called the COSE_Sign structure. The CBOR object that holds the signature and info about the signature is called the COSE_Signature structure. Examples of COSE Signed Messages are in Appendix C.1 ๐Ÿงš

The COSE_Sign structure is a CBOR array. Its fields, 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 so it gets transported without changes. If the payload is transported separately ("detached content"), then a nil CBOR object goes in this spot, and it's the application's job to make sure it gets transported without changes ๐Ÿซถ
  • signatures: This field is an array of signatures. Each signature is represented as a COSE_Signature structure.
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. ๐Ÿ˜ญ

The CDDL fragment that represents the text above for COSE_Sign follows ๐Ÿ’œ

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

The COSE_Signature structure is also a CBOR array, with these fields in order:

  • 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 for COSE_Signature:

COSE_Signature =  [
    Headers,
    signature : bstr
]

4.2. Signing with One Signer ๐ŸŒธ

The COSE_Sign1 signature structure is for when only one signature is going on the message ๐Ÿ“ Instead of splitting the content and signature headers into two separate spots like COSE_Sign does, it puts them in the same pair of buckets.

It can be encoded as tagged or untagged, depending on the context it's used in. A tagged COSE_Sign1 is identified by CBOR tag 18. The CDDL fragment is:

COSE_Sign1_Tagged = #6.18(COSE_Sign1)

The CBOR object that carries the body, the signature, and the info about both is called the COSE_Sign1 structure. Examples of COSE_Sign1 messages are in Appendix C.2 โœจ

The COSE_Sign1 structure is a CBOR array. Its fields, 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 for COSE_Sign1:

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

4.3. Externally Supplied Data ๐Ÿฆ‹

One of the best things about COSE is that applications can hand in extra data that gets authenticated but isn't actually carried inside the COSE object ๐Ÿงš The big reason for this comes from the CoAP message structure [RFC7252], where options can show up before the payload. Things like the CoAP code or CoAP options can go in this spot. If that data lives in the CoAP message headers, proxies can use it when they do their proxying work. For instance, a proxy can check the Accept option to see if the cache has a matching value ๐Ÿ‘€ The sender can use this extra-data feature to catch any changes that a proxy or an attacker makes to the set of Accept values. Since that field is in the externally supplied data, any later modification makes the server's processing of the message fail ๐Ÿ’€

This document describes how to use a byte array of externally supplied authenticated data. How you build that byte array is up to the application. Applications that use this feature need to define how the externally supplied authenticated data gets constructed, and that construction has to deal with these issues:

  • If multiple items are included, applications need to make sure the same byte string can't be produced from different inputs. For example, concatenating the text strings "AB" and "CDE" gives the same result as concatenating "ABC" and "DE". This is usually handled by making fields a fixed width and/or encoding the length of the field as part of the output. Using CoAP [RFC7252] options as the example, those 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 CoAP options as an example, an application could say the fields are ordered by option number ๐ŸŽ€
  • Applications need to make sure the byte string is the same on both sides. Using CoAP options might be a problem if the same relative numbering is kept. An intermediate node could insert or remove an option, which changes how the relative numbering works. An application would need to specify that the relative number must be re-encoded so it's relative only to the options in the external data ๐Ÿซถ

4.4. Signing and Verification Process ๐Ÿ‘‘

To make a signature, you need a well-defined byte string, and the Sig_structure is what creates that 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. Its fields, 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.

  1. 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.
  2. 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.
  3. 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.)
  4. 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 ๐Ÿ“

  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).
Signature verification isn't the whole story! The application also has to do the appropriate checks to make sure the key is correctly paired with the signing identity, and that the signing identity is authorized before it performs any actions. ๐Ÿ™ˆ

5. Encryption Objects โœจ

okay so COSE has two encryption structures ๐ŸŽ€. COSE_Encrypt0 is for when you don't need a recipient structure because the key is already known implicitly ๐Ÿ”‘. COSE_Encrypt is for literally everything else ๐Ÿ’…. That includes multiple recipients, or a recipient algorithm that isn't direct (aka a preshared secret) ๐Ÿซถ.

5.1. Enveloped COSE Structure

the enveloped structure lets you have one or more recipients for a message ๐Ÿ’Œ. you can carry header params about the content AND header params about the recipient info ๐ŸŽ. the protected header params tied to the content get authenticated by the content encryption alg ๐Ÿ”, and the protected header params tied to the recipient (when the alg supports it) get authenticated by the recipient alg ๐Ÿ›ก๏ธ. examples of content headers are the content type and the content encryption alg ๐Ÿ‘—. examples of recipient headers are the recipient's key id and the recipient's encryption alg ๐Ÿฆ‹.

the same techniques and almost the exact same structure get used to encrypt both the plaintext and the keys ๐Ÿง. this is different from "Cryptographic Message Syntax (CMS)" [RFC5652] and "JSON Web Encryption (JWE)" [RFC7516], which use different structures for the content layer and the recipient layer ๐Ÿ™„. COSE has two structures: COSE_Encrypt holds the encrypted content, and COSE_recipient holds the encrypted keys for recipients ๐Ÿ’–. examples of enveloped messages live in Appendix C.3 ๐Ÿ“š.

The COSE_Encrypt structure can be encoded as either tagged or untagged, depending on the context it's used in โœจ. a tagged COSE_Encrypt structure is identified by the CBOR tag 96. the CDDL fragment for that looks like:

COSE_Encrypt_Tagged = #6.96(COSE_Encrypt)

the COSE_Encrypt structure is a CBOR array ๐Ÿงบ. the fields of the array, in order, are:

  • protected: this works exactly as described in Section 3 ๐ŸŒธ.
  • unprotected: this works exactly as described in Section 3 ๐ŸŒธ.
  • ciphertext: this field has the ciphertext, encoded as a bstr ๐Ÿ”’. if the ciphertext is transported separately from the control info about the encryption process (aka detached content), then this field is encoded as a nil value ๐Ÿซฅ.
  • recipients: this field has an array of recipient information structures ๐Ÿ“ฌ. the type for each recipient information structure is a COSE_recipient.

the CDDL fragment that matches the text above is:

COSE_Encrypt = [
    Headers,
    ciphertext : bstr / nil,
    recipients : [+COSE_recipient]
]

the COSE_recipient structure is also a CBOR array ๐Ÿง. the fields of the array, in order, are:

  • protected: this works exactly as described in Section 3 ๐ŸŒธ.
  • unprotected: this works exactly as described in Section 3 ๐ŸŒธ.
  • ciphertext: this field has the encrypted key, encoded as a bstr ๐Ÿ—๏ธ. all encoded keys are symmetric keys, and the binary value of the key is the content ๐Ÿ“. if there isn't an encrypted key, this field is encoded as a nil value.
  • recipients: this field has an array of recipient information structures ๐Ÿ“ฌ. the type for each one is a COSE_recipient (an example is in Appendix B). if there are no recipient information structures, this element is absent ๐Ÿ‘ป.

the CDDL fragment that matches the text above for COSE_recipient is:

COSE_recipient = [
    Headers,
    ciphertext : bstr / nil,
    ? recipients : [+COSE_recipient]
]

5.1.1. Content Key Distribution Methods

an encrypted message is made of encrypted content plus an encrypted CEK for one or more recipients ๐Ÿ’ซ. the CEK gets encrypted for each recipient using a key that's specific to that recipient ๐ŸŽฏ. the details of this depend on which class the recipient algorithm falls into ๐Ÿงฉ. specific details on each class are in Section 8.5. here's a quick summary of the five content key distribution methods ๐Ÿ’…:

  • direct: the CEK is the same as the previously distributed symmetric key you identified, or it's derived from a previously distributed secret ๐Ÿ”. no CEK gets transported in the message.
  • symmetric key-encryption keys (KEKs): the CEK is encrypted using a previously distributed symmetric KEK ๐Ÿ”. also known as key wrap.
  • key agreement: the recipient's public key and the sender's private key are used to generate a pairwise secret ๐Ÿค. a Key Derivation Function (KDF) is applied to derive a key, and then the CEK is either that derived key or gets encrypted by it ๐Ÿช„.
  • key transport: the CEK is encrypted with the recipient's public key ๐Ÿ“ฎ.
  • passwords: the CEK is encrypted in a KEK that's derived from a password ๐Ÿ™ˆ. as of when this document was published, no password algorithms had been defined.

5.2. Single Recipient Encrypted

the COSE_Encrypt0 structure can't specify recipients for the message ๐Ÿ™…โ€โ™€๏ธ. it assumes the recipient already knows which key to use in order to decrypt the message ๐Ÿ—๏ธ. if a key needs to be identified to the recipient, the enveloped structure ought to be used instead ๐Ÿ’โ€โ™€๏ธ.

examples of encrypted messages are in Appendix C.4 ๐Ÿ“š.

the COSE_Encrypt0 structure can be encoded as either tagged or untagged, depending on the context it's used in โœจ. a tagged COSE_Encrypt0 structure is identified by the CBOR tag 16. the CDDL fragment for that looks like:

COSE_Encrypt0_Tagged = #6.16(COSE_Encrypt0)

the COSE_Encrypt0 structure is a CBOR array ๐Ÿงบ. the fields of the array, in order, are:

  • protected: this works exactly as described in Section 3 ๐ŸŒธ.
  • unprotected: this works exactly as described in Section 3 ๐ŸŒธ.
  • ciphertext: this works exactly as described in Section 5.1 ๐ŸŽ€.

the CDDL fragment for COSE_Encrypt0 that matches the text above is:

COSE_Encrypt0 = [
    Headers,
    ciphertext : bstr / nil,
]

5.3. How to Encrypt and Decrypt for AEAD Algorithms

the encryption process for AEAD algorithms is pretty simple ๐Ÿฆ‹. the first step is to create a consistent byte string for the authenticated data structure ๐Ÿงต. for this we use an Enc_structure, which is a CBOR array ๐Ÿงบ. the fields of the Enc_structure, in order, are:

  1. A context text string that identifies the context of the authenticated data structure ๐Ÿท๏ธ. the context text string is one of these:
    • "Encrypt0" for the content encryption of a COSE_Encrypt0 data structure.
    • "Encrypt" for the first layer of a COSE_Encrypt data structure (aka content encryption).
    • "Enc_Recipient" for a recipient encoding to be placed in a COSE_Encrypt data structure.
    • "Mac_Recipient" for a recipient encoding to be placed in a MACed message structure.
    • "Rec_Recipient" for a recipient encoding to be placed in a recipient 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 externally supplied data from the application, encoded in a bstr type ๐ŸŽ. if this field isn't supplied, it defaults to a zero-length byte string. (See Section 4.3 for app guidance on building this field.)

the CDDL fragment that describes the text above is:

Enc_structure = [
    context : "Encrypt" / "Encrypt0" / "Enc_Recipient" /
        "Mac_Recipient" / "Rec_Recipient",
    protected : empty_or_serialized_map,
    external_aad : bstr
]

How to encrypt a message:

  1. Create an Enc_structure and fill it with the right fields ๐Ÿงฉ.
  2. Encode the Enc_structure to a byte string (Additional Authenticated Data (AAD)), using the encoding described in Section 9 ๐Ÿช„.
  3. Determine the encryption key (K) ๐Ÿ—๏ธ. this step depends on the class of recipient algorithm being used. for:
    • No Recipients: the key is determined by the algorithm and key at the current layer. examples are key wrap keys (Section 8.5.2) and preshared secrets.
    • Direct Encryption and Direct Key Agreement: the key is determined by the key and algorithm in the recipient structure. the encryption algorithm and the size of the key to be used are inputs into the KDF used for the recipient. (For direct, the KDF can be thought of as the identity operation ๐Ÿชž.) examples of these algorithms are in Sections 6.1 and 6.3 of [RFC9053].
    • Other: the key is randomly generated ๐ŸŽฒ.
  4. Call the encryption algorithm with K (the encryption key), P (the plaintext), and AAD ๐Ÿ’ซ. put the returned ciphertext into the "ciphertext" field of the structure ๐ŸŽ€.
  5. For recipients of the message using non-direct algorithms, recursively perform the encryption algorithm for that recipient, using K (the encryption key) as the plaintext ๐Ÿ”.

How to decrypt a message:

  1. Create an Enc_structure and fill it with the right fields ๐Ÿงฉ.
  2. Encode the Enc_structure to a byte string (AAD), using the encoding described in Section 9 ๐Ÿช„.
  3. Determine the decryption key ๐Ÿ”‘. this step depends on the class of recipient algorithm being used. for:
    • No Recipients: the key is determined by the algorithm and key at the current layer. examples are key wrap keys (Section 8.5.2) and preshared secrets.
    • Direct Encryption and Direct Key Agreement: the key is determined by the key and algorithm in the recipient structure. the encryption algorithm and the size of the key to be used are inputs into the KDF used for the recipient. (For direct, the KDF can be thought of as the identity operation ๐Ÿชž.)
    • Other: the key is determined by decoding and decrypting one of the recipient structures ๐Ÿ•ต๏ธโ€โ™€๏ธ.
  4. Call the decryption algorithm with K (the decryption key to use), C (the ciphertext), and AAD ๐ŸŒˆ.

5.4. How to Encrypt and Decrypt for AE Algorithms โœจ

okay so this is the step-by-step recipe for AE algorithms ๐Ÿ’– bestie, it's giving very "do these in order or the bytes get sad" energy ๐Ÿ˜ญ

How to encrypt a message:

  1. Verify that the "protected" field is a zero-length byte string.
  2. Verify that there was no external additional authenticated data supplied for this operation.
  3. Determine the encryption key. This step depends on the class of recipient algorithm being used. For:
    • No Recipients: The key to be used is determined by the algorithm and key at the current layer. Examples are key wrap keys (Section 8.5.2) and preshared secrets.
    • Direct Encryption and Direct Key Agreement: The key is determined by the key and algorithm in the recipient structure. The encryption algorithm and size of the key to be used are inputs into the KDF used for the recipient. (For direct, the KDF can be thought of as the identity operation.) Examples of these algorithms are found in Sections 6.1 and 6.3 of [RFC9053].
    • Other: The key is randomly generated.
  4. Call the encryption algorithm with K (the encryption key to use) and P (the plaintext). Place the returned ciphertext into the "ciphertext" field of the structure.
  5. For recipients of the message using non-direct algorithms, recursively perform the encryption algorithm for that recipient, using K (the encryption key) as the plaintext.

How to decrypt a message:

  1. Verify that the "protected" field is a zero-length byte string.
  2. Verify that there was no external additional authenticated data supplied for this operation.
  3. Determine the decryption key. This step depends on the class of recipient algorithm being used. For:
    • No Recipients: The key to be used is determined by the algorithm and key at the current layer. Examples are key wrap keys (Section 8.5.2) and preshared secrets.
    • Direct Encryption and Direct Key Agreement: The key is determined by the key and algorithm in the recipient structure. The encryption algorithm and size of the key to be used are inputs into the KDF used for the recipient. (For direct, the KDF can be thought of as the identity operation.) Examples of these algorithms are found in Sections 6.1 and 6.3 of [RFC9053].
    • Other: The key is determined by decoding and decrypting one of the recipient structures.
  4. Call the decryption algorithm with K (the decryption key to use) and C (the ciphertext).

6. MAC Objects ๐Ÿ”

okay the MAC section ๐Ÿซถ COSE has two MAC structures, and they're both giving different main character energy:

  • COSE_Mac0 is used when a recipient structure is not needed, because the key to be used is implicitly known.
  • COSE_Mac is used for all other cases. These include needing multiple recipients, the key being unknown, or a recipient algorithm other than direct.

In this section we walk through the structure and the methods for doing MAC authentication in COSE. This document lets you use all of the same classes of recipient algorithms that are allowed for encryption, so no being left out ๐Ÿ’…

There are two modes for MAC operations ๐Ÿฆ‹. The first is just a check that the content hasn't been changed since the MAC was computed. Any class of recipient algorithm can be used for this. The second mode does both: it checks that the content hasn't changed and uses the recipient algorithm to verify who sent it. The classes that support this are the ones that use a preshared secret or do Static-Static (SS) key agreement (without the key wrap step). In both of these cases, the entity that created and sent the message MAC can be validated. (This knowledge of the sender assumes that there are only two parties involved, and that you did not send the message to yourself ๐Ÿ™ˆ.) The origination property can be obtained with both of the MAC message structures.

6.1. MACed Message with Recipients ๐ŸŒธ

A multiple-recipient MACed message uses two structures: the COSE_Mac structure defined in this section for carrying the body, and the COSE_recipient structure (Section 5.1) to hold the key used for the MAC computation. Examples of MACed messages can be found in Appendix C.5.

The MAC structure can be encoded as either tagged or untagged, depending on the context it will be used in. A tagged COSE_Mac structure is identified by the CBOR tag 97. The CDDL fragment for this is:

COSE_Mac_Tagged = #6.97(COSE_Mac)

The COSE_Mac 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 MACed. 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 (i.e., detached content), then a nil CBOR value is placed in this location, and it is the responsibility of the application to ensure that it will be transported without changes.
  • tag: This field contains the MAC value.
  • recipients: This is as described in Section 5.1.

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

COSE_Mac = [
   Headers,
   payload : bstr / nil,
   tag : bstr,
   recipients : [+COSE_recipient]
]

6.2. MACed Messages with Implicit Key ๐Ÿ—๏ธ

In this section we cover the structure and methods for doing MAC authentication in the cases where the recipient is implicitly known.

The MACed message uses the COSE_Mac0 structure defined in this section for carrying the body. Examples of MACed messages with an implicit key can be found in Appendix C.6.

The MAC structure can be encoded as either tagged or untagged, depending on the context it will be used in. A tagged COSE_Mac0 structure is identified by the CBOR tag 17. The CDDL fragment for this is:

COSE_Mac0_Tagged = #6.17(COSE_Mac0)

The COSE_Mac0 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 6.1.
  • tag: This field contains the MAC value.

The CDDL fragment that corresponds to the text above is:

COSE_Mac0 = [
   Headers,
   payload : bstr / nil,
   tag : bstr,
]

6.3. How to Compute and Verify a MAC ๐Ÿงฎ

To get a consistent encoding of the data to be authenticated, the MAC_structure is used to create the canonical form. The MAC_structure is a CBOR array. The fields of the MAC_structure, in order, are:

  1. A context text string that identifies the structure being encoded. This context text string is "MAC" for the COSE_Mac structure. This context text string is "MAC0" for the COSE_Mac0 structure.
  2. The protected attributes from the body structure. If there are no protected attributes, a zero-length bstr is used.
  3. The externally supplied data from the application, encoded as 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.)
  4. The payload to be MACed, encoded in a bstr type. The full payload is used here, independent of how it is transported.

The CDDL fragment that corresponds to the text above is:

MAC_structure = [
     context : "MAC" / "MAC0",
     protected : empty_or_serialized_map,
     external_aad : bstr,
     payload : bstr
]

The steps to compute a MAC are:

  1. Create a MAC_structure and populate it with the appropriate fields.
  2. Create the value ToBeMaced by encoding the MAC_structure to a byte string, using the encoding described in Section 9.
  3. Call the MAC creation algorithm, passing in K (the key to use), alg (the algorithm to MAC with), and ToBeMaced (the value to compute the MAC on).
  4. Place the resulting MAC in the "tag" field of the COSE_Mac or COSE_Mac0 structure.
  5. For COSE_Mac structures, encrypt and encode the MAC key for each recipient of the message.

The steps to verify a MAC are:

  1. Create a MAC_structure and populate it with the appropriate fields.
  2. Create the value ToBeMaced by encoding the MAC_structure to a byte string, using the encoding described in Section 9.
  3. For COSE_Mac structures, obtain the cryptographic key by decoding and decrypting one of the recipient structures.
  4. Call the MAC creation algorithm, passing in K (the key to use), alg (the algorithm to MAC with), and ToBeMaced (the value to compute the MAC on).
  5. Compare the MAC value to the "tag" field of the COSE_Mac or COSE_Mac0 structure.

7. Key Objects ๐Ÿ”‘โœจ

A COSE Key structure is built on a CBOR map ๐Ÿ—บ๏ธ The set of common parameters that can appear in a COSE Key is in the IANA "COSE Key Common Parameters" registry [COSE.KeyParameters] (see Section 11.2). Extra parameters defined for specific key types live in the IANA "COSE Key Type Parameters" registry [COSE.KeyTypes].

A COSE Key Set uses a CBOR array as its underlying type. The array elements are COSE Keys. A COSE Key Set MUST have at least one element in the array. Examples of COSE Key Sets can be found in Appendix C.7.

Each element in a COSE Key Set MUST be processed independently ๐Ÿ’œ If one element in a COSE Key Set is either malformed or uses a key that is not understood by an application, that key is ignored, and the other keys are processed normally. Honestly, that's such a fair deal for every key in the set ๐Ÿฅน

The element "kty" is a required element in a COSE_Key map.

The CDDL grammar describing COSE_Key and COSE_KeySet is:

COSE_Key = {
    1 => tstr / int,          ; kty
    ? 2 => bstr,              ; kid
    ? 3 => tstr / int,        ; alg
    ? 4 => [+ (tstr / int) ], ; key_ops
    ? 5 => bstr,              ; Base IV
    * label => values
}

COSE_KeySet = [+COSE_Key]

7.1. COSE Key Common Parameters โœจ

okay so this bit is all about the common parameters that any COSE Key object can carry ๐Ÿ’… there are also parameters for specific key types, and those live over in [RFC9053] ๐Ÿฆ‹ Table 4 is your cheat sheet for everything in here:

+=========+=======+========+============+====================+
| Name    | Label | CBOR   | Value      | Description        |
|         |       | Type   | Registry   |                    |
+=========+=======+========+============+====================+
| kty     | 1     | tstr / | COSE Key   | Identification of  |
|         |       | int    | Types      | the key type       |
+---------+-------+--------+------------+--------------------+
| kid     | 2     | bstr   |            | Key identification |
|         |       |        |            | value -- match to  |
|         |       |        |            | "kid" in message   |
+---------+-------+--------+------------+--------------------+
| alg     | 3     | tstr / | COSE       | Key usage          |
|         |       | int    | Algorithms | restriction to     |
|         |       |        |            | this algorithm     |
+---------+-------+--------+------------+--------------------+
| key_ops | 4     | [+     |            | Restrict set of    |
|         |       | (tstr/ |            | permissible        |
|         |       | int)]  |            | operations         |
+---------+-------+--------+------------+--------------------+
| Base IV | 5     | bstr   |            | Base IV to be xor- |
|         |       |        |            | ed with Partial    |
|         |       |        |            | IVs                |
+---------+-------+--------+------------+--------------------+

                         Table 4: Key Map Labels

kty: this one tells you the family of keys for the structure, which means it tells you which key-type-specific parameters to look for ๐Ÿ”‘ the set of values defined in this document can be found in [COSE.KeyTypes] โœจ This parameter MUST be present in a key object. Implementations MUST verify that the key type is appropriate for the algorithm being processed. The key type MUST be included as part of the trust-decision process ๐Ÿซถ

alg: this one restricts which algorithm the key can be used with ๐Ÿ”’ If this parameter is present in the key structure, the application MUST verify that this algorithm matches the algorithm the key is being used for. If the algorithms do not match, then this key object MUST NOT be used to perform the cryptographic operation. Note that the same key can sit in a different key structure with a different algorithm or no algorithm specified, but this is considered to be a poor security practice ๐Ÿ™ˆ

kid: this is an identifier for a key ๐Ÿชช It isn't structured, so it can be anything from a user-provided byte string to a value computed on the public portion of the key. It's meant for matching against a "kid" parameter in a message, so you can filter down the set of keys that need checking.

heads up: a kid is not a unique value, and it can show up in other key objects, even for different keys ๐Ÿ’€

key_ops: this restricts the set of operations a key is meant for ๐Ÿ‘‘ The value is an array of values from Table 5. Algorithms define which key_ops values are allowed to appear, and which ones are required for specific operations. The set of values matches the ones in [RFC7517] and [W3C.WebCrypto] โœจ

Base IV: this carries the base portion of an IV ๐ŸŒธ It's designed to be used with the Partial IV header parameter defined in Section 3.1. It gives you a way to associate a Base IV with a key, which then gets modified on a per-message basis with the Partial IV.

extreme care needs to be taken when using a Base IV in an application ๐Ÿ˜ญ Many encryption algorithms lose security if the same IV is used twice.

If different keys are derived for each sender, starting at the same Base IV is likely to satisfy that condition. If the same key is used for multiple senders, then the application needs to provide a method of dividing the IV space up between the senders. This could be done by providing a different base point to start from, or a different Partial IV to start with, and restricting the number of messages sent before rekeying ๐Ÿฆ‹

Here's Table 5, the list of key operation values:

    +=========+=======+==============================================+
    | Name    | Value | Description                                  |
    +=========+=======+==============================================+
    | sign    | 1     | The key is used to create signatures.        |
    |         |       | Requires private key fields.                 |
    +---------+-------+----------------------------------------------+
    | verify  | 2     | The key is used for verification of          |
    |         |       | signatures.                                  |
    +---------+-------+----------------------------------------------+
    | encrypt | 3     | The key is used for key transport            |
    |         |       | encryption.                                  |
    +---------+-------+----------------------------------------------+
    | decrypt | 4     | The key is used for key transport            |
    |         |       | decryption.  Requires private key fields.    |
    +---------+-------+----------------------------------------------+
    | wrap    | 5     | The key is used for key wrap encryption.     |
    | key     |       |                                              |
    +---------+-------+----------------------------------------------+
    | unwrap  | 6     | The key is used for key wrap decryption.     |
    | key     |       | Requires private key fields.                 |
    +---------+-------+----------------------------------------------+
    | derive  | 7     | The key is used for deriving keys.  Requires |
    | key     |       | private key fields.                          |
    +---------+-------+----------------------------------------------+
    | derive  | 8     | The key is used for deriving bits not to be  |
    | bits    |       | used as a key.  Requires private key fields. |
    +---------+-------+----------------------------------------------+
    | MAC     | 9     | The key is used for creating MACs.           |
    | create  |       |                                              |
    +---------+-------+----------------------------------------------+
    | MAC     | 10    | The key is used for validating MACs.         |
    | verify  |       |                                              |
    +---------+-------+----------------------------------------------+

                      Table 5: Key Operation Values

8. Taxonomy of Algorithms Used by COSE ๐ŸŒˆ

okay so in this section, we lay out a taxonomy of the different algorithm types that can be used in COSE ๐Ÿงš heads up though: this taxonomy should not be considered to be exhaustive. New algorithms will be created that won't fit into it, and that's totally fine ๐Ÿ’–

8.1. Signature Algorithms โœจ

Okay bestie, signature algorithms are doing two jobs ๐Ÿ”. Data origination tells you who originated the data, based on who signed it. Data integrity lets you check that nobody touched the data after it was signed. Iconic. ๐Ÿ’…

There are two general signature algorithm schemes. The first is signature with appendix ๐ŸŽ€. The message content gets processed and a signature is produced, and that signature is called the appendix. ECDSA and the RSA Probabilistic Signature Scheme (RSASSA-PSS) both work this way. (Fun fact: the SSA in RSASSA-PSS stands for Signature Scheme with Appendix ๐Ÿค“.)

The signature functions for this scheme are:

signature = Sign(message content, key)

valid = Verification(message content, key, signature)

The second scheme is signature with message recovery ๐Ÿฆ‹. An example of this one is [PVSig]. Here the message content is still processed, but part of it gets included inside the signature. Moving bytes of the message content into the signature means smaller signed messages ๐Ÿ“‰. The signature can still be pretty big, but the message content has shrunk. This has consequences for systems that implement these algorithms and for the apps that use them ๐Ÿ‘€.

First, the message content isn't fully available until after a signature has been validated. Until then, the part of the message sitting inside the signature can't be recovered ๐Ÿ™ˆ. Second, how strong the signature is can depend heavily on the structure of the message content. Third, if multiple signatures get applied to one message, every signature algorithm is going to be required to consume the same bytes of message content. So mixing signature-with-message-recovery and signature-with-appendix in a single message is not supported ๐Ÿšซ. Not me being dramatic about it, but it's a real constraint.

The signature functions for this scheme are:

signature, message sent = Sign(message content, key)

valid, message content = Verification(message sent, key, signature)

No message recovery signature algorithms have been formally defined for COSE yet ๐ŸŒธ. Given the new constraints from this scheme, some issues have already been spotted, but there's a high probability that more will pop up when message recovery algorithms get integrated. The first algorithm defined is going to have to make decisions about these issues, and those decisions are likely to be binding on any algorithms defined after it ๐Ÿงš.

We use the following terms below:

  • message content bytes: The byte string provided by the application to be signed.
  • to-be-signed bytes: The byte string passed into the signature algorithm.
  • recovered bytes: The bytes recovered during the signature verification process.

Some of the issues that have already been identified are:

  • The to-be-signed bytes are not the same as the message content bytes. That's because a larger to-be-signed message gets built during signature processing. The length of the recovered bytes may exceed the length of the message content, but not the length of the to-be-signed bytes. This may lead to privacy considerations if, for example, the externally supplied data contains confidential information ๐Ÿ‘€.
  • It may be tricky to work out where the recovered bytes match up with the to-be-signed bytes, because the recovered bytes contain data that isn't in the message content bytes. One possible option would be to create a padding scheme to prevent that ๐Ÿฉน.
  • Not all message recovery signature algorithms take the recovered bytes from the end of the to-be-signed bytes. This is a problem, because the message content bytes sit at the end of the to-be-signed bytes. If the bytes to be recovered are taken from the start of the to-be-signed bytes, then, by default, none of the message content bytes may be included in the recovered bytes ๐Ÿ˜ญ. One possible way to deal with this is to reverse the to-be-signed data in the event that recovered bytes are taken from the start rather than the end.

Signature algorithms are used with the COSE_Signature and COSE_Sign1 structures. At the time of writing, only signatures with appendices are defined for use with COSE. Still, there's been a lot of interest in a signature-with-message-recovery algorithm, because of the effective size reduction it makes possible ๐Ÿ”ฅ.

8.2. Message Authentication Code (MAC) Algorithms

Message Authentication Codes (MACs) provide data authentication and integrity protection ๐Ÿ›ก๏ธ. They provide either no or very limited data origination. A MAC, for example, cannot be used to prove the identity of the sender to a third party, so don't try that one, bestie ๐Ÿ™…โ€โ™€๏ธ.

MACs use the same scheme as signature-with-appendix algorithms. The message content gets processed, and an authentication code is produced. That authentication code is frequently called a tag ๐Ÿท๏ธ.

The MAC functions are:

tag = MAC_Create(message content, key)

valid = MAC_Verify(message content, key, tag)

MAC algorithms can be based on either a block cipher algorithm (i.e., AES-MAC) or a hash algorithm (i.e., a Hash-based Message Authentication Code (HMAC)). [RFC9053] defines a MAC algorithm using each of these constructions ๐Ÿง.

MAC algorithms are used in the COSE_Mac and COSE_Mac0 structures.

8.3. Content Encryption Algorithms

Content encryption algorithms provide data confidentiality for potentially large blocks of data using a symmetric key ๐Ÿ”’. They provide integrity on the data that was encrypted. However, they provide either no or very limited data origination. (One cannot, for example, be used to prove the identity of the sender to a third party. Same energy as the MACs, no cap ๐Ÿซถ.) The ability to provide data origination is linked to how the CEK is obtained.

COSE restricts the set of legal content encryption algorithms to those that support authentication of both the content and the additional data โœ…. The encryption process will generate some type of authentication value, but that value may be either explicit or implicit in terms of the algorithm definition. For simplicity's sake, the authentication code will normally be defined as being appended to the ciphertext stream. The encryption functions are:

ciphertext = Encrypt(message content, key, additional data)

valid, message content = Decrypt(ciphertext, key, additional data)

Most AEAD algorithms are logically defined as returning the message content only if the decryption is valid. Many, but not all, implementations follow this convention. The message content MUST NOT be used if the decryption does not validate. Seriously, don't use it ๐Ÿšจ.

These algorithms are used in COSE_Encrypt and COSE_Encrypt0.

8.4. Key Derivation Functions (KDFs)

KDFs take some secret value and generate a different one โœจ. The secret value comes in three flavors:

  • Secrets that are uniformly random. This is the type of secret created by a good random number generator ๐ŸŽฒ.
  • Secrets that are not uniformly random. This is the type of secret created by operations like key agreement ๐Ÿค.
  • Secrets that are not random. This is the type of secret people generate for things like passwords ๐Ÿ”‘.

General KDFs work well with the first type of secret, can do reasonably well with the second type, and generally do poorly with the last type ๐Ÿ’€. Functions like Argon2 [RFC9106] need to be used for nonrandom secrets.

The same KDF can be set up to deal with the first two types of secrets in different ways. The KDF defined in Section 5.1 of [RFC9053] is such a function. This is reflected in the set of algorithms defined around the HMAC-based Extract-and-Expand Key Derivation Function (HKDF) ๐ŸŒˆ.

When using KDFs, one component that gets included is context information. Context information lets different keying information be derived from the same secret. Using context-based keying material is considered a good security practice ๐Ÿง .

8.5. Content Key Distribution Methods

Content key distribution methods (recipient algorithms) can be sorted into a number of different classes ๐Ÿ—‚๏ธ. COSE can support many classes of recipient algorithms. This section lists a number of them. For the recipient algorithm classes defined in [RFC7516], the same names are used. Other specifications use different terms for these classes, or don't support some of them at all ๐Ÿคทโ€โ™€๏ธ.

8.5.1. Direct Encryption

The Direct Encryption class of algorithms shares a secret between the sender and the recipient. That secret is used either directly or after manipulation as the CEK. When direct-encryption mode is used, it MUST be the only mode used on the message ๐Ÿšซ.

The COSE_Recipient structure for the recipient is organized as follows:

  • The "protected" field MUST be a zero-length byte string unless it is used in the computation of the content key.
  • The "alg" header parameter MUST be present.
  • A header parameter identifying the shared secret SHOULD be present.
  • The "ciphertext" field MUST be a zero-length byte string.
  • The "recipients" field MUST be absent.

8.5.2. Key Wrap

In key wrap mode, the CEK is randomly generated, and that key is then encrypted by a shared secret between the sender and the recipient ๐ŸŽ. All of the currently defined key wrap algorithms for COSE are AE algorithms. Key wrap mode is considered superior to Direct Encryption if the system can do any random-key generation at all. That's because the shared key is used to wrap random data, rather than data that has some degree of organization and may in fact be repeating the same content ๐ŸŒ€. The trade-off is that using key wrap loses the weak data origination that the direct-encryption algorithms provide.

The COSE_Recipient structure for the recipient is organized as follows:

  • The "protected" field MUST be a zero-length byte string if the key wrap algorithm is an AE algorithm.
  • The "recipients" field is normally absent but can be used. Applications MUST deal with a recipient field being present that has an unsupported algorithm. Failing to decrypt that specific recipient is an acceptable way of dealing with it. Failing to process the message is not an acceptable way of dealing with it ๐Ÿ™…โ€โ™€๏ธ.
  • The plaintext to be encrypted is the key from the next layer down (usually the content layer).
  • At a minimum, the "unprotected" field MUST contain the "alg" header parameter and SHOULD contain a header parameter identifying the shared secret ๐Ÿ’œ.

8.5.3. Key Transport

Okay so key transport mode is also called key encryption mode in some standards, so you might see either name ๐Ÿซถ It's basically the cousin of key wrap, except it uses an asymmetric encryption algorithm instead of a symmetric one to protect the key ๐Ÿ” A set of key transport algorithms is defined in [RFC8230].

When you're using a key transport algorithm, the COSE_Recipient structure for the recipient gets organized like this ๐Ÿ’…

  • The "protected" field MUST be a zero-length byte string.
  • The plaintext to be encrypted is the key from the next layer down (usually the content layer) โœจ
  • At a minimum, the "unprotected" field MUST contain the "alg" header parameter and SHOULD contain a parameter identifying the asymmetric key ๐Ÿ”‘

8.5.4. Direct Key Agreement

The Direct Key Agreement class of recipient algorithms uses a key agreement method to create a shared secret ๐Ÿค Then a KDF gets applied to that shared secret to derive a key that protects the data. This key is normally used as a CEK or MAC key, but it could be used for other purposes if more than two layers are in use (see Appendix B) ๐Ÿฆ‹

Diffie-Hellman is the most commonly used key agreement algorithm, but other variants exist ๐Ÿ’– Here's the catch though: COSE is built for a store-and-forward environment and not an online one. That means a lot of the DH variants can't be used, because the receiver of the message can't provide any dynamic key material ๐Ÿ™ˆ One side effect is that forward secrecy (see [RFC4949]) is not achievable. A static key will always be used for the receiver of the COSE object. Not me being like "wait, so no forward secrecy??" ๐Ÿ˜ญ

Two variants of DH are supported:

  • Ephemeral-Static (ES) DH: The sender of the message creates a one-time DH key and uses a static key for the recipient. Because the ephemeral sender key is randomly generated for each message, no additional random input is needed โœจ
  • Static-Static (SS) DH: A static key is used for both the sender and the recipient. Using static keys lets the recipient get a weak version of data origination for the message ๐Ÿ‘€ When Static-Static key agreement is used, some piece of unique data for the KDF is required so that a different key is created for each message.

When direct key agreement mode is used, there MUST be only one recipient in the message. This method creates the key directly, and that makes it hard to mix with additional recipients ๐Ÿšซ If you need multiple recipients, you have to use the version with key wrap instead.

The COSE_Recipient structure for the recipient is organized as follows:

  • At a minimum, headers MUST contain the "alg" header parameter and SHOULD contain a header parameter identifying the recipient's asymmetric key ๐ŸŽ€
  • The headers SHOULD identify the sender's key for the Static-Static versions and MUST contain the sender's ephemeral key for the ephemeral-static versions ๐Ÿงš

8.5.5. Key Agreement with Key Wrap

Key Agreement with Key Wrap uses a randomly generated CEK ๐ŸŽฒ The CEK then gets encrypted using a key wrap algorithm and a key derived from the shared secret that the key agreement algorithm computed. The function for this would be:

encryptedKey = KeyWrap(KDF(DH-Shared, context), CEK)

The COSE_Recipient structure for the recipient is organized as follows:

  • The "protected" field is fed into the KDF context structure ๐ŸŒˆ
  • The plaintext to be encrypted is the key from the next layer down (usually the content layer) ๐Ÿ“
  • The "alg" header parameter MUST be present in the layer.
  • A header parameter identifying the recipient's key SHOULD be present. A header parameter identifying the sender's key SHOULD be present ๐Ÿซถ

9. CBOR Encoding Restrictions

This document limits the restrictions it imposes on how the CBOR Encoder needs to work ๐Ÿง The new encoding restrictions line up with the Core Deterministic Encoding Requirements in Section 4.2.1 of RFC 8949 [STD94]. They've been narrowed down to the following restrictions:

  • The restriction applies to the encoding of the Sig_structure, the Enc_structure, and the MAC_structure.
  • Encoding MUST be done using definite lengths, and the length of the (encoded) argument MUST be the minimum possible length. This means that the integer 1 is encoded as 0x01 and not 0x1801 ๐Ÿ”ข
  • Applications MUST NOT generate messages with the same label used twice as a key in a single map. Applications MUST NOT parse and process messages with the same label used twice as a key in a single map. Applications can enforce the parse-and-process requirement by using parsers that will fail the parse step, or by using parsers that will pass all keys to the application, and the application can perform the check for duplicate keys. Duplicate keys are a no-go, bestie ๐Ÿšซ๐Ÿ’€

10. Application Profiling Considerations

This document is designed to provide a set of security services, but it does not impose algorithm implementation requirements for specific usage ๐Ÿ’œ The interoperability requirements are provided for how each individual service is used, and how the algorithms are to be used for interoperability. The requirements about which algorithms and which services are needed are deferred to each application.

An example of a profile can be found in [RFC8613], where one was developed for carrying content in combination with CoAP headers โœจ

It's intended that a profile of this document gets created that defines the interoperability requirements for that specific application. This section gives a set of guidelines and topics that need to be considered when profiling this document ๐Ÿ“‹

  • Applications need to determine the set of messages defined in this document that they will be using. The set of messages corresponds fairly directly to the needed set of security services and security levels ๐ŸŽฏ
  • Applications may define new header parameters for a specific purpose. Applications will oftentimes select specific header parameters to use or not to use. For example, an application would normally state a preference for using either the IV or the Partial IV header parameter. If the Partial IV header parameter is specified, then the application also needs to define how the fixed portion of the IV is determined ๐Ÿงฉ
  • When applications use externally defined authenticated data, they need to define how that data is encoded. This document assumes that the data will be provided as a byte string. More information can be found in Section 4.3 ๐Ÿ“ฆ
  • Applications need to determine the set of security algorithms that is to be used. When selecting the algorithms to be used as the mandatory-to-implement set, consideration should be given to choosing different types of algorithms when two are chosen for a specific purpose. An example of this would be choosing HMAC-SHA512 and AES-CMAC (Cipher-Based Message Authentication Code) as different MAC algorithms, since the construction is vastly different between these two algorithms. This means that a weakening of one algorithm would be unlikely to lead to a weakening of the other algorithms. Of course, these algorithms do not provide the same level of security and thus may not be comparable for the desired security functionality. Additional guidance can be found in [BCP201] ๐Ÿ”
  • Applications may need to provide some type of negotiation or discovery method if multiple algorithms or message structures are permitted. The method can range from something as simple as requiring preconfiguration of the set of algorithms to providing a discovery method built into the protocol. S/MIME provided a number of different ways to approach the problem that applications could follow ๐Ÿ™Œ
    • Advertising in the message (S/MIME capabilities) [RFC8551].
    • Advertising in the certificate (capabilities extension) [RFC4262].
    • Minimum requirements for the S/MIME, which have been updated over time [RFC2633] [RFC3851] [RFC5751] [RFC8551]. (Note that [RFC2633] was obsoleted by [RFC3851], which was obsoleted by [RFC5751], which was obsoleted by [RFC8551].) ๐Ÿ“œ

11. IANA Considerations

The registries and registrations listed below were defined by RFC 8152 [RFC8152] ๐Ÿ—‚๏ธ Most of the following actions are updating the references to point to this document.

Note that while [RFC9053] also updates the registries and registrations originally established by [RFC8152], the requested updates are mutually exclusive. The updates requested in this document do not conflict or overlap with the updates requested in [RFC9053], and vice versa ๐Ÿค

11.1. COSE Header Parameters Registry

The "COSE Header Parameters" registry was defined by [RFC8152]. IANA has updated the reference for this registry to point to this document instead of [RFC8152]. IANA has also updated all entries that referenced [RFC8152], except "counter signature" and "CounterSignature0", to refer to this document. The references for "counter signature" and "CounterSignature0" continue to reference [RFC8152] ๐Ÿ’…

11.2. COSE Key Common Parameters Registry

The "COSE Key Common Parameters" registry [COSE.KeyParameters] was defined in [RFC8152]. IANA has updated the reference for this registry to point to this document instead of [RFC8152]. IANA has also updated the entries that referenced [RFC8152] to refer to this document ๐Ÿ”‘

11.3. Media Type Registrations

11.3.1. COSE Security Message

IANA has registered the application/cose media type in the "Media Types" registry ๐ŸŽ‰ This media type is used to indicate that the content is a COSE message.

Type name:  application

Subtype name:  cose

Required parameters:  N/A

Optional parameters:  cose-type

Encoding considerations:  binary

Security considerations:  See the Security Considerations section of
   RFC 9052.

Interoperability considerations:  N/A

Published specification:  RFC 9052

Applications that use this media type:  IoT applications sending
   security content over HTTP(S) transports.

Fragment identifier considerations:  N/A

Additional information:
   *  Deprecated alias names for this type: N/A

   *  Magic number(s): N/A

   *  File extension(s): cbor

   *  Macintosh file type code(s): N/A

Person & email address to contact for further information:
   iesg@ietf.org

Intended usage:  COMMON

Restrictions on usage:  N/A

Author:  Jim Schaad

Change Controller:  IESG

Provisional registration?  No

11.3.2. COSE Key Media Type โœจ

IANA registered the "application/cose-key" and "application/cose-key-set" media types in the "Media Types" registry ๐ŸŽ€ They tell you the content is a COSE_Key object or a COSE_KeySet object, respectively.

The template for "application/cose-key" looks like this:

   Type name:  application

   Subtype name:  cose-key

   Required parameters:  N/A

   Optional parameters:  N/A

   Encoding considerations:  binary

   Security considerations:  See the Security Considerations section of
      RFC 9052.

   Interoperability considerations:  N/A

   Published specification:  RFC 9052

   Applications that use this media type:  Distribution of COSE-based
      keys for IoT applications.

   Fragment identifier considerations:  N/A

   Additional information:
      *  Deprecated alias names for this type: N/A

      *  Magic number(s): N/A

      *  File extension(s): cbor

      *  Macintosh file type code(s): N/A

   Person & email address to contact for further information:
      iesg@ietf.org

   Intended usage:  COMMON

   Restrictions on usage:  N/A

   Author:  Jim Schaad

   Change Controller:  IESG

   Provisional registration?  No

The template for registering "application/cose-key-set" is:

   Type name:  application

   Subtype name:  cose-key-set

   Required parameters:  N/A

   Optional parameters:  N/A

   Encoding considerations:  binary

   Security considerations:  See the Security Considerations section of
      RFC 9052.

   Interoperability considerations:  N/A

   Published specification:  RFC 9052

   Applications that use this media type:  Distribution of COSE-based
      keys for IoT applications.

   Fragment identifier considerations:  N/A

   Additional information:
      *  Deprecated alias names for this type: N/A

      *  Magic number(s): N/A

      *  File extension(s): cbor

      *  Macintosh file type code(s): N/A

   Person & email address to contact for further information:  iesg@ietf
      .org

   Intended usage:  COMMON

   Restrictions on usage:  N/A

   Author:  Jim Schaad

   Change Controller:  IESG

   Provisional registration?  No

11.4. CoAP Content-Formats Registry ๐ŸŒธ

IANA added entries to the "CoAP Content-Formats" registry as listed in [RFC8152]. IANA also updated the reference so it points to this document instead of [RFC8152] ๐Ÿ’–


11.5. CBOR Tags Registry ๐Ÿฆ‹

IANA added entries to the "CBOR Tags" registry as listed in [RFC8152]. The references were updated too, so they now point to this document instead of [RFC8152] โœจ


11.6. Expert Review Instructions ๐Ÿง

All of the IANA registries set up by [RFC8152] are, at least partly, defined as Expert Review [RFC8126]. This section gives some general guidelines on what the experts should be looking for. They were picked as experts for a reason, though, so they should get a lot of room to make calls ๐Ÿซถ

Expert reviewers should keep the following in mind:

  • Point squatting should be discouraged. Reviewers are encouraged to get enough information in registration requests to make sure the usage won't duplicate an existing registration and that the code point is likely to show up in real deployments. The ranges tagged as private use are meant for testing and closed environments, so code points in other ranges should not be assigned for testing ๐Ÿ™…โ€โ™€๏ธ
  • Standards Track or BCP RFCs are required to register a code point in the Standards Action range. Specifications should exist for Specification Required ranges, but early assignment before an RFC is available is considered permissible. Specifications are needed for the first-come, first-served range if the points are expected to be used outside of closed environments in an interoperable way. When specifications are not provided, the description needs to have enough information to identify what the point is being used for ๐Ÿ“
  • Experts should take the expected usage of fields into account when approving code point assignments. The fact that the Standards Action range is only available to Standards Track documents does not mean that a Standards Track document can't have points assigned outside of that range. The length of the encoded value should be weighed against how many code points of that length are left and the size of device it will be used on ๐Ÿ”ข
  • When algorithms are registered, vanity registrations should be discouraged. One way to do this is to require registrations to provide extra documentation on the security analysis of the algorithm. Another thing to consider is asking for an opinion on the algorithm from the Crypto Forum Research Group (CFRG). Algorithms are expected to meet the security requirements of the community and the requirements of the message structures in order to be suitable for registration ๐Ÿ”

12. Security Considerations ๐Ÿšจ

Okay bestie, this part is SERIOUS ๐Ÿ˜ญ There are a number of security considerations implementers need to take into account. Some are highlighted here, but more can be found in the documents listed in the references.

Implementations need to protect the private key material for all individuals. A few cases in this document need to be called out specifically on this issue ๐Ÿ‘€

  • Using the same key for two different algorithms can leak information about the key. It's therefore recommended that keys be restricted to a single algorithm ๐Ÿ”‘
  • Using "direct" as a recipient algorithm together with a second recipient algorithm exposes the direct key to the second recipient. Section 8.5 forbids combining "direct" recipient algorithms with other modes ๐Ÿ’€
  • Several of the algorithms in [RFC9053] have limits on how many times a key can be used without leaking information about the key โณ

The use of Elliptic Curve Diffie-Hellman (ECDH) and direct plus KDF (with no key wrap) won't directly lead to the private key being leaked, because the one-way function of the KDF prevents that. There is, however, a different issue that needs to be addressed ๐Ÿซฃ Having two recipients means the CEK has to be shared between them. The second recipient therefore has a CEK that was derived from material that can be used for the weak proof of origin. The second recipient could create a message using that same CEK and send it to the first recipient. For either Static-Static ECDH or direct plus KDF, the first recipient would assume the CEK could be used for proof of origin, even though it came from the wrong entity. If the key wrap step is added, then no proof of origin is implied and this isn't an issue โœจ

It's been mentioned before, but it's worth repeating: using a single key for multiple algorithms has been shown in some cases to leak information about a key, which gives attackers the chance to forge integrity tags or learn about encrypted content. Binding a key to a single algorithm prevents these problems. Key creators and key consumers are strongly encouraged not only to create new keys for each different algorithm, but also to include that algorithm selection in any distribution of key material, and to strictly enforce matching the algorithms in the key structure against the algorithms in the message structure. Checking that the algorithms are correct isn't enough, either. The key form needs to be checked too. Do not use an "EC2" key where an "OKP" key is expected ๐Ÿ™…โ€โ™€๏ธ

Before using a key for transmission, or before acting on information received, a trust decision about the key needs to be made. Is the data or action something the entity associated with the key has a right to see or a right to request? A number of factors go into this trust decision. Some highlighted here are:

  • What are the permissions associated with the key owner? ๐Ÿชช
  • Is the cryptographic algorithm acceptable in the current context? ๐Ÿงช
  • Have the restrictions associated with the key, such as algorithm or freshness, been checked, and are they correct? โœ…
  • Is the request reasonable, given the current state of the application? ๐Ÿค”
  • Have any security considerations that are part of the message been enforced (as specified by the application or crit header parameter)? ๐Ÿ“‹

One area that's been getting attention is traffic analysis of encrypted messages based on message length. This specification doesn't provide a uniform way to add padding as part of the message structure. An observer can tell two different messages apart (for example, "YES" and "NO") based on length, for all of the content encryption algorithms defined in [RFC9053]. This means it's up to applications to document how content padding is done, in order to prevent or discourage that kind of analysis. (For example, the text strings could be defined as "YES" and "NO ".) ๐Ÿซ 


13. References ๐Ÿ“š

13.1. Normative References

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC9053]  Schaad, J., "CBOR Object Signing and Encryption (COSE):
           Initial Algorithms", RFC 9053, DOI 10.17487/RFC9053,
           August 2022, <https://www.rfc-editor.org/info/rfc9053>.

[STD94]    Bormann, C. and P. Hoffman, "Concise Binary Object
           Representation (CBOR)", STD 94, RFC 8949, December 2020,
           <https://www.rfc-editor.org/info/std94>.

13.2. Informative References โœจ

okay bestie, these are the extra reads ๐Ÿ“š not required to implement COSE, but they're where the rest of the story lives ๐Ÿ’…

that's the whole reference list, and it's a whole lot of reading ๐ŸŒˆ๐Ÿซถ

Appendix A. Guidelines for External Data Authentication of Algorithms ๐Ÿฆ‹โœจ

okay so bestie, this appendix is the "what if we just don't send the algorithm name" chapter ๐Ÿ’… During COSE's development, the rule that the algorithm identifier had to live in the protected attributes got softened from a MUST to a SHOULD ๐Ÿซถ Two reasons were pushed for this. First, messages get smaller if you leave the algorithm identifier out of the most common messages in a CoAP environment ๐Ÿ“ Second, there's a potential bug lurking if the full checking isn't done right across all the different spots an algorithm identifier could live: the message itself, an application statement, the key structure the sender has, and the key structure the recipient has ๐Ÿ’€

This appendix lays out how a change like that can be made, and what an application needs to spell out to use this option ๐ŸŽ€ There are two different sets of details: the ones needed to omit an algorithm identifier, and the ones needed to use the countersignature attribute variant that contains no attributes about itself ๐Ÿ‘‘

There are three sets of recommendations. The first set is for having one implicit algorithm on a single layer of a COSE object. The second set is for having multiple implicit algorithms across multiple layers of a COSE object. The third set is for having implicit algorithms across multiple COSE object constructs ๐ŸŒˆ

The BCP 14 key words ([RFC2119] and [RFC8174]) are deliberately NOT used in here, bestie ๐Ÿ™ˆ This specification can give recommendations, but it can't enforce them ๐Ÿงš

Appendix A.1. Single-Layer Implicit Algorithms ๐Ÿ’–

This set of recommendations is for when an application is distributing a fixed algorithm along with the key info for use in a single COSE object. This normally applies to the smallest COSE objects, which are COSE_Sign1, COSE_Mac0, and COSE_Encrypt0, but it could apply to the other structures too โœจ

The following items should be taken into account:

  • Applications need to list the set of COSE structures that implicit algorithms are going to be used in. Applications need to require that receiving an explicit algorithm identifier in one of these structures leads to the message being rejected ๐Ÿšซ The reason is so there is NEVER any ambiguity about whether the implicit or the explicit algorithm should be used ๐Ÿ˜ญ This applies even if the transported algorithm identifier is a protected attribute, and even if the transported algorithm is the same as the implicit one ๐Ÿ’œ
  • Applications need to define the set of information that counts as part of a context when omitting algorithm identifiers. At a minimum, that's the key identifier (if needed), the key, the algorithm, and the COSE structure it's used with ๐ŸŒธ Applications should restrict a single key to a single algorithm ๐Ÿ”ฅ As noted for some algorithms in [RFC9053], using the same key across different, related algorithms can lead to leakage of info about the key, leakage about the data, or the ability to forge stuff ๐Ÿ’€
  • Applications that make the algorithm identifier implicit will often want to make the context identifier implicit too, for the same reason ๐Ÿฆ‹ Leaving out the context identifier shrinks the message (potentially a lot, depending on how long the identifier is) ๐Ÿซง Applications that do this need to describe when the context identifier gets omitted and how it's inferred in those cases. (An exhaustive search over all the keys would normally NOT be considered acceptable ๐Ÿ™…โ€โ™€๏ธ) One example is tying the context to a transaction identifier. Both get sent on the original message, but after that only the transaction identifier needs to be sent, since the context is tied into it ๐ŸŽ€ Another way is to associate a context with a network address. All messages coming from one network address can be assumed to belong to a specific context. (In this case, the address would normally be distributed as part of the context.) ๐ŸŒท
  • Applications can't rely on key identifiers being unique unless they put serious effort into computing them so that uniqueness is guaranteed ๐Ÿ˜ต Even then, uniqueness might get violated if the application runs in different contexts (meaning a different context provider), or if the system merges the security contexts from different applications into one store ๐Ÿ’…
  • Applications should keep protecting the algorithm identifier. Since it's not going in the protected attributes field, applications should define an application-specific external data structure that includes this value ๐Ÿง This external data field can be used that way for content encryption, MAC, and signature algorithms ๐Ÿ’– It can go in the SuppPrivInfo field for algorithms that use a KDF to derive a key value ๐Ÿ”‘ Applications may also want to protect other info that's part of the context structure ๐Ÿซถ It should be noted that fields like the key or a Base IV, which are already protected by being used in the cryptographic computation, do NOT need to be included in the external data field ๐ŸŒˆ

Appendix A.2. Multiple Implicit Algorithms in One Multi-Layer Object ๐Ÿงšโ€โ™€๏ธ

The second case is having multiple implicit algorithm identifiers specified for a multiple-layer COSE object ๐Ÿฆ‹ An example of how this works is the encryption context an application specifies, which holds a content encryption algorithm, a key wrap algorithm, a key identifier, and a shared secret โœจ The sender leaves out the algorithm identifier for both the content layer and the recipient layer, so only the key identifier gets sent ๐ŸŽ€ The receiver then uses that key identifier to look up the implicit algorithm identifiers ๐Ÿ‘‘

The following additional items need to be taken into consideration:

  • Applications that want to support this need to define a structure that allows for, and clearly identifies, both the COSE structure used with a given key and the structure and algorithm used for the secondary layer ๐Ÿ’œ The key for the secondary layer is computed as normal from the recipient layer ๐ŸŒธ

Appendix A.3. Implicit Algorithms Across Different Objects ๐Ÿ“

The third case is having multiple implicit algorithm identifiers, but targeted at potentially unrelated layers or different COSE objects ๐Ÿซถ There are a bunch of different scenarios where this might apply. Some of them are:

  • Two contexts are distributed as a pair. Each context is for use with a COSE_Encrypt message. Each context consists of distinct secret keys and IVs, and potentially even different algorithms ๐Ÿ” One context is for sending messages from party A to party B, and the second is for sending messages from party B to party A ๐Ÿฆ‹ This means there's NO chance for a reflection attack, because each party uses different secret keys to send its messages, so a message reflected back to it would fail to decrypt ๐Ÿ’€โœจ
  • Two contexts are distributed as a pair. The first context is used to encrypt the message, and the second context is used to place a countersignature on the message ๐ŸŽ€ The intention is that the second context can be distributed to other entities independently of the first ๐ŸŒท That lets those entities check that the message came from an individual without being able to decrypt it and see the content ๐Ÿ‘€
  • Two contexts are distributed as a pair. The first context holds a key for dealing with MACed messages, and the second context holds a different key for dealing with encrypted messages ๐Ÿงš This allows a unified distribution of keys to participants for different types of messages that use different keys, but where the keys may be used in a coordinated manner ๐Ÿ’–

For these cases, the following additional items need to be considered:

  • Applications need to make sure the multiple contexts stay associated ๐Ÿ”— If one of the contexts is invalidated for any reason, ALL of the contexts associated with it should also be invalidated ๐Ÿšจ

Appendix B. Two Layers of Recipient Information ๐ŸŒˆ

okay so all of the currently defined recipient algorithm classes only use two layers of the COSE structure ๐Ÿ’… The first layer (COSE_Encrypt) is the message content, and the second layer (COSE_Recipient) is the content key encryption ๐Ÿ” BUT if you use a recipient algorithm like the RSA Key Encapsulation Mechanism (RSA-KEM) (see Appendix A of RSA-KEM [RFC5990]), then it totally makes sense to have two layers of the COSE_Recipient structure ๐Ÿซถ

These layers would be:

  • Layer 0: The content encryption layer. This layer contains the payload of the message ๐ŸŽ€
  • Layer 1: The encryption of the CEK by a KEK ๐Ÿ”‘
  • Layer 2: The encryption of a long random secret using an RSA key and a key derivation function to convert that secret into the KEK ๐Ÿง

This is an example of what a triple-layer message looks like ๐Ÿ‘‘ To make it easier to read, it's presented using the extended CBOR diagnostic notation (defined in [RFC8610]) rather than as a binary dump ๐Ÿค“

Size of binary file is 183 bytes

96(
  [ / COSE_Encrypt /
    / protected h'a10101' / << {
        / alg / 1:1 / AES-GCM 128 /
      } >>,
    / unprotected / {
      / iv / 5:h'02d1f7e6f26c43d4868d87ce'
    },
    / ciphertext / h'64f84d913ba60a76070a9a48f26e97e863e2852948658f0
811139868826e89218a75715b',
    / recipients / [
      [ / COSE_Recipient /
        / protected / h'',
        / unprotected / {
          / alg / 1:-3 / A128KW /
        },
        / ciphertext / h'dbd43c4e9d719c27c6275c67d628d493f090593db82
18f11',
        / recipients / [
          [ / COSE_Recipient /
            / protected h'a1013818' / << {
                / alg / 1:-25 / ECDH-ES + HKDF-256 /
              } >> ,
            / unprotected / {
              / ephemeral / -1:{
                / kty / 1:2,
                / crv / -1:1,
                / x / -2:h'b2add44368ea6d641f9ca9af308b4079aeb519f11
e9b8a55a600b21233e86e68',
                / y / -3:false
              },
              / kid / 4:'meriadoc.brandybuck@buckland.example'
            },
            / ciphertext / h''
          ]
        ]
      ]
    ]
  ]
)

This is an example of a triple-layer message, so it's got three layers of the COSE_Recipient structure stacked up ๐Ÿซง The message has the following layers:

  • Layer 0: Has content encrypted with AES-GCM using a 128-bit key ๐Ÿ’–
  • Layer 1: Uses the AES Key Wrap algorithm with a 128-bit key ๐ŸŒธ
  • Layer 2: Uses ECDH Ephemeral-Static direct to generate the Layer 1 key ๐Ÿฆ‹

In effect, this example is a decomposed version of using the ECDH-ES+A128KW algorithm โœจ

Appendix C. Examples โœจ

okay bestie, this appendix is the fun part ๐ŸŽ€ It's a bunch of examples showing off every feature and message type from the doc. To make them easier to read, they're written in the extended CBOR diagnostic notation from [RFC8610] instead of a binary dump ๐Ÿงš

There's also a GitHub project at [GitHub-Examples] with these examples plus a way bigger set of testing examples ๐Ÿ’œ Each example lives in a JSON file that has the inputs used to make it, some of the intermediate values (super handy for debugging ๐Ÿซถ), and the output in both hex dump and CBOR diagnostic notation. Some of the examples over there are failure-testing cases, and those are clearly marked in the JSON file. If anyone finds errors in the examples in this doc, the GitHub ones get updated and there's a note about it in the JSON file ๐Ÿ’…

Okay so, the examples are in CBOR diagnostic notation, and there's a Ruby tool that converts between diagnostic notation and binary. You can install it with this command line:

gem install cbor-diag

Then you can turn the diagnostic notation into binary files with this one:

diag2cbor.rb < inputfile > outputfile

Lowkey my favourite trick: you can pull the examples straight out of the XML version of this doc with an XPath expression, since all the source code is tagged with the attribute type='cbor-diag' ๐Ÿฆ‹ Depending on which XPath evaluator you're using, you might need to deal with &gt; as an entity, so keep that in mind ๐Ÿ™ˆ

//sourcecode[@type='cbor-diag']/text()

C.1. Examples of Signed Messages

C.1.1. Single Signature

This one is giving simple and clean ๐ŸŒธ Here's what it uses:

  • Signature Algorithm: ECDSA w/ SHA-256, Curve P-256

The binary file is 103 bytes ๐Ÿ“

98(
  [
    / protected / h'',
    / unprotected / {},
    / payload / 'This is the content.',
    / signatures / [
      [
        / protected h'a10126' / << {
            / alg / 1:-7 / ECDSA 256 /
          } >>,
        / unprotected / {
          / kid / 4:'11'
        },
        / signature / h'e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb
5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b
98f53afd2fa0f30a'
      ]
    ]
  ]
)

C.1.2. Multiple Signers

Main character energy with two signers ๐Ÿ‘‘ This one uses:

  • Signature Algorithm: ECDSA w/ SHA-256, Curve P-256
  • Signature Algorithm: ECDSA w/ SHA-512, Curve P-521

The binary file is 277 bytes ๐Ÿฆ‹

98(
  [
    / protected / h'',
    / unprotected / {},
    / payload / 'This is the content.',
    / signatures / [
      [
        / protected h'a10126' / << {
            / alg / 1:-7 / ECDSA 256 /
          } >>,
        / unprotected / {
          / kid / 4:'11'
        },
        / signature / h'e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb
5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b
98f53afd2fa0f30a'
      ],
      [
        / protected h'a1013823' / << {
            / alg / 1:-36 / ECDSA 521 /
          } >> ,
        / unprotected / {
          / kid / 4:'bilbo.baggins@hobbiton.example'
        },
        / signature / h'00a2d28a7c2bdb1587877420f65adf7d0b9a06635dd1
de64bb62974c863f0b160dd2163734034e6ac003b01e8705524c5c4ca479a952f024
7ee8cb0b4fb7397ba08d009e0c8bf482270cc5771aa143966e5a469a09f613488030
c5b07ec6d722e3835adb5b2d8c44e95ffb13877dd2582866883535de3bb03d01753f
83ab87bb4f7a0297'
      ]
    ]
  ]
)

C.1.3. Signature with Criticality

Okay this one's a little extra ๐Ÿ™ˆ There's a criticality marker sitting on the "reserved" header parameter. It uses:

  • Signature Algorithm: ECDSA w/ SHA-256, Curve P-256
  • There is a criticality marker on the "reserved" header parameter.

The binary file is 125 bytes ๐ŸŒˆ

98(
  [
    / protected h'a2687265736572766564f40281687265736572766564' /
    << {
        "reserved":false,
        / crit / 2:[
          "reserved"
        ]
      } >>,
    / unprotected / {},
    / payload / 'This is the content.',
    / signatures / [
      [
        / protected h'a10126' / << {
            / alg / 1:-7 / ECDSA 256 /
          } >>,
        / unprotected / {
          / kid / 4:'11'
        },
        / signature / h'3fc54702aa56e1b2cb20284294c9106a63f91bac658d
69351210a031d8fc7c5ff3e4be39445b1a3e83e1510d1aca2f2e8a7c081c7645042b
18aba9d1fad1bd9c'
      ]
    ]
  ]
)

C.2. Single Signer Examples

C.2.1. Single ECDSA Signature

Simple, iconic, and it only has one signer ๐Ÿ’– Here's what it uses:

  • Signature Algorithm: ECDSA w/ SHA-256, Curve P-256

The binary file is 98 bytes ๐Ÿงš

18(
  [
    / protected h'a10126' / << {
        / alg / 1:-7 / ECDSA 256 /
      } >>,
    / unprotected / {
      / kid / 4:'11'
    },
    / payload / 'This is the content.',
    / signature / h'8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4
d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5
a4c345cacb36'
  ]
)

C.3. Examples of Enveloped Messages

C.3.1. Direct ECDH

Ephemeral keys, a recipient, the whole vibe ๐Ÿฆ‹ This one uses:

  • CEK: AES-GCM w/ 128-bit key
  • Recipient class: ECDH Ephemeral-Static, Curve P-256

The binary file is 151 bytes ๐Ÿ’œ

96(
  [
    / protected h'a10101' / << {
        / alg / 1:1 / AES-GCM 128 /
      } >>,
    / unprotected / {
      / iv / 5:h'c9cf4df2fe6c632bf7886413'
    },
    / ciphertext / h'7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0
c52a357da7a644b8070a151b0',
    / recipients / [
      [
        / protected h'a1013818' / << {
            / alg / 1:-25 / ECDH-ES + HKDF-256 /
          } >>,
        / unprotected / {
          / ephemeral / -1:{
            / kty / 1:2,
            / crv / -1:1,
            / x / -2:h'98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbf
bf054e1c7b4d91d6280',
            / y / -3:true
          },
          / kid / 4:'meriadoc.brandybuck@buckland.example'
        },
        / ciphertext / h''
      ]
    ]
  ]
)

C.3.2. Direct Plus Key Derivation

This one is all about the context ๐ŸŽ€ The key derivation gets extra implicit fields. It uses:

  • CEK: AES-CCM w/ 128-bit key, truncate the tag to 64 bits
  • Recipient class: Use HKDF on a shared secret with the following implicit fields as part of the context.
    • salt: "aabbccddeeffgghh"
    • PartyU identity: "lighting-client"
    • PartyV identity: "lighting-server"
    • Supplementary Public Other: "Encryption Example 02"

The binary file is 91 bytes ๐ŸŒธ

96(
  [
    / protected h'a1010a' / << {
        / alg / 1:10 / AES-CCM-16-64-128 /
      } >>,
    / unprotected / {
      / iv / 5:h'89f52f65a1c580933b5261a76c'
    },
    / ciphertext / h'753548a19b1307084ca7b2056924ed95f2e3b17006dfe93
1b687b847',
    / recipients / [
      [
        / protected h'a10129' / << {
            / alg / 1:-10
          } >>,
        / unprotected / {
          / salt / -20:'aabbccddeeffgghh',
          / kid / 4:'our-secret'
        },
        / ciphertext / h''
      ]
    ]
  ]
)

C.3.3. Encrypted Content with External Data

Externally supplied AAD is in the mix here, so it's giving extra protection ๐Ÿซถ This one uses:

  • CEK: AES-GCM w/ 128-bit key
  • Recipient class: ECDH Static-Static, Curve P-256 with AES Key Wrap
  • Externally Supplied AAD: h'0011bbcc22dd44ee55ff660077'

The binary file is 173 bytes ๐Ÿ”ฅ

96(
  [
    / protected h'a10101' / << {
        / alg / 1:1 / AES-GCM 128 /
      } >> ,
    / unprotected / {
      / iv / 5:h'02d1f7e6f26c43d4868d87ce'
    },
    / ciphertext / h'64f84d913ba60a76070a9a48f26e97e863e28529d8f5335
e5f0165eee976b4a5f6c6f09d',
    / recipients / [
      [
        / protected / h'a101381f' / {
            \ alg \ 1:-32 \ ECDH-SS+A128KW \
          } / ,
        / unprotected / {
          / static kid / -3:'peregrin.took@tuckborough.example',
          / kid / 4:'meriadoc.brandybuck@buckland.example',
          / U nonce / -22:h'0101'
        },
        / ciphertext / h'41e0d76f579dbd0d936a662d54d8582037de2e366fd
e1c62'
      ]
    ]
  ]
)

C.4. Examples of Encrypted Messages

C.4.1. Simple Encrypted Message

Keeping it simple with a single encrypted message โœจ This one uses:

  • CEK: AES-CCM w/ 128-bit key and a 64-bit tag

The binary file is 52 bytes ๐ŸŒˆ

16(
  [
    / protected h'a1010a' / << {
        / alg / 1:10 / AES-CCM-16-64-128 /
      } >> ,
    / unprotected / {
      / iv / 5:h'89f52f65a1c580933b5261a78c'
    },
    / ciphertext / h'5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce
460ffb569'
  ]
)

C.4.2. Encrypted Message with a Partial IV

Here the IV gets trimmed down to a partial IV, which is a cute little space-saver ๐Ÿงš This one uses:

  • CEK: AES-CCM w/ 128-bit key and a 64-bit tag
  • Prefix for IV is 89F52F65A1C580933B52

The binary file is 41 bytes ๐Ÿ’…

16(
  [
    / protected h'a1010a' / << {
        / alg / 1:10 / AES-CCM-16-64-128 /
      } >> ,
    / unprotected / {
      / partial iv / 6:h'61a7'
    },
    / ciphertext / h'252a8911d465c125b6764739700f0141ed09192de139e05
3bd09abca'
  ]
)

C.5. Examples of MACed Messages

C.5.1. Shared Secret Direct MAC

Direct MAC with a shared secret, no cap it's a classic ๐Ÿ’œ This one uses:

  • MAC: AES-CMAC, 256-bit key, truncated to 64 bits
  • Recipient class: direct shared secret

The binary file is 57 bytes ๐Ÿฆ‹

97(
  [
    / protected h'a1010f' / << {
        / alg / 1:15 / AES-CBC-MAC-256//64 /
      } >> ,
    / unprotected / {},
    / payload / 'This is the content.',
    / tag / h'9e1226ba1f81b848',
    / recipients / [
      [
        / protected / h'',
        / unprotected / {
          / alg / 1:-6 / direct /,
          / kid / 4:'our-secret'
        },
        / ciphertext / h''
      ]
    ]
  ]
)

C.5.2. ECDH Direct MAC

Two static keys agreeing on a MAC, with HKDF and a context structure doing the heavy lifting ๐ŸŒธ This one uses:

  • MAC: HMAC w/SHA-256, 256-bit key
  • Recipient class: ECDH key agreement, two static keys, HKDF w/ context structure

The binary file is 214 bytes โœจ

97(
  [
    / protected h'a10105' / << {
        / alg / 1:5 / HMAC 256//256 /
      } >> ,
    / unprotected / {},
    / payload / 'This is the content.',
    / tag / h'81a03448acd3d305376eaa11fb3fe416a955be2cbe7ec96f012c99
4bc3f16a41',
    / recipients / [
      [
        / protected h'a101381a' / << {
            / alg / 1:-27 / ECDH-SS + HKDF-256 /
          } >> ,
        / unprotected / {
          / static kid / -3:'peregrin.took@tuckborough.example',
          / kid / 4:'meriadoc.brandybuck@buckland.example',
          / U nonce / -22:h'4d8553e7e74f3c6a3a9dd3ef286a8195cbf8a23d
19558ccfec7d34b824f42d92bd06bd2c7f0271f0214e141fb779ae2856abf585a583
68b017e7f2a9e5ce4db5'
        },
        / ciphertext / h''
      ]
    ]
  ]
)

C.5.3. Wrapped MAC

The last one, and it's a wrapped MAC ๐ŸŽ€ It uses a preshared key to wrap things up. This one uses:

  • MAC: AES-MAC, 128-bit key, truncated to 64 bits
  • Recipient class: AES Key Wrap w/ a preshared 256-bit key

The binary file is 109 bytes ๐Ÿ’–

97(
  [
    / protected h'a1010e' / << {
        / alg / 1:14 / AES-CBC-MAC-128//64 /
      } >> ,
    / unprotected / {},
    / payload / 'This is the content.',
    / tag / h'36f5afaf0bab5d43',
    / recipients / [
      [
        / protected / h'',
        / unprotected / {
          / alg / 1:-5 / A256KW /,
          / kid / 4:'018c0ae5-4d9b-471b-bfd6-eef314bc7037'
        },
        / ciphertext / h'711ab0dc2fc4585dce27effa6781c8093eba906f227
b6eb0'
      ]
    ]
  ]
)

C.5.4. Multi-Recipient MACed Message

This example uses the following:

  • MAC: HMAC w/ SHA-256, 128-bit key
  • Recipient class: Uses two different methods.
    1. ECDH Ephemeral-Static, Curve P-521, AES Key Wrap w/ 128-bit key
    2. AES Key Wrap w/ 256-bit key

Size of binary file is 309 bytes ๐ŸŽ€

97(
  [
    / protected h'a10105' / << {
        / alg / 1:5 / HMAC 256//256 /
      } >> ,
    / unprotected / {},
    / payload / 'This is the content.',
    / tag / h'bf48235e809b5c42e995f2b7d5fa13620e7ed834e337f6aa43df16
1e49e9323e',
    / recipients / [
      [
        / protected h'a101381c' / << {
            / alg / 1:-29 / ECDH-ES+A128KW /
          } >> ,
        / unprotected / {
          / ephemeral / -1:{
            / kty / 1:2,
            / crv / -1:3,
            / x / -2:h'0043b12669acac3fd27898ffba0bcd2e6c366d53bc4db
71f909a759304acfb5e18cdc7ba0b13ff8c7636271a6924b1ac63c02688075b55ef2
d613574e7dc242f79c3',
            / y / -3:true
          },
          / kid / 4:'bilbo.baggins@hobbiton.example'
        },
        / ciphertext / h'339bc4f79984cdc6b3e6ce5f315a4c7d2b0ac466fce
a69e8c07dfbca5bb1f661bc5f8e0df9e3eff5'
      ],
      [
        / protected / h'',
        / unprotected / {
          / alg / 1:-5 / A256KW /,
          / kid / 4:'018c0ae5-4d9b-471b-bfd6-eef314bc7037'
        },
        / ciphertext / h'0b2c7cfce04e98276342d6476a7723c090dfdd15f9a
518e7736549e998370695e6d6a83b4ae507bb'
      ]
    ]
  ]
)

C.6. Examples of MAC0 Messages ๐Ÿงš

C.6.1. Shared-Secret Direct MAC

This example uses the following:

  • MAC: AES-CMAC, 256-bit key, truncated to 64 bits
  • Recipient class: direct shared secret

Size of binary file is 37 bytes โœจ

17(
  [
    / protected h'a1010f' / << {
        / alg / 1:15 / AES-CBC-MAC-256//64 /
      } >> ,
    / unprotected / {},
    / payload / 'This is the content.',
    / tag / h'726043745027214f'
  ]
)

Note that this example uses the same inputs as Appendix C.5.1. ๐Ÿ’œ

C.7. COSE Keys ๐Ÿ”‘

C.7.1. Public Keys

This is an example of a COSE Key Set. This example includes the public keys for all of the previous examples. ๐ŸŒธ

In order, the keys are:

  • An EC key with a kid of "meriadoc.brandybuck@buckland.example"
  • An EC key with a kid of "11"
  • An EC key with a kid of "bilbo.baggins@hobbiton.example"
  • An EC key with a kid of "peregrin.took@tuckborough.example"

Size of binary file is 481 bytes ๐Ÿ’…

[
  {
    -1:1,
    -2:h'65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c0
8551d',
    -3:h'1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd008
4d19c',
    1:2,
    2:'meriadoc.brandybuck@buckland.example'
  },
  {
    -1:1,
    -2:h'bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a
09eff',
    -3:h'20138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbf
c117e',
    1:2,
    2:'11'
  },
  {
    -1:3,
    -2:h'0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de
7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8
f42ad',
    -3:h'01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e
60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1
d9475',
    1:2,
    2:'bilbo.baggins@hobbiton.example'
  },
  {
    -1:1,
    -2:h'98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91
d6280',
    -3:h'f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf
822bb',
    1:2,
    2:'peregrin.took@tuckborough.example'
  }
]

C.7.2. Private Keys

This is an example of a COSE Key Set. This example includes the private keys for all of the previous examples. ๐Ÿฆ‹

In order the keys are:

  • An EC key with a kid of "meriadoc.brandybuck@buckland.example"
  • An EC key with a kid of "11"
  • An EC key with a kid of "bilbo.baggins@hobbiton.example"
  • A shared-secret key with a kid of "our-secret"
  • An EC key with a kid of "peregrin.took@tuckborough.example"
  • A shared-secret key with kid "our-secret2"
  • A shared-secret key with a kid of "018c0ae5-4d9b-471b-bfd6-eef314bc7037"

Size of binary file is 816 bytes ๐ŸŒˆ

[
  {
    1:2,
    2:'meriadoc.brandybuck@buckland.example',
    -1:1,
    -2:h'65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c0
8551d',
    -3:h'1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd008
4d19c',
    -4:h'aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa
208cf'
  },
  {
    1:2,
    2:'11',
    -1:1,
    -2:h'bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a
09eff',
    -3:h'20138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbf
c117e',
    -4:h'57c92077664146e876760c9520d054aa93c3afb04e306705db609030850
7b4d3'
  },
  {
    1:2,
    2:'bilbo.baggins@hobbiton.example',
    -1:3,
    -2:h'0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de
7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8
f42ad',
    -3:h'01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e
60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1
d9475',
    -4:h'00085138ddabf5ca975f5860f91a08e91d6d5f9a76ad4018766a476680b
55cd339e8ab6c72b5facdb2a2a50ac25bd086647dd3e2e6e99e84ca2c3609fdf177f
eb26d'
  },
  {
    1:4,
    2:'our-secret',
    -1:h'849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c4
27188'
  },
  {
    1:2,
    -1:1,
    2:'peregrin.took@tuckborough.example',
    -2:h'98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91
d6280',
    -3:h'f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf
822bb',
    -4:h'02d1f7e6f26c43d4868d87ceb2353161740aacf1f7163647984b522a848
df1c3'
  },
  {
    1:4,
    2:'our-secret2',
    -1:h'849b5786457c1491be3a76dcea6c4271'
  },
  {
    1:4,
    2:'018c0ae5-4d9b-471b-bfd6-eef314bc7037',
    -1:h'849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c4
27188'
  }
]

Acknowledgments ๐Ÿซถ

This document is a product of the COSE Working Group of the IETF. ๐Ÿ’–

The following individuals are to blame for getting me started on this project in the first place: Richard Barnes, Matt Miller, and Martin Thomson. ๐Ÿ˜ญ

The initial draft version of the specification was based to some degree on the outputs of the JOSE and S/MIME Working Groups. ๐Ÿง 

The following individuals provided input into the final form of the document: Carsten Bormann, John Bradley, Brian Campbell, Michael B. Jones, Ilari Liusvaara, Francesca Palombini, Ludwig Seitz, and Gรถran Selander. ๐Ÿ‘‘

Author's Address ๐Ÿฆ‹

Jim Schaad August Cellars

๐Ÿ“ฆ where this page comes from