tests/test_signer.py
| 1 | """Tests for BPFSigner / BPFVerifier / SignedBPFProgram.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from dataclasses import replace |
| 6 | |
| 7 | from pqc_ebpf_attestation import ( |
| 8 | BPFProgram, |
| 9 | BPFVerifier, |
| 10 | SignedBPFProgram, |
| 11 | ) |
| 12 | |
| 13 | |
| 14 | def test_sign_populates_envelope_fields(signed_program) -> None: |
| 15 | assert signed_program.signer_did.startswith("did:pqaid:") |
| 16 | assert signed_program.algorithm.startswith("ML-DSA") |
| 17 | assert len(signed_program.signature) > 0 |
| 18 | assert len(signed_program.public_key) > 0 |
| 19 | assert signed_program.signed_at # iso string |
| 20 | assert signed_program.program.bytecode_hash |
| 21 | |
| 22 | |
| 23 | def test_verify_success(signed_program) -> None: |
| 24 | result = BPFVerifier.verify(signed_program) |
| 25 | assert result.valid is True |
| 26 | assert result.signature_valid is True |
| 27 | assert result.hash_consistent is True |
| 28 | assert result.error is None |
| 29 | assert result.program_name == signed_program.program.metadata.name |
| 30 | |
| 31 | |
| 32 | def test_bytecode_tamper_detected(signed_program) -> None: |
| 33 | # Mutate the bytecode after signing; stored hash no longer matches. |
| 34 | tampered_program = replace( |
| 35 | signed_program.program, |
| 36 | bytecode=signed_program.program.bytecode + b"\x90", |
| 37 | ) |
| 38 | tampered = replace(signed_program, program=tampered_program) |
| 39 | result = BPFVerifier.verify(tampered) |
| 40 | assert result.hash_consistent is False |
| 41 | assert result.valid is False |
| 42 | |
| 43 | |
| 44 | def test_signature_tamper_detected(signed_program) -> None: |
| 45 | # Flip a hex char in the signature. |
| 46 | sig = signed_program.signature |
| 47 | flipped = ("0" if sig[0] != "0" else "1") + sig[1:] |
| 48 | tampered = replace(signed_program, signature=flipped) |
| 49 | result = BPFVerifier.verify(tampered) |
| 50 | assert result.signature_valid is False |
| 51 | assert result.valid is False |
| 52 | |
| 53 | |
| 54 | def test_wrong_algorithm_rejected(signed_program) -> None: |
| 55 | bogus = replace(signed_program, algorithm="RSA-4096") |
| 56 | result = BPFVerifier.verify(bogus) |
| 57 | assert result.valid is False |
| 58 | assert result.error is not None |
| 59 | assert "unknown algorithm" in result.error |
| 60 | |
| 61 | |
| 62 | def test_signed_program_roundtrip(signed_program) -> None: |
| 63 | serialized = signed_program.to_dict(include_bytecode=True) |
| 64 | restored = SignedBPFProgram.from_dict(serialized) |
| 65 | assert restored.signer_did == signed_program.signer_did |
| 66 | assert restored.algorithm == signed_program.algorithm |
| 67 | assert restored.signature == signed_program.signature |
| 68 | assert restored.public_key == signed_program.public_key |
| 69 | assert restored.program.bytecode == signed_program.program.bytecode |
| 70 | assert restored.program.bytecode_hash == signed_program.program.bytecode_hash |
| 71 | |
| 72 | # And the roundtripped envelope still verifies. |
| 73 | result = BPFVerifier.verify(restored) |
| 74 | assert result.valid is True |
| 75 | |
| 76 | |
| 77 | def test_verify_without_bytecode_is_signature_only(signed_program) -> None: |
| 78 | """Envelopes stripped of bytecode should still verify via the signed manifest.""" |
| 79 | no_bytes = SignedBPFProgram.from_dict(signed_program.to_dict(include_bytecode=False)) |
| 80 | assert no_bytes.program.bytecode == b"" |
| 81 | result = BPFVerifier.verify(no_bytes) |
| 82 | # hash_consistent defaults True when no bytecode is present to compare. |
| 83 | assert result.hash_consistent is True |
| 84 | assert result.signature_valid is True |
| 85 | assert result.valid is True |
| 86 | |
| 87 | |
| 88 | def test_hash_helper_consistent(sample_bpf_metadata, sample_bytecode) -> None: |
| 89 | prog = BPFProgram.from_bytes(sample_bpf_metadata, sample_bytecode) |
| 90 | assert prog.bytecode_hash == BPFProgram.hash_bytecode(sample_bytecode) |
| 91 | |