mirror of
https://github.com/mvt-project/mvt.git
synced 2026-08-21 18:37:46 +02:00
* Load installed module packages via entry points Python packages can already register custom CLI commands which load automatically, but custom modules still require --load-module or the MVT_CUSTOM_MODULES environment variable on every invocation. Add an mvt.modules entry-point group so installed packages can register forensic modules which load automatically into every module-running check-* command. An entry point resolves to an iterable of MVTModule subclasses, or a callable returning one. Broken entry points are skipped with a warning so a faulty package cannot break MVT. * Record the source of loaded modules for auditability Now that installed module packages load automatically, record where every module came from: - --list-modules groups the available modules by source, one line per source with the modules comma-separated: MVT itself with its version, each installed package with its version and VCS commit when recorded (PEP 610 direct_url.json), and each --load-module/MVT_CUSTOM_MODULES file with its SHA-256 hash. - Commands log one line per module source with its version or hash and the modules loaded from it, so command.log records exactly which modules ran and where they came from. - Make init_logging() idempotent: a loaded module package importing an MVT CLI module would previously add a second console handler and duplicate every console log line. * Route loaded module logging under the mvt.ext namespace Modules loaded from installed packages or file paths live outside the mvt logger hierarchy, so their log records never reach MVT's console and file handlers and instead fall through to logging.lastResort: alerts print as bare unformatted lines and INFO messages are dropped entirely. Add get_module_logger() and use it everywhere module loggers are created. Built-in mvt.* modules keep their existing logger names, and everything external is parented under a dedicated mvt.ext namespace so records reach the handlers and external names can never collide with MVT's internal logger tree. File-path modules are named after their file (mvt.ext.<stem>) instead of the mangled internal import name. Document a naming convention for community module packages: distribute as mvt-plugin-<name> with import package mvt_plugin_<name>, including the publishing organization in the name. The prefix is advisory (loading is by entry point, and it is no mark of authenticity), but conforming packages get a cleaner logger namespace: the mvt_plugin_ prefix is stripped, so mvt_plugin_amnesty_custom logs as mvt.ext.amnesty_custom.
125 lines
4.0 KiB
Python
125 lines
4.0 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
# https://license.mvt.re/1.1/
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from mvt.common.log import MVTLogHandler
|
|
from mvt.common.utils import (
|
|
CustomJSONEncoder,
|
|
convert_datetime_to_iso,
|
|
convert_mactime_to_iso,
|
|
convert_unix_to_iso,
|
|
convert_unix_to_utc_datetime,
|
|
generate_hashes_from_path,
|
|
get_sha256_from_file_path,
|
|
init_logging,
|
|
)
|
|
|
|
from ..utils import get_artifact_folder
|
|
|
|
TEST_DATE_EPOCH = 1626566400
|
|
TEST_DATE_ISO = "2021-07-18 00:00:00.000000"
|
|
TEST_DATE_MAC = TEST_DATE_EPOCH - 978307200
|
|
|
|
|
|
class TestDateConversions:
|
|
def test_convert_unix_to_iso(self):
|
|
assert convert_unix_to_iso(TEST_DATE_EPOCH) == TEST_DATE_ISO
|
|
|
|
def test_convert_mactime_to_iso(self):
|
|
assert convert_mactime_to_iso(TEST_DATE_MAC) == TEST_DATE_ISO
|
|
|
|
def test_convert_unix_to_utc_datetime(self):
|
|
converted = convert_unix_to_utc_datetime(TEST_DATE_EPOCH)
|
|
assert converted.year == 2021
|
|
assert converted.month == 7
|
|
assert converted.day == 18
|
|
|
|
def test_convert_datetime_to_iso(self):
|
|
converted = convert_unix_to_utc_datetime(TEST_DATE_EPOCH)
|
|
assert convert_datetime_to_iso(converted) == TEST_DATE_ISO
|
|
|
|
def test_convert_timezone_aware_to_iso(self):
|
|
assert (
|
|
convert_datetime_to_iso(
|
|
datetime.strptime("2024-09-30 11:21:20+0200", "%Y-%m-%d %H:%M:%S%z")
|
|
)
|
|
== "2024-09-30 09:21:20.000000"
|
|
)
|
|
|
|
|
|
class TestHashes:
|
|
def test_hash_from_file(self):
|
|
path = os.path.join(get_artifact_folder(), "androidqf", "backup.ab")
|
|
sha256 = get_sha256_from_file_path(path)
|
|
assert (
|
|
sha256 == "f0e32fe8a7fd5ac0e2de19636d123c0072e979396986139ba2bc49ec385dc325"
|
|
)
|
|
|
|
def test_hash_from_folder(self):
|
|
path = os.path.join(get_artifact_folder(), "androidqf")
|
|
hashes = list(generate_hashes_from_path(path, logging))
|
|
assert len(hashes) == 8
|
|
# Sort the files to have reliable order for tests.
|
|
hashes = sorted(hashes, key=lambda x: x["file_path"])
|
|
assert hashes[0]["file_path"] == os.path.join(path, "backup.ab")
|
|
assert (
|
|
hashes[0]["sha256"]
|
|
== "f0e32fe8a7fd5ac0e2de19636d123c0072e979396986139ba2bc49ec385dc325"
|
|
)
|
|
assert hashes[1]["file_path"] == os.path.join(path, "dumpsys.txt")
|
|
|
|
# This needs to be updated when we add or edit files in AndroidQF folder
|
|
assert (
|
|
hashes[1]["sha256"]
|
|
== "9fb6396b64cfff30e2a459a64496d3c1386926d09edd68be2d878de45fa7b3a9"
|
|
)
|
|
|
|
|
|
class TestCustomJSONEncoder:
|
|
def test__normal_input(self):
|
|
assert json.dumps({"a": "b"}, cls=CustomJSONEncoder) == '{"a": "b"}'
|
|
|
|
def test__datetime_object(self):
|
|
assert (
|
|
json.dumps(
|
|
{"timestamp": datetime(2023, 11, 13, 12, 21, 49, 727467)},
|
|
cls=CustomJSONEncoder,
|
|
)
|
|
== '{"timestamp": "2023-11-13 12:21:49.727467"}'
|
|
)
|
|
|
|
def test__bytes_non_utf_8(self):
|
|
assert (
|
|
json.dumps({"identifier": b"\xa8\xa9"}, cls=CustomJSONEncoder)
|
|
== """{"identifier": "\\\\xa8\\\\xa9"}"""
|
|
)
|
|
|
|
def test__bytes_valid_utf_8(self):
|
|
assert (
|
|
json.dumps({"name": "家".encode()}, cls=CustomJSONEncoder)
|
|
== '{"name": "\\u5bb6"}'
|
|
)
|
|
|
|
|
|
class TestInitLogging:
|
|
def test__init_logging_is_idempotent(self):
|
|
# Loaded module packages may import an MVT CLI module, which calls
|
|
# init_logging() again at import time. A second call must not add
|
|
# a duplicate console handler.
|
|
log = logging.getLogger("mvt")
|
|
init_logging()
|
|
handler_count = sum(
|
|
isinstance(handler, MVTLogHandler) for handler in log.handlers
|
|
)
|
|
init_logging()
|
|
assert (
|
|
sum(isinstance(handler, MVTLogHandler) for handler in log.handlers)
|
|
== handler_count
|
|
)
|