|
| 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