Skip to content

Commit 031c839

Browse files
authored
Merge pull request #521 from sigstore/update-conformance
Update conformance tests
2 parents ce3d3a1 + bf3c247 commit 031c839

File tree

8 files changed

+123
-16
lines changed

8 files changed

+123
-16
lines changed

.github/workflows/conformance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ jobs:
3131
- name: Unpack sigstore-java distribution
3232
run: tar -xvf ${{ github.workspace }}/sigstore-cli/build/distributions/sigstore-cli-*.tar --strip-components 1
3333

34-
- uses: sigstore/sigstore-conformance@064fb32a890c30235f305281f3509c5e65e6f9e5 # tag=v0.0.4
34+
- uses: sigstore/sigstore-conformance@1abc82cdefe80bd907855d8447f903ba8b4918e0 # v0.0.6
3535
with:
3636
entrypoint: ${{ github.workspace }}/bin/sigstore-cli

sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
import picocli.CommandLine.Option;
3737
import picocli.CommandLine.Parameters;
3838

39-
@Command(name = "verify", description = "verify an artifact")
39+
@Command(
40+
name = "verify",
41+
aliases = {"verify-bundle"},
42+
description = "verify an artifact")
4043
public class Verify implements Callable<Integer> {
4144
@Parameters(arity = "1", paramLabel = "<artifact>", description = "artifact to verify")
4245
Path artifact;
@@ -52,19 +55,20 @@ static class Policy {
5255
names = {"--certificate-identity"},
5356
description = "subject alternative name in certificate",
5457
required = true)
55-
private String certificateSan;
58+
String certificateSan;
5659

5760
@Option(
5861
names = {"--certificate-oidc-issuer"},
5962
description = "sigstore issuer in certificate",
6063
required = true)
61-
private String certificateIssuer;
64+
String certificateIssuer;
6265
}
6366

