From 57e134db39ed0dfb44a2dab9082493144b68579a Mon Sep 17 00:00:00 2001 From: Appu Goundan Date: Thu, 21 Nov 2024 20:31:20 -0500 Subject: [PATCH] Rekor Verifier should match artifact hash Signed-off-by: Appu Goundan --- .../java/fuzzing/RekorVerifierFuzzer.java | 3 +- .../main/java/dev/sigstore/KeylessSigner.java | 2 +- .../java/dev/sigstore/KeylessVerifier.java | 4 +- .../rekor/client/HashedRekordRequest.java | 2 +- .../dev/sigstore/rekor/client/RekorTypes.java | 2 +- .../sigstore/rekor/client/RekorVerifier.java | 52 ++++++++++++++++++- .../v0.0.1}/hashedRekord.json | 10 ++-- .../dev/sigstore/KeylessVerifierTest.java | 21 ++++++++ .../rekor/client/RekorVerifierTest.java | 32 ++++++++++-- .../bundle-with-wrong-tlog-entry.sigstore | 39 ++++++++++++++ 10 files changed, 149 insertions(+), 18 deletions(-) rename sigstore-java/src/main/resources/rekor/model/{ => hashedRekord/v0.0.1}/hashedRekord.json (82%) create mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-with-wrong-tlog-entry.sigstore diff --git a/fuzzing/src/main/java/fuzzing/RekorVerifierFuzzer.java b/fuzzing/src/main/java/fuzzing/RekorVerifierFuzzer.java index 5a12a580..89aa7363 100644 --- a/fuzzing/src/main/java/fuzzing/RekorVerifierFuzzer.java +++ b/fuzzing/src/main/java/fuzzing/RekorVerifierFuzzer.java @@ -32,6 +32,7 @@ public class RekorVerifierFuzzer { public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { var tLogs = Tuf.transparencyLogsFrom(data); + byte[] digest = data.consumeBytes(32); byte[] byteArray = data.consumeRemainingAsBytes(); String string = new String(byteArray, StandardCharsets.UTF_8); @@ -39,7 +40,7 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { RekorEntry entry = RekorResponse.newRekorResponse(uri, string).getEntry(); RekorVerifier verifier = RekorVerifier.newRekorVerifier(tLogs); - verifier.verifyEntry(entry); + verifier.verifyHashedRekord(entry, digest); } catch (URISyntaxException | RekorParseException | RekorVerificationException e) { // Known exception } diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java b/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java index 0227bc3d..af7682d6 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java @@ -336,7 +336,7 @@ public List sign(List artifactDigests) throws KeylessSignerExcep } try { - rekorVerifier.verifyEntry(rekorResponse.getEntry()); + rekorVerifier.verifyHashedRekord(rekorResponse.getEntry(), artifactDigest); } catch (RekorVerificationException ex) { throw new KeylessSignerException("Failed to validate rekor response after signing", ex); } diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java index c68e919a..63b9b765 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java @@ -175,9 +175,9 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt RekorEntry rekorEntry = bundle.getEntries().get(0); - // verify the rekor entry is signed by the log keys + // verify the rekor entry is signed by the log keys and is relevant to this artifact try { - rekorVerifier.verifyEntry(rekorEntry); + rekorVerifier.verifyHashedRekord(rekorEntry, artifactDigest); } catch (RekorVerificationException ex) { throw new KeylessVerificationException("Rekor entry signature was not valid", ex); } diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/HashedRekordRequest.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/HashedRekordRequest.java index 3b982513..a00021f2 100644 --- a/sigstore-java/src/main/java/dev/sigstore/rekor/client/HashedRekordRequest.java +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/HashedRekordRequest.java @@ -19,7 +19,7 @@ import com.google.common.hash.Hashing; import com.google.common.primitives.Bytes; -import dev.sigstore.rekor.*; +import dev.sigstore.rekor.hashedRekord.v0_0_1.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorTypes.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorTypes.java index c82a938b..c2d92206 100644 --- a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorTypes.java +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorTypes.java @@ -17,7 +17,7 @@ import static dev.sigstore.json.GsonSupplier.GSON; -import dev.sigstore.rekor.HashedRekord; +import dev.sigstore.rekor.hashedRekord.v0_0_1.HashedRekord; /** Parser for the body.spec element of {@link RekorEntry}. */ public class RekorTypes { diff --git a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorVerifier.java b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorVerifier.java index 7017467e..fad85c7d 100644 --- a/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorVerifier.java @@ -16,8 +16,12 @@ package dev.sigstore.rekor.client; import com.google.common.hash.Hashing; +import com.google.gson.JsonSyntaxException; import dev.sigstore.encryption.signers.Verifiers; +import dev.sigstore.json.GsonSupplier; import dev.sigstore.rekor.client.RekorEntry.Checkpoint; +import dev.sigstore.rekor.hashedRekord.v0_0_1.Hash.Algorithm; +import dev.sigstore.rekor.hashedRekord.v0_0_1.HashedRekord; import dev.sigstore.trustroot.SigstoreTrustedRoot; import dev.sigstore.trustroot.TransparencyLog; import java.security.InvalidKeyException; @@ -27,6 +31,8 @@ import java.util.Arrays; import java.util.Base64; import java.util.List; +import java.util.Objects; +import org.bouncycastle.util.encoders.DecoderException; import org.bouncycastle.util.encoders.Hex; /** Verifier for rekor entries. */ @@ -46,12 +52,54 @@ private RekorVerifier(List tlogs) { } /** - * Verify that a Rekor Entry is signed with the rekor public key loaded into this verifier + * Verify that a Rekor Entry is signed with any public key loaded into this verifier. Also checks + * if the embedded hashrekord has a matching artifactDigest. * * @param entry the entry to verify * @throws RekorVerificationException if the entry cannot be verified */ - public void verifyEntry(RekorEntry entry) throws RekorVerificationException { + public void verifyHashedRekord(RekorEntry entry, byte[] artifactDigest) + throws RekorVerificationException { + verifyEntry(entry); + + try { + RekorEntryBody entryBody = entry.getBodyDecoded(); + if (!Objects.equals(entryBody.getKind(), "hashedrekord") + || !Objects.equals(entryBody.getApiVersion(), "0.0.1")) { + throw new RekorVerificationException( + "Rekor entry was not expected type hashedrekord:0.0.1"); + } + HashedRekord hashedRekord = + GsonSupplier.GSON.get().fromJson(entryBody.getSpec(), HashedRekord.class); + var algorithm = hashedRekord.getData().getHash().getAlgorithm(); + if (hashedRekord.getData().getHash().getAlgorithm() != Algorithm.SHA_256) { + throw new RekorVerificationException( + "Cannot process hashedRekord with hashing algorithm " + algorithm.toString()); + } + var entryDigest = Hex.decode(hashedRekord.getData().getHash().getValue()); + if (!Arrays.equals(artifactDigest, entryDigest)) { + throw new RekorVerificationException( + "Provided artifact digest does not match digest in log entry" + + "\nprovided(hex) : " + + Hex.toHexString(artifactDigest) + + "\nlog(hex) : " + + Hex.toHexString(entryDigest)); + } + } catch (JsonSyntaxException jse) { + throw new RekorVerificationException("Could not parse hashrekord during verification", jse); + } catch (DecoderException de) { + throw new RekorVerificationException( + "Could not decode hex sha256 artifact hash in hashrekord", de); + } + } + + /** + * Verify that a Rekor Entry is signed with the rekor public key loaded into this verifier. + * + * @param entry the entry to verify + * @throws RekorVerificationException if the entry cannot be verified + */ + private void verifyEntry(RekorEntry entry) throws RekorVerificationException { if (entry.getVerification() == null) { throw new RekorVerificationException("No verification information in entry."); } diff --git a/sigstore-java/src/main/resources/rekor/model/hashedRekord.json b/sigstore-java/src/main/resources/rekor/model/hashedRekord/v0.0.1/hashedRekord.json similarity index 82% rename from sigstore-java/src/main/resources/rekor/model/hashedRekord.json rename to sigstore-java/src/main/resources/rekor/model/hashedRekord/v0.0.1/hashedRekord.json index b1ef6505..47cbac0e 100644 --- a/sigstore-java/src/main/resources/rekor/model/hashedRekord.json +++ b/sigstore-java/src/main/resources/rekor/model/hashedRekord/v0.0.1/hashedRekord.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://rekor.sigstore.dev/types/rekord/rekord_v0_0_1_schema.json", + "$id": "http://rekor.sigstore.dev/types/rekord/hashedrekord_v0_0_1_schema.json", "title": "Hashed Rekor v0.0.1 Schema", "description": "Schema for Hashed Rekord object", "type": "object", @@ -15,11 +15,11 @@ "format": "byte" }, "publicKey" : { - "description": "The public key that can verify the signature", + "description": "The public key that can verify the signature; this can also be an X509 code signing certificate that contains the raw public key information", "type": "object", "properties": { "content": { - "description": "Specifies the content of the public key inline within the document", + "description": "Specifies the content of the public key or code signing certificate inline within the document", "type": "string", "format": "byte" } @@ -38,10 +38,10 @@ "algorithm": { "description": "The hashing function used to compute the hash value", "type": "string", - "enum": [ "sha256" ] + "enum": [ "sha256", "sha384", "sha512" ] }, "value": { - "description": "The hash value for the content", + "description": "The hash value for the content, as represented by a lower case hexadecimal string", "type": "string" } }, diff --git a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java index c9549193..733ada9f 100644 --- a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java @@ -65,6 +65,27 @@ public void testVerify_mismatchedSet() throws Exception { VerificationOptions.empty())); } + @Test + public void testVerify_mismatchedArtifactHash() throws Exception { + // a bundle file that uses the tlog entry from another artifact signed with the same + // certificate. The Bundle is fully valid except that the artifact hash doesn't match + var bundleFile = + Resources.toString( + Resources.getResource( + "dev/sigstore/samples/bundles/bundle-with-wrong-tlog-entry.sigstore"), + StandardCharsets.UTF_8); + var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath(); + + var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build(); + Assertions.assertThrows( + KeylessVerificationException.class, + () -> + verifier.verify( + Path.of(artifact), + Bundle.from(new StringReader(bundleFile)), + VerificationOptions.empty())); + } + @Test public void testVerify_errorsOnDSSEBundle() throws Exception { var bundleFile = diff --git a/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorVerifierTest.java index ec1e4aea..42b60b60 100644 --- a/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorVerifierTest.java @@ -42,6 +42,7 @@ public class RekorVerifierTest { public String rekorResponse; public byte[] rekorPub; + public byte[] artifactHash; public static SigstoreTrustedRoot trustRoot; @@ -54,6 +55,7 @@ public void loadResources() throws IOException { rekorPub = Resources.toByteArray( Resources.getResource("dev/sigstore/samples/rekor-response/valid/rekor.pub")); + artifactHash = Hex.decode("47c1db9fb5fe7ff66e3d7c15b2ca5d3a046ee8f4aa043dedd317e46b321c4390"); } @BeforeAll @@ -77,17 +79,34 @@ public void verifyEntry_invalid() throws Exception { var thrown = Assertions.assertThrows( - RekorVerificationException.class, () -> verifier.verifyEntry(response.getEntry())); + RekorVerificationException.class, + () -> verifier.verifyHashedRekord(response.getEntry(), artifactHash)); Assertions.assertEquals("Entry SET was not valid", thrown.getMessage()); } + @Test + public void verifyEntry_withMismatchedArtifactHash() throws Exception { + var response = RekorResponse.newRekorResponse(new URI("https://somewhere"), rekorResponse); + var verifier = RekorVerifier.newRekorVerifier(trustRoot); + // change the provided artifact hash + artifactHash[0] = 10; + + var thrown = + Assertions.assertThrows( + RekorVerificationException.class, + () -> verifier.verifyHashedRekord(response.getEntry(), artifactHash)); + MatcherAssert.assertThat( + thrown.getMessage(), + CoreMatchers.startsWith("Provided artifact digest does not match digest in log entry")); + } + @Test public void verifyEntry() throws Exception { var response = RekorResponse.newRekorResponse(new URI("https://somewhere"), rekorResponse); var verifier = RekorVerifier.newRekorVerifier(trustRoot); var entry = response.getEntry(); - verifier.verifyEntry(entry); + verifier.verifyHashedRekord(entry, artifactHash); } @Test @@ -101,7 +120,8 @@ public void verifyEntry_withInvalidInclusionProof() throws Exception { var entry = response.getEntry(); var thrown = Assertions.assertThrows( - RekorVerificationException.class, () -> verifier.verifyEntry(entry)); + RekorVerificationException.class, + () -> verifier.verifyHashedRekord(entry, artifactHash)); MatcherAssert.assertThat( thrown.getMessage(), @@ -139,7 +159,8 @@ public void verifyEntry_logIdMismatch() throws Exception { var thrown = Assertions.assertThrows( - RekorVerificationException.class, () -> verifier.verifyEntry(response.getEntry())); + RekorVerificationException.class, + () -> verifier.verifyHashedRekord(response.getEntry(), artifactHash)); Assertions.assertEquals( "Log entry (logid, timestamp) does not match any provided transparency logs.", thrown.getMessage()); @@ -180,7 +201,8 @@ public void verifyEntry_logIdTimeMismatch() throws Exception { var thrown = Assertions.assertThrows( - RekorVerificationException.class, () -> verifier.verifyEntry(response.getEntry())); + RekorVerificationException.class, + () -> verifier.verifyHashedRekord(response.getEntry(), artifactHash)); Assertions.assertEquals( "Log entry (logid, timestamp) does not match any provided transparency logs.", thrown.getMessage()); diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-with-wrong-tlog-entry.sigstore b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-with-wrong-tlog-entry.sigstore new file mode 100644 index 00000000..74abc0b6 --- /dev/null +++ b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-with-wrong-tlog-entry.sigstore @@ -0,0 +1,39 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", + "verificationMaterial": { + "tlogEntries": [{ + "logIndex": "150603746", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "hashedrekord", + "version": "0.0.1" + }, + "integratedTime": "1732221080", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCIQDY5kN3WEDvMndKO5JImUGrtV5jW1cVGnEDylEKRk7ZsAIgZN1MIv+eILssoHtsqqk+b9Tda+2ZN3Lkz1Mw8GwTD9k=" + }, + "inclusionProof": { + "logIndex": "28699484", + "rootHash": "p+AbctDd928yM1oCzeJPou8nHME2Qyka0DMB1tOlqsI=", + "treeSize": "28699488", + "hashes": ["fcyRKy95RMsZh/HeRQaZK8jFhhrdqqA5iFs+wInZsaI=", "zl59JwaJVeTvAF5pCTPbaXXdXgsYidumj8TgjRvcZIA=", "4mck2Czn6tiBqOQYPlh3ATO9DaTvouT7biFAkCz4bWM=", "BipkUG/DzduS/XjrbEd3uC5odEYaC9OTJTYElA10POo=", "9yDO9YZftL6yDveO6bWY32s8D4y3+uWUKVLEPmRwbHk=", "GMMIMtm+cQ0ajPC1YWzZCJIZ517pDeYJ5+FnZinynQg=", "9henJICU+Lo3pa+Rrtnt8+5esX8hhksuDcqmaYvxLHA=", "aL2yyr/c5w5R72E6L+AagxQB/oMUftqB7fHIs625QR0=", "Fvp+S31pMTO9ts92nEBj6sYay8OFauXxYrevM73sg3U=", "tk7EpIXIblK0ktOJNY9T+WCMpW0loWy+GouFKk7me8k=", "Ky28cDNBQqCVLHhOLegW99yo/fkUTkjmWsr56fXTt+E=", "PTN8SB4uaeclDiwUlPK/FSmiK7voPpkD8GBfNeRCFnw=", "0C1qnoT2c+8KErA01/VosqRATcsPFXeswb8bk/eb/3g=", "5EVC7yaMdjhwDR6OLXLyveRuRrF8xvXTzlm+8+vzfxE=", "bulsENariUUsC4xiR1yFtqKzD8evI9p/s+YCpl8t9tE=", "E2rLOYPJFKiizYiyu07QLqkMVTVL7i2ZgXiQywdI9KQ=", "4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=", "gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="], + "checkpoint": { + "envelope": "rekor.sigstore.dev - 1193050959916656506\n28699488\np+AbctDd928yM1oCzeJPou8nHME2Qyka0DMB1tOlqsI\u003d\n\n— rekor.sigstore.dev wNI9ajBFAiEA+50Xtrawp0K2slioi3f8lpCrZGu8k919PDoZ+Z80/WUCIDaUTds/GljSB8/Mu7DLj879oi/odHrPZ0OUeubKlBGd\n" + } + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkNDBlYzNlZWJjYjI5OTE0OTRmMjI3MzZhM2Y1ZmE3YjJlNGI2OTcyZDdjMDQ2ZDVjNWQ3ZTUwNmFiZTlmNzUyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRWhQanlEZXFCdDJyNnBkNTVTczVYdi9rTG54NXozb0hDOE42TCtWOW1MSUFpQktpQ2xLcTNPV2ttcHZUcXpyRDZ1a2RmdTQvRXhGVVVmdHoxcnMvL04ySnc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTjRla05EUVdzeVowRjNTVUpCWjBsVlJteERWeTlHUWtabFVrRmtkVU12Y1c0eGVrNTBOVWt2YlN0SmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJlRTFVU1hoTmFrRjZUVlJGTlZkb1kwNU5hbEY0VFZSSmVFMXFRVEJOVkVVMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZzWlhwUWFXeEtWVlZqTkV0cGJtWkVURTl0UTNobVRVNXpLMlJ4Ums5c2JYWkNZMnNLUW1aaWRHeHRXRWRpWTNKRGVtTkxNWGRuTm5SdVYzVklObmhHZUhKclJFUmxaMU5ITDBoYU1HcGpVekJMVUU0M1EyRlBRMEZYZDNkblowWnZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ4TVZsQ0NteHZUbXg1VDFkU2FuUkZOREZtTVdkeU5GbEZiR2haZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBoUldVUldVakJTUVZGSUwwSkNUWGRGV1VWUVdWaENkMlJWUW01aU1qbHVZa2RWZFZreU9YUk5RMnRIUTJselIwRlJVVUpuTnpoM1FWRkZSUXBITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVWQldVOHZUVUZGU1VKQ01FMUhNbWd3Q21SSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sUldVdExkMWxDUWtGSVYyVlJTVVZCWjFJM1FraHJRV1IzUWpFS1FVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNhekZDYzBWTU5FRkJRVkZFUVVWWmR3cFNRVWxuUkdkNE5tUkNNMHR2YlhoUGVVSXdaakY1SzNZMGJUYzNNWE4yY1dWMUszZHVjazFUVG14WWNHNVNXVU5KUkRkTlZVUjVRa1ZhVlhobmVUSmFDbFJWZDNnd1NUWnllR2hOTm5weU5qRXJNamc0YXpkQmVYQktMMlZOUVc5SFEwTnhSMU5OTkRsQ1FVMUVRVEpuUVUxSFZVTk5VVU16VGpaR05sUlZWVk1LYTA5bVNrSnRZMWhPV2psSFRGaEhWbFYzV2xaQ1IwWmtSSEF2VkZkT1ZrNVZTeTlyT1ZZNE1VcFFXbFF6TkRWcVVUVm5SMUEzVFVOTlJXWjNPVEZUV2dwV00zSllZVWd6VkdGRFZYZGFSM0pQY205RE5XSmFNMGRzYW1oQ05raEdSMk5OUjFSM1JXUlFObE0ySzNsNVNHOTBkbkpYVW5kSFEwOVJQVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0=" + }], + "certificate": { + "rawBytes": "MIICxzCCAk2gAwIBAgIUFlCW/FBFeRAduC/qn1zNt5I/m+IwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQxMTIxMjAzMTE5WhcNMjQxMTIxMjA0MTE5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElezPilJUUc4KinfDLOmCxfMNs+dqFOlmvBckBfbtlmXGbcrCzcK1wg6tnWuH6xFxrkDDegSG/HZ0jcS0KPN7CaOCAWwwggFoMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUq1YBloNlyOWRjtE41f1gr4YElhYwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHQYDVR0RAQH/BBMwEYEPYXBwdUBnb29nbGUuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABk1BsEL4AAAQDAEYwRAIgDgx6dB3KomxOyB0f1y+v4m771svqeu+wnrMSNlXpnRYCID7MUDyBEZUxgy2ZTUwx0I6rxhM6zr61+288k7AypJ/eMAoGCCqGSM49BAMDA2gAMGUCMQC3N6F6TUUSkOfJBmcXNZ9GLXGVUwZVBGFdDp/TWNVNUK/k9V81JPZT345jQ5gGP7MCMEfw91SZV3rXaH3TaCUwZGrOroC5bZ3GljhB6HFGcMGTwEdP6S6+yyHotvrWRwGCOQ==" + } + }, + "messageSignature": { + "messageDigest": { + "algorithm": "SHA2_256", + "digest": "oM/HEnHW4njlfNMy/5V8P3BD/do1TEy7GQow1W76Ab8=" + }, + "signature": "MEYCIQCGZcJ+4mTIMPAyIKV7XZjZ5KyvX1tz/glbin9vVF+9fwIhANUb5z/2Rb88vtvtABHAmQyBl4Yp8ipMLCnv36Ln8/Eb" + } +}