|
33 | 33 | import dev.sigstore.fulcio.client.FulcioVerifier;
|
34 | 34 | import dev.sigstore.json.ProtoJson;
|
35 | 35 | import dev.sigstore.proto.common.v1.HashAlgorithm;
|
| 36 | +import dev.sigstore.proto.rekor.v2.DSSELogEntryV002; |
36 | 37 | import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
|
| 38 | +import dev.sigstore.proto.rekor.v2.Signature; |
37 | 39 | import dev.sigstore.rekor.client.HashedRekordRequest;
|
38 | 40 | import dev.sigstore.rekor.client.RekorEntry;
|
39 | 41 | import dev.sigstore.rekor.client.RekorTypeException;
|
@@ -306,18 +308,22 @@ private void checkMessageSignature(
|
306 | 308 | throw new KeylessVerificationException(
|
307 | 309 | "Could not parse HashedRekordLogEntryV002 from log entry body");
|
308 | 310 | }
|
| 311 | + |
309 | 312 | if (!logEntrySpec.getData().getAlgorithm().equals(HashAlgorithm.SHA2_256)) {
|
310 | 313 | throw new KeylessVerificationException(
|
311 | 314 | "Unsupported digest algorithm in log entry: " + logEntrySpec.getData().getAlgorithm());
|
312 | 315 | }
|
| 316 | + |
313 | 317 | if (!Arrays.equals(logEntrySpec.getData().getDigest().toByteArray(), artifactDigest)) {
|
314 | 318 | throw new KeylessVerificationException(
|
315 | 319 | "Artifact digest does not match digest in log entry spec");
|
316 | 320 | }
|
| 321 | + |
317 | 322 | if (!Arrays.equals(logEntrySpec.getSignature().getContent().toByteArray(), signature)) {
|
318 | 323 | throw new KeylessVerificationException(
|
319 | 324 | "Signature does not match signature in log entry spec");
|
320 | 325 | }
|
| 326 | + |
321 | 327 | var verifier = logEntrySpec.getSignature().getVerifier();
|
322 | 328 | if (!verifier.hasX509Certificate()) {
|
323 | 329 | throw new KeylessVerificationException("Rekor entry verifier is missing X.509 certificate");
|
@@ -399,46 +405,108 @@ private void checkDsseEnvelope(
|
399 | 405 | throw new KeylessVerificationException("Signature could not be processed", se);
|
400 | 406 | }
|
401 | 407 |
|
402 |
| - // check if the digest over the dsse payload matches the digest in the transparency log entry |
403 |
| - Dsse rekorDsse; |
404 |
| - try { |
405 |
| - rekorDsse = RekorTypes.getDsse(rekorEntry); |
406 |
| - } catch (RekorTypeException re) { |
407 |
| - throw new KeylessVerificationException("Unexpected rekor type", re); |
408 |
| - } |
| 408 | + String version = rekorEntry.getBodyDecoded().getApiVersion(); |
| 409 | + if ("0.0.1".equals(version)) { |
| 410 | + Dsse rekorDsse; |
| 411 | + try { |
| 412 | + rekorDsse = RekorTypes.getDsse(rekorEntry); |
| 413 | + } catch (RekorTypeException re) { |
| 414 | + throw new KeylessVerificationException("Unexpected rekor type", re); |
| 415 | + } |
409 | 416 |
|
410 |
| - var algorithm = rekorDsse.getPayloadHash().getAlgorithm(); |
411 |
| - if (algorithm != PayloadHash.Algorithm.SHA_256) { |
412 |
| - throw new KeylessVerificationException( |
413 |
| - "Cannot process DSSE entry with hashing algorithm " + algorithm.toString()); |
414 |
| - } |
| 417 | + var algorithm = rekorDsse.getPayloadHash().getAlgorithm(); |
| 418 | + if (algorithm != PayloadHash.Algorithm.SHA_256) { |
| 419 | + throw new KeylessVerificationException( |
| 420 | + "Cannot process DSSE entry with hashing algorithm " + algorithm.toString()); |
| 421 | + } |
415 | 422 |
|
416 |
| - byte[] payloadDigest; |
417 |
| - try { |
418 |
| - payloadDigest = Hex.decode(rekorDsse.getPayloadHash().getValue()); |
419 |
| - } catch (DecoderException de) { |
420 |
| - throw new KeylessVerificationException( |
421 |
| - "Could not decode hex sha256 artifact hash in hashrekord", de); |
422 |
| - } |
| 423 | + // check if the digest over the dsse payload matches the digest in the transparency log entry |
| 424 | + byte[] payloadDigest; |
| 425 | + try { |
| 426 | + payloadDigest = Hex.decode(rekorDsse.getPayloadHash().getValue()); |
| 427 | + } catch (DecoderException de) { |
| 428 | + throw new KeylessVerificationException( |
| 429 | + "Could not decode hex sha256 artifact hash in hashrekord", de); |
| 430 | + } |
423 | 431 |
|
424 |
| - byte[] calculatedDigest = Hashing.sha256().hashBytes(dsseEnvelope.getPayload()).asBytes(); |
425 |
| - if (!Arrays.equals(calculatedDigest, payloadDigest)) { |
426 |
| - throw new KeylessVerificationException( |
427 |
| - "Digest of DSSE payload in bundle does not match DSSE payload digest in log entry"); |
428 |
| - } |
| 432 | + byte[] calculatedDigest = Hashing.sha256().hashBytes(dsseEnvelope.getPayload()).asBytes(); |
| 433 | + if (!Arrays.equals(calculatedDigest, payloadDigest)) { |
| 434 | + throw new KeylessVerificationException( |
| 435 | + "Digest of DSSE payload in bundle does not match DSSE payload digest in log entry"); |
| 436 | + } |
429 | 437 |
|
430 |
| - // check if the signature over the dsse payload matches the signature in the rekorEntry |
431 |
| - if (rekorDsse.getSignatures().size() != 1) { |
432 |
| - throw new KeylessVerificationException( |
433 |
| - "DSSE log entry must have exactly 1 signature, but found: " |
434 |
| - + rekorDsse.getSignatures().size()); |
435 |
| - } |
| 438 | + // check if the signature over the dsse payload matches the signature in the rekorEntry |
| 439 | + if (rekorDsse.getSignatures().size() != 1) { |
| 440 | + throw new KeylessVerificationException( |
| 441 | + "DSSE log entry must have exactly 1 signature, but found: " |
| 442 | + + rekorDsse.getSignatures().size()); |
| 443 | + } |
436 | 444 |
|
437 |
| - if (!Base64.getEncoder() |
438 |
| - .encodeToString(dsseEnvelope.getSignature()) |
439 |
| - .equals(rekorDsse.getSignatures().get(0).getSignature())) { |
440 |
| - throw new KeylessVerificationException( |
441 |
| - "Provided DSSE signature materials are inconsistent with DSSE log entry"); |
| 445 | + if (!Base64.getEncoder() |
| 446 | + .encodeToString(dsseEnvelope.getSignature()) |
| 447 | + .equals(rekorDsse.getSignatures().get(0).getSignature())) { |
| 448 | + throw new KeylessVerificationException( |
| 449 | + "Provided DSSE signature materials are inconsistent with DSSE log entry"); |
| 450 | + } |
| 451 | + } else if ("0.0.2".equals(version)) { |
| 452 | + DSSELogEntryV002 logEntrySpec; |
| 453 | + try { |
| 454 | + DSSELogEntryV002.Builder builder = DSSELogEntryV002.newBuilder(); |
| 455 | + ProtoJson.parser() |
| 456 | + .merge( |
| 457 | + new Gson() |
| 458 | + .toJson( |
| 459 | + rekorEntry.getBodyDecoded().getSpec().getAsJsonObject().get("dsseV002")), |
| 460 | + builder); |
| 461 | + logEntrySpec = builder.build(); |
| 462 | + } catch (InvalidProtocolBufferException ipbe) { |
| 463 | + throw new KeylessVerificationException( |
| 464 | + "Could not parse DSSELogEntryV002 from log entry body", ipbe); |
| 465 | + } |
| 466 | + |
| 467 | + if (!logEntrySpec.getPayloadHash().getAlgorithm().equals(HashAlgorithm.SHA2_256)) { |
| 468 | + throw new KeylessVerificationException( |
| 469 | + "Unsupported digest algorithm in log entry: " |
| 470 | + + logEntrySpec.getPayloadHash().getAlgorithm()); |
| 471 | + } |
| 472 | + |
| 473 | + // check if the digest over the dsse payload matches the digest in the transparency log entry |
| 474 | + byte[] calculatedDigest = Hashing.sha256().hashBytes(dsseEnvelope.getPayload()).asBytes(); |
| 475 | + if (!Arrays.equals( |
| 476 | + logEntrySpec.getPayloadHash().getDigest().toByteArray(), calculatedDigest)) { |
| 477 | + throw new KeylessVerificationException( |
| 478 | + "Digest of DSSE payload in bundle does not match DSSE payload digest in log entry"); |
| 479 | + } |
| 480 | + |
| 481 | + // check if the signature over the dsse payload matches the signature in the rekorEntry |
| 482 | + if (logEntrySpec.getSignaturesCount() != 1) { |
| 483 | + throw new KeylessVerificationException( |
| 484 | + "Log entry spec must have exactly 1 signature, but found: " |
| 485 | + + logEntrySpec.getSignaturesCount()); |
| 486 | + } |
| 487 | + |
| 488 | + Signature logSignature = logEntrySpec.getSignatures(0); |
| 489 | + if (!Arrays.equals(dsseEnvelope.getSignature(), logSignature.getContent().toByteArray())) { |
| 490 | + throw new KeylessVerificationException( |
| 491 | + "Signature in DSSE envelope does not match signature in log entry spec"); |
| 492 | + } |
| 493 | + |
| 494 | + var verifier = logSignature.getVerifier(); |
| 495 | + if (!verifier.hasX509Certificate()) { |
| 496 | + throw new KeylessVerificationException( |
| 497 | + "Rekor entry DSSE verifier is missing X.509 certificate"); |
| 498 | + } |
| 499 | + try { |
| 500 | + byte[] certFromRekor = verifier.getX509Certificate().getRawBytes().toByteArray(); |
| 501 | + byte[] certFromBundle = leafCert.getEncoded(); |
| 502 | + if (!Arrays.equals(certFromRekor, certFromBundle)) { |
| 503 | + throw new KeylessVerificationException( |
| 504 | + "Certificate in rekor entry does not match certificate in bundle"); |
| 505 | + } |
| 506 | + } catch (CertificateEncodingException e) { |
| 507 | + throw new KeylessVerificationException( |
| 508 | + "Could not encode leaf certificate for comparison", e); |
| 509 | + } |
442 | 510 | }
|
443 | 511 | }
|
444 | 512 | }
|
0 commit comments