-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle parsing checkpoints from a rekor entry
This does not do validation on the checkpoint yet Signed-off-by: Appu Goundan <[email protected]>
- Loading branch information
1 parent
7076d94
commit 52f7210
Showing
12 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
98 changes: 98 additions & 0 deletions
98
sigstore-java/src/main/java/dev/sigstore/rekor/client/Checkpoints.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright 2024 The Sigstore Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package dev.sigstore.rekor.client; | ||
|
||
import com.google.common.base.Splitter; | ||
import dev.sigstore.rekor.client.RekorEntry.Checkpoint; | ||
import dev.sigstore.rekor.client.RekorEntry.CheckpointSignature; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Base64; | ||
import java.util.List; | ||
import java.util.regex.Pattern; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* Checkpoint helper class to parse from a string in the format described in | ||
* https://github.com/transparency-dev/formats/blob/12bf59947efb7ae227c12f218b4740fb17a87e50/log/README.md | ||
*/ | ||
class Checkpoints { | ||
static Checkpoint from(String encoded) throws RekorParseException { | ||
var split = Splitter.on("\n\n").splitToList(encoded); | ||
if (split.size() != 2) { | ||
throw new RekorParseException( | ||
"Checkpoint must contain one blank line, delineating the header from the signature block"); | ||
} | ||
var header = split.get(0); | ||
var data = split.get(1); | ||
|
||
// note that the string actually contains \n literally, not newlines | ||
var headers = Splitter.on("\n").splitToList(header); | ||
if (headers.size() < 3) { | ||
throw new RekorParseException("Checkpoint header must contain at least 3 lines"); | ||
} | ||
|
||
var origin = headers.get(0); | ||
long size; | ||
try { | ||
size = Long.parseLong(headers.get(1)); | ||
} catch (NumberFormatException nfe) { | ||
throw new RekorParseException("Checkpoint header attribute size must be a number"); | ||
} | ||
var base64Hash = headers.get(2); | ||
// we don't care about any other headers after this | ||
|
||
if (data.length() == 0) { | ||
throw new RekorParseException("Checkpoint body must contain at least one signature"); | ||
} | ||
if (!data.endsWith("\n")) { | ||
throw new RekorParseException("Checkpoint signature section must end with newline"); | ||
} | ||
|
||
List<CheckpointSignature> signatures = new ArrayList<>(); | ||
for (String sig : data.lines().collect(Collectors.toList())) { | ||
signatures.add(sigFrom(sig)); | ||
} | ||
|
||
return ImmutableCheckpoint.builder() | ||
.origin(origin) | ||
.size(size) | ||
.base64Hash(base64Hash) | ||
.addAllSignatures(signatures) | ||
.build(); | ||
} | ||
|
||
static CheckpointSignature sigFrom(String signatureLine) throws RekorParseException { | ||
var sigBlock = Pattern.compile("\\u2014 (\\S+) (\\S+)"); | ||
var m = sigBlock.matcher(signatureLine); | ||
if (!m.find()) { | ||
// really shouldn't be getting here because we check | ||
throw new RekorParseException("Checkpoint signature was invalid or improperly formatted"); | ||
} | ||
var identity = m.group(1); | ||
var keySig = Base64.getDecoder().decode(m.group(2)); | ||
if (keySig.length < 5) { | ||
throw new RekorParseException("Checkpoint signature contains too few bytes"); | ||
} | ||
var keyHint = Arrays.copyOfRange(keySig, 0, 4); | ||
var signature = Arrays.copyOfRange(keySig, 4, keySig.length); | ||
return ImmutableCheckpointSignature.builder() | ||
.identity(identity) | ||
.keyHint(keyHint) | ||
.signature(signature) | ||
.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
sigstore-java/src/test/java/dev/sigstore/rekor/client/CheckpointsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* Copyright 2024 The Sigstore Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package dev.sigstore.rekor.client; | ||
|
||
import com.google.common.io.Resources; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Arrays; | ||
import java.util.Base64; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class CheckpointsTest { | ||
|
||
public static final String REKOR_PUB_KEYID = "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="; | ||
|
||
public String getResource(String filename) throws IOException { | ||
return Resources.toString( | ||
Resources.getResource("dev/sigstore/samples/checkpoints/" + filename), | ||
StandardCharsets.UTF_8); | ||
} | ||
|
||
@Test | ||
public void from_valid() throws Exception { | ||
var checkpoint = Checkpoints.from(getResource("valid.txt")); | ||
Assertions.assertEquals("rekor.sigstore.dev - 2605736670972794746", checkpoint.getOrigin()); | ||
Assertions.assertEquals(37795272, checkpoint.getSize()); | ||
Assertions.assertEquals( | ||
"60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4=", checkpoint.getBase64Hash()); | ||
|
||
var keyBytesHintExpected = | ||
Arrays.copyOfRange(Base64.getDecoder().decode(REKOR_PUB_KEYID), 0, 4); | ||
var sig = checkpoint.getSignatures().get(0); | ||
Assertions.assertEquals(1, checkpoint.getSignatures().size()); | ||
Assertions.assertEquals("rekor.sigstore.dev", sig.getIdentity()); | ||
Assertions.assertArrayEquals(keyBytesHintExpected, sig.getKeyHint()); | ||
Assertions.assertEquals( | ||
"MEYCIQCVZQfYdI9rogwhEGAVwhemHcyP3EzvRZHRVUAO8YiX+gIhAKB+9RSNH9fmN7CWqkBYjw24kiJwqlMbri+jpQzl+lKB", | ||
Base64.getEncoder().encodeToString(sig.getSignature())); | ||
} | ||
|
||
@Test | ||
public void from_validMultiSig() throws Exception { | ||
var checkpoint = Checkpoints.from(getResource("valid_multi_sig.txt")); | ||
Assertions.assertEquals("rekor.sigstore.dev - 2605736670972794746", checkpoint.getOrigin()); | ||
Assertions.assertEquals(37795272, checkpoint.getSize()); | ||
Assertions.assertEquals( | ||
"60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4=", checkpoint.getBase64Hash()); | ||
|
||
Assertions.assertEquals(2, checkpoint.getSignatures().size()); | ||
var keyBytesHintExpected = | ||
Arrays.copyOfRange(Base64.getDecoder().decode(REKOR_PUB_KEYID), 0, 4); | ||
|
||
var sig1 = checkpoint.getSignatures().get(0); | ||
Assertions.assertEquals("rekor.sigstore.dev", sig1.getIdentity()); | ||
Assertions.assertArrayEquals(keyBytesHintExpected, sig1.getKeyHint()); | ||
Assertions.assertEquals( | ||
"MEYCIQCVZQfYdI9rogwhEGAVwhemHcyP3EzvRZHRVUAO8YiX+gIhAKB+9RSNH9fmN7CWqkBYjw24kiJwqlMbri+jpQzl+lKB", | ||
Base64.getEncoder().encodeToString(sig1.getSignature())); | ||
|
||
var sig2 = checkpoint.getSignatures().get(1); | ||
Assertions.assertEquals("bob.loblaw.dev", sig2.getIdentity()); | ||
Assertions.assertArrayEquals(keyBytesHintExpected, sig2.getKeyHint()); | ||
Assertions.assertEquals( | ||
"MEYCIQCVZQfYdI9rogwhEGAVwhGmHcyP3EzvRZHRVUAO8YiX+gIhAKB+9RSNH9fmN7CWqkBYjw24kiJwqlMbri+jpQzl+lKB", | ||
Base64.getEncoder().encodeToString(sig2.getSignature())); | ||
} | ||
|
||
@Test | ||
public void from_noSeparator() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_header_body_separator.txt"))); | ||
Assertions.assertEquals( | ||
"Checkpoint must contain one blank line, delineating the header from the signature block", | ||
ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_notEnoughHeaders() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_header_count.txt"))); | ||
Assertions.assertEquals("Checkpoint header must contain at least 3 lines", ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_notANumber() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_not_a_number.txt"))); | ||
Assertions.assertEquals("Checkpoint header attribute size must be a number", ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_noSignatures() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_no_signatures.txt"))); | ||
Assertions.assertEquals("Checkpoint body must contain at least one signature", ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_noNewlineAfterSignatures() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_no_newline_after_signature.txt"))); | ||
Assertions.assertEquals("Checkpoint signature section must end with newline", ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_signatureFormatInvalid() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_signature_format_invalid.txt"))); | ||
Assertions.assertEquals( | ||
"Checkpoint signature was invalid or improperly formatted", ex.getMessage()); | ||
} | ||
|
||
@Test | ||
public void from_signatureLengthInsufficient() throws Exception { | ||
var ex = | ||
Assertions.assertThrows( | ||
RekorParseException.class, | ||
() -> Checkpoints.from(getResource("error_signature_length_insufficient.txt"))); | ||
Assertions.assertEquals("Checkpoint signature contains too few bytes", ex.getMessage()); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...-java/src/test/resources/dev/sigstore/samples/checkpoints/error_header_body_separator.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
4 changes: 4 additions & 0 deletions
4
sigstore-java/src/test/resources/dev/sigstore/samples/checkpoints/error_header_count.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
|
||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
6 changes: 6 additions & 0 deletions
6
.../src/test/resources/dev/sigstore/samples/checkpoints/error_no_newline_after_signature.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
5 changes: 5 additions & 0 deletions
5
sigstore-java/src/test/resources/dev/sigstore/samples/checkpoints/error_no_signatures.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
6 changes: 6 additions & 0 deletions
6
sigstore-java/src/test/resources/dev/sigstore/samples/checkpoints/error_not_a_number.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
abcdefg | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
6 changes: 6 additions & 0 deletions
6
...va/src/test/resources/dev/sigstore/samples/checkpoints/error_signature_format_invalid.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
6 changes: 6 additions & 0 deletions
6
...c/test/resources/dev/sigstore/samples/checkpoints/error_signature_length_insufficient.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
— rekor.sigstore.dev wNI9aj |
6 changes: 6 additions & 0 deletions
6
sigstore-java/src/test/resources/dev/sigstore/samples/checkpoints/valid.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |
7 changes: 7 additions & 0 deletions
7
sigstore-java/src/test/resources/dev/sigstore/samples/checkpoints/valid_multi_sig.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
rekor.sigstore.dev - 2605736670972794746 | ||
37795272 | ||
60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4= | ||
Timestamp: 1697034484441201852 | ||
|
||
— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== | ||
— bob.loblaw.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIRph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ== |