You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by as...@apache.org on 2026/05/05 20:58:22 UTC

(tooling-agents) branch main updated: ASVS audit: 11.2.4.md (redacted) [source: apache/steve/v3 @ d0aa7e9]

This is an automated email from the ASF dual-hosted git repository.

asf-tooling pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new b58452d  ASVS audit: 11.2.4.md (redacted) [source: apache/steve/v3 @ d0aa7e9]
b58452d is described below

commit b58452d317f719463415d449cdfa02263e707b12
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Tue May 5 22:51:55 2026 +0200

    ASVS audit: 11.2.4.md (redacted) [source: apache/steve/v3 @ d0aa7e9]
---
 ASVS/reports/steve/v3/d0aa7e9/all/11.2.4.md | 178 ++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)

diff --git a/ASVS/reports/steve/v3/d0aa7e9/all/11.2.4.md b/ASVS/reports/steve/v3/d0aa7e9/all/11.2.4.md
new file mode 100644
index 0000000..6160be1
--- /dev/null
+++ b/ASVS/reports/steve/v3/d0aa7e9/all/11.2.4.md
@@ -0,0 +1,178 @@
+# Security Audit Report: ASVS 11.2.4
+
+## Executive Summary
+
+| Field | Value |
+|-------|-------|
+| **Repository** | apache/steve/v3 |
+| **Audit Date** | May 05, 2026 |
+| **Auditor** | Tooling Agents (bundled-pass mode) |
+| **ASVS Requirement** | ASVS 11.2.4 |
+| **Files Analyzed** | 5 relevant / 49 total |
+| **Files Skipped** | 12 |
+
+**Requirement description:** Verify that all cryptographic operations are constant-time, with no 'short-circuit' operations in comparisons, calculations, or returns, to avoid leaking information.
+
+### Findings Overview
+
+| Severity | Count |
+|----------|-------|
+| 🔴 Critical | 0 |
+| 🟠 High | 1 |
+| 🟡 Medium | 1 |
+| 🟢 Low | 0 |
+
+---
+
+## ASVS-11.2.4: Constant-Time Operations
+
+### Findings for 11.2.4
+
+### [HIGH]
+
+**Finding ID:** ASVS-1124-HIGH-001
+
+**File:** `v3/steve/election.py`, function `is_tampered()`, line 331
+
+**Vulnerable Code:**
+```python
+def is_tampered(self, pdb):
+    md = self._all_metadata()  # no required state
+
+    # Compute an opened_key based on the current data.
+    edata = self.gather_election_data(pdb)
+    opened_key = crypto.gen_opened_key(edata, md.salt)
+
+    # The computed key should be unchanged.
+    return opened_key != md.opened_key
+```
+
+**Data Flow:** `crypto.gen_opened_key()` → `opened_key` (32 bytes) → `!=` comparison with `md.opened_key` → timing oracle
+
+**Impact:** Python's `!=` operator on `bytes` objects performs a short-circuit comparison that returns `False` as soon as the first differing byte is found. An attacker who can observe response timing could potentially determine how many leading bytes of the `opened_key` match, gradually reconstructing the stored key. The `opened_key` is derived from election data and serves as the anti-tamper seal; leaking it could allow an attacker to forge tamper checks.
+
+**Proof of Concept:**
+```python
+# An attacker with network timing capabilities observes response times
+# when triggering is_tampered() checks (e.g., via election state transitions).
+# Bytes that match take longer to process than immediate mismatches.
+# Statistical analysis over many requests can leak byte-by-byte info.
+import time
+import requests
+
+timings = {}
+for i in range(1000):  # statistical sampling
+    start = time.perf_counter_ns()
+    r = requests.get(f'https://target/do-open/{eid}')  # triggers tamper check
+    elapsed = time.perf_counter_ns() - start
+    timings.setdefault(i, []).append(elapsed)
+```
+
+**Remediation:**
+```python
+import hmac
+
+def is_tampered(self, pdb):
+    md = self._all_metadata()
+    edata = self.gather_election_data(pdb)
+    opened_key = crypto.gen_opened_key(edata, md.salt)
+
+    # Use constant-time comparison to prevent timing side-channels
+    return not hmac.compare_digest(opened_key, md.opened_key)
+```
+
+---
+
+### [MEDIUM]
+
+**Finding ID:** ASVS-1124-MED-001
+
+**File:** `v3/steve/crypto.py`, function `_hash()`, line 87
+
+**Vulnerable Code:**
+```python
+def _hash(data: bytes, salt: bytes) -> bytes:
+    "Use Argon2 to hash the data, with default tuning parameters."
+
+    return argon2.low_level.hash_secret_raw(
+        secret=data,
+        salt=salt,
+        time_cost=2,
+        memory_cost=65536,
+        parallelism=4,
+        hash_len=32,
+        type=argon2.low_level.Type.D,
+    )
+```
+
+**Data Flow:** Secret data → Argon2d → data-dependent memory access pattern → potential side-channel leakage
+
+**Impact:** Argon2d uses data-dependent memory access patterns, making it vulnerable to side-channel attacks (cache-timing attacks, memory access pattern observation). The OWASP and RFC 9106 recommendation for general-purpose use is Argon2id, which combines the side-channel resistance of Argon2i with the GPU resistance of Argon2d. Notably, the `benchmark_argon2()` function in the same file correctly uses `Type.ID`, indicating awareness of Argon2id but inconsistent application.
+
+**Proof of Concept:** In a shared-hosting or cloud environment where an attacker can observe memory access patterns (e.g., via Flush+Reload or Prime+Probe cache attacks), the data-dependent memory accesses in Argon2d could leak information about the secret being hashed.
+
+**Remediation:**
+```python
+def _hash(data: bytes, salt: bytes) -> bytes:
+    "Use Argon2id to hash the data, with default tuning parameters."
+
+    return argon2.low_level.hash_secret_raw(
+        secret=data,
+        salt=salt,
+        time_cost=2,
+        memory_cost=65536,
+        parallelism=4,
+        hash_len=32,
+        type=argon2.low_level.Type.ID,  # Argon2id: side-channel resistant
+    )
+```
+
+---
+
+### Security Controls Inventory for 11.2.4
+
+| Control | Location | Constant-Time? | Status |
+|---------|----------|---------------|--------|
+| Tamper check comparison | `election.py:is_tampered()` line 331 | ❌ Uses `!=` | Non-compliant |
+| Argon2 variant | `crypto.py:_hash()` line 87 | ❌ Type.D (data-dependent) | Non-compliant |
+| Fernet HMAC verification | `cryptography` library internal | ✅ Uses `hmac.compare_digest` | Compliant |
+| Fisher-Yates shuffle | `crypto.py:shuffle()` | ✅ `secrets.randbelow()` | Compliant |
+
+### Positive Patterns for 11.2.4
+
+1. **Fernet library**: The `cryptography` library's Fernet implementation internally uses `hmac.compare_digest()` for constant-time HMAC verification.
+2. **Fisher-Yates shuffle**: Uses `secrets.randbelow()` which is uniform and doesn't leak information about the permutation via timing.
+3. **No short-circuit in key derivation**: `_b64_vote_key()` uses HKDF which processes all input data regardless of content.
+
+---
+
+
+
+## Cross-cutting Architecture Observations
+
+1. **Cryptographic key hierarchy is well-designed**: The system uses a layered key derivation approach: Election data → BLAKE2b → Argon2 → `opened_key` → per-voter tokens → per-vote encryption keys. This provides good key separation and ensures that compromise of one vote doesn't compromise others.
+
+2. **Incomplete migration from Fernet to AEAD**: The codebase is in a transitional state, as evidenced by the `### still using Fernet now, but will switch soon` comment and the `info=b'xchacha20_key'` parameter. The HKDF derives 32 bytes intended for XChaCha20-Poly1305 but actually feeds it to Fernet which only uses 16 bytes for AES-128. This means the full key material isn't utilized.
+
+3. **Security margin inconsistency**: The system uses strong 256-bit outputs from Argon2 and HKDF but then feeds them into Fernet which only provides 128-bit encryption security (AES-128). The 40-bit IDs (`create_id()`) are the weakest link in the entropy chain.
+
+4. **Side-channel considerations partially addressed**: The use of `secrets` module throughout is excellent. However, Argon2d and non-constant-time comparison in `is_tampered()` show that side-channel resistance wasn't uniformly applied.
+
+5. **Fernet's timestamp exposure**: Fernet tokens include a plaintext timestamp of when encryption occurred. In a voting system, this could leak information about when a specific vote was cast, potentially de-anonymizing voters if combined with server access logs.
+
+## Cross-cutting Recommendations
+
+### Immediate (Before Next Release)
+1. **Fix `is_tampered()` to use `hmac.compare_digest()`** — Simple one-line fix that eliminates the timing oracle (ASVS-1124-HIGH-001).
+2. **Switch from Argon2d to Argon2id** — Change `Type.D` to `Type.ID` in `_hash()` to gain side-channel resistance (ASVS-1124-MED-001).
+3. **Add error handling around `decrypt_votestring()`** — Wrap in try/except to prevent DoS via corrupted votes (ASVS-1125-MED-001).
+
+### Short-term (Next Development Cycle)
+4. **Complete migration to XChaCha20-Poly1305** — Replace Fernet with a proper AEAD cipher as already planned. This addresses ASVS 11.3.2 and eliminates the key material waste (ASVS-1132-MED-001).
+5. **Increase `create_id()` entropy to at least 128 bits** — Use `secrets.token_hex(16)` for 32-character hex IDs, updating schema constraints accordingly (ASVS-1123-MED-001).
+
+### Long-term (Architecture)
+6. **Implement a unified cryptographic abstraction layer** — Create a single module interface for all crypto operations with versioning, allowing cipher agility without code changes throughout the application.
+7. **Add integration tests for timing resistance** — Automated tests that verify constant-time behavior of security-critical comparisons.
+8. **Consider removing Fernet timestamp from vote tokens** — Or migrate to a scheme that doesn't embed plaintext metadata in ciphertext.
+        
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org