# Chapter 10 Labs: Cryptography Theory and Practice (Advanced)

This chapter has six hands-on labs covering AES internals, CBC padding oracles, RSA
cryptanalysis, TLS auditing, post-quantum hybrid handshakes, and crypto inventory.
Four labs run entirely in Docker; one (Lab (d)) uses a host-side Bash tool; and two
(Labs 1 and 3) are pure Python scripts you run inside the provided `tools` container.

## Prerequisites

- Docker Desktop 4.x or later (or Docker Engine + Compose plugin v2.x)
- At least 4 GB free disk space (the PQ-TLS build is large)
- For Lab (d): Bash + OpenSSL on the host; git to clone testssl.sh
- For Labs 1 and 3: all Python dependencies are installed automatically inside the container

## Quick Start

```bash
# Start the padding-oracle server (Lab 2 target)
docker compose up -d oracle

# Open the Python workspace (Labs 1, 2 attacker, 3)
docker compose run --rm tools

# Start the post-quantum TLS server (Lab (e)) -- first build is slow (10-20 min)
docker compose up -d pq-server

# Run the CBOM scanner (Lab 6) and print results
docker compose run --rm cbom
```

---

## Lab 1: Implement AES-CTR from scratch and validate against NIST vectors

**Objective:** Build AES-128 from its four byte-level transforms and wrap it in
CTR mode; validate against the published SP 800-38A test vectors.

**Setup:** Open the Python workspace:

```bash
docker compose run --rm tools
```

The container has `cryptography`, `sympy`, and a plain Python 3.12 REPL.

**Walkthrough:**

1. Implement `xtime(b)`: left-shift one bit, XOR with `0x11B` if the top bit was set.
2. Build `gf_mul(a, b)` from repeated `xtime` and XOR.
3. Generate the 256-entry S-box: for each byte compute the GF(2^8) multiplicative
   inverse, then apply the affine transform from FIPS 197 section 5.1.1.
4. Implement the four transforms: `SubBytes`, `ShiftRows`,
   `MixColumns` (coefficient vector `[0x02, 0x01, 0x01, 0x03]`), `AddRoundKey`.
5. Implement the AES-128 key schedule (10 rounds, FIPS 197 section 5.2).
6. Assemble the cipher: initial `AddRoundKey`, 9 full rounds, then a final round
   that **omits MixColumns** (SubBytes, ShiftRows, AddRoundKey only).
7. Wrap in CTR mode: encrypt a counter block, XOR into plaintext.
   Decryption is identical.
8. Validate: use the NIST SP 800-38A Appendix F.5 test vector:
   - Key:   `2b7e151628aed2a6abf7158809cf4f3c`
   - IV/Counter: `f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff`
   - Plaintext block 1:  `6bc1bee22e409f96e93d7e117393172a`
   - Expected ciphertext: `874d6191b620e3261bef6864990db6ce`

   If your output differs, check two common mistakes: using `0x1B` instead of
   `0x11B` in field reduction, or running MixColumns in the final round.

**Expected output:** byte-exact match with the SP 800-38A F.5 vector.

**Verify completion:** Print `assert ct_block1.hex() == "874d6191b620e3261bef6864990db6ce"` -
if no AssertionError, the implementation is correct.

**Cleanup:** Exit the container with `exit` or Ctrl-D.

---

## Lab 2: CBC padding-oracle attack in Docker

**Objective:** Recover the plaintext of an AES-CBC ciphertext using only a
one-bit "valid/invalid padding" oracle -- no key, no decryption.

**Setup:** Start the oracle server:

```bash
docker compose up -d oracle
```

Verify it is up: `curl http://localhost:5000/target` should return JSON with `iv` and `ct`.

**Walkthrough:**

1. Fetch the target:
   ```bash
   docker compose run --rm tools bash -c \
     "python /labs/padding-oracle/attacker.py --url http://oracle:5000"
   ```
   The attacker script is at `padding-oracle/attacker.py`.

2. Study the attack logic in `attacker.py`:
   - For a single 16-byte ciphertext block `C_i` with preceding block `C_{i-1}` as IV:
   - The decrypted value is `P_i = D_K(C_i) XOR C_{i-1}`.
   - For the last byte, try all 256 values for `crafted_iv[-1]`.
   - Ask the oracle: does this decrypt to valid PKCS#7 padding?
   - When the answer is "yes" with `0x01` padding, `D_K(C_i)[-1] = guess XOR 0x01`.
   - Recover the full plaintext byte by byte, working from right to left.

3. Watch the script print recovered intermediate bytes and assemble the plaintext.

4. Write one paragraph explaining: why does this attack work even though the attacker
   never sees the key? What does "valid padding" reveal about `D_K(C_i)`?

**Expected output:**
```
[+] Recovered plaintext (ASCII): TARGETPLAINTXT!!
```

**Verify completion:** The ASCII output matches the oracle server's hardcoded plaintext.

**Cleanup:**
```bash
docker compose stop oracle
```

**Safety note:** This lab runs only against the provided `oracle` container on the
lab network. Do NOT point the attacker at any server you do not own or are not
explicitly authorised to test.

