Add Phase 1 utility modules: crypto, networking, logging, stealth, resource, permissions, config_loader, bettercap_api
9 files in utils/ providing the shared infrastructure layer: - crypto: AES-256-GCM file encryption, argon2id/PBKDF2 key derivation, HKDF network unlock, LUKS container management - networking: interface detection, MAC/IP helpers, BPF compilation via libpcap ctypes, gratuitous ARP, VLAN creation - logging: encrypted log writer (BBLogger) with rotation, per-module files, stdout suppression for stealth - stealth: process rename via prctl, cmdline spoofing, sysctl helpers, timestomping, core dump disable - resource: hardware tier detection (OPi Zero 3 / Pi Zero / generic) via /proc/device-tree and cpuinfo, resource monitoring - permissions: root/capability checks via capget ctypes, privilege dropping, directory permission enforcement - config_loader: YAML merge hierarchy (hardware_tiers -> bigbrother -> modules -> stealth -> CLI), tier auto-detection, SIGHUP reload - bettercap_api: REST client with per-session random credentials, session/events/command methods
This commit is contained in:
+189
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""AES-256-GCM encryption, key derivation, and LUKS container management."""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
try:
|
||||
from argon2.low_level import hash_secret_raw, Type
|
||||
HAS_ARGON2 = True
|
||||
except ImportError:
|
||||
HAS_ARGON2 = False
|
||||
|
||||
NONCE_SIZE = 12
|
||||
KEY_SIZE = 32
|
||||
SALT_SIZE = 16
|
||||
HEADER_MAGIC = b"BB01"
|
||||
HEADER_FMT = f"!4s{SALT_SIZE}s{NONCE_SIZE}s"
|
||||
HEADER_SIZE = struct.calcsize(HEADER_FMT)
|
||||
|
||||
|
||||
def derive_key(password: bytes, salt: bytes, method: str = "argon2id") -> bytes:
|
||||
if method == "argon2id" and HAS_ARGON2:
|
||||
return hash_secret_raw(
|
||||
secret=password,
|
||||
salt=salt,
|
||||
time_cost=3,
|
||||
memory_cost=65536,
|
||||
parallelism=4,
|
||||
hash_len=KEY_SIZE,
|
||||
type=Type.ID,
|
||||
)
|
||||
return PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=KEY_SIZE,
|
||||
salt=salt,
|
||||
iterations=600_000,
|
||||
).derive(password)
|
||||
|
||||
|
||||
def derive_network_key(network_secret: bytes, device_serial: bytes) -> bytes:
|
||||
return HKDF(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=KEY_SIZE,
|
||||
salt=device_serial,
|
||||
info=b"bigbrother-luks-unlock",
|
||||
).derive(network_secret)
|
||||
|
||||
|
||||
class CryptoEngine:
|
||||
def __init__(self, key: bytes):
|
||||
if len(key) != KEY_SIZE:
|
||||
raise ValueError(f"Key must be {KEY_SIZE} bytes")
|
||||
self._aesgcm = AESGCM(key)
|
||||
self._key = key
|
||||
|
||||
def encrypt(self, plaintext: bytes, aad: Optional[bytes] = None) -> bytes:
|
||||
nonce = os.urandom(NONCE_SIZE)
|
||||
ct = self._aesgcm.encrypt(nonce, plaintext, aad)
|
||||
return nonce + ct
|
||||
|
||||
def decrypt(self, data: bytes, aad: Optional[bytes] = None) -> bytes:
|
||||
nonce = data[:NONCE_SIZE]
|
||||
ct = data[NONCE_SIZE:]
|
||||
return self._aesgcm.decrypt(nonce, ct, aad)
|
||||
|
||||
|
||||
def encrypt_file(
|
||||
src: str,
|
||||
dst: str,
|
||||
password: bytes,
|
||||
method: str = "argon2id",
|
||||
chunk_size: int = 64 * 1024,
|
||||
) -> None:
|
||||
salt = os.urandom(SALT_SIZE)
|
||||
key = derive_key(password, salt, method)
|
||||
engine = CryptoEngine(key)
|
||||
|
||||
with open(src, "rb") as fin:
|
||||
plaintext = fin.read()
|
||||
|
||||
nonce = os.urandom(NONCE_SIZE)
|
||||
aesgcm = AESGCM(key)
|
||||
ct = aesgcm.encrypt(nonce, plaintext, None)
|
||||
|
||||
with open(dst, "wb") as fout:
|
||||
fout.write(struct.pack(HEADER_FMT, HEADER_MAGIC, salt, nonce))
|
||||
fout.write(ct)
|
||||
|
||||
|
||||
def decrypt_file(
|
||||
src: str,
|
||||
dst: str,
|
||||
password: bytes,
|
||||
method: str = "argon2id",
|
||||
) -> None:
|
||||
with open(src, "rb") as fin:
|
||||
header = fin.read(HEADER_SIZE)
|
||||
magic, salt, nonce = struct.unpack(HEADER_FMT, header)
|
||||
if magic != HEADER_MAGIC:
|
||||
raise ValueError("Invalid encrypted file header")
|
||||
ct = fin.read()
|
||||
|
||||
key = derive_key(password, salt, method)
|
||||
aesgcm = AESGCM(key)
|
||||
plaintext = aesgcm.decrypt(nonce, ct, None)
|
||||
|
||||
with open(dst, "wb") as fout:
|
||||
fout.write(plaintext)
|
||||
|
||||
|
||||
class LUKSContainer:
|
||||
def __init__(self, container_path: str, mount_point: str, mapper_name: str = "bb_storage"):
|
||||
self.container_path = container_path
|
||||
self.mount_point = mount_point
|
||||
self.mapper_name = mapper_name
|
||||
self.device_path = f"/dev/mapper/{mapper_name}"
|
||||
|
||||
def create(self, size_mb: int, passphrase: bytes) -> bool:
|
||||
try:
|
||||
subprocess.run(
|
||||
["dd", "if=/dev/zero", f"of={self.container_path}",
|
||||
"bs=1M", f"count={size_mb}"],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
proc = subprocess.run(
|
||||
["cryptsetup", "luksFormat", "--type", "luks2",
|
||||
"--cipher", "aes-xts-plain64", "--key-size", "512",
|
||||
"--hash", "sha256", "--batch-mode", self.container_path],
|
||||
input=passphrase + b"\n",
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def open(self, passphrase: bytes) -> bool:
|
||||
try:
|
||||
subprocess.run(
|
||||
["cryptsetup", "open", self.container_path,
|
||||
self.mapper_name, "--type", "luks2"],
|
||||
input=passphrase + b"\n",
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
Path(self.mount_point).mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
["mount", self.device_path, self.mount_point],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def close(self) -> bool:
|
||||
try:
|
||||
subprocess.run(
|
||||
["umount", self.mount_point],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["cryptsetup", "close", self.mapper_name],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def destroy_header(self) -> bool:
|
||||
try:
|
||||
subprocess.run(
|
||||
["dd", "if=/dev/urandom", f"of={self.container_path}",
|
||||
"bs=1M", "count=2", "conv=notrunc"],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_open(self) -> bool:
|
||||
return Path(self.device_path).exists()
|
||||
Reference in New Issue
Block a user