205 lines
7.1 KiB
Python
205 lines
7.1 KiB
Python
# tests/cli/test_forensics.py
|
|
# {{{ imports
|
|
import struct
|
|
from pathlib import Path
|
|
from click.testing import CliRunner
|
|
from ctf.cli.forensics import forensics_group
|
|
# }}}
|
|
|
|
# Define the persistent test environment path
|
|
TEST_ENV = Path("tests/env")
|
|
|
|
# {{{ test_forensics_metadata_cli_success
|
|
def test_forensics_metadata_cli_success():
|
|
"""
|
|
Verifies that 'forensics metadata' successfully runs via the CLI and prints
|
|
the metadata in a formatted table.
|
|
"""
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "test_file.png"
|
|
with open(test_file, "wb") as f:
|
|
f.write(b"\x89PNG\r\n\x1a\n")
|
|
|
|
result = runner.invoke(forensics_group, ["metadata", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "Metadata: test_file.png" in result.output
|
|
assert "Size" in result.output
|
|
assert "89504E47" in result.output
|
|
assert "Detected Type" in result.output
|
|
assert "PNG Image" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_nested_metadata_cli_success
|
|
def test_nested_metadata_cli_success():
|
|
"""
|
|
Verifies that 'ctf forensics metadata' successfully runs via the root CLI.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "test_file_nested.png"
|
|
with open(test_file, "wb") as f:
|
|
f.write(b"\x89PNG\r\n\x1a\n")
|
|
|
|
result = runner.invoke(cli, ["forensics", "metadata", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "Metadata: test_file_nested.png" in result.output
|
|
assert "PNG Image" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_forensics_metadata_cli_missing_file
|
|
def test_forensics_metadata_cli_missing_file():
|
|
"""
|
|
Verifies that the CLI fails gracefully when a non-existent file path is specified.
|
|
"""
|
|
runner = CliRunner()
|
|
result = runner.invoke(forensics_group, ["metadata", "non_existent_file.png"])
|
|
assert result.exit_code != 0
|
|
assert "does not exist" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_forensics_signatures_cli
|
|
def test_forensics_signatures_cli():
|
|
"""
|
|
Verifies that 'forensics signatures' runs successfully and displays
|
|
the list of supported file signatures.
|
|
"""
|
|
runner = CliRunner()
|
|
result = runner.invoke(forensics_group, ["signatures"])
|
|
assert result.exit_code == 0
|
|
assert "Supported Magic Signatures" in result.output
|
|
assert "PNG Image" in result.output
|
|
assert "89504E47" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_exif_cli_options
|
|
def test_exif_cli_options():
|
|
"""
|
|
Verifies CLI options -i/--inode and -e/--exif work as expected.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "mock_exif_image.png"
|
|
|
|
# Verify mutual exclusion error
|
|
result_err = runner.invoke(cli, ["forensics", "metadata", "-i", "-e", str(test_file)])
|
|
assert result_err.exit_code != 0
|
|
assert "Cannot specify more than one" in result_err.output
|
|
|
|
# Verify --inode hides EXIF data table
|
|
result_inode = runner.invoke(cli, ["forensics", "metadata", "-i", str(test_file)])
|
|
assert result_inode.exit_code == 0
|
|
assert "Metadata: mock_exif_image.png" in result_inode.output
|
|
assert "EXIF Metadata" not in result_inode.output
|
|
|
|
# Verify --exif hides POSIX table but displays EXIF data table
|
|
result_exif = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
|
assert result_exif.exit_code == 0
|
|
assert "Metadata: mock_exif_image.png" not in result_exif.output
|
|
assert "EXIF Metadata" in result_exif.output
|
|
assert "mock_exif_image" in result_exif.output
|
|
assert "Make" in result_exif.output
|
|
assert "TEST" in result_exif.output
|
|
# }}}
|
|
|
|
# {{{ test_exif_cli_options_warning
|
|
def test_exif_cli_options_warning():
|
|
"""
|
|
Verifies CLI warns when no EXIF or comment is found and -e option is used.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "clean_no_exif.png"
|
|
with open(test_file, "wb") as f:
|
|
f.write(b"\x89PNG\r\n\x1a\n")
|
|
|
|
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "No EXIF or comment metadata found" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_exif_cli_comment_only
|
|
def test_exif_cli_comment_only():
|
|
"""
|
|
Verifies CLI displays comment when -e is used and only comment is found.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
comment_text = b"flag{comment_only}"
|
|
comment_len = len(comment_text) + 2
|
|
mock_jpeg = b"\xff\xd8\xff\xfe" + struct.pack(">H", comment_len) + comment_text
|
|
|
|
test_file = TEST_ENV / "comment_only.jpg"
|
|
with open(test_file, "wb") as f:
|
|
f.write(mock_jpeg)
|
|
|
|
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "Comment" in result.output
|
|
assert "flag{comment_only}" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_adobe_xmp_cli
|
|
def test_adobe_xmp_cli():
|
|
"""
|
|
Verifies that the ctf inspect CLI prints the parsed XMP fields.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "mock_xmp.jpg"
|
|
|
|
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "EXIF Metadata" in result.output
|
|
assert "cc:license" in result.output
|
|
assert "cGljb0NURnt0ZXN0X3htcF9mbGFnfQ==" in result.output
|
|
# }}}
|
|
|
|
# {{{ test_cli_physical_option
|
|
def test_cli_physical_option():
|
|
"""
|
|
Verifies the ctf forensics metadata CLI supports -p/--physical and mutual exclusion rules.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
test_file = TEST_ENV / "mock_physical.jpg"
|
|
|
|
# Mutual exclusion check
|
|
result_err = runner.invoke(cli, ["forensics", "metadata", "-p", "-e", str(test_file)])
|
|
assert result_err.exit_code != 0
|
|
assert "Cannot specify more than one" in result_err.output
|
|
|
|
# Physical view check
|
|
result_phys = runner.invoke(cli, ["forensics", "metadata", "-p", str(test_file)])
|
|
assert result_phys.exit_code == 0
|
|
assert "Physical Metadata" in result_phys.output
|
|
assert "Image Size" in result_phys.output
|
|
assert "1500x1000" in result_phys.output
|
|
assert "Metadata: mock_physical.jpg" not in result_phys.output
|
|
assert "EXIF Metadata" not in result_phys.output
|
|
# }}}
|
|
|
|
# {{{ test_metadata_decoded_hints_cli
|
|
def test_metadata_decoded_hints_cli():
|
|
"""
|
|
Verifies that metadata command successfully decodes and prints hex/base64 encoded metadata hints.
|
|
"""
|
|
from ctf.main import cli
|
|
runner = CliRunner()
|
|
|
|
# Create a mock JPEG with a base64 encoded comment: "ZmxhZ3tiNjRfbWV0YWRhdGF9" -> "flag{b64_metadata}"
|
|
comment_text = b"ZmxhZ3tiNjRfbWV0YWRhdGF9"
|
|
comment_len = len(comment_text) + 2
|
|
mock_jpeg = b"\xff\xd8\xff\xfe" + struct.pack(">H", comment_len) + comment_text + b"\xff\xd9"
|
|
|
|
test_file = TEST_ENV / "mock_encoded_comment.jpg"
|
|
with open(test_file, "wb") as f:
|
|
f.write(mock_jpeg)
|
|
|
|
result = runner.invoke(cli, ["forensics", "metadata", str(test_file)])
|
|
assert result.exit_code == 0
|
|
assert "Decoded Metadata Hints" in result.output
|
|
assert "Comment" in result.output
|
|
assert "base64" in result.output
|
|
assert "flag{b64_metadata}" in result.output
|
|
# }}}
|