---

## Lab 3: Recover an RSA plaintext -- Hastad broadcast vs the common-modulus attack

**Objective:** Implement Hastad's broadcast attack to recover an RSA plaintext
encrypted with exponent e=3 to three recipients, using only public data.

**Setup:** Open the Python workspace:

```bash
docker compose run --rm tools
```

`sympy` is available for arbitrary-precision integer cube roots and CRT.

**Walkthrough:**

1. Generate three distinct 512-bit RSA moduli `n1, n2, n3` (pairwise coprime, each
   larger than your plaintext `m`), each with public exponent `e = 3`.
2. Encode a short message (e.g., `b"HELLO"`) as an integer `m`.
3. Compute ciphertexts: `c1 = pow(m, 3, n1)`, `c2 = pow(m, 3, n2)`, `c3 = pow(m, 3, n3)`.
4. Apply CRT to combine `(c1 mod n1, c2 mod n2, c3 mod n3)` into `M3 mod (n1*n2*n3)`.
   Because `m < ni` for each i, `m^3 < n1*n2*n3`, so the CRT result IS `m^3` as an
   ordinary integer with no modular wrap-around.
5. Take the integer cube root of `M3` to recover `m`.
   Tip: `sympy.integer_nthroot(M3, 3)` returns `(root, exact_bool)`.
6. Decode `m` back to bytes and print.

**Contrast in your write-up (do not implement):** the common-modulus attack uses a
SINGLE modulus shared across two users with coprime exponents `e1, e2`. The attacker
computes `alpha*e1 + beta*e2 = 1` via extended Euclid, then `m = c1^alpha * c2^beta mod n`.
It needs no small exponent; the flaw is sharing one modulus. Keep Hastad and
common-modulus distinct -- they are routinely conflated.

**Expected output:** the recovered `m` decoded from bytes matches the original message.

**Verify completion:** `assert recovered_bytes == original_message`

**Cleanup:** Exit the container.

---

## Lab (d): Audit three TLS deployments with testssl.sh

**Host setup (not containerised):** testssl.sh is a Bash script that speaks
directly to TLS endpoints. It runs on the host alongside Docker.

**Authorisation -- this is not optional:** testssl.sh opens real connections and
probes for vulnerabilities. Run it only against (a) domains you own, (b) hosts you
have written permission to test, or (c) deliberately public test endpoints such as
`badssl.com`. Scanning third-party hosts without permission can be treated as an
attack. When in doubt, scan the `pq-server` container from Lab (e) -- it is your
own server, no external authorisation needed.

**Install testssl.sh (pinned to v3.2.3):**

```bash
git clone --branch v3.2.3 --depth 1 https://github.com/testssl/testssl.sh.git
cd testssl.sh
./testssl.sh --version   # should print 3.2.3
```

No build step; testssl.sh ships its own OpenSSL binary under `bin/`.

**Walkthrough:**

1. Scan your first authorised target:
   ```bash
   ./testssl.sh https://your-authorised-target.example
   ```
   Read the Protocols block: a healthy modern server shows TLS 1.3 and TLS 1.2
   and refuses TLS 1.0/1.1 and SSLv3. Read the Forward Secrecy line.

2. Focus the scan:
   ```bash
   ./testssl.sh --protocols https://your-target
   ./testssl.sh --vulnerable https://your-target    # or -U
   ```
   On a TLS 1.3-only server, all CBC-era vulnerabilities (POODLE, BEAST, Lucky 13)
   should report "not vulnerable" because the modes that enabled them were removed.

3. Machine-readable output:
   ```bash
   ./testssl.sh --jsonfile out.json https://your-target
   ```

4. Repeat against three authorised targets with different postures (a modern CDN,
   a self-hosted server, an intentionally weak test endpoint such as
   `https://tls-v1-2.badssl.com`).

5. Write two sentences per target explaining WHY each result follows from the
   protocol design -- that is the deliverable, not the raw log. Example: "POODLE
   does not apply because TLS 1.3 removed CBC-mode record encryption entirely, so
   there is no padding byte for the oracle to observe."

**Expected output:** a `--jsonfile` per target and a written explanation paragraph
for each.

**Verify completion:** at least one target shows TLS 1.3 in the Protocols block and
all CBC-era vulnerabilities report "not vulnerable."

---

## Lab (e): A post-quantum hybrid TLS handshake, end to end

**Objective:** Stand up a TLS 1.3 server that negotiates the hybrid group
X25519MLKEM768 (NamedGroup 0x11EC), connect to it, and measure the handshake
overhead vs. a classical X25519 baseline.

**Setup:** Build and start the post-quantum server:

```bash
docker compose up -d pq-server
# First build downloads and compiles liboqs 0.15.0 + oqs-provider 0.11.0 -- allow 10-20 min
# Watch progress with: docker compose logs -f pq-server
```

**Walkthrough:**

1. Confirm the hybrid group is available in the server container:
   ```bash
   docker compose exec pq-server \
     openssl list -kem-algorithms -provider oqsprovider | grep -i mlkem
   ```
   You should see `X25519MLKEM768` listed.

2. Connect from the host using `openssl s_client` (if it has a recent-enough
   OpenSSL, or use a second `pq-server` container as client):
   ```bash
   openssl s_client -connect localhost:4433 -groups X25519MLKEM768
   ```
   The handshake output should include the negotiated group `X25519MLKEM768`.

3. Run the server inside the container manually to experiment:
   ```bash
   docker compose exec pq-server bash
   # Inside the container:
   openssl s_server -groups X25519MLKEM768 \
     -cert /usr/local/oqs/server.crt \
     -key  /usr/local/oqs/server.key \
     -www -accept 4433
   ```

4. Measure the overhead -- compare the client key_share sizes:
   - Classical X25519: 32 bytes
   - Hybrid X25519MLKEM768: ~1,216 bytes (1,184 B ML-KEM-768 encapsulation key + 32 B X25519)
   Capture the ClientHello with tcpdump or Wireshark (on the host, `lo` interface,
   port 4433) and find the `key_share` extension; verify the size matches prediction.

5. Connect with classical-only baseline for comparison:
   ```bash
   openssl s_client -connect localhost:4433 -groups X25519
   ```
   Note the negotiated group falls back to X25519.

**Expected output:** handshake summary showing `Server Temp Key: X25519MLKEM768`
(or equivalent provider output), and a ClientHello ~1,216 bytes vs ~32 bytes
for the classical baseline.

**Verify completion:** `openssl s_client` output contains `X25519MLKEM768` in the
negotiated parameters line.

**Cleanup:**
```bash
docker compose stop pq-server
```

---

## Lab 6: Crypto inventory to a CBOM migration plan

**Objective:** Scan a sample codebase for cryptographic primitive usage, emit a
CycloneDX-format Cryptographic Bill of Materials (CBOM), prioritise findings by
quantum-risk and confidentiality lifetime, and draft a migration schedule.

**Setup:** The scanner and sample application are in `crypto-inventory/`. Run the
scan with:

```bash
docker compose run --rm cbom
```

Output is written to the `cbom-output` Docker volume and printed to the terminal.

**Walkthrough:**

1. Review the terminal output: the scanner prints a Migration Priority Table sorted
   into four tiers -- CRITICAL (HNDL), HIGH (PQC signatures), URGENT (broken
   primitives), and REVIEW.

2. Inspect the sample application (`crypto-inventory/sample_app/`) to understand
   what each finding refers to. Look at `config.py` and `auth.py`.

3. Read the CBOM output from the volume:
   ```bash
   docker compose run --rm -v cbom-output:/out python:3.12-slim \
     python -c "import json,sys; print(json.dumps(json.load(open('/out/cbom.json')), indent=2))"
   ```
   Each component has `type: cryptographic-asset`, the algorithm name, usage context,
   and `quantumVulnerable` flag.

4. Prioritise -- the scanner does this automatically, but verify the ordering:
   - RSA/ECDH key-exchange go to the top (HNDL threat: capture-now, decrypt-later)
   - MD5, DES, SHA-1 flag as URGENT (broken, disallowed by NIST SP 800-131A)
   - TLS 1.2 config is REVIEW (acceptable if AEAD+ECDHE, but plan migration)

5. Draft a migration plan with four columns:
   - Asset (algorithm + location)
   - Deprecated-by date (from NIST IR 8547: RSA/ECDH deprecated after 2030,
     disallowed after 2035; DES/MD5/SHA-1 already disallowed)
   - Replacement (AES-256-GCM, ML-KEM-768 for key establishment, ML-DSA for
     signatures, SHA-256+ for hashing, TLS 1.3 for transport)
   - Migration effort estimate

**Expected output:** a `cbom.json` file with `bomFormat: "CycloneDX"` and one
`cryptographic-asset` component per detected usage; a prioritised terminal table.

**Verify completion:** `cbom.json` contains at least one entry with
`"quantum_vulnerable": true` and one with `"algorithm": "MD5"`.

**Cleanup:**
```bash
docker compose down -v   # removes cbom-output volume too
```

---

## Teardown

Remove all containers, networks, and volumes created by this lab pack:

```bash
docker compose -f /path/to/ch10/docker-compose.yml down -v
```

---

## Safety and Authorisation

- **Lab 2 (padding oracle):** run the attacker script only against the provided
  `oracle` container or a server you own. The oracle is a deliberately broken
  service on the lab network.
- **Lab (d) (TLS audit):** testssl.sh opens real network connections and probes for
  vulnerabilities. Run it only against hosts you own or are explicitly authorised
  to test. Public teaching endpoints (badssl.com) are acceptable for learning.
- **Lab (e) (PQ-TLS):** run only against your own `pq-server` container. Sending
  non-standard post-quantum ClientHello packets to third-party servers without
  permission may be treated as a probe or attack.
- **Labs 1, 3, 6:** purely local computation; no external network access required.
- No real malware, real PCAPs, or real credentials are used in any lab.
  All sample data is synthetic and created for this training pack.
