mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-29 02:12:36 +02:00
parent a19e341378
author Alexis Métaireau <alexis@freedom.press> 1739380992 +0100 committer Alexis Métaireau <alexis@freedom.press> 1744646096 +0200 gpgsig -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRFQpTG/4uXFqX2QanGXHqJqP/FbgUCZ/0v0AAKCRDGXHqJqP/F bmA8AP9QZjs6bSmxvmzvYvwJa8wYIo6OsdUyQdoZ4kAMd5X7XwEA+WIbuxU/o2bG KisPBI+N8LSwwIke3eNS+ne6Vil7qwg= =XCYF -----END PGP SIGNATURE----- (WIP) Add tests
This commit is contained in:
parent
9035620b80
commit
b6fa4fa35b
18 changed files with 513 additions and 47 deletions
|
@ -254,7 +254,12 @@ def get_local_image_digest(image: str) -> str:
|
||||||
raise errors.MultipleImagesFoundException(
|
raise errors.MultipleImagesFoundException(
|
||||||
f"Expected a single line of output, got {len(lines)} lines"
|
f"Expected a single line of output, got {len(lines)} lines"
|
||||||
)
|
)
|
||||||
return lines[0].replace("sha256:", "")
|
image_digest = lines[0].replace("sha256:", "")
|
||||||
|
if not image_digest:
|
||||||
|
raise errors.ImageNotPresentException(
|
||||||
|
f"The image {image} does not exist locally"
|
||||||
|
)
|
||||||
|
return image_digest
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
raise errors.ImageNotPresentException(
|
raise errors.ImageNotPresentException(
|
||||||
f"The image {image} does not exist locally"
|
f"The image {image} does not exist locally"
|
||||||
|
|
|
@ -96,23 +96,21 @@ class Container(IsolationProvider):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def install() -> bool:
|
def install() -> bool:
|
||||||
"""Check if an update is available and install it if necessary."""
|
"""Check if an update is available and install it if necessary."""
|
||||||
# XXX Do this only if users have optted in to auto-updates
|
# XXX Do this only if users have opted in to auto-updates
|
||||||
|
if False: # Comment this for now, just as an exemple of this can be implemented
|
||||||
# # Load the image tarball into the container runtime.
|
# # Load the image tarball into the container runtime.
|
||||||
update_available, image_digest = updater.is_update_available(
|
update_available, image_digest = updater.is_update_available(
|
||||||
container_utils.CONTAINER_NAME
|
container_utils.CONTAINER_NAME
|
||||||
)
|
|
||||||
if update_available and image_digest:
|
|
||||||
updater.upgrade_container_image(
|
|
||||||
container_utils.CONTAINER_NAME,
|
|
||||||
image_digest,
|
|
||||||
updater.DEFAULT_PUBKEY_LOCATION,
|
|
||||||
)
|
)
|
||||||
|
if update_available and image_digest:
|
||||||
|
updater.upgrade_container_image(
|
||||||
|
container_utils.CONTAINER_NAME,
|
||||||
|
image_digest,
|
||||||
|
updater.DEFAULT_PUBKEY_LOCATION,
|
||||||
|
)
|
||||||
for tag in old_tags:
|
for tag in old_tags:
|
||||||
tag = container_utils.CONTAINER_NAME + ":" + tag
|
tag = container_utils.CONTAINER_NAME + ":" + tag
|
||||||
container_utils.delete_image_tag(tag)
|
container_utils.delete_image_tag(tag)
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
updater.verify_local_image(
|
updater.verify_local_image(
|
||||||
container_utils.CONTAINER_NAME, updater.DEFAULT_PUBKEY_LOCATION
|
container_utils.CONTAINER_NAME, updater.DEFAULT_PUBKEY_LOCATION
|
||||||
|
@ -207,7 +205,6 @@ class Container(IsolationProvider):
|
||||||
updater.verify_local_image(
|
updater.verify_local_image(
|
||||||
container_utils.CONTAINER_NAME,
|
container_utils.CONTAINER_NAME,
|
||||||
updater.DEFAULT_PUBKEY_LOCATION,
|
updater.DEFAULT_PUBKEY_LOCATION,
|
||||||
image_digest,
|
|
||||||
)
|
)
|
||||||
security_args = self.get_runtime_security_args()
|
security_args = self.get_runtime_security_args()
|
||||||
debug_args = []
|
debug_args = []
|
||||||
|
|
|
@ -33,7 +33,7 @@ LAST_LOG_INDEX = SIGNATURES_PATH / "last_log_index"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"verify_signature",
|
"verify_signature",
|
||||||
"load_signatures",
|
"load_and_verify_signatures",
|
||||||
"store_signatures",
|
"store_signatures",
|
||||||
"verify_offline_image_signature",
|
"verify_offline_image_signature",
|
||||||
]
|
]
|
||||||
|
@ -61,18 +61,22 @@ def signature_to_bundle(sig: Dict) -> Dict:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def verify_signature(signature: dict, image_digest: str, pubkey: str) -> bool:
|
def verify_signature(signature: dict, image_digest: str, pubkey: str | Path) -> bool:
|
||||||
"""Verify a signature against a given public key"""
|
"""Verify a signature against a given public key"""
|
||||||
# XXX - Also verfy the identity/docker-reference field against the expected value
|
# XXX - Also verfy the identity/docker-reference field against the expected value
|
||||||
# e.g. ghcr.io/freedomofpress/dangerzone/dangerzone
|
# e.g. ghcr.io/freedomofpress/dangerzone/dangerzone
|
||||||
|
|
||||||
cosign.ensure_installed()
|
cosign.ensure_installed()
|
||||||
signature_bundle = signature_to_bundle(signature)
|
signature_bundle = signature_to_bundle(signature)
|
||||||
|
try:
|
||||||
payload_bytes = b64decode(signature_bundle["Payload"])
|
payload_bytes = b64decode(signature_bundle["Payload"])
|
||||||
payload_digest = json.loads(payload_bytes)["critical"]["image"][
|
payload_digest = json.loads(payload_bytes)["critical"]["image"][
|
||||||
"docker-manifest-digest"
|
"docker-manifest-digest"
|
||||||
]
|
]
|
||||||
|
except Exception as e:
|
||||||
|
raise errors.SignatureVerificationError(
|
||||||
|
f"Unable to extract the payload digest from the signature: {e}"
|
||||||
|
)
|
||||||
if payload_digest != f"sha256:{image_digest}":
|
if payload_digest != f"sha256:{image_digest}":
|
||||||
raise errors.SignatureMismatch(
|
raise errors.SignatureMismatch(
|
||||||
f"The signature does not match the image digest ({payload_digest}, {image_digest})"
|
f"The signature does not match the image digest ({payload_digest}, {image_digest})"
|
||||||
|
@ -88,11 +92,14 @@ def verify_signature(signature: dict, image_digest: str, pubkey: str) -> bool:
|
||||||
payload_file.write(payload_bytes)
|
payload_file.write(payload_bytes)
|
||||||
payload_file.flush()
|
payload_file.flush()
|
||||||
|
|
||||||
|
if isinstance(pubkey, str):
|
||||||
|
pubkey = Path(pubkey)
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"cosign",
|
"cosign",
|
||||||
"verify-blob",
|
"verify-blob",
|
||||||
"--key",
|
"--key",
|
||||||
pubkey,
|
str(pubkey.absolute()),
|
||||||
"--bundle",
|
"--bundle",
|
||||||
signature_file.name,
|
signature_file.name,
|
||||||
payload_file.name,
|
payload_file.name,
|
||||||
|
@ -143,9 +150,14 @@ def verify_signatures(
|
||||||
image_digest: str,
|
image_digest: str,
|
||||||
pubkey: str,
|
pubkey: str,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
if len(signatures) < 1:
|
||||||
|
raise errors.SignatureVerificationError("No signatures found")
|
||||||
|
|
||||||
for signature in signatures:
|
for signature in signatures:
|
||||||
if not verify_signature(signature, image_digest, pubkey):
|
if not verify_signature(signature, image_digest, pubkey):
|
||||||
raise errors.SignatureVerificationError()
|
msg = f"Unable to verify signature for {image_digest} with pubkey {pubkey}"
|
||||||
|
raise errors.SignatureVerificationError(msg)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,9 +171,14 @@ def get_last_log_index() -> int:
|
||||||
|
|
||||||
|
|
||||||
def get_log_index_from_signatures(signatures: List[Dict]) -> int:
|
def get_log_index_from_signatures(signatures: List[Dict]) -> int:
|
||||||
return reduce(
|
def _reducer(accumulator: int, signature: Dict) -> int:
|
||||||
lambda acc, sig: max(acc, sig["Bundle"]["Payload"]["logIndex"]), signatures, 0
|
try:
|
||||||
)
|
logIndex = int(signature["Bundle"]["Payload"]["logIndex"])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
return accumulator
|
||||||
|
return max(accumulator, logIndex)
|
||||||
|
|
||||||
|
return reduce(_reducer, signatures, 0)
|
||||||
|
|
||||||
|
|
||||||
def write_log_index(log_index: int) -> None:
|
def write_log_index(log_index: int) -> None:
|
||||||
|
@ -297,13 +314,21 @@ def get_file_digest(file: Optional[str] = None, content: Optional[bytes] = None)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def load_signatures(image_digest: str, pubkey: str) -> List[Dict]:
|
def load_and_verify_signatures(
|
||||||
|
image_digest: str,
|
||||||
|
pubkey: str,
|
||||||
|
bypass_verification: bool = False,
|
||||||
|
signatures_path: Optional[Path] = None,
|
||||||
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Load signatures from the local filesystem
|
Load signatures from the local filesystem
|
||||||
|
|
||||||
See store_signatures() for the expected format.
|
See store_signatures() for the expected format.
|
||||||
"""
|
"""
|
||||||
pubkey_signatures = SIGNATURES_PATH / get_file_digest(pubkey)
|
if not signatures_path:
|
||||||
|
signatures_path = SIGNATURES_PATH
|
||||||
|
|
||||||
|
pubkey_signatures = signatures_path / get_file_digest(pubkey)
|
||||||
if not pubkey_signatures.exists():
|
if not pubkey_signatures.exists():
|
||||||
msg = (
|
msg = (
|
||||||
f"Cannot find a '{pubkey_signatures}' folder."
|
f"Cannot find a '{pubkey_signatures}' folder."
|
||||||
|
@ -313,7 +338,12 @@ def load_signatures(image_digest: str, pubkey: str) -> List[Dict]:
|
||||||
|
|
||||||
with open(pubkey_signatures / f"{image_digest}.json") as f:
|
with open(pubkey_signatures / f"{image_digest}.json") as f:
|
||||||
log.debug("Loading signatures from %s", f.name)
|
log.debug("Loading signatures from %s", f.name)
|
||||||
return json.load(f)
|
signatures = json.load(f)
|
||||||
|
|
||||||
|
if not bypass_verification:
|
||||||
|
verify_signatures(signatures, image_digest, pubkey)
|
||||||
|
|
||||||
|
return signatures
|
||||||
|
|
||||||
|
|
||||||
def store_signatures(signatures: list[Dict], image_digest: str, pubkey: str) -> None:
|
def store_signatures(signatures: list[Dict], image_digest: str, pubkey: str) -> None:
|
||||||
|
@ -375,32 +405,27 @@ def verify_local_image(image: str, pubkey: str) -> bool:
|
||||||
raise errors.ImageNotFound(f"The image {image} does not exist locally")
|
raise errors.ImageNotFound(f"The image {image} does not exist locally")
|
||||||
|
|
||||||
log.debug(f"Image digest: {image_digest}")
|
log.debug(f"Image digest: {image_digest}")
|
||||||
signatures = load_signatures(image_digest, pubkey)
|
load_and_verify_signatures(image_digest, pubkey)
|
||||||
if len(signatures) < 1:
|
|
||||||
raise errors.LocalSignatureNotFound("No signatures found")
|
|
||||||
|
|
||||||
for signature in signatures:
|
|
||||||
if not verify_signature(signature, image_digest, pubkey):
|
|
||||||
msg = f"Unable to verify signature for {image} with pubkey {pubkey}"
|
|
||||||
raise errors.SignatureVerificationError(msg)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_remote_signatures(image: str, digest: str) -> List[Dict]:
|
def get_remote_signatures(image: str, digest: str) -> List[Dict]:
|
||||||
"""Retrieve the signatures from the registry, via `cosign download`."""
|
"""Retrieve the signatures from the registry, via `cosign download signatures`."""
|
||||||
cosign.ensure_installed()
|
cosign.ensure_installed()
|
||||||
|
|
||||||
# XXX: try/catch here
|
try:
|
||||||
process = subprocess.run(
|
process = subprocess.run(
|
||||||
["cosign", "download", "signature", f"{image}@sha256:{digest}"],
|
["cosign", "download", "signature", f"{image}@sha256:{digest}"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise errors.NoRemoteSignatures(e)
|
||||||
|
|
||||||
# XXX: Check the output first.
|
|
||||||
# Remove the last return, split on newlines, convert from JSON
|
# Remove the last return, split on newlines, convert from JSON
|
||||||
signatures_raw = process.stdout.decode("utf-8").strip().split("\n")
|
signatures_raw = process.stdout.decode("utf-8").strip().split("\n")
|
||||||
signatures = list(map(json.loads, signatures_raw))
|
signatures = list(map(json.loads, signatures_raw))
|
||||||
|
breakpoint()
|
||||||
if len(signatures) < 1:
|
if len(signatures) < 1:
|
||||||
raise errors.NoRemoteSignatures("No signatures found for the image")
|
raise errors.NoRemoteSignatures("No signatures found for the image")
|
||||||
return signatures
|
return signatures
|
||||||
|
@ -413,8 +438,8 @@ def prepare_airgapped_archive(image_name: str, destination: str) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
cosign.ensure_installed()
|
cosign.ensure_installed()
|
||||||
# Get the image from the registry
|
|
||||||
|
|
||||||
|
# Get the image from the registry
|
||||||
with TemporaryDirectory() as tmpdir:
|
with TemporaryDirectory() as tmpdir:
|
||||||
msg = f"Downloading image {image_name}. \nIt might take a while."
|
msg = f"Downloading image {image_name}. \nIt might take a while."
|
||||||
log.info(msg)
|
log.info(msg)
|
||||||
|
|
7
tests/assets/signatures/README.md
Normal file
7
tests/assets/signatures/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
This folder contains signature-folders used for the testing the signatures implementation.
|
||||||
|
|
||||||
|
The following folders are used:
|
||||||
|
|
||||||
|
- `valid`: this folder contains signatures which should be considered valid and generated with the key available at `tests/assets/test.pub.key`
|
||||||
|
- `invalid`: this folder contains signatures which should be considered invalid, because their format doesn't match the expected one. e.g. it uses plain text instead of base64-encoded text.
|
||||||
|
- `tempered`: This folder contain signatures which have been tempered-with. The goal is to have signatures that looks valid, but actually aren't.
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "Invalid base64 signature",
|
||||||
|
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjoxOWU4ZWFjZDc1ODc5ZDA1ZjY2MjFjMmVhOGRkOTU1ZTY4ZWUzZTA3YjQxYjlkNTNmNGM4Y2M5OTI5YTY4YTY3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "MEUCIC9oXH9VVP96frVOmDw704FBqMN/Bpm2RMdTm6BtSwL/AiEA6mCIjhV65fYuy4CwjsIzQHi/oW6IBwtd6oCvN2dI6HQ=",
|
||||||
|
"Payload": {
|
||||||
|
"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJmMjEwNDJjY2RjOGU0ZjA1ZGEzNmE5ZjU4ODg5MmFlZGRlMzYzZTQ2ZWNjZGZjM2MyNzAyMTkwZDU0YTdmZmVlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNWaTJGUFI3Mjl1aHAvY3JFdUNTOW9yQzRhMnV0OHN3dDdTUnZXYUVSTGp3SWhBSlM1dzU3MHhsQnJsM2Nhd1Y1akQ1dk85RGh1dkNrdCtzOXJLdGc2NzVKQSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=",
|
||||||
|
"integratedTime": 1738752154,
|
||||||
|
"logIndex": 168898587,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "MEQCICi2AOAJbS1k3334VMSo+qxaI4f5VoNnuVExZ4tfIu7rAiAiwuKdo8rGfFMGMLSFSQvoLF3JuwFy4JtNW6kQlwH7vg==",
|
||||||
|
"Payload": "Invalid base64 payload",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "MEUCIEvx6NtFeAag9TplqMLjVczT/tC6lpKe9SnrxbehBlxfAiEA07BE3f5JsMLsUsmHD58D6GaZr2yz+yQ66Os2ps8oKz8=",
|
||||||
|
"Payload": {
|
||||||
|
"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4YmJmNGRiNjBmMmExM2IyNjI2NTI3MzljNWM5ZTYwNjNiMDYyNjVlODU1Zjc3MTdjMTdlYWY4YzViZTQyYWUyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQ2kyQU9BSmJTMWszMzM0Vk1TbytxeGFJNGY1Vm9ObnVWRXhaNHRmSXU3ckFpQWl3dUtkbzhyR2ZGTUdNTFNGU1F2b0xGM0p1d0Z5NEp0Tlc2a1Fsd0g3dmc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=",
|
||||||
|
"integratedTime": 1738859497,
|
||||||
|
"logIndex": 169356501,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "MEQCIDJxvB7lBU+VNYBD0xw/3Bi8wY7GPJ2fBP7mUFbguApoAiAIpuQT+sgatOY6yXkkA8K/sM40d5/gt7jQywWPbq5+iw==",
|
||||||
|
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hcHlyZ2lvL2RhbmdlcnpvbmUvZGFuZ2Vyem9uZSJ9LCJpbWFnZSI6eyJkb2NrZXItbWFuaWZlc3QtZGlnZXN0Ijoic2hhMjU2OjRkYTQ0MTIzNWU4NGU5MzUxODc3ODgyN2E1YzU3NDVkNTMyZDdhNDA3OTg4NmUxNjQ3OTI0YmVlN2VmMWMxNGQifSwidHlwZSI6ImNvc2lnbiBjb250YWluZXIgaW1hZ2Ugc2lnbmF0dXJlIn0sIm9wdGlvbmFsIjpudWxsfQ==",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "Invalid signed entry timestamp",
|
||||||
|
"Payload": {
|
||||||
|
"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIyMGE2ZDU1NTk4Y2U0NjU3NWZkZjViZGU3YzhhYWE2YTU2ZjZlMGRmOWNiYTY1MTJhMDAxODhjMTU1NGIzYjE3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJREp4dkI3bEJVK1ZOWUJEMHh3LzNCaTh3WTdHUEoyZkJQN21VRmJndUFwb0FpQUlwdVFUK3NnYXRPWTZ5WGtrQThLL3NNNDBkNS9ndDdqUXl3V1BicTUraXc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=",
|
||||||
|
"integratedTime": 1738688492,
|
||||||
|
"logIndex": 168652066,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "MEUCIQC2WlJH+B8VuX1c6i4sDwEGEZc53hXUD6/ds9TMJ3HrfwIgCxSnrNYRD2c8XENqfqc+Ik1gx0DK9kPNsn/Lt8V/dCo=",
|
||||||
|
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1Njo3YjIxZGJkZWJmZmVkODU1NjIxZGZjZGVhYTUyMjMwZGM2NTY2OTk3Zjg1MmVmNWQ2MmIwMzM4YjQ2Nzk2ZTAxIn0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "MEYCIQDn04gOHqiZcwUO+NVV9+29+abu6O/k1ve9zatJ3gVu9QIhAJL3E+mqVPdMPfMSdhHt2XDQsYzfRDDJNJEABQlbV3Jg",
|
||||||
|
"Payload": {
|
||||||
|
"body": "Invalid bundle payload body",
|
||||||
|
"integratedTime": 1738862352,
|
||||||
|
"logIndex": 169369149,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "MAIhAJWLYU9Hvb26Gn9ysS4JL2isLhra63yzC3tJG9ZoREuPAiEAlLnDnvTGUGuXdxrBXmMPm870OG68KS36z2sq2DrvkkAK",
|
||||||
|
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjoxOWU4ZWFjZDc1ODc5ZDA1ZjY2MjFjMmVhOGRkOTU1ZTY4ZWUzZTA3YjQxYjlkNTNmNGM4Y2M5OTI5YTY4YTY3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "MEUCIC9oXH9VVP96frVOmDw704FBqMN/Bpm2RMdTm6BtSwL/AiEA6mCIjhV65fYuy4CwjsIzQHi/oW6IBwtd6oCvN2dI6HQ=",
|
||||||
|
"Payload": {
|
||||||
|
"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJmMjEwNDJjY2RjOGU0ZjA1ZGEzNmE5ZjU4ODg5MmFlZGRlMzYzZTQ2ZWNjZGZjM2MyNzAyMTkwZDU0YTdmZmVlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNWaTJGUFI3Mjl1aHAvY3JFdUNTOW9yQzRhMnV0OHN3dDdTUnZXYUVSTGp3SWhBSlM1dzU3MHhsQnJsM2Nhd1Y1akQ1dk85RGh1dkNrdCtzOXJLdGc2NzVKQSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=",
|
||||||
|
"integratedTime": 1738752154,
|
||||||
|
"logIndex": 168898587,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Base64Signature": "MEQCICi2AOAJbS1k3334VMSo+qxaI4f5VoNnuVExZ4tfIu7rAiAiwuKdo8rGfFMGMLSFSQvoLF3JuwFy4JtNW6kQlwH7vg==",
|
||||||
|
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9oNHh4MHIvZGFuZ2Vyem9uZS9kYW5nZXJ6b25lIn0sImltYWdlIjp7ImRvY2tlci1tYW5pZmVzdC1kaWdlc3QiOiJzaGEyNTY6MjIwYjUyMjAwZTNlNDdiMWI0MjAxMDY2N2ZjYWE5MzM4NjgxZTY0ZGQzZTM0YTM0ODczODY2Y2IwNTFkNjk0ZSJ9LCJ0eXBlIjoiY29zaWduIGNvbnRhaW5lciBpbWFnZSBzaWduYXR1cmUifSwib3B0aW9uYWwiOm51bGx9Cg==",
|
||||||
|
"Cert": null,
|
||||||
|
"Chain": null,
|
||||||
|
"Bundle": {
|
||||||
|
"SignedEntryTimestamp": "MEUCIEvx6NtFeAag9TplqMLjVczT/tC6lpKe9SnrxbehBlxfAiEA07BE3f5JsMLsUsmHD58D6GaZr2yz+yQ66Os2ps8oKz8=",
|
||||||
|
"Payload": {
|
||||||
|
"body": "eyJhcGlWZXJzaW9uIjoiNi42LjYiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4YmJmNGRiNjBmMmExM2IyNjI2NTI3MzljNWM5ZTYwNjNiMDYyNjVlODU1Zjc3MTdjMTdlYWY4YzViZTQyYWUyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQ2kyQU9BSmJTMWszMzM0Vk1TbytxeGFJNGY1Vm9ObnVWRXhaNHRmSXU3ckFpQWl3dUtkbzhyR2ZGTUdNTFNGU1F2b0xGM0p1d0Z5NEp0Tlc2a1Fsd0g3dmc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0K",
|
||||||
|
"integratedTime": 1738859497,
|
||||||
|
"logIndex": 169356501,
|
||||||
|
"logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RFC3161Timestamp": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
This folder contain signatures which have been tempered-with. The goal is to have signatures that looks valid, but actually aren't.
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Base64Signature": "MEYCIQCVi2FPR729uhp/crEuCS9orC4a2ut8swt7SRvWaERLjwIhAJS5w570xlBrl3cawV5jD5vO9DhuvCkt+s9rKtg675JA", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjoxOWU4ZWFjZDc1ODc5ZDA1ZjY2MjFjMmVhOGRkOTU1ZTY4ZWUzZTA3YjQxYjlkNTNmNGM4Y2M5OTI5YTY4YTY3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEUCIC9oXH9VVP96frVOmDw704FBqMN/Bpm2RMdTm6BtSwL/AiEA6mCIjhV65fYuy4CwjsIzQHi/oW6IBwtd6oCvN2dI6HQ=", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJmMjEwNDJjY2RjOGU0ZjA1ZGEzNmE5ZjU4ODg5MmFlZGRlMzYzZTQ2ZWNjZGZjM2MyNzAyMTkwZDU0YTdmZmVlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNWaTJGUFI3Mjl1aHAvY3JFdUNTOW9yQzRhMnV0OHN3dDdTUnZXYUVSTGp3SWhBSlM1dzU3MHhsQnJsM2Nhd1Y1akQ1dk85RGh1dkNrdCtzOXJLdGc2NzVKQSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1738752154, "logIndex": 168898587, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}]
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Base64Signature": "MEQCICi2AOAJbS1k3334VMSo+qxaI4f5VoNnuVExZ4tfIu7rAiAiwuKdo8rGfFMGMLSFSQvoLF3JuwFy4JtNW6kQlwH7vg==", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjoyMjBiNTIyMDBlM2U0N2IxYjQyMDEwNjY3ZmNhYTkzMzg2ODFlNjRkZDNlMzRhMzQ4NzM4NjZjYjA1MWQ2OTRlIn0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEUCIEvx6NtFeAag9TplqMLjVczT/tC6lpKe9SnrxbehBlxfAiEA07BE3f5JsMLsUsmHD58D6GaZr2yz+yQ66Os2ps8oKz8=", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4YmJmNGRiNjBmMmExM2IyNjI2NTI3MzljNWM5ZTYwNjNiMDYyNjVlODU1Zjc3MTdjMTdlYWY4YzViZTQyYWUyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQ2kyQU9BSmJTMWszMzM0Vk1TbytxeGFJNGY1Vm9ObnVWRXhaNHRmSXU3ckFpQWl3dUtkbzhyR2ZGTUdNTFNGU1F2b0xGM0p1d0Z5NEp0Tlc2a1Fsd0g3dmc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1738859497, "logIndex": 169356501, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}]
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Base64Signature": "MEQCIDJxvB7lBU+VNYBD0xw/3Bi8wY7GPJ2fBP7mUFbguApoAiAIpuQT+sgatOY6yXkkA8K/sM40d5/gt7jQywWPbq5+iw==", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hcHlyZ2lvL2RhbmdlcnpvbmUvZGFuZ2Vyem9uZSJ9LCJpbWFnZSI6eyJkb2NrZXItbWFuaWZlc3QtZGlnZXN0Ijoic2hhMjU2OjRkYTQ0MTIzNWU4NGU5MzUxODc3ODgyN2E1YzU3NDVkNTMyZDdhNDA3OTg4NmUxNjQ3OTI0YmVlN2VmMWMxNGQifSwidHlwZSI6ImNvc2lnbiBjb250YWluZXIgaW1hZ2Ugc2lnbmF0dXJlIn0sIm9wdGlvbmFsIjpudWxsfQ==", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEYCIQDuuuHoyZ2i4HKxik4Ju/MWkELwc1w5SfzcpCV7G+vZHAIhAO25R/+lIfQ/kMfC4PfeoWDwLpvnH9cq6dVSzl12i1su", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIyMGE2ZDU1NTk4Y2U0NjU3NWZkZjViZGU3YzhhYWE2YTU2ZjZlMGRmOWNiYTY1MTJhMDAxODhjMTU1NGIzYjE3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJREp4dkI3bEJVK1ZOWUJEMHh3LzNCaTh3WTdHUEoyZkJQN21VRmJndUFwb0FpQUlwdVFUK3NnYXRPWTZ5WGtrQThLL3NNNDBkNS9ndDdqUXl3V1BicTUraXc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1738688492, "logIndex": 168652066, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}]
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Base64Signature": "MEUCIQC2WlJH+B8VuX1c6i4sDwEGEZc53hXUD6/ds9TMJ3HrfwIgCxSnrNYRD2c8XENqfqc+Ik1gx0DK9kPNsn/Lt8V/dCo=", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1Njo3YjIxZGJkZWJmZmVkODU1NjIxZGZjZGVhYTUyMjMwZGM2NTY2OTk3Zjg1MmVmNWQ2MmIwMzM4YjQ2Nzk2ZTAxIn0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEYCIQDn04gOHqiZcwUO+NVV9+29+abu6O/k1ve9zatJ3gVu9QIhAJL3E+mqVPdMPfMSdhHt2XDQsYzfRDDJNJEABQlbV3Jg", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzZWQwNWJlYTc2ZWFmMzBmYWM1NzBlNzhlODBlZmQxNDNiZWQxNzFjM2VjMDY5MWI2MDU3YjdhMDAzNGEyMzhlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUMyV2xKSCtCOFZ1WDFjNmk0c0R3RUdFWmM1M2hYVUQ2L2RzOVRNSjNIcmZ3SWdDeFNuck5ZUkQyYzhYRU5xZnFjK0lrMWd4MERLOWtQTnNuL0x0OFYvZENvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1738862352, "logIndex": 169369149, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}]
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Base64Signature": "MEQCIHqXEMuAmt1pFCsHC71+ejlG5kjKrf1+AQW202OY3vhsAiA0BoDAVgAk9K7SgIRBpIV6u0veyB1iypzV0DteNh3IoQ==", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjpmYTk0ODcyNmFhYzI5YTZhYzQ5ZjAxZWM4ZmJiYWMxODUyMmIzNWIyNDkxZmRmNzE2MjM2YTBiMzUwMmEyY2E3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEUCIQCrZ+2SSYdpIOEbyUXXaBxeqT8RTujpqdXipls9hmNvDgIgdWV84PiCY2cI49QjHjun7lj25/znGMDiwjCuPjIPA6Q=", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ZjcwM2I4NTM4MjM4N2U2OTgwNzYxNDg1YzU0NGIzNmJmMThmNTA5ODQwMTMxYzRmOTJhMjE4OTI3MTJmNDJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJSHFYRU11QW10MXBGQ3NIQzcxK2VqbEc1a2pLcmYxK0FRVzIwMk9ZM3Zoc0FpQTBCb0RBVmdBazlLN1NnSVJCcElWNnUwdmV5QjFpeXB6VjBEdGVOaDNJb1E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1737478056, "logIndex": 164177381, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}, {"Base64Signature": "MEYCIQDg8MeymBLOn+Khue0yK1yQy4Fu/+GXmyC/xezXO/p1JgIhAN6QLojKzkZGxyYirbqRbZCVcIM4YN3Y18FXwpW4RuUy", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjpmYTk0ODcyNmFhYzI5YTZhYzQ5ZjAxZWM4ZmJiYWMxODUyMmIzNWIyNDkxZmRmNzE2MjM2YTBiMzUwMmEyY2E3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEUCIQCQLlrH2xo/bA6r386vOwA0OjUe0TqcxROT/Wo220jvGgIgPgRlKnQxWoXlD/Owf1Ogk5XlfXAt2f416LDbk4AoEvk=", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ZjcwM2I4NTM4MjM4N2U2OTgwNzYxNDg1YzU0NGIzNmJmMThmNTA5ODQwMTMxYzRmOTJhMjE4OTI3MTJmNDJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUURnOE1leW1CTE9uK0todWUweUsxeVF5NEZ1LytHWG15Qy94ZXpYTy9wMUpnSWhBTjZRTG9qS3prWkd4eVlpcmJxUmJaQ1ZjSU00WU4zWTE4Rlh3cFc0UnVVeSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1737557525, "logIndex": 164445483, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}, {"Base64Signature": "MEQCIEhUVYVW6EdovGDSSZt1Ffc86OfzEKAas94M4eFK7hoFAiA4+6219LktmgJSKuc2ObsnL5QjHyNLk58BwY0s8gBHbQ==", "Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiZ2hjci5pby9hbG1ldC9kYW5nZXJ6b25lL2RhbmdlcnpvbmUifSwiaW1hZ2UiOnsiZG9ja2VyLW1hbmlmZXN0LWRpZ2VzdCI6InNoYTI1NjpmYTk0ODcyNmFhYzI5YTZhYzQ5ZjAxZWM4ZmJiYWMxODUyMmIzNWIyNDkxZmRmNzE2MjM2YTBiMzUwMmEyY2E3In0sInR5cGUiOiJjb3NpZ24gY29udGFpbmVyIGltYWdlIHNpZ25hdHVyZSJ9LCJvcHRpb25hbCI6bnVsbH0=", "Cert": null, "Chain": null, "Bundle": {"SignedEntryTimestamp": "MEQCIDRUTMwL+/eW79ARRLE8h/ByCrvo0rOn3vUYQg1E6KIBAiBi/bzoqcL2Ik27KpwfFosww4l7yI+9IqwCvUlkQgEB7g==", "Payload": {"body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ZjcwM2I4NTM4MjM4N2U2OTgwNzYxNDg1YzU0NGIzNmJmMThmNTA5ODQwMTMxYzRmOTJhMjE4OTI3MTJmNDJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRWhVVllWVzZFZG92R0RTU1p0MUZmYzg2T2Z6RUtBYXM5NE00ZUZLN2hvRkFpQTQrNjIxOUxrdG1nSlNLdWMyT2Jzbkw1UWpIeU5MazU4QndZMHM4Z0JIYlE9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjBVd1ExaE1SMlptTnpsbVVqaExlVkJ1VTNaUFdUYzBWVUpyZEFveWMweHBLMkZXUmxWNlV6RlJkM1EwZDI5emVFaG9ZMFJPTWtJMlVWTnpUR3gyWjNOSU9ESnhObkZqUVRaUVRESlRaRk12Y0RScVYwZEJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1737567664, "logIndex": 164484602, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}, "RFC3161Timestamp": null}]
|
4
tests/assets/test.pub.key
Normal file
4
tests/assets/test.pub.key
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoE0CXLGff79fR8KyPnSvOY74UBkt
|
||||||
|
2sLi+aVFUzS1Qwt4wosxHhcDN2B6QSsLlvgsH82q6qcA6PL2SdS/p4jWGA==
|
||||||
|
-----END PUBLIC KEY-----
|
314
tests/test_signatures.py
Normal file
314
tests/test_signatures.py
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pytest_subprocess import FakeProcess
|
||||||
|
|
||||||
|
from dangerzone import errors as dzerrors
|
||||||
|
from dangerzone.updater import errors
|
||||||
|
from dangerzone.updater.signatures import (
|
||||||
|
Signature,
|
||||||
|
get_config_dir,
|
||||||
|
get_last_log_index,
|
||||||
|
get_log_index_from_signatures,
|
||||||
|
get_remote_signatures,
|
||||||
|
is_update_available,
|
||||||
|
load_and_verify_signatures,
|
||||||
|
prepare_airgapped_archive,
|
||||||
|
store_signatures,
|
||||||
|
upgrade_container_image,
|
||||||
|
verify_local_image,
|
||||||
|
verify_signature,
|
||||||
|
verify_signatures,
|
||||||
|
)
|
||||||
|
|
||||||
|
ASSETS_PATH = Path(__file__).parent / "assets"
|
||||||
|
TEST_PUBKEY_PATH = ASSETS_PATH / "test.pub.key"
|
||||||
|
INVALID_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "invalid"
|
||||||
|
VALID_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "valid"
|
||||||
|
TEMPERED_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "tempered"
|
||||||
|
|
||||||
|
RANDOM_DIGEST = "aacc9b586648bbe3040f2822153b1d5ead2779af45ff750fd6f04daf4a9f64b4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_valid_signatures(mocker):
|
||||||
|
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", VALID_SIGNATURES_PATH)
|
||||||
|
valid_signatures = list(VALID_SIGNATURES_PATH.glob("**/*.json"))
|
||||||
|
assert len(valid_signatures) > 0
|
||||||
|
for file in valid_signatures:
|
||||||
|
signatures = load_and_verify_signatures(file.stem, TEST_PUBKEY_PATH)
|
||||||
|
assert isinstance(signatures, list)
|
||||||
|
assert len(signatures) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_invalid_signatures(mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.SIGNATURES_PATH", INVALID_SIGNATURES_PATH
|
||||||
|
)
|
||||||
|
invalid_signatures = list(INVALID_SIGNATURES_PATH.glob("**/*.json"))
|
||||||
|
assert len(invalid_signatures) > 0
|
||||||
|
for file in invalid_signatures:
|
||||||
|
with pytest.raises(errors.SignatureError):
|
||||||
|
load_and_verify_signatures(file.stem, TEST_PUBKEY_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_tempered_signatures(mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.SIGNATURES_PATH", TEMPERED_SIGNATURES_PATH
|
||||||
|
)
|
||||||
|
tempered_signatures = list(TEMPERED_SIGNATURES_PATH.glob("**/*.json"))
|
||||||
|
assert len(tempered_signatures) > 0
|
||||||
|
for file in tempered_signatures:
|
||||||
|
with pytest.raises(errors.SignatureError):
|
||||||
|
load_and_verify_signatures(file.stem, TEST_PUBKEY_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_log_index_from_signatures():
|
||||||
|
signatures = [{"Bundle": {"Payload": {"logIndex": 1}}}]
|
||||||
|
assert get_log_index_from_signatures(signatures) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_log_index_from_signatures_empty():
|
||||||
|
signatures = []
|
||||||
|
assert get_log_index_from_signatures(signatures) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_log_index_from_malformed_signatures():
|
||||||
|
signatures = [{"Bundle": {"Payload": {"logIndex": "foo"}}}]
|
||||||
|
assert get_log_index_from_signatures(signatures) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_log_index_from_missing_log_index():
|
||||||
|
signatures = [{"Bundle": {"Payload": {}}}]
|
||||||
|
assert get_log_index_from_signatures(signatures) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_upgrade_container_image_if_already_up_to_date(mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.is_update_available", return_value=(False, None)
|
||||||
|
)
|
||||||
|
with pytest.raises(errors.ImageAlreadyUpToDate):
|
||||||
|
upgrade_container_image(
|
||||||
|
"ghcr.io/freedomofpress/dangerzone/dangerzone", "sha256:123456", "test.pub"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_upgrade_container_without_signatures(mocker):
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.is_update_available",
|
||||||
|
return_value=(True, "sha256:123456"),
|
||||||
|
)
|
||||||
|
mocker.patch("dangerzone.updater.signatures.get_remote_signatures", return_value=[])
|
||||||
|
with pytest.raises(errors.SignatureVerificationError):
|
||||||
|
upgrade_container_image(
|
||||||
|
"ghcr.io/freedomofpress/dangerzone/dangerzone",
|
||||||
|
"sha256:123456",
|
||||||
|
"test.pub",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_upgrade_container_lower_log_index(mocker):
|
||||||
|
image_digest = "4da441235e84e93518778827a5c5745d532d7a4079886e1647924bee7ef1c14d"
|
||||||
|
signatures = load_and_verify_signatures(
|
||||||
|
image_digest,
|
||||||
|
TEST_PUBKEY_PATH,
|
||||||
|
bypass_verification=True,
|
||||||
|
signatures_path=VALID_SIGNATURES_PATH,
|
||||||
|
)
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.is_update_available",
|
||||||
|
return_value=(
|
||||||
|
True,
|
||||||
|
image_digest,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.get_remote_signatures",
|
||||||
|
return_value=signatures,
|
||||||
|
)
|
||||||
|
# Mock to avoid loosing time on test failures
|
||||||
|
mocker.patch("dangerzone.container_utils.container_pull")
|
||||||
|
# The log index of the incoming signatures is 168652066
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.get_last_log_index",
|
||||||
|
return_value=168652067,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(errors.InvalidLogIndex):
|
||||||
|
upgrade_container_image(
|
||||||
|
"ghcr.io/freedomofpress/dangerzone/dangerzone",
|
||||||
|
image_digest,
|
||||||
|
TEST_PUBKEY_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_airgapped_archive_requires_digest():
|
||||||
|
with pytest.raises(errors.AirgappedImageDownloadError):
|
||||||
|
prepare_airgapped_archive(
|
||||||
|
"ghcr.io/freedomofpress/dangerzone/dangerzone", "test.tar"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_remote_signatures_error(fp: FakeProcess, mocker):
|
||||||
|
image = "ghcr.io/freedomofpress/dangerzone/dangerzone"
|
||||||
|
digest = "123456"
|
||||||
|
mocker.patch("dangerzone.updater.cosign.ensure_installed", return_value=True)
|
||||||
|
fp.register_subprocess(
|
||||||
|
["cosign", "download", "signature", f"{image}@sha256:{digest}"], returncode=1
|
||||||
|
)
|
||||||
|
with pytest.raises(errors.NoRemoteSignatures):
|
||||||
|
get_remote_signatures(image, digest)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_remote_signatures_empty(fp: FakeProcess, mocker):
|
||||||
|
image = "ghcr.io/freedomofpress/dangerzone/dangerzone"
|
||||||
|
digest = "123456"
|
||||||
|
mocker.patch("dangerzone.updater.cosign.ensure_installed", return_value=True)
|
||||||
|
fp.register_subprocess(
|
||||||
|
["cosign", "download", "signature", f"{image}@sha256:{digest}"],
|
||||||
|
stdout=json.dumps([]),
|
||||||
|
)
|
||||||
|
with pytest.raises(errors.NoRemoteSignatures):
|
||||||
|
get_remote_signatures(image, digest)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_remote_signatures_cosign_error():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_store_signatures_with_different_digests(
|
||||||
|
valid_signature, signature_other_digest, mocker, tmp_path
|
||||||
|
):
|
||||||
|
"""Test that store_signatures raises an error when a signature's digest doesn't match."""
|
||||||
|
|
||||||
|
image_digest = "sha256:123456"
|
||||||
|
|
||||||
|
# Mock the signatures path
|
||||||
|
signatures_path = tmp_path / "signatures"
|
||||||
|
signatures_path.mkdir()
|
||||||
|
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", signatures_path)
|
||||||
|
|
||||||
|
# Mock get_log_index_from_signatures
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.get_log_index_from_signatures",
|
||||||
|
return_value=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock get_last_log_index
|
||||||
|
# Verify that the signatures file was not created
|
||||||
|
assert not (signatures_path / f"{image_digest}.json").exists()
|
||||||
|
assert not (signatures_path / "last_log_index").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_path):
|
||||||
|
"""Test that store_signatures updates the last log index file."""
|
||||||
|
signatures = [valid_signature]
|
||||||
|
# Extract the digest from the signature
|
||||||
|
image_digest = Signature(valid_signature).manifest_digest
|
||||||
|
|
||||||
|
# Mock the signatures path
|
||||||
|
signatures_path = tmp_path / "signatures"
|
||||||
|
signatures_path.mkdir()
|
||||||
|
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", signatures_path)
|
||||||
|
|
||||||
|
# Create an existing last_log_index file with a lower value
|
||||||
|
with open(signatures_path / "last_log_index", "w") as f:
|
||||||
|
f.write("50")
|
||||||
|
|
||||||
|
# Mock get_log_index_from_signatures to return a higher value
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.get_log_index_from_signatures",
|
||||||
|
return_value=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call store_signatures
|
||||||
|
with pytest.raises(errors.SignatureMismatch):
|
||||||
|
store_signatures(signatures, image_digest, TEST_PUBKEY_PATH)
|
||||||
|
("dangerzone.updater.signatures.get_last_log_index",)
|
||||||
|
# Verify that the signatures file was not created
|
||||||
|
assert not (signatures_path / f"{image_digest}.json").exists()
|
||||||
|
|
||||||
|
# Verify that the log index file was not updated
|
||||||
|
assert not (signatures_path / "last_log_index").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_path):
|
||||||
|
"""Test that store_signatures updates the last log index file."""
|
||||||
|
signatures = [valid_signature]
|
||||||
|
# Extract the digest from the signature
|
||||||
|
image_digest = Signature(valid_signature).manifest_digest
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_file_digest():
|
||||||
|
# Mock the signatures path
|
||||||
|
signatures_path = tmp_path / "signatures"
|
||||||
|
signatures_path.mkdir()
|
||||||
|
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", signatures_path)
|
||||||
|
|
||||||
|
# Create an existing last_log_index file with a lower value
|
||||||
|
with open(signatures_path / "last_log_index", "w") as f:
|
||||||
|
f.write("50")
|
||||||
|
|
||||||
|
# Mock get_log_index_from_signatures to return a higher value
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.signatures.get_log_index_from_signatures",
|
||||||
|
return_value=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call store_signatures
|
||||||
|
store_signatures(signatures, image_digest, TEST_PUBKEY_PATH)
|
||||||
|
|
||||||
|
# Verify that the log index file was updated
|
||||||
|
assert (signatures_path / "last_log_index").exists()
|
||||||
|
with open(signatures_path / "last_log_index", "r") as f:
|
||||||
|
assert f.read() == "100"
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_update_available_when_no_local_image(mocker):
|
||||||
|
"""
|
||||||
|
Test that is_update_available returns True when no local image is
|
||||||
|
currently present.
|
||||||
|
"""
|
||||||
|
# Mock container_image_exists to return False
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.container_utils.get_local_image_digest",
|
||||||
|
side_effect=dzerrors.ImageNotPresentException,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock get_manifest_digest to return a digest
|
||||||
|
mocker.patch(
|
||||||
|
"dangerzone.updater.registry.get_manifest_digest",
|
||||||
|
return_value=RANDOM_DIGEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call is_update_available
|
||||||
|
update_available, digest = is_update_available("ghcr.io/freedomofpress/dangerzone")
|
||||||
|
|
||||||
|
# Verify the result
|
||||||
|
assert update_available is True
|
||||||
|
assert digest == RANDOM_DIGEST
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature(valid_signature):
|
||||||
|
"""Test that verify_signature raises an error when the payload digest doesn't match."""
|
||||||
|
verify_signature(
|
||||||
|
valid_signature,
|
||||||
|
Signature(valid_signature).manifest_digest,
|
||||||
|
TEST_PUBKEY_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signature_tempered(tempered_signature):
|
||||||
|
"""Test that verify_signature raises an error when the payload digest doesn't match."""
|
||||||
|
# Call verify_signature and expect an error
|
||||||
|
with pytest.raises(errors.SignatureError):
|
||||||
|
verify_signature(
|
||||||
|
tempered_signature,
|
||||||
|
Signature(tempered_signature).manifest_digest,
|
||||||
|
TEST_PUBKEY_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_signatures_not_0():
|
||||||
|
pass
|
Loading…
Reference in a new issue