From 26638a5f2a63b51788c0cbe7f640f415c4d4e49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Thu, 13 Feb 2025 19:12:25 +0100 Subject: [PATCH] (WIP) some more tests --- dangerzone/updater/signatures.py | 3 +- tests/conftest.py | 12 ++++++ tests/test_signatures.py | 73 +++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/dangerzone/updater/signatures.py b/dangerzone/updater/signatures.py index cc006ad..7113fc5 100644 --- a/dangerzone/updater/signatures.py +++ b/dangerzone/updater/signatures.py @@ -424,8 +424,7 @@ def get_remote_signatures(image: str, digest: str) -> List[Dict]: # Remove the last return, split on newlines, convert from JSON signatures_raw = process.stdout.decode("utf-8").strip().split("\n") - signatures = list(map(json.loads, signatures_raw)) - breakpoint() + signatures = list(filter(bool, map(json.loads, signatures_raw))) if len(signatures) < 1: raise errors.NoRemoteSignatures("No signatures found for the image") return signatures diff --git a/tests/conftest.py b/tests/conftest.py index 4a80f17..5afbb58 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,13 @@ from dangerzone.gui import Application sys.dangerzone_dev = True # type: ignore[attr-defined] +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" + + # Use this fixture to make `pytest-qt` invoke our custom QApplication. # See https://pytest-qt.readthedocs.io/en/latest/qapplication.html#testing-custom-qapplications @pytest.fixture(scope="session") @@ -133,6 +140,11 @@ for_each_doc = pytest.mark.parametrize( ) +@pytest.fixture +def signature(): + return {} + + # External Docs - base64 docs encoded for externally sourced documents # XXX to reduce the chance of accidentally opening them test_docs_external_dir = Path(__file__).parent.joinpath(SAMPLE_EXTERNAL_DIRECTORY) diff --git a/tests/test_signatures.py b/tests/test_signatures.py index 2681896..5e4fdde 100644 --- a/tests/test_signatures.py +++ b/tests/test_signatures.py @@ -32,6 +32,29 @@ TEMPERED_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "tempered" RANDOM_DIGEST = "aacc9b586648bbe3040f2822153b1d5ead2779af45ff750fd6f04daf4a9f64b4" +@pytest.fixture +def valid_signature(): + signature_file = next(VALID_SIGNATURES_PATH.glob("**/*.json")) + with open(signature_file, "r") as signature_file: + signatures = json.load(signature_file) + return signatures.pop() + + +@pytest.fixture +def tempered_signature(): + signature_file = next(TEMPERED_SIGNATURES_PATH.glob("**/*.json")) + with open(signature_file, "r") as signature_file: + signatures = json.load(signature_file) + return signatures.pop() + + +@pytest.fixture +def signature_other_digest(valid_signature): + signature = valid_signature.copy() + signature["Bundle"]["Payload"]["digest"] = "sha256:123456" + return signature + + def test_load_valid_signatures(mocker): mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", VALID_SIGNATURES_PATH) valid_signatures = list(VALID_SIGNATURES_PATH.glob("**/*.json")) @@ -167,21 +190,30 @@ def test_get_remote_signatures_empty(fp: FakeProcess, mocker): mocker.patch("dangerzone.updater.cosign.ensure_installed", return_value=True) fp.register_subprocess( ["cosign", "download", "signature", f"{image}@sha256:{digest}"], - stdout=json.dumps([]), + stdout=json.dumps({}), ) with pytest.raises(errors.NoRemoteSignatures): get_remote_signatures(image, digest) -def test_get_remote_signatures_cosign_error(): - pass +def test_get_remote_signatures_cosign_error(mocker, fp: FakeProcess): + 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, + stderr="Error: no signatures associated", + ) + with pytest.raises(errors.NoRemoteSignatures): + get_remote_signatures(image, digest) 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.""" - + signatures = [valid_signature, signature_other_digest] image_digest = "sha256:123456" # Mock the signatures path @@ -238,6 +270,34 @@ def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_p signatures = [valid_signature] # Extract the digest from the signature image_digest = Signature(valid_signature).manifest_digest + signatures = [valid_signature, signature_other_digest] + breakpoint() + 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 + mocker.patch( + "dangerzone.updater.signatures.get_last_log_index", + return_value=50, + ) + + +def test_stores_signatures_updates_last_log_index(): + pass def test_get_file_digest(): @@ -310,5 +370,6 @@ def test_verify_signature_tempered(tempered_signature): ) -def test_verify_signatures_not_0(): - pass +def test_verify_signatures_empty_list(): + with pytest.raises(errors.SignatureVerificationError): + verify_signatures([], "1234", TEST_PUBKEY_PATH)