6467
@Override
6568
public Integer call() throws Exception {
6669
byte[] digest = asByteSource(artifact.toFile()).hash(Hashing.sha256()).asBytes();
67-
KeylessSignature keylessSignature = null;
70+
KeylessSignature keylessSignature;
71+
6872
if (signatureFiles.sigAndCert != null) {
6973
byte[] signature = Files.readAllBytes(signatureFiles.sigAndCert.signatureFile);
7074
CertPath certPath =

sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,44 @@ public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
179179

180180
var signature = request.getKeylessSignature().getSignature();
181181

182-
var rekorEntry =
183-
request.getVerificationOptions().isOnline()
184-
? getEntryFromRekor(artifactDigest, leafCert, signature)
185-
: request
186-
.getKeylessSignature()
187-
.getEntry()
188-
.orElseThrow(
189-
() ->
190-
new KeylessVerificationException(
191-
"No rekor entry was provided for offline verification"));
182+
// Logic is a bit convoluted for obtaining rekor entry for further processing
183+
// 1. if we're in "online mode":
184+
// a. grab the entry from rekor remote to use for verification
185+
// b. if an entry was also provided directly to this library, verify it is valid and the
186+
// same signable content as the one we obtained from rekor. SETs will be different
187+
// because rekor can generate those using a non-idempotent signer, but all signatures
188+
// should still be valid
189+
// 2. if we're in offline mode, ensure an entry was provided
190+
191+
RekorEntry rekorEntry;
192+
193+
if (request.getVerificationOptions().isOnline()) {
194+
rekorEntry = getEntryFromRekor(artifactDigest, leafCert, signature);
195+
if (request.getKeylessSignature().getEntry().isPresent()) {
196+
var provided = request.getKeylessSignature().getEntry().get();
197+
if (!Arrays.equals(
198+
rekorEntry.getSignableContent(),
199+
request.getKeylessSignature().getEntry().get().getSignableContent())) {
200+
throw new KeylessVerificationException(
201+
"Entry obtained from rekor does not match provided entry");
202+
}
203+
// verify the provided rekor entry is valid even if we are in online mode
204+
try {
205+
rekorVerifier.verifyEntry(provided);
206+
} catch (RekorVerificationException ex) {
207+
throw new KeylessVerificationException("Rekor entry signature was not valid");
208+
}
209+
}
210+
} else {
211+
rekorEntry =
212+
request
213+
.getKeylessSignature()
214+
.getEntry()
215+
.orElseThrow(
216+
() ->
217+
new KeylessVerificationException(
218+
"No rekor entry was provided for offline verification"));
219+
}
192220

193221
// verify the rekor entry is signed by the log keys
194222
try {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore;
17+
18+
import com.google.common.io.Resources;
19+
import dev.sigstore.KeylessVerificationRequest.VerificationOptions;
20+
import dev.sigstore.bundle.BundleFactory;
21+
import java.io.StringReader;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Path;
24+
import org.junit.jupiter.api.Assertions;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.ValueSource;
27+
28+
public class KeylessVerifierTest {
29+
30+
@ParameterizedTest
31+
@ValueSource(booleans = {true, false})
32+
public void testVerify(boolean isOnline) throws Exception {
33+
var bundleFile =
34+
Resources.toString(
35+
Resources.getResource("dev/sigstore/samples/bundles/bundle.sigstore"),
36+
StandardCharsets.UTF_8);
37+
var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath();
38+
39+
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
40+
var verificationReq =
41+
KeylessVerificationRequest.builder()
42+
.keylessSignature(BundleFactory.readBundle(new StringReader(bundleFile)))
43+
.verificationOptions(VerificationOptions.builder().isOnline(isOnline).build())
44+
.build();
45+
verifier.verify(Path.of(artifact), verificationReq);
46+
}
47+
48+
@ParameterizedTest
49+
@ValueSource(booleans = {true, false})
50+
public void testVerify_mismatchedSet(boolean isOnline) throws Exception {
51+
// a bundle file where the SET is replaced with a valid SET for another artifact
52+
var bundleFile =
53+
Resources.toString(
54+
Resources.getResource(
55+
"dev/sigstore/samples/bundles/bundle-with-mismatched-set.sigstore"),
56+
StandardCharsets.UTF_8);
57+
var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath();
58+
59+
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
60+
var verificationReq =
61+
KeylessVerificationRequest.builder()
62+
.keylessSignature(BundleFactory.readBundle(new StringReader(bundleFile)))
63+
.verificationOptions(VerificationOptions.builder().isOnline(isOnline).build())
64+
.build();
65+
Assertions.assertThrows(
66+
KeylessVerificationException.class,
67+
() -> verifier.verify(Path.of(artifact), verificationReq));
68+
}
69+
}

sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifierTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static void loadResources() throws IOException {
6262

6363
bundleFile =
6464
Resources.toString(
65-
Resources.getResource("dev/sigstore/samples/bundles/bundle-with-leaf-cert.sigstore"),
65+
Resources.getResource("dev/sigstore/samples/bundles/bundle.sigstore"),
6666
StandardCharsets.UTF_8);
6767
}
6868

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DO NOT MODIFY ME!
2+
3+
this is "a.txt", a sample input for sigstore-conformance's test suite.
4+
5+
DO NOT MODIFY ME!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "MIIIGTCCB5+gAwIBAgIUBPWs4OPN1kte0mUMGZrZ6ozMVRkwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwNzEyMTU1NjM1WhcNMjMwNzEyMTYwNjM1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVr33uVAPA1SpA5w/mmBF9ariW8E7oizIQKqiYfxwSb1zftqZZX045y3tPbRkIWe+t7MUYliQknQ954rDDEASnKOCBr4wgga6MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUx2TNZkruHC2aCdyIXscI8N/8q2owHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wgaUGA1UdEQEB/wSBmjCBl4aBlGh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi8uZ2l0aHViL3dvcmtmbG93cy9leHRyZW1lbHktZGFuZ2Vyb3VzLW9pZGMtYmVhY29uLnltbEByZWZzL2hlYWRzL21haW4wOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/MAEDBChhZjc4NWI2ZDNiMGZhMGMwYWExMzA1ZmFlZTdjZTYwMzZlOGQ5MGM0MC0GCisGAQQBg78wAQQEH0V4dHJlbWVseSBkYW5nZXJvdXMgT0lEQyBiZWFjb24wSQYKKwYBBAGDvzABBQQ7c2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24wHQYKKwYBBAGDvzABBgQPcmVmcy9oZWFkcy9tYWluMDsGCisGAQQBg78wAQgELQwraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTCBpgYKKwYBBAGDvzABCQSBlwyBlGh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi8uZ2l0aHViL3dvcmtmbG93cy9leHRyZW1lbHktZGFuZ2Vyb3VzLW9pZGMtYmVhY29uLnltbEByZWZzL2hlYWRzL21haW4wOAYKKwYBBAGDvzABCgQqDChhZjc4NWI2ZDNiMGZhMGMwYWExMzA1ZmFlZTdjZTYwMzZlOGQ5MGM0MB0GCisGAQQBg78wAQsEDwwNZ2l0aHViLWhvc3RlZDBeBgorBgEEAYO/MAEMBFAMTmh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbjA4BgorBgEEAYO/MAENBCoMKGFmNzg1YjZkM2IwZmEwYzBhYTEzMDVmYWVlN2NlNjAzNmU4ZDkwYzQwHwYKKwYBBAGDvzABDgQRDA9yZWZzL2hlYWRzL21haW4wGQYKKwYBBAGDvzABDwQLDAk2MzI1OTY4OTcwNwYKKwYBBAGDvzABEAQpDCdodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUtY29uZm9ybWFuY2UwGQYKKwYBBAGDvzABEQQLDAkxMzE4MDQ1NjMwgaYGCisGAQQBg78wARIEgZcMgZRodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24vLmdpdGh1Yi93b3JrZmxvd3MvZXh0cmVtZWx5LWRhbmdlcm91cy1vaWRjLWJlYWNvbi55bWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoYWY3ODViNmQzYjBmYTBjMGFhMTMwNWZhZWU3Y2U2MDM2ZThkOTBjNDAhBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMIGBBgorBgEEAYO/MAEVBHMMcWh0dHBzOi8vZ2l0aHViLmNvbS9zaWdzdG9yZS1jb25mb3JtYW5jZS9leHRyZW1lbHktZGFuZ2Vyb3VzLXB1YmxpYy1vaWRjLWJlYWNvbi9hY3Rpb25zL3J1bnMvNTUzMzc0MTQ5Ny9hdHRlbXB0cy8xMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGJStGTCwAABAMARzBFAiBCA4jZQP4CwMiWoeS7WMW46QkI4e7OsNH3yVhf5wdBvgIhAPJYxdsi9NqOXVZsEUtCup8m1m/2zG39FTGlgE0MorDFMAoGCCqGSM49BAMDA2gAMGUCMEYWRwI5QJeOwNCuV4tnZ0n5QNlUlP0BtX5V2ZTQLqcQbWtneC7tLptiYgr0Z62UDQIxAO6ItXAH+sbZcsbj08xr3GApM6hjvyTAl39pS3Y3sZwAz8lfQDHNL4eALEo1heAYVg=="}]}, "tlogEntries": [{"logIndex": "27246492", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1689177396", "inclusionPromise": {"signedEntryTimestamp": "MEYCIQD4cnKdU4nfajoRoG033TPGqGfVn9kkHvIduZlVyHkwsgIhANFCGXm5GPzrDRw7g9+66CfiEBalWAf4VcnnnOBEdTGD"}, "inclusionProof": {"logIndex": "23083061", "rootHash": "dauhleYK4YyAdxwwDtR0l0KnSOWZdG2bwqHftlanvcI=", "treeSize": "23083062", "hashes": ["/vK4Da3g7ZhRgme8FxCi8QIBL7DSlwpSwyERyIV+uS8=", "k9N2htTx+7tA55tT0tiB/BO4uqaqSxqTV34ouZgCBWk=", "/ArdEa96pCZQOyrxyKhOHxd2HHEsXBldxS8p23CNIkk=", "5GQbI53IAmYSDjLtEjG0PNzp0hk2+W/eEkCbJlNqTKc=", "O86OnjaE1s+pcHWna1/xdeZ7ubCXgVmtV1f7nWlWYLI=", "Hj0TEef/bwMHB10DPoFLu5+RSLTGJ5YRaQWqwwVIp0s=", "aRtyVlt3GYDP4qaYrEAWqiGqcYDELxyk7Fl3icGXOoA=", "ZdynT2d4F3NFQNBurFDaZBoYMVRjbQjlTnSL1hL67+s=", "3VHoQOiS1wCTrX4dseLeo9UDNMc0XTYORE0i/Entn14=", "rXEsmEJN4PEoTU8US4qVtdIsGB1MCiRlGOepoiC99kM="], "checkpoint": {"envelope": "rekor.sigstore.dev - 2605736670972794746\n23083062\ndauhleYK4YyAdxwwDtR0l0KnSOWZdG2bwqHftlanvcI=\nTimestamp: 1689177396617352539\n\n\u2014 rekor.sigstore.dev wNI9ajBFAiBxaGyEtxkzFLkaCSEJqFuSS3dJjEZCNiyByVs1CNVQ8gIhAOoNnXtmMtTctV2oRnSRUZAo4EWUYPK/vBsqOzAU6TMs\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhMGNmYzcxMjcxZDZlMjc4ZTU3Y2QzMzJmZjk1N2MzZjcwNDNmZGRhMzU0YzRjYmIxOTBhMzBkNTZlZmEwMWJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRk9wYVhLV3Z2QkR3VGhEalRIWDd0RkY4bGlSb1N4TFpJc1Nlb1VNLzZENEFpQnhWOS9SblRNTXcxdDZubmlYMHJDdXdyZjhWaCtmZUxGdTk5bTRpciszeUE9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVbEhWRU5EUWpVclowRjNTVUpCWjBsVlFsQlhjelJQVUU0eGEzUmxNRzFWVFVkYWNsbzJiM3BOVmxKcmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDU2UlhsTlZGVXhUbXBOTVZkb1kwNU5hazEzVG5wRmVVMVVXWGRPYWsweFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZXY2pNemRWWkJVRUV4VTNCQk5YY3ZiVzFDUmpsaGNtbFhPRVUzYjJsNlNWRkxjV2tLV1daNGQxTmlNWHBtZEhGYVdsZ3dORFY1TTNSUVlsSnJTVmRsSzNRM1RWVlpiR2xSYTI1Uk9UVTBja1JFUlVGVGJrdFBRMEp5TkhkbloyRTJUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlY0TWxST0NscHJjblZJUXpKaFEyUjVTVmh6WTBrNFRpODRjVEp2ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJkaFZVZEJNVlZrUlZGRlFpOTNVMEp0YWtOQ2JEUmhRbXhIYURCa1NFSjZUMms0ZGxveWJEQmhTRlpwVEcxT2RtSlRPWHBoVjJSNlpFYzVlUXBhVXpGcVlqSTFiV0l6U25SWlZ6VnFXbE01YkdWSVVubGFWekZzWWtocmRGcEhSblZhTWxaNVlqTldla3hZUWpGWmJYaHdXWGt4ZG1GWFVtcE1WMHBzQ2xsWFRuWmlhVGgxV2pKc01HRklWbWxNTTJSMlkyMTBiV0pIT1ROamVUbHNaVWhTZVZwWE1XeGlTR3QwV2tkR2RWb3lWbmxpTTFaNlRGYzVjRnBIVFhRS1dXMVdhRmt5T1hWTWJteDBZa1ZDZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QxRlpTMHQzV1VKQ1FVZEVkbnBCUWtGUlVYSmhTRkl3WTBoTk5ncE1lVGt3WWpKMGJHSnBOV2haTTFKd1lqSTFla3h0WkhCa1IyZ3hXVzVXZWxwWVNtcGlNalV3V2xjMU1FeHRUblppVkVGbVFtZHZja0puUlVWQldVOHZDazFCUlVOQ1FrWXpZak5LY2xwdGVIWmtNVGxyWVZoT2QxbFlVbXBoUkVFeVFtZHZja0puUlVWQldVOHZUVUZGUkVKRGFHaGFhbU0wVGxkSk1scEVUbWtLVFVkYWFFMUhUWGRaVjBWNFRYcEJNVnB0Um14YVZHUnFXbFJaZDAxNldteFBSMUUxVFVkTk1FMURNRWREYVhOSFFWRlJRbWMzT0hkQlVWRkZTREJXTkFwa1NFcHNZbGRXYzJWVFFtdFpWelZ1V2xoS2RtUllUV2RVTUd4RlVYbENhVnBYUm1waU1qUjNVMUZaUzB0M1dVSkNRVWRFZG5wQlFrSlJVVGRqTW14dUNtTXpVblpqYlZWMFdUSTVkVnB0T1hsaVYwWjFXVEpWZGxwWWFEQmpiVlowV2xkNE5VeFhVbWhpYldSc1kyMDVNV041TVhka1YwcHpZVmROZEdJeWJHc0tXWGt4YVZwWFJtcGlNalIzU0ZGWlMwdDNXVUpDUVVkRWRucEJRa0puVVZCamJWWnRZM2s1YjFwWFJtdGplVGwwV1Zkc2RVMUVjMGREYVhOSFFWRlJRZ3BuTnpoM1FWRm5SVXhSZDNKaFNGSXdZMGhOTmt4NU9UQmlNblJzWW1rMWFGa3pVbkJpTWpWNlRHMWtjR1JIYURGWmJsWjZXbGhLYW1JeU5UQmFWelV3Q2t4dFRuWmlWRU5DY0dkWlMwdDNXVUpDUVVkRWRucEJRa05SVTBKc2QzbENiRWRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVlbUZYWkhvS1pFYzVlVnBUTVdwaU1qVnRZak5LZEZsWE5XcGFVemxzWlVoU2VWcFhNV3hpU0d0MFdrZEdkVm95Vm5saU0xWjZURmhDTVZsdGVIQlplVEYyWVZkU2FncE1WMHBzV1ZkT2RtSnBPSFZhTW13d1lVaFdhVXd6WkhaamJYUnRZa2M1TTJONU9XeGxTRko1V2xjeGJHSklhM1JhUjBaMVdqSldlV0l6Vm5wTVZ6bHdDbHBIVFhSWmJWWm9XVEk1ZFV4dWJIUmlSVUo1V2xkYWVrd3lhR3haVjFKNlRESXhhR0ZYTkhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUTJkUmNVUkRhR2dLV21wak5FNVhTVEphUkU1cFRVZGFhRTFIVFhkWlYwVjRUWHBCTVZwdFJteGFWR1JxV2xSWmQwMTZXbXhQUjFFMVRVZE5NRTFDTUVkRGFYTkhRVkZSUWdwbk56aDNRVkZ6UlVSM2QwNWFNbXd3WVVoV2FVeFhhSFpqTTFKc1drUkNaVUpuYjNKQ1owVkZRVmxQTDAxQlJVMUNSa0ZOVkcxb01HUklRbnBQYVRoMkNsb3liREJoU0ZacFRHMU9kbUpUT1hwaFYyUjZaRWM1ZVZwVE1XcGlNalZ0WWpOS2RGbFhOV3BhVXpsc1pVaFNlVnBYTVd4aVNHdDBXa2RHZFZveVZua0tZak5XZWt4WVFqRlpiWGh3V1hreGRtRlhVbXBNVjBwc1dWZE9kbUpxUVRSQ1oyOXlRbWRGUlVGWlR5OU5RVVZPUWtOdlRVdEhSbTFPZW1jeFdXcGFhd3BOTWtsM1dtMUZkMWw2UW1oWlZFVjZUVVJXYlZsWFZteE9NazVzVG1wQmVrNXRWVFJhUkd0M1dYcFJkMGgzV1V0TGQxbENRa0ZIUkhaNlFVSkVaMUZTQ2tSQk9YbGFWMXA2VERKb2JGbFhVbnBNTWpGb1lWYzBkMGRSV1V0TGQxbENRa0ZIUkhaNlFVSkVkMUZNUkVGck1rMTZTVEZQVkZrMFQxUmpkMDUzV1VzS1MzZFpRa0pCUjBSMmVrRkNSVUZSY0VSRFpHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaak1teHVZek5TZG1OdFZYUlpNamwxV20wNWVRcGlWMFoxV1RKVmQwZFJXVXRMZDFsQ1FrRkhSSFo2UVVKRlVWRk1SRUZyZUUxNlJUUk5SRkV4VG1wTmQyZGhXVWREYVhOSFFWRlJRbWMzT0hkQlVrbEZDbWRhWTAxbldsSnZaRWhTZDJONmIzWk1NbVJ3WkVkb01WbHBOV3BpTWpCMll6SnNibU16VW5aamJWVjBXVEk1ZFZwdE9YbGlWMFoxV1RKVmRscFlhREFLWTIxV2RGcFhlRFZNVjFKb1ltMWtiR050T1RGamVURjNaRmRLYzJGWFRYUmlNbXhyV1hreGFWcFhSbXBpTWpSMlRHMWtjR1JIYURGWmFUa3pZak5LY2dwYWJYaDJaRE5OZGxwWWFEQmpiVlowV2xkNE5VeFhVbWhpYldSc1kyMDVNV041TVhaaFYxSnFURmRLYkZsWFRuWmlhVFUxWWxkNFFXTnRWbTFqZVRsdkNscFhSbXRqZVRsMFdWZHNkVTFFWjBkRGFYTkhRVkZSUW1jM09IZEJVazFGUzJkM2IxbFhXVE5QUkZacFRtMVJlbGxxUW0xWlZFSnFUVWRHYUUxVVRYY0tUbGRhYUZwWFZUTlpNbFV5VFVSTk1scFVhR3RQVkVKcVRrUkJhRUpuYjNKQ1owVkZRVmxQTDAxQlJWVkNRazFOUlZoa2RtTnRkRzFpUnpreldESlNjQXBqTTBKb1pFZE9iMDFKUjBKQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtoTlRXTlhhREJrU0VKNlQyazRkbG95YkRCaFNGWnBURzFPZG1KVE9YcGhWMlI2Q21SSE9YbGFVekZxWWpJMWJXSXpTblJaVnpWcVdsTTViR1ZJVW5sYVZ6RnNZa2hyZEZwSFJuVmFNbFo1WWpOV2VreFlRakZaYlhod1dYa3hkbUZYVW1vS1RGZEtiRmxYVG5aaWFUbG9XVE5TY0dJeU5YcE1NMG94WW01TmRrNVVWWHBOZW1Nd1RWUlJOVTU1T1doa1NGSnNZbGhDTUdONU9IaE5TVWRMUW1kdmNncENaMFZGUVdSYU5VRm5VVU5DU0hkRlpXZENORUZJV1VFelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdDamR2TkVGQlFVZEtVM1JIVkVOM1FVRkNRVTFCVW5wQ1JrRnBRa05CTkdwYVVWQTBRM2ROYVZkdlpWTTNWMDFYTkRaUmEwazBaVGRQYzA1SU0zbFdhR1lLTlhka1FuWm5TV2hCVUVwWmVHUnphVGxPY1U5WVZscHpSVlYwUTNWd09HMHhiUzh5ZWtjek9VWlVSMnhuUlRCTmIzSkVSazFCYjBkRFEzRkhVMDAwT1FwQ1FVMUVRVEpuUVUxSFZVTk5SVmxYVW5kSk5WRktaVTkzVGtOMVZqUjBibG93YmpWUlRteFZiRkF3UW5SWU5WWXlXbFJSVEhGalVXSlhkRzVsUXpkMENreHdkR2xaWjNJd1dqWXlWVVJSU1hoQlR6WkpkRmhCU0N0ellscGpjMkpxTURoNGNqTkhRWEJOTm1ocWRubFVRV3d6T1hCVE0xa3pjMXAzUVhvNGJHWUtVVVJJVGt3MFpVRk1SVzh4YUdWQldWWm5QVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0="}]}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "oM/HEnHW4njlfNMy/5V8P3BD/do1TEy7GQow1W76Ab8="}, "signature": "MEQCIFOpaXKWvvBDwThDjTHX7tFF8liRoSxLZIsSeoUM/6D4AiBxV9/RnTMMw1t6nniX0rCuwrf8Vh+feLFu99m4ir+3yA=="}}

0 commit comments

Comments
 (0)