Skip to content

feat: steganographic config watermarking — HMAC-SHA384 provenance tracking per delivery #40

Description

@nielsweistra

Problem

Config files delivered to field devices have no verifiable provenance — a forensic examiner cannot determine which specific config, from which specific server instance, was delivered to a compromised device.

Solution — Steganographic Config Watermarking

Each delivered config receives a unique, verifiable HMAC-SHA384 watermark embedded in a non-operational field. The server can identify any config retrieved from a captured device.

Watermark Construction

# services/watermark.py
def apply_watermark(config: dict, machine_id: str, ek_fingerprint: str, nonce: str) -> dict:
    watermark = hmac_sha384(
        key=settings.watermark_secret,
        msg=f"{machine_id}:{ek_fingerprint}:{nonce}:{utcnow_iso()}"
    )
    config["_itl_wm"] = watermark[:32]   # 256-bit prefix, not full hash
    return config

Registry

# models/watermark.py
class WatermarkRegistry(Base):
    id: uuid
    machine_id: uuid
    ek_fingerprint: str
    watermark_prefix: str    # stored — the 32-char prefix embedded in config
    delivery_nonce: str
    delivered_at: datetime
    config_hash: str         # SHA-384 of the plaintext config before encryption

Identification

POST /api/v1/forensics/watermark/identify
Authorization: Bearer <forensic_admin>
{ "watermark_fragment": "base64-fragment-from-captured-config" }

Server brute-forces the registry for matching prefix → returns machine identity, delivery time, operator.

Key Rotation

ITL_WATERMARK_SECRET rotated quarterly. Old registry entries remain verifiable against their archived key version.

MITRE ATT&CK

  • T1005 — Data from Local System (source tracing)
  • T1041 — Exfiltration Over C2 Channel (watermark identifies leak source)

Files

  • New: services/watermark.py
  • New: models/watermark.py
  • New: routers/forensics.pyPOST /forensics/watermark/identify
  • Extend: handlers/config_delivery.py — apply watermark on every delivery
  • New env var: ITL_WATERMARK_SECRET, ITL_WATERMARK_KEY_VERSION
  • Alembic migration: add_watermark_registry_table

Acceptance Criteria

  • Every config delivery inserts a WatermarkRegistry row
  • POST /forensics/watermark/identify returns correct machine + delivery time for a valid fragment
  • Unknown fragment → 404
  • Watermark field _itl_wm does not break MachineConfig schema validation
  • ITL_WATERMARK_ENABLED=false disables watermarking (default true)
  • Test: deliver config → extract watermark → identify returns correct machine

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort:8Effort score 8 (Fibonacci)impact:2Impact score 2 (Fibonacci)military-gradesecuritySecurity vulnerability or hardeningtype:storyUser story -- a piece of deliverable value

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions