Chapter 15 — Quantum and post-quantum security: guided lab
Lesson map
Lesson 15 published. Map, example, EX029.
flowchart TD
N0["Learning outcomes"]
N1["15.1 Lab environment"]
N2["15.2 Practical activity — Lab V"]
N3["15.3 Lab records"]
N4["15.4 Negative controls"]
N5["15.7 Lab hygiene"]
N6["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Worked example"]
EX --> Q["Exercise EX029"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["15.1 Lab environment"]
N2["15.2 Practical activity — Lab V"]
N3["15.3 Lab records"]
N4["15.4 Negative controls"]
N5["15.7 Lab hygiene"]
N6["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX029"]
[Learning outcomes]
|
v
[15.1 Lab environment]
|
v
[15.2 Practical activity — Lab V]
|
v
[15.3 Lab records]
|
v
[15.4 Negative controls]
|
v
[15.7 Lab hygiene]
|
v
[Synthesis]
|
v
[Worked example] --> [Exercise EX029]
Learning outcomes
You will (1) run known-answer and negative tests; (2) build a reproducible lab environment for PQC configuration; (3) record evidence properly; and (4) use negative controls.
15.1 Lab environment
Build an isolated lab: two VMs or containers (server and client) with a current TLS library supporting ML-KEM, a capture point on the lab network, and scripts to run handshakes and record outcomes. No production data; no connection to production networks.
KLS's lab is two containers on an isolated bridge network, a server image with OpenSSL 3.5 and NGINX and a client image holding four TLS libraries, plus a third container running tcpdump on the bridge. Everything is built from a script in the lab repository, so the environment can be destroyed and recreated in minutes.
15.2 Practical activity — Lab V
# Lab V (ch 15): known-answer and negative tests - the habit behind validating PQC libraries.
import hashlib
KATS = [ # (function, input, expected hex prefix) - published test values for SHA-3 / SHAKE on empty input
("sha3_256", b"", "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"),
("shake_128/32", b"", "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26"),
]
def run(name, data):
if name == "sha3_256": return hashlib.sha3_256(data).hexdigest()
return hashlib.shake_128(data).hexdigest(32)
for name, data, want in KATS:
print(f"KAT {name:13s}", "PASS" if run(name, data) == want else "FAIL")
SIZES = {"ML-KEM-512": 800, "ML-KEM-768": 1184, "ML-KEM-1024": 1568} # encapsulation-key bytes, FIPS 203
def validate_ek(param, ek):
if len(ek) != SIZES[param]: raise ValueError(f"{param}: expected {SIZES[param]} bytes, got {len(ek)}")
return True
for param, n in [("ML-KEM-768", 1184), ("ML-KEM-768", 1183), ("ML-KEM-512", 1184)]:
try: validate_ek(param, bytes(n)); print(f"negative/positive test {param} len {n}: accepted")
except ValueError as e: print(f"negative/positive test {param} len {n}: rejected ({e})")
Expected output:
KAT sha3_256 PASS
KAT shake_128/32 PASS
negative/positive test ML-KEM-768 len 1184: accepted
negative/positive test ML-KEM-768 len 1183: rejected (ML-KEM-768: expected 1184 bytes, got 1183)
negative/positive test ML-KEM-512 len 1184: rejected (ML-KEM-512: expected 800 bytes, got 1184)
Known-answer tests confirm primitives match references; negative tests confirm malformed inputs are rejected. Libraries supporting FIPS 203 should also reject encapsulation keys whose coefficients are out of range.
The lab has two halves. The known-answer tests compare SHA3-256 and SHAKE128 on empty input with published digests; a PASS shows the platform's hash functions match the standard. The length checks then exercise validation: a correct 1,184-byte ML-KEM-768 key is accepted, a 1,183-byte key is rejected, and a 1,184-byte value labelled ML-KEM-512 is rejected because the label demands 800 bytes. That third case is the one teams forget, a correct length for the wrong parameter set. Each rejection message names the parameter set, expected length and actual length, which is what an operator needs in a log.
15.3 Lab records
Record versions, configuration hashes, commands, outputs and interpretations. Store them in version control next to scripts. A lab result you cannot reproduce is an anecdote.
A KLS lab record is a Markdown file named by date and question, with fields for library versions, image digests, the SHA-256 of each configuration file, exact commands, raw output and a short interpretation. The record and the script that produced it are committed together, so a reviewer can rerun the commands and compare outputs line by line.
15.4 Negative controls
Before trusting a check, show it can fail: configure the server without the hybrid group and confirm your script reports classical; flip a byte in a KAT and confirm FAIL.
KLS adds a third control for the handshake script: a client that cannot speak the hybrid group must still be reported as classical, not as an error. A script passing all three cases has shown it can distinguish hybrid, classical and broken; one that only ever printed "hybrid" has shown nothing.
15.5 Worked example
KLS lab record "LAB-015": server OpenSSL 3.5.x, client matrix of four libraries, script handshake.sh printing negotiated group; results: three hybrid, one classical (older Java client, expected). Negative control: server without hybrid → script printed classical for all four. Interpretation: configuration correct; Java client upgrade required for hybrid.
The record also shows what the result is good for. Three hybrid negotiations confirm the server profile; the classical Java result is predicted by its library version, so it becomes an upgrade ticket rather than a bug. The negative control proves the script would have noticed a missing group. LAB-015 is cited in the change request that later moved the profile to production.
15.6 Common errors and limits
- Labs connected to production.
- Results without versions.
- Checks never shown to fail.
KLS saw each one. A lab VM given a route to production for convenience could reach real services, so the lab network was rebuilt without any route out. A result written up as "hybrid works with Java" had no versions attached and could not be reproduced after the Java image changed. And a handshake checker that printed "hybrid" for every server turned out to read the configuration file, not the negotiation; it had never been shown a classical server, so nobody noticed.
15.7 Lab hygiene
Tear down and rebuild the lab from scripts regularly so that it never drifts into an undocumented state. Pin package versions, record image digests and keep secrets used in the lab separate from any production secret store. When a lab produces a surprising result, rebuild from scratch and repeat before drawing conclusions; many apparent library bugs turn out to be leftovers from an earlier experiment.
Finally, share the lab. A lab that other teams can clone lets application owners test their own clients against the hybrid-enabled staging gateway before rollout, which finds incompatibilities earlier and spreads the work beyond the platform team.
Exercises
- EX029 — Negative control. Design a negative control for a script that checks SSH KEX negotiation. Rubric: 3 = server configured without hybrid KEX, expect classical.
- EX030 — Record fields. List six fields of a lab record. Rubric: 3 = six relevant fields.
Synthesis
Isolated, reproducible labs with known-answer tests, negative tests and negative controls produce evidence that configurations work as intended.
Continue studying — chapter 15 of 30
← Chapter 14 (draft) · Course chapter index · Chapter 16 (draft) →