Skip to content

[Technical-Track] Close OI-ECDH-persistence — Wrap session_key in SecureKeyBuffer #3

Description

@Cortex-psylead

Labels: Technical-Track · security · good-first-issue · v0.5.2
Priority: Medium — Known security gap, ~1h fix
Estimated effort: ~30–50 lines of Python
Skill level required: Beginner-Intermediate Python, basic understanding of memory security


The problem

After SensorCertificationAuthority.full_handshake() completes (implemented in v0.5.1, item J3.2), the derived X25519 session key is returned as a plain Python bytes object:

# src/keros/sensor_channel.py — current behavior
session_key: bytes = hkdf_derive(shared_secret, salt, info, length=32)
return session_key  # ← lives on CPython heap, NOT mlock'd

A Python bytes object on the CPython heap is not covered by mlock(2). This means:

  • The OS can swap it to disk during memory pressure
  • A memory forensics tool could extract it from a swap partition
  • The key may persist in memory after the session ends (GC is non-deterministic)

This is documented in the v0.5.1 backlog as [OI-ECDH-persistence]:

"After full_handshake(), the derived session_key is a Python bytes object on the CPython heap. mlock does not cover it without wrapping in SecureKeyBuffer. To fully close: wrap session_key in SecureKeyBuffer and pass as export_bytearray() to callers. Estimate: ~1h."


The fix

SecureKeyBuffer already exists in src/sal/state_buffer_secure.py. It provides:

  • mlock(2) pinning (prevents swap)
  • ctypes volatile zeroing on __exit__ (deterministic destruction)
  • Constant-time comparison via hmac.compare_digest
  • export_bytearray() for use with hmac module

The fix is to use it in sensor_channel.py:

# BEFORE (current — insecure)
def full_handshake(self, ...):
    ...
    session_key: bytes = hkdf_derive(...)
    return {"session_key": session_key, "status": "ok"}

# AFTER (target)
from src.sal.state_buffer_secure import SecureKeyBuffer

def full_handshake(self, ...):
    ...
    raw_key: bytes = hkdf_derive(...)
    secure_key = SecureKeyBuffer(raw_key)
    # caller is responsible for secure_key.__exit__() when session ends
    return {"session_key": secure_key, "status": "ok"}

All callers that currently use session_key as bytes for HMAC operations must be updated to use secure_key.export_bytearray():

# BEFORE
hmac.new(session_key, data, hashlib.sha256).digest()

# AFTER
with secure_key.export_bytearray() as key_bytes:
    hmac.new(bytes(key_bytes), data, hashlib.sha256).digest()

Files to modify

File Change
src/keros/sensor_channel.py Wrap HKDF output in SecureKeyBuffer in perform_ecdh_handshake() and full_handshake()
src/sal/cognitive_shield_v2.py Update any caller that unpacks result["session_key"] as bytes
tests/ (SAL vectors) Update test vectors that assert isinstance(session_key, bytes) — should now assert isinstance(session_key, SecureKeyBuffer)

Constraints

  • SecureKeyBuffer.__exit__() zeroes the key — ensure the buffer's lifetime covers all HMAC operations in the session
  • The existing 92 SAL test vectors and 70 governance tests must still pass
  • All tests must pass with python -W error (zero warnings)
  • If mlock is unavailable (non-Linux or no permissions), SecureKeyBuffer gracefully degrades — this behavior is already implemented and must be preserved

How to verify the fix

After your change, run:

python src/sal/cognitive_shield_v2.py --test    # 92 vectors, 0 failures
python tests/test_cognitive_neutrality.py        # 70 tests, 0 failures
python -m src.keros.sensor_channel               # 7 tests, 0 failures

And verify that session_key is no longer a plain bytes in the return value of full_handshake().


Resources

  • src/sal/state_buffer_secure.pySecureKeyBuffer implementation (lines ~6379–6450)
  • src/keros/sensor_channel.pyperform_ecdh_handshake(), full_handshake() (lines ~8986+)
  • docs/security/SECURITY.md §3 — Anatomical Privacy and memory zeroing policy
  • Python hmac.compare_digest docs for constant-time comparison

Acceptance criteria

  • full_handshake() returns SecureKeyBuffer not bytes for session_key
  • All HMAC operations use export_bytearray() — no plain bytes extraction
  • SecureKeyBuffer.__exit__() is called at session end (verify in test)
  • 92 + 70 = 162 test vectors pass with python -W error
  • OPEN_ISSUES.md entry [OI-ECDH-persistence] updated to CLOSED — v0.5.2

This is an ideal first security contribution — well-scoped, with a clear fix path and existing infrastructure to build on.
Questions? Comment here with tag [Technical-Track].

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions