Skip to content

Commit ced505e

Browse files
committed
Document required client changes for Rekor v2
Fixes #108 Signed-off-by: Hayden B <8418760+haydentherapper@users.noreply.github.com>
1 parent ec16bc7 commit ced505e

File tree

1 file changed

+369
-0
lines changed

1 file changed

+369
-0
lines changed

CLIENTS.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# Client Changes for Rekor v2
2+
3+
This document outlines the changes clients need to make to support
4+
Rekor v2.
5+
6+
## Rekor v2 API
7+
8+
Rekor v2 supports HTTP or gRPC, like Fulcio. For Go, we have implemented a client
9+
already. For other languages, they can either use the
10+
[OpenAPI docs](https://github.com/sigstore/rekor-tiles/tree/main/docs/openapi),
11+
[gRPC service proto](https://github.com/sigstore/rekor-tiles/tree/main/api/proto),
12+
or create their own client.
13+
14+
The service implements one write API, `/api/v2/log/entries`. Example JSON request bodies
15+
below:
16+
17+
```jsonc
18+
// Request with a Fulcio certificate
19+
{
20+
"hashedRekordRequestV0_0_2": {
21+
// Must use hash algorithm from key_details
22+
"digest": "<base64 digest of artifact>",
23+
"signature": {
24+
"content": "<base64 signature>",
25+
"verifier": {
26+
"x509Certificate": "<base64 DER-encoded certificate>",
27+
// Must match signing algorithm
28+
"keyDetails": "PKIX_ECDSA_P256_SHA_256"
29+
}
30+
}
31+
}
32+
}
33+
34+
// Request with a self-managed key
35+
{
36+
"hashedRekordRequestV0_0_2": {
37+
"digest": "<base64 digest of artifact>",
38+
"signature": {
39+
"content": "<base64 signature>",
40+
"verifier": {
41+
"publicKey": {
42+
"rawBytes": "<base64 DER-encoded public key>"
43+
},
44+
"keyDetails": "PKIX_ECDSA_P256_SHA_256"
45+
}
46+
}
47+
}
48+
}
49+
50+
// Request with an attestation
51+
{
52+
"dsseRequestV0_0_2": {
53+
"envelope": {
54+
"payload": "<base64-encoded message>",
55+
"payloadType": "<type, e.g. application/vnd.in-toto+json>",
56+
"signatures": [
57+
{
58+
"sig": "<base64-encoded signature>",
59+
"keyid": ""
60+
}
61+
]
62+
},
63+
"verifier": {
64+
"x509Certificate": "<base64 DER-encoded certificate>",
65+
// Must match signing algorithm
66+
"keyDetails": "PKIX_ECDSA_P256_SHA_256"
67+
}
68+
}
69+
}
70+
71+
// Request with an attestation with a self-managed key
72+
{
73+
"dsseRequestV0_0_2": {
74+
"envelope": {
75+
"payload": "<base64-encoded message>",
76+
"payloadType": "<type, e.g. application/vnd.in-toto+json>",
77+
"signatures": [
78+
{
79+
"sig": "<base64-encoded signature>",
80+
"keyid": ""
81+
}
82+
]
83+
},
84+
"verifier": {
85+
"publicKey": {
86+
"rawBytes": "<base64 DER-encoded public key>"
87+
},
88+
// Must match signing algorithm
89+
"keyDetails": "PKIX_ECDSA_P256_SHA_256"
90+
}
91+
}
92+
}
93+
```
94+
95+
The response will be a
96+
[`TransparencyLogEntry` message](https://github.com/sigstore/protobuf-specs/blob/5296f13d62e7fad428581d969f664c30cc52f549/protos/sigstore_rekor.proto#L94),
97+
which should be persisted in a bundle. Clients no longer need to transform the
98+
Rekor response into a `TLE` message to store in the bundle.
99+
100+
### Two Entry Types
101+
102+
Rekor v2 only supports `hashedrekord` (`HashedRekordRequestV0_0_2`) and
103+
`dsse` (`DSSERequestV0_0_2`) entry types, dropping a number of unused types
104+
such as `jar`, `alpine`, `rpm`, and the older types `rekord` and `intoto`.
105+
Additional types may be added in the future if there is demand, but this
106+
will require updating the client specification so that all clients implement
107+
support for these types.
108+
109+
### Certificate and Public Key Verifiers
110+
111+
Rekor v2 only supports signature verification using a certificate or a
112+
public key, dropping support for PGP, minisign, pkcs7, SSH and TUF.
113+
Additional verifiers may be added in the future, but this will also require
114+
updating the client specification.
115+
116+
### TrustedRoot lookup by checkpoint key ID rather than log ID
117+
118+
Log ID, the SHA-256 digest of the log's public key, is used as a "unique"
119+
identifier for a log. The TransparencyLogEntry message includes it in a
120+
bundle, and clients use that log ID to lookup the correct log public key
121+
in the TrustedRoot to verify the bundle.
122+
123+
Rekor will no longer include log IDs in the response, and instead clients
124+
should use the checkpoint key ID as specified in the
125+
[C2SP spec](https://github.com/C2SP/C2SP/blob/main/signed-note.md#signatures)
126+
to lookup the correct log public key to verify a bundle. Each transparency
127+
log instance in the TrustedRoot will include a `checkpoint_key_id` instead
128+
of a `log_id`.
129+
130+
For more information, log IDs are not necessarily unique identifiers for
131+
a log, since a log may reuse its public key among instances. Additionally,
132+
the log origin is not necessarily a unique identifier, because multiple logs
133+
may be hosted by one origin. Even the combination of both is not necessarily
134+
unique, as a log may create signatures for different purposes with the same
135+
key.
136+
137+
A checkpoint key ID is a truly unique log identifier, which incorporates
138+
the log origin, public key, and the signature type as per the C2SP signed-note
139+
spec linked above.
140+
141+
### Handling Longer Requests
142+
143+
Clients need to increase request timeouts to at least 10 seconds.
144+
145+
Rekor now batches uploads so that checkpoints are published less frequently.
146+
Additionally, we plan to support synchronous witnessing, where third-party
147+
witnesses independently verify the consistency of the log and Rekor provides
148+
co-signed checkpoints with each upload response.
149+
150+
If a client needs to create multiple entries, it is recommended to upload those
151+
entries in parallel.
152+
153+
## Signed RFC 3161 Timestamps
154+
155+
Rekor will no longer return SignedEntryTimestamps or include integrated time
156+
in the response. Clients must fetch an RFC 3161 signed timestamp from a trusted
157+
timestamp authority and include the signed timestamp in the bundle.
158+
159+
Sigstore now operates a timestamp authority at `timestamp.sigstore.dev` and
160+
`timestamp.sigstage.dev` for staging, and the roots for these services will
161+
be included in the TrustedRoot distributed via TUF. Clients may request timestamps
162+
from other trusted timestamp authorities as well. As with other services,
163+
users should specify the verification material for the additional timestamp
164+
authorities in the TrustedRoot.
165+
166+
## SigningConfig support
167+
168+
Clients must implement support for the SigningConfig message, which specifies
169+
the list of URLs that clients should use during signing. Since Rekor shards
170+
will now have unique URLs, we will use the SigningConfig to distribute
171+
the URLs for new shards.
172+
173+
We have published a v2 SigningConfig message to support handling log
174+
sharding, with validity windows (which will prevent a client from writing to a
175+
log before the TrustedRoot is fully distributed) and services with different API
176+
versions (for the transition between Rekor v1 and v2).
177+
178+
A more detailed description of how clients must, should and may handle
179+
the SigningConfig message is in the
180+
[protobuf-specs repo](https://github.com/sigstore/protobuf-specs/blob/dda47952957722e943829af6fe531c005a9fbed6/protos/sigstore_trustroot.proto#L147).
181+
182+
An example SigningConfig with annotations is below:
183+
184+
```jsonc
185+
{
186+
// Clients do not need to support v0.1
187+
"mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json",
188+
189+
// Fulcio service URLs.
190+
// Clients must select the first service from the list whose validity window
191+
// is active and the API version is supported by the client. Clients must
192+
// select the highest supported API version.
193+
// Clients can assume that this list is sorted from most recent to oldest.
194+
"caUrls": [
195+
{
196+
"url": "https://fulcio.sigstore.dev",
197+
"majorApiVersion": 1,
198+
"validFor": {
199+
// When clients should start using this service
200+
"start": "<UTC timestamp>",
201+
// Optional, when a service is turned down and clients should
202+
// treat the service as offline
203+
"end": "<UTC timestamp>"
204+
},
205+
}
206+
],
207+
208+
// OIDC service URLs
209+
"oidcUrls": [
210+
{
211+
"url": "https://oauth2.sigstore.dev/auth",
212+
"majorApiVersion": 1,
213+
"validFor": {
214+
"start": "<UTC timestamp>"
215+
},
216+
}
217+
],
218+
219+
// Rekor service URLs. This example shows multiple active logs.
220+
// The client should select the log with the highest API version
221+
// it supports.
222+
"rekorTlogUrls": [
223+
{
224+
"url": "https://rekor-2025-1.sigstore.dev",
225+
"majorApiVersion": 2,
226+
"validFor": {
227+
"start": "<UTC timestamp>"
228+
},
229+
},
230+
{
231+
"url": "https://rekor.sigstore.dev",
232+
"majorApiVersion": 1,
233+
"validFor": {
234+
"start": "<UTC timestamp>"
235+
},
236+
}
237+
],
238+
239+
// Rekor service selection
240+
// "Valid" is defined above to mean the service's validity window is
241+
// active and the API version is supported by the client.
242+
"rekorTlogConfig": {
243+
// EXACT specifies that a client must upload entries to exactly
244+
// "count" number of valid logs. Clients must throw an error
245+
// if less than "count" logs are valid. Clients should select
246+
// logs from the highest available API version, even if "count"
247+
// logs are not available.
248+
// May also be ANY, meaning the client should select exactly
249+
// one valid log. The client can decide how to select it, e.g. random
250+
// or round-robin if the client tracks state.
251+
// May also be ALL, which should be all valid logs.
252+
"selector": "EXACT",
253+
// Optional, only when EXACT is specified
254+
"count": 2
255+
},
256+
257+
// Timestamp authority URLs
258+
// Like Rekor, clients should use the TSA config which dictates
259+
// how many TSAs should be used to request timestamps from.
260+
"tsaUrls": [
261+
{
262+
"url": "https://oauth2.sigstore.dev/auth",
263+
"majorApiVersion": 1,
264+
"validFor": {
265+
"start": "<UTC timestamp>"
266+
},
267+
}
268+
],
269+
270+
// Timestamp authority service selection
271+
"tsaConfig": {
272+
"selector": "ANY"
273+
}
274+
}
275+
```
276+
277+
## Removing Online Verification and Search
278+
279+
Rekor no longer provides an API for online verification and search. This includes
280+
the APIs for requesting inclusion proofs by index, by leaf hash and by entry,
281+
and searching for an entry by artifact hash or identity. In the near future,
282+
we will spin up a separate service to support search.
283+
284+
Clients must be given the inclusion proof and checkpoint for an entry,
285+
which must be stored in a bundle.
286+
287+
This should have no impact on clients as inclusion proofs were already
288+
required in bundles. This will only impact monitors, and the read API
289+
changes are detailed below.
290+
291+
## No Attestation Storage
292+
293+
Rekor v1's `intoto` type persisted attestations. Rekor v1's `dsse` type
294+
removed attestation storage as Rekor was not designed to be used as storage
295+
for verification metadata.
296+
297+
In Rekor v2, the `DSSERequestV0_0_2` type will also not support attestation
298+
storage. Attestations should be persisted alongside an artifact, e.g. in
299+
OCI or a package registry, or in a dedicated attestation storage service.
300+
301+
## C2SP Checkpoints
302+
303+
The checkpoints the log provides will conform to the
304+
[C2SP checkpoint spec](https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md).
305+
Clients must check that their checkpoint verification implementation properly
306+
handles these checkpoints, which could include:
307+
308+
* Multiple signatures
309+
* Optional extension lines
310+
* Updated key names, which will match the shard URL
311+
* Key IDs calculated per the [signed note spec](https://github.com/C2SP/C2SP/blob/main/signed-note.md#signatures)
312+
* ECDSA and Ed25519 key IDs are based on the spec
313+
* For RSA, the signature type is `0xFF` and we append `PKIX-RSA-PKCS#1v1.5`,
314+
`key ID = SHA-256(key name || 0x0A || 0xFF || PKIX-RSA-PKCS#1v1.5 || public key)[:4]`
315+
316+
## TrustedRoot With Multiple Logs
317+
318+
Clients must verify that verification works with a TrustedRoot
319+
with multiple logs with overlapping validity windows. Confirm that
320+
the client will fetch the correct verification key from the TrustedRoot
321+
by using the log ID (which is the hash of the log's public key).
322+
323+
## Monitoring/Auditing
324+
325+
One of the more significant changes in a tile-backed log is the changes
326+
to the read API. Inclusion and consistency proofs are not served via an
327+
API, rather the client requests the set of tiles necessary to compute
328+
the inclusion or consistency proof.
329+
330+
When monitoring the log searching for entries, the monitor will not request
331+
entries by index, but by tile. Monitors can choose to only fetch complete
332+
tiles or request [partial tiles](https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md#partial-tiles),
333+
which are the rightmost tiles in a tree that may contain somewhere between 1 and 255 hashes.
334+
It is recommended to request partial tiles, or else the monitor might lag behind
335+
if tiles are not filled frequently.
336+
337+
The APIs to request checkpoints, tiles, and entry bundles are defined
338+
in the
339+
[tlog-tiles spec](https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md#apis).
340+
For Go, [trillian-tessera](https://github.com/transparency-dev/trillian-tessera/tree/main/client)
341+
provides a client to compute proofs and fetch tiles.
342+
343+
## Future: Witnessing
344+
345+
Witnessing provides independent verification that the log
346+
remains consistent (append-only). Witnessing can either be
347+
asynchronous, where a client requests witnesses verify consistency
348+
proofs, or synchronous, where the log requests witnesses
349+
verify proofs for every checkpoint issued. This results in a
350+
longer request times and a dependency on third-party witnesses,
351+
but results in a strong offline proof of log inclusion. We
352+
will publish a doc later on with more details.
353+
354+
In the initial launch of Rekor v2, checkpoints will not be
355+
witnessed, while we wait for the launch of a public witness
356+
network. Clients do not need to implement verification of
357+
witness signatures initially, but clients should increase
358+
request timeouts to account for the additional time to
359+
sign checkpoints, which we estimate to be <10s.
360+
361+
To verify cosignatures, see the
362+
[spec](https://github.com/C2SP/C2SP/blob/main/tlog-cosignature.md).
363+
364+
The log will determine which set of witnesses is trusted and the
365+
M-of-N witnesses to fetch signatures from, and this policy will
366+
be distributed by Sigstore's TUF root.
367+
368+
Co-signed checkpoints will also be timestamped, so they can serve
369+
as an independent signed timestamp instead of an RFC 3161 timestamp.

0 commit comments

Comments
 (0)