Internet-Draft | SCITT Receipts | September 2022 |
Birkholz, et al. | Expires 9 March 2023 | [Page] |
A transparent and authentic ledger service in support of a supply chain's integrity, transparency, and trust requires all peers that contribute to the ledgers operations to be trustworthy and authentic. In this document, a countersigning variant is specified that enables trust assertions on Merkle-tree based operations for global supply chain ledgers. A generic procedure for producing payloads to be signed and validated is defined and leverages solutions and principles from the Concise Signing and Encryption (COSE) space.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 9 March 2023.¶
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
This document defines a method for issuing and verifying countersignatures on COSE_Sign1 messages included in an authenticated data structure such as a Merkle Tree.¶
We adopt the terminology of the Supply Chain integrity, Transparency, and Trust (SCITT) architecture document (An Architecture for Trustworthy and Transparent Digital Supply Chains, see [I-D.birkholz-scitt-architecture]): Claim, Envelope, Transparency Service, Ledger, Receipt, and Verifier.¶
[TODO] Do we need to explain or introduce them here? We may also define Tree (our shorthand for authenticated data structure), Root (a succinct commitment to the Tree, e.g., a hand) and use Issuer instead of TS.¶
From the Verifier's viewpoint, a Receipt is similar to a countersignature V2 on a single signed message: it is a universally-verifiable cryptographic proof of endorsement of the signed envelope by the countersigner.¶
Compared with countersignatures on single COSE envelopes,¶
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.¶
Verifiers are configured by a collection of parameters to identify a Transparency Service and verify its Receipts. These parameters MUST be fixed for the lifetime of the Transparency Service and securely communicated to all Verifiers.¶
At minimum, these parameters include:¶
The Tree algorithm used for issuing receipts, and its additional parameters, if any. This document creates a registry (see Section 9.2.1) and describes an initial set of tree algorithms.¶
[TODO] The architecture also has fixed TS registration policies.¶
A Receipt represents a countersignature issued by a Transparency Service.¶
The Receipt structure is a CBOR array with two items, in order:¶
service_id
: The service identifier as tstr.¶
contents
: The proof as a CBOR structure determined by the tree algorithm.¶
Receipt = [ service_id: tstr, contents: any ]¶
Each tree algorithm MUST define its contents type and procedures for issuing and verifying a receipt.¶
While the tree algorithms may differ in the way they aggregate multiple envelopes to compute a digest to be signed by the TS, they all share the same representation of the individual envelopes to be countersigned (intuitively, their leaves).¶
This document uses the principles and structure definitions
of COSE_Sign1 countersigning V2 ([I-D.ietf-cose-countersign]).
Each envelope is authenticated using a Countersign_structure
array, recalled below.¶
Countersign_structure = [ context: "CounterSignatureV2", body_protected: empty_or_serialized_map, sign_protected: empty_or_serialized_map, external_aad: bstr, payload: bstr, other_fields: [ signature: bstr ] ]¶
The body_protected
, payload
, and signature
fields are copied from the COSE_Sign1 message being countersigned.¶
The sign_protected
field is provided by the TS, see Section 4.1 below. This field
is included in the Receipt contents to enable the Verifier to re-construct Countersign_structure
, as specified by the tree algorithm.¶
By convention, the TS always provides an empty external_aad
: a zero-length bytestring.¶
Procedure for reconstruction of Countersign_structure:¶
The following parameters MUST be included in the protected header of the countersigner (sign_protected in Section 4):¶
The CCF 2 tree algorithm specifies an algorithm based on a binary Merkle tree over the sequence of all ledger entries, as implemented in the CCF version 2 framework (see [CCF_Merkle_Tree]).¶
The algorithm requires that the TS define additional parameters:¶
All definitions in this section use the hash algorithm set in the TS parameters (see Section Section 5.1). We write HASH to refer to this algorithm, and HASH_SIZE for the fixed length of its output in bytes.¶
Note: This section is adapted from Section 2.1 of [RFC9162], which provides additional discussion of Merkle trees.¶
The input of the Merkle Tree Hash (MTH) function is a list of n bytestrings, written D_n = {d[0], d[1], ..., d[n-1]}. The output is a single HASH_SIZE bytestring, also called the tree root hash.¶
This function is defined as follows:¶
The hash of an empty list is the hash of an empty string:¶
MTH({}) = HASH().¶
The hash of a list with one entry (also known as a leaf hash) is:¶
MTH({d[0]}) = HASH(d[0]).¶
For n > 1, let k be the largest power of two smaller than n (i.e., k < n <= 2k). The Merkle Tree Hash of an n-element list D_n is then defined recursively as:¶
MTH(D_n) = HASH(MTH(D[0:k]) || MTH(D[k:n])),¶
where:¶
A Merkle inclusion proof for a leaf in a Merkle Tree is the shortest list of intermediate hash values required to re-compute the tree root hash from the digest of the leaf bytestring. Each node in the tree is either a leaf node or is computed from the two nodes immediately below it (i.e., towards the leaves). At each step up the tree (towards the root), a node from the inclusion proof is combined with the node computed so far. In other words, the inclusion proof consists of the list of missing nodes required to compute the nodes leading from a leaf to the root of the tree. If the root computed from the inclusion proof matches the true root, then the inclusion proof proves that the leaf exists in the tree.¶
When a client has received an inclusion proof and wishes to verify inclusion of a leaf_hash for a given root_hash, the following algorithm may be used to prove the hash was included in the root_hash:¶
recompute_root(leaf_hash, proof): h := leaf_hash for [left, hash] in proof: if left h := HASH(hash || h) else h := HASH(h || hash) return h¶
Given the MTH input D_n = {d[0], d[1], ..., d[n-1]} and an index i < n in this list, run the MTH algorithm and record the position and value of every intermediate hash concatenated and hashed first with the digest of the leaf, then with the resulting intermediate hash value. (Most implementations instead record all intermediate hash computations, so that they can produce all inclusion proofs for a given tree by table lookups.)¶
This section describes the encoding of signed envelopes and auxiliary ledger entries into the leaf bytestrings passed as input to the Merkle Tree function.¶
Each bytestring is computed from three inputs:¶
internal_hash
: a string of HASH_SIZE bytes;¶
internal_data
: a string of at most 1024 bytes; and¶
data_hash
: either the HASH of the CBOR-encoded Countersign_structure of the signed envelope, using the CBOR encoding described in Section 6, or a bytestring of size HASH_SIZE filled with zeroes for auxiliary ledger entries.¶
as the concatenation of three hashes:¶
LeafBytes = internal_hash || HASH(internal_data) || data_hash¶
This ensures that leaf bytestrings are always distinct from the inputs of the intermediate computations in MTH, which always consist of two hashes, and also that leaf bytestrings for signed envelopes and for auxiliary ledger entries are always distinct.¶
The internal_hash
and internal_data
bytestrings are internal to the CCF implementation. Similarly, the auxiliary ledger entries are internal to CCF. They are opaque to receipt Verifiers, but they commit the TS to the whole ledger contents and may be used for additional, CCF-specific auditing.¶
The Receipt contents structure is a CBOR array. The items of the array in order are:¶
signature
: the signature over the Merkle tree root as bstr.¶
node_certificate
: a DER-encoded X.509 certificate for the public key for signature verification.
This certificate MUST be a valid CCF node certificate
for the service; in particular, it MUST form a valid X.509 certificate chain with the service certificate.¶
inclusion_proof
: the intermediate hashes to recompute the signed root of the Merkle tree from the leaf digest of the envelope.¶
leaf_info
: auxiliary inputs to recompute the leaf digest included in the Merkle tree: the internal hash, the internal data, and the protected header of the
countersigner.¶
The inclusion of an additional, short-lived certificate endorsed by the TS enables flexibility in its distributed implementation, and may support additional CCF-specific auditing.¶
The CDDL fragment that represents the above text follows.¶
ReceiptContents = [ signature: bstr, node_certificate: bstr, inclusion_proof: [+ ProofElement], leaf_info: LeafInfo ] ProofElement = [ left: bool hash: bstr ] LeafInfo = [ internal_hash: bstr, internal_data: bstr, sign_protected: empty_or_serialized_map ]¶
Given the TS parameters, a signed envelope, and a Receipt for it, the following steps must be followed to verify this Receipt.¶
Countersign_structure
as described in Section 4, using sign_protected
from the leaf_info
field of the receipt contents.¶
Compute LeafBytes
as the bytestring concatenation of the internal hash, the hash of internal data, and the hash of the CBOR-encoding of Countersign_structure
, using the CBOR encoding described in Section 6.¶
LeafBytes := internal_hash || HASH(internal_data) || HASH(cbor(Countersign_structure))¶
Compute the leaf digest.¶
LeafHash := HASH(LeafBytes)¶
Compute the root hash from the leaf hash and the Merkle proof using the Merkle Tree Hash Algorithm found in the service's parameters (see Section 5.1):¶
root := recompute_root(LeafHash, inclusion_proof)¶
sign_protected
to verify the validity periods of the certificates. The chain MUST enable the use of the public key in the receipt certificate for signature verification with the Signature Algorithm of the TS parameters.¶
signature
is a valid signature value of the root hash, using the public key of the receipt certificate and the Signature Algorithm of the TS parameters.¶
The Verifier SHOULD apply additional checks before accepting the countersigned envelope as valid, based on its protected headers and payload.¶
This document provides a reference algorithm for producing valid receipts, but it omits any discussion of TS registration policy and any CCF-specific implementation details.¶
The algorithm takes as input a list of entries to be jointly countersigned, each entry consisting of internal_hash
, internal_data
, and an optional signed envelope.
(This optional item reflects that a CCF ledger records both signed envelopes and auxiliary entries.)¶
Countersign_structure
as described in Section 4.¶
LeafBytes
as the bytestring concatenation of the internal hash, the hash of internal data and, if the envelope is present, the hash of the CBOR-encoding of Countersign_structure
, using the CBOR encoding described in Section 6, otherwise a HASH_SIZE bytestring of zeroes.¶
node_certificate
and compute a signature
of the root of the tree with the corresponding signing key.¶
For each signed envelope provided in the input,¶
inclusion_proof
by selecting intermediate hash values, as described above.¶
inclusion_proof
, the fixed node_certificate
and signature
, and the bytestrings internal_hash
and internal_data
provided with the envelope.¶
In order to always regenerate the same byte string for the "to be signed" and "to be hashed" values, the core deterministic encoding rules defined in Section 4.2.1 of [RFC8949] MUST be used for all their CBOR structures.¶
IANA is requested to register the new COSE Header parameters defined below in the "COSE Header Parameters" registry.¶