A Python port of the Invisible Internet Project (I2P)
at main 181 lines 7.4 kB view raw view rendered
1# Crypto Implementation Audit Checklist 2 3_Last audited: 2026-03-23_ 4_Auditor: Claude (automated) + manual review_ 5 6--- 7 8## 1. Constant-Time Comparisons 9 10**Target files**: `i2p_crypto/chacha20.py`, `i2p_crypto/aes_cbc.py`, `i2p_crypto/hmac_generator.py`, `i2p_crypto/eddsa.py`, `i2p_crypto/dsa.py`, `i2p_crypto/noise.py`, `i2p_crypto/garlic_crypto.py`, `i2p_data/i2np_codec.py` 11 12**Check**: Every MAC/tag/signature/hash verification uses `hmac.compare_digest()`. 13 14| Location | Pattern | Status | 15|----------|---------|--------| 16| `hmac_generator.py:63` | `hmac.compare_digest()` | PASS | 17| `password.py:31` | `secrets.compare_digest()` | PASS | 18| `console/auth.py:28` | `secrets.compare_digest()` | PASS | 19| `garlic_crypto.py:257` | `hmac.compare_digest()` on SHA-256 hash | PASS (fixed 2026-03-23) | 20| `i2np_codec.py:80` | `hmac.compare_digest()` on checksum byte | PASS (fixed 2026-03-23) | 21| `dsa.py`, `eddsa.py` | Delegated to `cryptography` library verify() | PASS | 22| `noise.py` CipherState | Delegated to `ChaCha20Poly1305.decrypt()` | PASS | 23 24**Result**: **PASS** -- All comparisons on security-sensitive data use constant-time functions. 25 26--- 27 28## 2. Key Zeroization 29 30**Target files**: `i2p_crypto/x25519.py`, `i2p_crypto/noise.py`, `i2p_crypto/hkdf.py`, `i2p_data/session_key_manager.py` 31 32**Check**: Ephemeral keys stored in `bytearray`, explicitly zeroed after use. 33 34| Location | Key Type | Storage | Zeroed? | Status | 35|----------|----------|---------|---------|--------| 36| `noise.py:32` | CipherState key | `bytes` (immutable) | No | KNOWN LIMITATION | 37| `noise.py:162` | Ephemeral private key | `bytes` tuple | No | KNOWN LIMITATION | 38| `noise.py:360` | `_x25519_private` | `bytes` | No | KNOWN LIMITATION | 39| `hkdf.py:20-21` | Output buffers | `bytearray` | No explicit zeroing | ADVISORY | 40| `session_key_manager.py:103` | Session keys | `bytes` | No | KNOWN LIMITATION | 41 42**Result**: **ADVISORY** -- Python's immutable `bytes` type makes key zeroization impossible without switching to `bytearray`. This is a language-level limitation. The `cryptography` library handles its own key zeroization in C. For pure-Python key material, use `bytearray` with explicit zeroing in `finally` blocks. 43 44**Note**: Key zeroization in Python is best-effort. The garbage collector may retain copies, and `bytearray` zeroing can be optimized away. For production deployments, rely on the `cryptography` library's C-level key handling. 45 46--- 47 48## 3. CSPRNG Usage 49 50**Target files**: `i2p_crypto/`, `i2p_transport/` 51 52**Check**: All randomness sourced from `os.urandom()` or `secrets.*`. 53 54| Location | Function | Source | Status | 55|----------|----------|--------|--------| 56| `elgamal.py:38-41` | Key generation | `os.urandom()` | PASS | 57| `garlic_crypto.py:19-20` | Padding/tags | `os.urandom()` | PASS | 58| `ssu2_handshake.py` | Connection IDs | `os.urandom(8)` | PASS | 59| `ssu2_server.py` | Nonces | `os.urandom(4)` | PASS | 60| `ntcp2_real_handshake.py` | Padding | `os.urandom()` | PASS | 61| `ssu2_connection.py:194` | Challenge data | `os.urandom(8)` | PASS | 62 63**Result**: **PASS** -- Zero uses of `random.*` in crypto or transport modules. 64 65--- 66 67## 4. No Secrets in Logs 68 69**Target**: All files in `i2p_crypto/`, `i2p_transport/`, `i2p_data/` 70 71**Check**: No `logging.*`, `print()`, `repr()` calls that include key material. 72 73| Location | Log Content | Status | 74|----------|-------------|--------| 75| `ssu2_server.py` | Addresses, packet sizes | PASS | 76| `manager.py` | Peer hash prefix (first 8 bytes only) | PASS | 77| `upnp.py` | URLs only | PASS | 78 79**Result**: **PASS** -- No key material found in any logging statements. 80 81--- 82 83## 5. Nonce Management 84 85**Target files**: NTCP2 and SSU2 frame encryption in `i2p_transport/` 86 87**Check**: Nonce counters increment monotonically, overflow detection, separate nonce spaces. 88 89| Check | Location | Status | 90|-------|----------|--------| 91| Monotonic increment (Noise) | `noise.py:51,60` | PASS -- `_n += 1` after each op | 92| Overflow detection (Noise) | `noise.py:48,57` | PASS (fixed 2026-03-23) -- checks `_n >= MAX_NONCE` | 93| Overflow detection (SSU2) | `ssu2_connection.py:119` | PASS (fixed 2026-03-23) -- checks `>= 0xFFFFFFFF` | 94| Separate nonce spaces | `ssu2_connection.py:65-67` | PASS -- send/recv use separate CipherState | 95| Nonce construction (SSU2) | `ssu2_connection.py:105-107` | PASS -- 12-byte nonce from packet number | 96 97**Result**: **PASS** -- All nonce management issues addressed. 98 99--- 100 101## 6. AEAD Correctness 102 103**Target files**: `i2p_crypto/chacha20.py`, NTCP2/SSU2 frame processing 104 105**Check**: AD parameter includes header bytes; decryption returns no partial plaintext on auth failure. 106 107| Check | Location | Status | 108|-------|----------|--------| 109| AD binding (SSU2) | `ssu2_connection.py:127,170` | PASS -- header bound as AD | 110| AD binding (NTCP2) | `ntcp2_real_connection.py:81,103` | PASS -- empty AD (per NTCP2 data phase spec) | 111| Atomic decryption failure | `cryptography` library | PASS -- `InvalidTag` raised, no partial plaintext | 112| Nonce increment on failure | `noise.py:60` | PASS -- `_n += 1` only reached after successful decrypt | 113 114**Result**: **PASS** -- AEAD semantics correct. 115 116--- 117 118## 7. HKDF Usage 119 120**Target**: `i2p_crypto/hkdf.py`, `i2p_crypto/noise.py` 121 122**Check**: Two-phase (Extract + Expand) usage; info parameters are protocol-specific strings. 123 124| Check | Location | Status | 125|-------|----------|--------| 126| Extract + Expand | `hkdf.py` | PASS -- separate `extract()` and `expand()` functions | 127| Protocol-specific info | `noise.py` MixKey/MixHash | PASS -- chaining key derived per Noise spec | 128| Output length check | `hkdf.py` | PASS -- `expand()` output <= 255 * HashLen | 129 130**Result**: **PASS** 131 132--- 133 134## 8. Integer Overflow Masking 135 136**Target**: `i2p_crypto/siphash.py`, nonce counters 137 138**Check**: All arithmetic ported from Java applies explicit masks. 139 140| Check | Location | Status | 141|-------|----------|--------| 142| 64-bit mask in SipHash | `siphash.py:9,14,66,75,79` | PASS -- `& MASK64` on all operations | 143| IV masking | `siphash.py:98` | PASS -- `iv & MASK64` on init | 144| SipHash return | `siphash.py:61` | PASS -- `(v0 ^ v1 ^ v2 ^ v3) & MASK64` | 145 146**Result**: **PASS** -- All Java overflow semantics correctly ported with explicit masks. 147 148--- 149 150## 9. String Encoding 151 152**Target**: RouterInfo properties, address book, SU3 metadata 153 154**Check**: All string-to-bytes conversions use explicit `.encode('utf-8')`. 155 156| Check | Status | 157|-------|--------| 158| RouterInfo encoding | PASS -- explicit UTF-8 in data serialization | 159| Password hashing | PASS -- `password.encode("utf-8")` | 160| Console auth | PASS -- explicit UTF-8 encoding | 161 162**Result**: **PASS** 163 164--- 165 166## Summary 167 168| Section | Result | Fixes Applied | 169|---------|--------|---------------| 170| 1. Constant-time comparisons | PASS | 2 fixes (garlic_crypto, i2np_codec) | 171| 2. Key zeroization | ADVISORY | Language limitation documented | 172| 3. CSPRNG usage | PASS | None needed | 173| 4. No secrets in logs | PASS | None needed | 174| 5. Nonce management | PASS | 2 fixes (noise overflow, SSU2 overflow) | 175| 6. AEAD correctness | PASS | None needed | 176| 7. HKDF usage | PASS | None needed | 177| 8. Integer overflow masking | PASS | None needed | 178| 9. String encoding | PASS | None needed | 179 180**Overall**: 8/9 PASS, 1 ADVISORY (key zeroization -- Python language limitation) 181**Fixes applied**: 4 security hardening changes in this audit cycle