Skip to content

Commit b4f6f54

Browse files
committed
Raise VerificationError on malformed checkpoint text
LogCheckpoint.from_text parsed the log size with int() and the root hash with base64.b64decode() without guarding either call. A checkpoint whose log-size line isn't an integer, or whose root-hash line isn't valid base64, made those calls raise ValueError / binascii.Error. These are reached during bundle verification (verify_checkpoint), where callers only expect VerificationError, so a malformed bundle surfaced an unexpected exception type instead of a clean verification failure. Guard both conversions and raise VerificationError, matching how the rest of the checkpoint parser reports malformed input. Adds a unit test. Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
1 parent d4c83ab commit b4f6f54

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

sigstore/_internal/rekor/checkpoint.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import annotations
2020

2121
import base64
22+
import binascii
2223
import re
2324
import struct
2425
import typing
@@ -80,8 +81,15 @@ def from_text(cls, text: str) -> LogCheckpoint:
8081
if len(origin) == 0:
8182
raise VerificationError("malformed LogCheckpoint: empty origin")
8283

83-
log_size = int(lines[1])
84-
root_hash = base64.b64decode(lines[2]).hex()
84+
try:
85+
log_size = int(lines[1])
86+
except ValueError:
87+
raise VerificationError("malformed LogCheckpoint: invalid log size")
88+
89+
try:
90+
root_hash = base64.b64decode(lines[2]).hex()
91+
except binascii.Error:
92+
raise VerificationError("malformed LogCheckpoint: invalid root hash")
8593

8694
return LogCheckpoint(
8795
origin=origin,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2025 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import base64
16+
17+
import pytest
18+
19+
from sigstore._internal.rekor.checkpoint import LogCheckpoint
20+
from sigstore.errors import VerificationError
21+
22+
23+
class TestLogCheckpoint:
24+
def test_from_text_roundtrip(self):
25+
root_hash = base64.b64encode(b"\x00" * 32).decode()
26+
text = f"rekor.example - 123\n42\n{root_hash}\nTimestamp: 1\n"
27+
checkpoint = LogCheckpoint.from_text(text)
28+
assert checkpoint.origin == "rekor.example - 123"
29+
assert checkpoint.log_size == 42
30+
assert checkpoint.log_hash == (b"\x00" * 32).hex()
31+
assert checkpoint.other_content == ["Timestamp: 1"]
32+
33+
def test_from_text_too_few_lines(self):
34+
with pytest.raises(VerificationError, match="too few items"):
35+
LogCheckpoint.from_text("rekor.example - 123\n42\n")
36+
37+
def test_from_text_invalid_log_size(self):
38+
# A non-integer log size must surface as a VerificationError rather than
39+
# leaking a raw ValueError to callers that only expect VerificationError.
40+
root_hash = base64.b64encode(b"\x00" * 32).decode()
41+
with pytest.raises(VerificationError, match="invalid log size"):
42+
LogCheckpoint.from_text(f"rekor.example - 123\nNOTANINT\n{root_hash}\n")
43+
44+
def test_from_text_invalid_root_hash(self):
45+
# An undecodable base64 root hash must also surface as a VerificationError.
46+
with pytest.raises(VerificationError, match="invalid root hash"):
47+
LogCheckpoint.from_text("rekor.example - 123\n42\n!!!notbase64!!!\n")

0 commit comments

Comments
 (0)