Skip to content

Commit 0f085aa

Browse files
Add optional event provenance and tamper-evidence guidance
Adds an informational companion document describing two distinct properties: authenticity and integrity via DSSE, and existence-over-time via an optional commitment to the event canonical hash on an external timeline. Mechanism-agnostic per the discussion in #315: the document names the class of anchors without endorsing one. No schema changes; nothing is required of an implementer. Signed-off-by: Colin H Winter <hello@markovianprotocol.com>
1 parent 00b986b commit 0f085aa

3 files changed

Lines changed: 242 additions & 22 deletions

File tree

.spellcheck-en-custom.txt

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,3 @@
1-
CDF
2-
CVE
3-
Gerrit
4-
Github
5-
Gitlab
6-
JSON
7-
NIST
8-
Notational
9-
README
10-
SBOM
11-
SBOMs
12-
SCM
13-
SDLC
14-
SDK
15-
SIG
16-
SRE
17-
Tekton
18-
URI
19-
UUID
20-
YAML
211
american
222
assignees
233
br
@@ -32,6 +12,7 @@ customdatacontenttype
3212
datacontenttype
3313
dataschema
3414
deterministically
15+
dsse
3516
emmitted
3617
english
3718
eventdata
@@ -42,6 +23,7 @@ img
4223
interoperable
4324
json
4425
jsonschema
26+
keypair
4527
lifecycle
4628
markdownlint
4729
md
@@ -55,9 +37,9 @@ pre
5537
rolledback
5638
runtime
5739
sbom
58-
schemaUri
5940
schemas
6041
schemauri
42+
schemaUri
6143
specversion
6244
src
6345
subjectid
@@ -69,9 +51,38 @@ testsuite
6951
testsuiterun
7052
ticketURI
7153
toc
54+
toto
7255
typesystem
7356
unparsed
7457
uri
7558
url
7659
viewUrl
7760
wpaper
61+
CDF
62+
CVE
63+
DSSE
64+
Ed
65+
Gerrit
66+
Github
67+
Gitlab
68+
JCS
69+
JSON
70+
Merkle
71+
Notational
72+
NIST
73+
PAE
74+
README
75+
RFC
76+
SBOM
77+
SBOMs
78+
SCM
79+
SDK
80+
SDLC
81+
SHA
82+
SIG
83+
SLSA
84+
SRE
85+
Tekton
86+
URI
87+
UUID
88+
YAML

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ Handling Events associated with Test execution performed independently or as par
9090

9191
Defining how CDEvents are mapped to CloudEvents for transportation and delivery
9292

93+
### [Event Provenance and Tamper-Evidence](./event-provenance.md)
94+
95+
Optional guidance on signing CDEvents with DSSE, and on committing an event's canonical
96+
hash to an external timeline for existence-over-time. Additive only: it changes no schema.
97+
9398
### [Schemas](./schemas/) and [Conformance](./conformance/)
9499

95100
The [schemas](./schemas/) folder contains `jsonschemas` for all events in the spec. The [conformance](./conformance/) folder contains simple `JSON` examples for all events. The content of the conformance folder is used for testing purposes: the structure of the files in there is sound, the values have correct types but are not particularly meaningful.
@@ -123,4 +128,4 @@ guidelines.
123128
### Governance
124129

