mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
feat(toolbox-ng): wire mac_hash into clientHashFromConn + Python parity (ref #662)
clientHashFromConn now resolves the peer IP via macHashOf (WG persona hash, byte-identical to Python for 10.99.1.0/24), falling back to the raw peer IP for non-WG/test conns so poison stays deterministic. Updated the TODO block: WG mac_hash wiring DONE; remaining gap is only the transparent original-dst plumbing (Deliverable 2) and the intentionally-out-of-scope R0-R2 ARP path. test_machash_parity.py drives _common.mac_hash_of on the SAME fixtures; both engines agree. Anti-rig verified on the Python side too.
This commit is contained in:
parent
5fb67f5b88
commit
67e85ba4dd
|
|
@ -159,21 +159,36 @@ func (p *Policy) shouldPoison(host string) bool {
|
|||
// clientHashFromConn returns the per-client identity used to mint the stable
|
||||
// fake persona (jar fakeID first arg).
|
||||
//
|
||||
// PoC / CONNECT path: this is the peer IP string. A real TRANSPARENT R3 deploy
|
||||
// MUST replace this with the mac_hash the Python addon uses
|
||||
// (privacy_guard._client_hash → _common.mac_hash_of(peer_ip)), resolved via the
|
||||
// SO_ORIGINAL_DST original-destination socket option and the WireGuard-peer →
|
||||
// MAC map. Using the raw peer IP here is NOT identity-stable across NAT/DHCP
|
||||
// and is intentionally a Phase-6-cutover TODO, not a shipped behaviour.
|
||||
// It mirrors the Python privacy_guard._client_hash → _common.mac_hash_of(peer_ip)
|
||||
// for the WireGuard R3 path: the peer IP is resolved to the WG persona hash
|
||||
// (sha256(peer_pubkey)[:16]) by macHashOf. For 10.99.1.0/24 WG peers that hash
|
||||
// is byte-identical to the Python engine (proven in machash_test.go ↔
|
||||
// test_machash_parity.py), so a flow's fake persona is stable across the Go and
|
||||
// Python engines and across restarts.
|
||||
//
|
||||
// TODO(#662 Phase 6): wire mac_hash via SO_ORIGINAL_DST + WG-peer map.
|
||||
// macHashOf returns "" for any IP it cannot resolve (non-WG peers, the captive
|
||||
// R0-R2 ARP path which is out of scope for this R3 engine, missing WG DB). In
|
||||
// that case we fall back to the raw peer IP so non-WG / test conns still get a
|
||||
// deterministic seed and poison remains functional — the fallback value is just
|
||||
// not cross-engine-stable, which is acceptable for non-R3 traffic.
|
||||
//
|
||||
// DONE(#662): mac_hash wiring for the WG path. Remaining gaps, intentionally NOT
|
||||
// addressed here:
|
||||
// - the transparent original-dst plumbing that feeds the *real* peer IP into
|
||||
// this function lives in transparent.go (handleTransparent); the CONNECT PoC
|
||||
// still sees the proxy-hop peer IP.
|
||||
// - the R0-R2 captive-subnet ARP/HMAC branch of _common.mac_hash_of is out of
|
||||
// scope (this engine is WG-only — see machash.go macHashOf).
|
||||
func clientHashFromConn(conn net.Conn) string {
|
||||
if conn == nil {
|
||||
return ""
|
||||
}
|
||||
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return conn.RemoteAddr().String()
|
||||
host = conn.RemoteAddr().String()
|
||||
}
|
||||
if mh := macHashOf(host); mh != "" {
|
||||
return mh
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
|
|
|||
87
packages/secubox-toolbox/tests/test_machash_parity.py
Normal file
87
packages/secubox-toolbox/tests/test_machash_parity.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
"""Cross-engine mac_hash (WG persona identity) parity harness — Python side
|
||||
(#662 Phase 6 prep).
|
||||
|
||||
Loads the SAME ``machash-fixtures.json`` + ``wg-peers-fixture.json`` the Go core
|
||||
uses (``../secubox-toolbox-ng/testdata``), points ``_common._WG_PEERS_DB`` at the
|
||||
fixture WG DB (NOT the real ``/var/lib/secubox/toolbox/wg-peers.json``), resets
|
||||
the WG cache, and asserts ``_common.mac_hash_of`` == each fixture's ``expected``.
|
||||
|
||||
Python is the source of truth: the ``expected`` values were GENERATED by
|
||||
``sha256(pubkey.encode()).hexdigest()[:16]`` (the very algorithm
|
||||
``_common._wg_hash_of`` runs). The Go side (machash_test.go) must reproduce them
|
||||
byte-for-byte. Both files reading identical inputs is what makes the parity
|
||||
meaningful (and non-circular).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from mitmproxy_addons import _common
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
# tests/ → packages/secubox-toolbox → packages → packages/secubox-toolbox-ng
|
||||
_NG_TESTDATA = os.path.normpath(
|
||||
os.path.join(_HERE, "..", "..", "secubox-toolbox-ng", "testdata"))
|
||||
_FIXTURES = os.path.join(_NG_TESTDATA, "machash-fixtures.json")
|
||||
|
||||
|
||||
def _load():
|
||||
with open(_FIXTURES, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def wg_env(monkeypatch):
|
||||
"""Point _common at the fixture WG DB and reset the mtime cache so the
|
||||
override is (re)read. Mirrors exactly the (path, cache, mtime) surface the
|
||||
Go wgHashOf reads (wgPeersPath + resetWGCache)."""
|
||||
data = _load()
|
||||
wg_path = os.path.join(_NG_TESTDATA, data["wg_peers_file"].replace("/", os.sep))
|
||||
monkeypatch.setattr(_common, "_WG_PEERS_DB", Path(wg_path))
|
||||
monkeypatch.setattr(_common, "_WG_PEERS_MTIME", 0.0)
|
||||
_common._WG_PEERS_CACHE.clear()
|
||||
return data
|
||||
|
||||
|
||||
def test_machash_parity(wg_env):
|
||||
failures = []
|
||||
for fx in wg_env["fixtures"]:
|
||||
# _common returns None where Go returns ""; normalise None → "".
|
||||
got = _common.mac_hash_of(fx["ip"]) or ""
|
||||
if got != fx["expected"]:
|
||||
failures.append(
|
||||
f"mac_hash_of({fx['ip']!r})={got!r} want {fx['expected']!r}"
|
||||
f" ({fx.get('why')})")
|
||||
assert not failures, "Python↔fixture mac_hash parity mismatches:\n" + "\n".join(failures)
|
||||
|
||||
|
||||
def test_machash_coverage(wg_env):
|
||||
# The fixtures must exercise the discriminating cases, else parity is vacuous.
|
||||
resolved = subnet_miss = off_subnet = empty = False
|
||||
for fx in wg_env["fixtures"]:
|
||||
ip, exp = fx["ip"], fx["expected"]
|
||||
if ip == "":
|
||||
empty = True
|
||||
elif exp != "":
|
||||
resolved = True
|
||||
elif ip.startswith("10.99.1."):
|
||||
subnet_miss = True
|
||||
else:
|
||||
off_subnet = True
|
||||
assert resolved and subnet_miss and off_subnet and empty, (
|
||||
f"coverage incomplete: resolved={resolved} subnet_miss={subnet_miss} "
|
||||
f"off_subnet={off_subnet} empty={empty}")
|
||||
|
||||
|
||||
def test_machash_missing_db_fail_open(wg_env):
|
||||
# A missing WG DB fails open to None (best-effort), never raises.
|
||||
_common._WG_PEERS_DB = Path("/nonexistent/secubox/wg-peers.json")
|
||||
_common._WG_PEERS_MTIME = 0.0
|
||||
_common._WG_PEERS_CACHE.clear()
|
||||
assert _common.mac_hash_of("10.99.1.10") is None
|
||||
Loading…
Reference in New Issue
Block a user