Files
dlp-io/tests/test_dlp_io_encryption.py
p40000043244@byd.com 7b62c23e1e
publish-dlp-io / publish (push) Successful in 1m16s
feat: is_encrypted 加密状态判断与未加密写入通道 (0.2.0)
- is_encrypted(): certutil 原始哈希 + helper 视图对比, Python/pyd/EXE 三环境均可判定
- w/a/x 写入按后缀探测通道, 直写会被加密时自动切换 PowerShell stdin 中转, 保证落盘未加密
- 新增 WriteChannel/DlpWriteError 与 DLP_IO_WRITE_CHANNEL 强制通道开关
- tests: DLP 加解密样本 fixture 与 19 个结构化断言用例
2026-07-31 18:24:22 +08:00

263 lines
8.6 KiB
Python

from __future__ import annotations
import hashlib
import os
from pathlib import Path
import pytest
import dlp_io
pytestmark = pytest.mark.skipif(os.name != "nt", reason="DLP detection requires Windows")
SAMPLES_DIR = Path(__file__).resolve().parent
ENCRYPTED_SAMPLE = SAMPLES_DIR / "DLP加密文件.pptx"
DECRYPTED_SAMPLE = SAMPLES_DIR / "DLP解密文件.pptx"
@pytest.fixture(autouse=True)
def reset_module_state(monkeypatch):
monkeypatch.delenv(dlp_io._WRITE_CHANNEL_ENV, raising=False)
monkeypatch.setattr(dlp_io, "_config", dlp_io.DlpConfig())
dlp_io._write_channel_cache.clear()
dlp_io.shutdown()
yield
dlp_io._write_channel_cache.clear()
dlp_io.shutdown()
def _native_sha256(path: Path) -> bytes:
return hashlib.sha256(path.read_bytes()).digest()
def _dlp_host() -> bool:
"""本机是否启用 DLP:加密样本的原始盘内字节与本地视图是否不一致。"""
return dlp_io._raw_sha256(str(ENCRYPTED_SAMPLE)) != _native_sha256(ENCRYPTED_SAMPLE)
# ---------------------------------------------------------------------------
# is_encrypted against the real DLP samples
def test_encrypted_sample_matches_host_dlp_capability() -> None:
assert dlp_io.is_encrypted(ENCRYPTED_SAMPLE) == _dlp_host()
def test_decrypted_sample_is_never_reported_encrypted() -> None:
assert dlp_io.is_encrypted(DECRYPTED_SAMPLE) is False
def test_is_encrypted_missing_file_raises() -> None:
with pytest.raises(FileNotFoundError):
dlp_io.is_encrypted(SAMPLES_DIR / "missing-sample.bin")
# ---------------------------------------------------------------------------
# is_encrypted decision branches (mocked channels)
def test_is_encrypted_short_circuits_when_raw_digest_differs(monkeypatch) -> None:
monkeypatch.setattr(dlp_io, "_native_sha256", lambda path: b"\x01" * 32)
monkeypatch.setattr(dlp_io, "_raw_sha256", lambda path: b"\x02" * 32)
monkeypatch.setattr(
dlp_io,
"_get_default_session",
lambda: pytest.fail("helper must not start when digests differ"),
)
assert dlp_io.is_encrypted("payload.bin") is True
@pytest.mark.parametrize(
("helper_head", "native_head", "expected"),
[
(b"\x50\x4b\x03\x04", b"\x18\x1b\x03\x1a", True),
(b"\x50\x4b\x03\x04", b"\x50\x4b\x03\x04", False),
],
)
def test_is_encrypted_ambiguous_case_compares_helper_head(
monkeypatch, helper_head: bytes, native_head: bytes, expected: bool
) -> None:
digest = b"\x01" * 32
monkeypatch.setattr(dlp_io, "_native_sha256", lambda path: digest)
monkeypatch.setattr(dlp_io, "_raw_sha256", lambda path: digest)
monkeypatch.setattr(dlp_io, "_native_head", lambda path, size: native_head)
monkeypatch.setattr(dlp_io, "_helper_head", lambda path, size: helper_head)
assert dlp_io.is_encrypted("payload.bin") is expected
# ---------------------------------------------------------------------------
# Write channel selection
def test_select_write_channel_honours_probes(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(dlp_io, "_probe_direct_write", lambda suffix: True)
monkeypatch.setattr(
dlp_io,
"_probe_powershell_write",
lambda suffix: pytest.fail("powershell probe must be skipped"),
)
assert dlp_io._select_write_channel(".bin") is dlp_io.WriteChannel.DIRECT
monkeypatch.setattr(dlp_io, "_probe_direct_write", lambda suffix: False)
monkeypatch.setattr(dlp_io, "_probe_powershell_write", lambda suffix: True)
assert dlp_io._select_write_channel(".pptx") is dlp_io.WriteChannel.POWERSHELL
def test_select_write_channel_raises_when_no_channel_works(monkeypatch) -> None:
monkeypatch.setattr(dlp_io, "_probe_direct_write", lambda suffix: False)
monkeypatch.setattr(dlp_io, "_probe_powershell_write", lambda suffix: False)
with pytest.raises(dlp_io.DlpWriteError):
dlp_io._select_write_channel(".bin")
def test_select_write_channel_caches_per_suffix(monkeypatch) -> None:
calls = []
monkeypatch.setattr(
dlp_io, "_probe_direct_write", lambda suffix: calls.append(suffix) or True
)
assert dlp_io._select_write_channel(".BIN") is dlp_io.WriteChannel.DIRECT
assert dlp_io._select_write_channel(".bin") is dlp_io.WriteChannel.DIRECT
assert len(calls) == 1
def test_write_channel_env_override(monkeypatch) -> None:
monkeypatch.setenv(dlp_io._WRITE_CHANNEL_ENV, "PowerShell")
monkeypatch.setattr(
dlp_io,
"_probe_direct_write",
lambda suffix: pytest.fail("override must skip probing"),
)
assert dlp_io._select_write_channel(".bin") is dlp_io.WriteChannel.POWERSHELL
monkeypatch.setenv(dlp_io._WRITE_CHANNEL_ENV, "bogus")
with pytest.raises(dlp_io.DlpConfigurationError):
dlp_io._select_write_channel(".bin")
def test_real_probe_selects_a_working_channel(tmp_path) -> None:
try:
channel = dlp_io._select_write_channel(".bin")
except dlp_io.DlpWriteError:
pytest.skip("host cannot produce unencrypted writes")
direct_ok = dlp_io._probe_direct_write(".bin")
expected = dlp_io.WriteChannel.DIRECT if direct_ok else dlp_io.WriteChannel.POWERSHELL
assert channel is expected
# ---------------------------------------------------------------------------
# PowerShell relay writer
def test_powershell_relay_writes_exact_binary_and_lands_plaintext(tmp_path) -> None:
payload = bytes(range(256)) * 64 + b"\x00\x1a\xff"
target = tmp_path / "relay输出.bin"
with dlp_io._PowerShellRelayRaw(str(target), "Create") as raw:
assert raw.writable()
assert raw.write(payload) == len(payload)
assert target.read_bytes() == payload
assert dlp_io._raw_sha256(str(target)) == hashlib.sha256(payload).digest()
def test_powershell_relay_append_preserves_existing_content(tmp_path) -> None:
target = tmp_path / "relay.bin"
with dlp_io._PowerShellRelayRaw(str(target), "Create") as raw:
raw.write(b"ab")
with dlp_io._PowerShellRelayRaw(str(target), "Append") as raw:
raw.write(b"cd")
assert target.read_bytes() == b"abcd"
def test_powershell_relay_create_new_fails_on_existing_file(tmp_path) -> None:
target = tmp_path / "relay.bin"
target.write_bytes(b"existing")
with pytest.raises(dlp_io.DlpWriteError):
with dlp_io._PowerShellRelayRaw(str(target), "CreateNew"):
pass
def test_open_write_routes_through_relay_when_selected(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(
dlp_io, "_select_write_channel", lambda suffix: dlp_io.WriteChannel.POWERSHELL
)
payload = bytes(range(256)) * 32
target = tmp_path / "routed.bin"
with dlp_io.open(target, "wb") as file:
file.write(payload)
assert target.read_bytes() == payload
def test_open_text_write_routes_through_relay(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(
dlp_io, "_select_write_channel", lambda suffix: dlp_io.WriteChannel.POWERSHELL
)
payload = "中文内容\n第二行".encode("utf-8")
target = tmp_path / "routed.txt"
with dlp_io.open(target, "w", encoding="utf-8", newline="") as file:
file.write(payload.decode("utf-8"))
with dlp_io.open(target, "a", encoding="utf-8", newline="") as file:
file.write("追加")
assert target.read_bytes() == payload + "追加".encode("utf-8")
def test_open_exclusive_create_is_atomic_before_channel_selection(
monkeypatch, tmp_path
) -> None:
monkeypatch.setattr(
dlp_io,
"_select_write_channel",
lambda suffix: pytest.fail("existing target must fail before probing"),
)
target = tmp_path / "existing.bin"
target.write_bytes(b"data")
with pytest.raises(FileExistsError):
dlp_io.open(target, "xb")
assert target.read_bytes() == b"data"
def test_open_write_direct_channel_uses_native_open(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(
dlp_io, "_select_write_channel", lambda suffix: dlp_io.WriteChannel.DIRECT
)
target = tmp_path / "direct.bin"
with dlp_io.open(target, "wb") as file:
assert not isinstance(file, dlp_io._PowerShellRelayRaw)
file.write(b"native")
assert target.read_bytes() == b"native"
def test_patched_open_write_uses_same_channel_routing(monkeypatch, tmp_path) -> None:
import builtins
monkeypatch.setattr(
dlp_io, "_select_write_channel", lambda suffix: dlp_io.WriteChannel.POWERSHELL
)
target = tmp_path / "patched.bin"
dlp_io.install_open_patch()
try:
with builtins.open(target, "wb") as file:
file.write(b"patched-write")
finally:
dlp_io.uninstall_open_patch()
assert target.read_bytes() == b"patched-write"