125130
The project has been started by the CDF [SIG Events](https://github.com/cdfoundation/sig-events).
126-
Its governance is [documented in the community repository](https://github.com/cdevents/community/blob/main/governance.md).
131+
Its governance is [documented in the community repository](https://github.com/cdevents/community/blob/main/governance.md).

event-provenance.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Event provenance and tamper-evidence for CDEvents
2+
3+
Status: informational companion document. This guidance is optional and additive; it
4+
does not change the core CDEvents specification or any schema.
5+
6+
## Introduction
7+
8+
A CDEvent describes something that happened in a delivery pipeline: an artifact was
9+
published, a build finished, a change was merged. Downstream systems increasingly
10+
make decisions on these events (promotion gates, policy checks, audit trails), so two
11+
separate questions arise:
12+
13+
1. Authenticity and integrity. Who emitted this event, and has its payload been
14+
altered in transit or at rest?
15+
2. Existence over time. Did this event actually exist by some point in time, and can
16+
that be checked later without trusting the emitter's clock, or its continued
17+
custody of a signing key?
18+
19+
These are different properties and they need different mechanisms. A signature answers
20+
question 1. It does not answer question 2: a signature proves who and whether-tampered,
21+
but not when the event existed, and a holder of the signing key can re-sign a
22+
back-dated or quietly reissued event at any time. Question 2 needs a commitment to the
23+
event that is fixed against an external timeline.
24+
25+
This document describes both, as optional, additive guidance. Nothing here changes the
26+
core CDEvents schema. Part 1 covers signing a CDEvent with a
27+
[DSSE](https://github.com/secure-systems-lab/dsse) envelope. Part 2 covers an optional
28+
commitment to the event's canonical hash, anchored to an external timeline. Part 2 is
29+
strictly opt-in and deliberately anchor-agnostic: the commitment is just the event
30+
digest, and where it is anchored is an implementation choice.
31+
32+
## Background: how a CDEvent is carried
33+
34+
Per the CDEvents [CloudEvents binding](https://github.com/cdevents/spec/blob/main/cloudevents-binding.md),
35+
a CDEvent is transported as the `data` of a
36+
[CloudEvent](https://github.com/cloudevents/spec) with `datacontenttype`
37+
`application/json`, and the CloudEvents `id`, `source`, `type`, `subject` and `time`
38+
attributes are mapped from the CDEvent's context. The guidance below can be applied at
39+
either layer. Signing and hashing the CDEvent document (its `context` plus `subject`,
40+
the object defined by the CDEvents schema) keeps provenance tied to the event itself and
41+
independent of the transport, which is the approach used in the worked example.
42+
43+
## Part 1: authenticity and integrity with DSSE
44+
45+
[DSSE](https://github.com/secure-systems-lab/dsse) (Dead Simple Signing Envelope) is the
46+
signing envelope used by in-toto and SLSA attestations. It wraps an arbitrary payload
47+
plus a payload-type string and one or more signatures, and it signs over a
48+
Pre-Authentication Encoding (PAE) rather than the raw bytes, which removes
49+
canonicalization ambiguity and cross-protocol confusion from the signature.
50+
51+
### The envelope
52+
53+
The DSSE JSON envelope ([envelope.md](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md))
54+
is:
55+
56+
```json
57+
{
58+
"payload": "<Base64(SERIALIZED_BODY)>",
59+
"payloadType": "<PAYLOAD_TYPE>",
60+
"signatures": [{ "keyid": "<KEYID>", "sig": "<Base64(SIGNATURE)>" }]
61+
}
62+
```
63+
64+
- `payload` is the Base64 (RFC 4648) encoding of the serialized CDEvent.
65+
- `payloadType` is an opaque, case-sensitive string identifying how to interpret the
66+
payload. This guide uses `application/cdevents+json`. `payloadType` is authenticated,
67+
so a signature over a CDEvent cannot be replayed as a signature over a different
68+
document type.
69+
- Each `signatures[].sig` is the Base64 signature. `keyid` is an optional,
70+
unauthenticated hint for key selection.
71+
72+
### What is signed: the PAE
73+
74+
DSSE signs the Pre-Authentication Encoding of the payload, defined in
75+
[protocol.md](https://github.com/secure-systems-lab/dsse/blob/master/protocol.md):
76+
77+
```
78+
PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
79+
SP = ASCII space (0x20)
80+
LEN(s) = ASCII decimal byte length of s, no leading zeros
81+
"DSSEv1" = ASCII bytes 44 53 53 45 76 31
82+
```
83+
84+
A verifier reconstructs `PAE(payloadType, Base64Decode(payload))` and checks the
85+
signature against a trusted public key. Per DSSE guidance, the verified bytes are used
86+
as-is; they are not re-parsed or re-canonicalized before use.
87+
88+
### What DSSE proves, and what it does not
89+
90+
Proves:
91+
- Authenticity. The event was signed by a holder of the private key for `keyid`.
92+
- Integrity. Any change to the CDEvent payload, or to `payloadType`, invalidates the
93+
signature.
94+
95+
Does not prove:
96+
- When the event existed. A signature carries no trustworthy time. Any `timestamp`
97+
inside the event is asserted by the emitter and can be set to any value.
98+
- That the event was not reissued. A party holding the signing key can produce a fresh,
99+
validly-signed event with different content at any later time.
100+
101+
Note on CloudEvents: a message-level signature profile for CloudEvents itself is an open
102+
discussion in the CloudEvents project
103+
([cloudevents/spec#565](https://github.com/cloudevents/spec/issues/565)) rather than a
104+
finished standard. Applying DSSE to the CDEvent document, as described here, does not
105+
depend on that work and is compatible with signing at either the CDEvent or the
106+
CloudEvents layer.
107+
108+
## Part 2: existence over time with an optional hash commitment
109+
110+
To answer "did this event exist by time T", commit to the event and fix that commitment
111+
against a timeline that the emitter does not control. This is independent of, and
112+
complementary to, the DSSE signature.
113+
114+
### The commitment
115+
116+
1. Canonicalize the CDEvent with [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)
117+
(JSON Canonicalization Scheme, JCS). JCS gives a single, deterministic byte
118+
serialization for a JSON value, so any party recomputes the same bytes from the same
119+
event.
120+
2. Hash the canonical bytes with SHA-256. This digest is the commitment. In the worked
121+
example it is labeled `RFC8785-JCS+SHA-256`.
122+
3. Record the digest on an external timeline so that its existence-by-time-T can be
123+
checked later.
124+
125+
Step 3 is where the anchor lives, and this guide is deliberately anchor-agnostic. A
126+
digest can be fixed against a timeline in several ways, for example:
127+
128+
- an [RFC 3161](https://www.rfc-editor.org/rfc/rfc3161) trusted timestamp from a Time
129+
Stamping Authority,
130+
- an append-only transparency log (for example a Merkle-tree log in the style of
131+
RFC 9162),
132+
- inclusion in a public blockchain transaction.
133+
134+
Which anchor to use is an operational and threat-model choice. The only thing CDEvents
135+
tooling needs to agree on is the commitment itself: the canonicalization
136+
(RFC 8785) and the hash (SHA-256). Everything about the anchor is out of scope for this
137+
document and MUST NOT be assumed by consumers.
138+
139+
### What the commitment adds beyond DSSE
140+
141+
- Existence proof. Given the event, anyone can recompute the digest and check it against
142+
the anchor's record, establishing that the event existed by the time the anchor was
143+
written.
144+
- Independence from key custody. The check does not require the emitter's signing key or
145+
its clock. It survives key loss, key rotation, and emitter compromise after the anchor
146+
time.
147+
- Reissue detection. Because the digest is bound to a point on the timeline, a later
148+
re-signed variant of the event produces a different digest with no earlier anchor.
149+
150+
The commitment does not prove authorship. Only the DSSE signature does. The two are
151+
designed to be used together.
152+
153+
## When to use which
154+
155+
| Requirement | DSSE signature (Part 1) | Hash commitment + anchor (Part 2) |
156+
| --- | --- | --- |
157+
| Prove who emitted the event | Yes | No |
158+
| Detect payload tampering | Yes | Yes (digest mismatch) |
159+
| Prove the event existed by time T | No | Yes |
160+
| Verifiable without the emitter's key | No | Yes |
161+
| Verifiable without trusting the emitter's clock | No | Yes |
162+
| Detect a later re-signed / back-dated reissue | No | Yes |
163+
| Extra infrastructure required | A key and key distribution | An external timeline / anchor |
164+
| Status in this guidance | Recommended baseline | Optional, opt-in |
165+
166+
Baseline: sign CDEvents with DSSE. Add a Part 2 commitment when downstream decisions
167+
need existence-over-time or must survive emitter key compromise, for example regulated
168+
audit trails or long-lived provenance.
169+
170+
## Worked example
171+
172+
A runnable end-to-end example accompanies this guide at
173+
[MarkovianProtocol/cdevents-provenance](https://github.com/MarkovianProtocol/cdevents-provenance).
174+
It is kept outside this repository so that CDEvents takes no dependency on it.
175+
176+
The example uses real libraries rather than stubs: `rfc8785` for JCS canonicalization,
177+
`cryptography` for Ed25519, and `jsonschema` to validate the sample event against the
178+
published CDEvents artifact-published schema. It contains a valid
179+
`dev.cdevents.artifact.published.0.3.0` event (CDEvents v0.5.1), its JCS canonical
180+
serialization, the Part 2 commitment, a DSSE envelope over the canonical bytes, and a
181+
tampered variant of each. Its verifier shows the genuine artifacts passing both checks
182+
(the signature verifies, and SHA-256 of the signed payload equals the committed hash)
183+
and the tampered artifacts failing both.
184+
185+
The example is illustrative. It uses one possible anchor mechanism; as stated in Part 2,
186+
the choice of anchor is out of scope for this guidance.
187+
188+
## References
189+
190+
- DSSE envelope format: secure-systems-lab/dsse, `envelope.md`.
191+
https://github.com/secure-systems-lab/dsse/blob/master/envelope.md
192+
- DSSE protocol and PAE: secure-systems-lab/dsse, `protocol.md`.
193+
https://github.com/secure-systems-lab/dsse/blob/master/protocol.md
194+
- RFC 8785, JSON Canonicalization Scheme (JCS). https://www.rfc-editor.org/rfc/rfc8785
195+
- RFC 4648, Base64 (used by DSSE `payload`/`sig`). https://www.rfc-editor.org/rfc/rfc4648
196+
- RFC 3161, Time-Stamp Protocol (one possible anchor for Part 2).
197+
https://www.rfc-editor.org/rfc/rfc3161
198+
- RFC 9162, Certificate Transparency v2 (transparency-log style anchor for Part 2).
199+
https://www.rfc-editor.org/rfc/rfc9162
200+
- CDEvents CloudEvents binding.
201+
https://github.com/cdevents/spec/blob/main/cloudevents-binding.md
202+
- CloudEvents specification. https://github.com/cloudevents/spec
203+
- CloudEvents message-level signature discussion (open).
204+
https://github.com/cloudevents/spec/issues/565

0 commit comments

Comments
 (0)