mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
126 lines
5.5 KiB
Python
126 lines
5.5 KiB
Python
"""The Windows cookie path, exercised against the real DPAPI on a real Windows machine.
|
|
|
|
`test_browser_cookies_windows.py` covers the same code with the OS calls mocked, which proves the
|
|
branching and nothing about the layout. Every assumption that could actually be wrong is an
|
|
agreement with Windows itself:
|
|
|
|
- CryptUnprotectData through ctypes/crypt32 (struct layout, the LocalFree, the out-blob copy)
|
|
- the "DPAPI" prefix on the base64 key in Local State
|
|
- AES-256-GCM framing: nonce at [3:15], tag bundled at the tail
|
|
- LOCALAPPDATA resolution for the four Chromium roots
|
|
|
|
A mock can agree with a wrong belief forever, so these skip everywhere except Windows and are run
|
|
by the windows-verify CI job. They build a Local State and a cookie blob with the OS's own
|
|
CryptProtectData, then read them back with the shipping code: if our idea of the layout is wrong
|
|
the round trip fails, which is exactly the signal that cannot be faked from a Mac.
|
|
"""
|
|
import base64
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from backend.apps.onboarding.usage import browser_cookies as bc
|
|
|
|
pytestmark = pytest.mark.skipif(not bc.IS_WIN, reason="real DPAPI; Windows only")
|
|
|
|
|
|
def p_dpapi_protect(data: bytes) -> bytes:
|
|
"""CryptProtectData, the exact mirror of the shipping unprotect. Deliberately written against
|
|
the same ctypes surface so a struct-layout mistake shows up as a failed round trip."""
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
|
|
class DATA_BLOB(ctypes.Structure):
|
|
_fields_ = [("cbData", wintypes.DWORD), ("pbData", ctypes.POINTER(ctypes.c_char))]
|
|
|
|
buf = ctypes.create_string_buffer(data, len(data))
|
|
blob_in = DATA_BLOB(len(data), ctypes.cast(buf, ctypes.POINTER(ctypes.c_char)))
|
|
blob_out = DATA_BLOB()
|
|
ok = ctypes.windll.crypt32.CryptProtectData(
|
|
ctypes.byref(blob_in), None, None, None, None, 0, ctypes.byref(blob_out))
|
|
assert ok, "CryptProtectData failed; the test harness itself is broken"
|
|
n = int(blob_out.cbData)
|
|
out = ctypes.create_string_buffer(n)
|
|
ctypes.memmove(out, blob_out.pbData, n)
|
|
ctypes.windll.kernel32.LocalFree(blob_out.pbData)
|
|
return out.raw
|
|
|
|
|
|
def test_dpapi_round_trips_through_the_shipping_unprotect():
|
|
secret = os.urandom(32)
|
|
assert bc.win_dpapi_unprotect(p_dpapi_protect(secret)) == secret
|
|
|
|
|
|
def test_unprotecting_garbage_returns_none_rather_than_raising():
|
|
"""A corrupt or foreign-user blob must degrade to "no key", never take the process down."""
|
|
assert bc.win_dpapi_unprotect(b"not a dpapi blob") is None
|
|
|
|
|
|
def test_storage_key_is_read_from_a_real_local_state(tmp_path, monkeypatch):
|
|
"""Local State holds base64("DPAPI" + protected key). This pins the prefix strip and the
|
|
base64 handling against a file Windows itself produced."""
|
|
key = os.urandom(32)
|
|
root = tmp_path / "User Data"
|
|
root.mkdir()
|
|
(root / "Local State").write_text(json.dumps(
|
|
{"os_crypt": {"encrypted_key": base64.b64encode(b"DPAPI" + p_dpapi_protect(key)).decode()}}),
|
|
encoding="utf-8")
|
|
monkeypatch.setitem(bc.CHROMIUM_ROOTS, "Chrome", str(root))
|
|
assert bc.win_storage_key("Chrome") == key
|
|
|
|
|
|
def test_a_local_state_without_the_dpapi_prefix_is_refused(tmp_path, monkeypatch):
|
|
root = tmp_path / "User Data"
|
|
root.mkdir()
|
|
(root / "Local State").write_text(json.dumps(
|
|
{"os_crypt": {"encrypted_key": base64.b64encode(b"NOPE" + os.urandom(16)).decode()}}),
|
|
encoding="utf-8")
|
|
monkeypatch.setitem(bc.CHROMIUM_ROOTS, "Chrome", str(root))
|
|
assert bc.win_storage_key("Chrome") is None
|
|
|
|
|
|
@pytest.mark.parametrize("version", [b"v10", b"v11"])
|
|
def test_a_v10_cookie_decrypts_with_the_windows_gcm_framing(version):
|
|
"""The framing claim: nonce at [3:15], ciphertext+tag from [15:]."""
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
key, nonce = os.urandom(32), os.urandom(12)
|
|
blob = version + nonce + AESGCM(key).encrypt(nonce, b"session-token-xyz", None)
|
|
assert bc.decrypt_cookie_value(blob, key) == "session-token-xyz"
|
|
|
|
|
|
def test_the_32_byte_domain_hash_prefix_is_stripped():
|
|
"""Newer Chromium prepends a 32-byte domain hash inside the plaintext."""
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
key, nonce = os.urandom(32), os.urandom(12)
|
|
plain = os.urandom(32) + b"session-token-xyz"
|
|
blob = b"v10" + nonce + AESGCM(key).encrypt(nonce, plain, None)
|
|
assert bc.decrypt_cookie_value(blob, key) == "session-token-xyz"
|
|
|
|
|
|
def test_v20_is_reported_unreachable_rather_than_guessed():
|
|
"""App-bound encryption cannot be read without the browser. Saying so is the honest answer;
|
|
returning a wrong string here would silently corrupt a borrowed session."""
|
|
assert bc.decrypt_cookie_value(b"v20" + os.urandom(40), os.urandom(32)) is None
|
|
|
|
|
|
def test_the_wrong_key_fails_closed():
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
nonce = os.urandom(12)
|
|
blob = b"v10" + nonce + AESGCM(os.urandom(32)).encrypt(nonce, b"session-token-xyz", None)
|
|
assert bc.decrypt_cookie_value(blob, os.urandom(32)) is None
|
|
|
|
|
|
def test_every_chromium_root_resolves_under_localappdata():
|
|
"""Path resolution is the other half a Mac cannot check: these must sit under the real
|
|
LOCALAPPDATA, not a POSIX-shaped guess."""
|
|
local = os.environ.get("LOCALAPPDATA", "")
|
|
assert local, "LOCALAPPDATA is unset; the roots below would silently fall back"
|
|
for name in ("Chrome", "Brave", "Edge"):
|
|
root = bc.CHROMIUM_ROOTS[name]
|
|
assert root.startswith(local), f"{name} root {root!r} is not under {local!r}"
|
|
assert "\\" in root, f"{name} root {root!r} is not a Windows path"
|