Each MOAD now has a synthetic defective specimen and fixed specimen proven from first principles across three test tiers: Unit (tests/unit/Moad000X*.java): - Correctness: defective and fixed produce identical functional output - Defect behavior: defective specimen exhibits the defect (measurable) - Fix behavior: fixed specimen eliminates the defect Integration (tests/integration/AllMoadsIntegrationTest.java): - All 9 MOADs proven at medium scale (N=500-2000) - MOAD-0001: O(N^2) vs O(N) list scan at N=1000 - MOAD-0002: 500 sessions trample each other (defective) vs coexist (fixed) - MOAD-0003: 250 anonymous requests leak auth identity (defective) vs zero (fixed) - MOAD-0004: 3000 credential exposures across 1000 requests (defective) vs zero (fixed) - MOAD-0005: 500 computes for 500 concurrent misses vs exactly 1 - MOAD-0006: all 500 passwords extractable from DB (defective) vs unextractable (fixed) - MOAD-0007: N=2000 spatial objects, defective visits all 2000 vs O(log N + k) - MOAD-0009: 990 wasted firings for 1000 ticks / 10 events vs zero waste - MOAD-0011: 10240 NFA steps vs 13 steps on N=12 adversarial input (788x) Functional (tests/functional/AllMoadsFunctionalTest.java): - MOAD-0005: real-thread contention proves herd (defective >1 compute, fixed exactly 1) - MOAD-0007: N=50000 spatial objects, 50M defective probes vs 516K fixed (97x speedup) - MOAD-0009: 10000 ticks / 10 events, 9990 wasted firings vs zero (1000x ratio) - MOAD-0011: N=16 adversarial, 163840 defective steps vs 17 fixed (9638x ratio) Support algorithms (tests/support/Moad000X*.java): - Moad0002Algorithm: shared mutable global state (DefectiveAudioSystem / FixedAudioSystem + Context) - Moad0003Algorithm: ThreadLocal not cleared (handleDefective / handleFixed with finally) - Moad0004Algorithm: HTTP headers logged verbatim (logDefective / logFixed with CREDENTIAL_HEADERS denylist) - Moad0005Algorithm: get+null+compute+put (DefectiveCache HashMap / FixedCache ConcurrentHashMap.computeIfAbsent) - Moad0006Algorithm: Base64 password storage (DefectiveCredentialStore / FixedCredentialStore SHA-256+salt) - Moad0007Algorithm: linear spatial scan (queryDefective list / queryFixed sorted array + binary search) - Moad0009Algorithm: timer-driven polling (runDefectiveScheduler / runFixedEventDriven) - Moad0011Algorithm: PCRE nested quantifiers (matchDefective backtracking NFA / matchFixed linear NFA) Makefile: added unit-moad-0002 through unit-moad-0011 targets, integration-all-moads, functional-all-moads. integration and functional targets now depend on all-MOADs variants.
238 lines
9.9 KiB
Java
238 lines
9.9 KiB
Java
package unit;
|
|
|
|
import support.Moad0006Algorithm;
|
|
import support.Moad0006Algorithm.DefectiveCredentialStore;
|
|
import support.Moad0006Algorithm.FixedCredentialStore;
|
|
|
|
/**
|
|
* Unit tests for MOAD-0006: CWE-257 — A Glass Safe.
|
|
*
|
|
* Proves from first principles:
|
|
* 1. Defective: Base64 encoding is reversible — stored credential decodes
|
|
* to the original password. An attacker with DB read access extracts
|
|
* every password in plaintext.
|
|
* 2. Defective: stored value is deterministic — same password always produces
|
|
* same encoded string, enabling rainbow table attacks.
|
|
* 3. Fixed: SHA-256 + salt is one-way — the stored hash cannot be decoded
|
|
* to recover the original password.
|
|
* 4. Fixed: salt randomization — same password stored twice produces
|
|
* different hash values (defeats rainbow tables).
|
|
* 5. Both correctly verify a correct password and reject an incorrect one.
|
|
*
|
|
* No build tool required. Compile and run:
|
|
*
|
|
* cd tests
|
|
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
|
* support/Moad0006Algorithm.java unit/Moad0006UnitTest.java
|
|
* java -cp . unit.Moad0006UnitTest
|
|
*/
|
|
public class Moad0006UnitTest {
|
|
|
|
private static int passed = 0;
|
|
private static int failed = 0;
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== Moad0006UnitTest (A Glass Safe) ===\n");
|
|
|
|
System.out.println("-- Correctness: verification works for both --");
|
|
testDefectiveVerifyCorrect();
|
|
testDefectiveRejectWrong();
|
|
testFixedVerifyCorrect();
|
|
testFixedRejectWrong();
|
|
|
|
System.out.println("\n-- Defect: stored value is reversible (glass safe opens) --");
|
|
testDefectivePasswordExtractable();
|
|
testDefectiveDeterministicStorage();
|
|
testDefectiveMultipleUsersExtractable();
|
|
|
|
System.out.println("\n-- Fix: stored value is one-way (hash cannot be reversed) --");
|
|
testFixedPasswordNotExtractable();
|
|
testFixedSaltRandomization();
|
|
testFixedHashNotEqualToPassword();
|
|
|
|
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
|
if (failed > 0) System.exit(1);
|
|
}
|
|
|
|
// ── Correctness ───────────────────────────────────────────────────────────
|
|
|
|
static void testDefectiveVerifyCorrect() {
|
|
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
|
store.storePassword("alice", "hunter2");
|
|
assertTrue("defective: correct password verified", store.verify("alice", "hunter2"));
|
|
}
|
|
|
|
static void testDefectiveRejectWrong() {
|
|
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
|
store.storePassword("alice", "hunter2");
|
|
assertFalse("defective: wrong password rejected", store.verify("alice", "wrongpass"));
|
|
}
|
|
|
|
static void testFixedVerifyCorrect() {
|
|
FixedCredentialStore store = new FixedCredentialStore();
|
|
store.storePassword("bob", "correct-horse-battery-staple");
|
|
assertTrue("fixed: correct password verified", store.verify("bob", "correct-horse-battery-staple"));
|
|
}
|
|
|
|
static void testFixedRejectWrong() {
|
|
FixedCredentialStore store = new FixedCredentialStore();
|
|
store.storePassword("bob", "correct-horse-battery-staple");
|
|
assertFalse("fixed: wrong password rejected", store.verify("bob", "wrong-answer"));
|
|
}
|
|
|
|
// ── Defect ────────────────────────────────────────────────────────────────
|
|
|
|
static void testDefectivePasswordExtractable() {
|
|
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
|
store.storePassword("alice", "s3cr3t!");
|
|
|
|
// DEFECT: attacker with DB access calls extractPassword()
|
|
String extracted = store.extractPassword("alice");
|
|
assertEqual("defective: original password extracted from DB (glass safe opens)",
|
|
"s3cr3t!", extracted);
|
|
}
|
|
|
|
static void testDefectiveDeterministicStorage() {
|
|
DefectiveCredentialStore store1 = new DefectiveCredentialStore();
|
|
DefectiveCredentialStore store2 = new DefectiveCredentialStore();
|
|
store1.storePassword("u1", "password123");
|
|
store2.storePassword("u2", "password123");
|
|
|
|
// DEFECT: same password → same stored value → rainbow table attack works
|
|
String raw1 = store1.rawStored("u1");
|
|
String raw2 = store2.rawStored("u2");
|
|
assertEqual("defective: same password produces identical stored values (no salt)",
|
|
raw1, raw2);
|
|
}
|
|
|
|
static void testDefectiveMultipleUsersExtractable() {
|
|
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
|
store.storePassword("admin", "admin123");
|
|
store.storePassword("finance", "Q4RevenueReport!");
|
|
|
|
// DEFECT: all users' passwords extractable from one DB dump
|
|
assertEqual("defective: admin password extracted", "admin123", store.extractPassword("admin"));
|
|
assertEqual("defective: finance password extracted", "Q4RevenueReport!", store.extractPassword("finance"));
|
|
}
|
|
|
|
// ── Fix ───────────────────────────────────────────────────────────────────
|
|
|
|
static void testFixedPasswordNotExtractable() {
|
|
FixedCredentialStore store = new FixedCredentialStore();
|
|
store.storePassword("alice", "s3cr3t!");
|
|
|
|
// FIX: raw hash is stored — cannot reverse to original password
|
|
byte[] hash = store.rawHash("alice");
|
|
byte[] salt = store.rawSalt("alice");
|
|
|
|
assertNotNull("fixed: hash exists in store", hash);
|
|
assertNotNull("fixed: salt exists in store", salt);
|
|
|
|
// Verify hash does not equal password bytes
|
|
String hashHex = bytesToHex(hash);
|
|
assertNotContains("fixed: hash does not contain plaintext password",
|
|
hashHex, "s3cr3t!");
|
|
|
|
// FIX: no extractPassword() method exists on FixedCredentialStore
|
|
// (This is verified by the fact that FixedCredentialStore does not have that method —
|
|
// the API surface itself proves the fix.)
|
|
assertTrue("fixed: FixedCredentialStore has no extractPassword() method (API enforces fix)",
|
|
!hasExtractMethod(store));
|
|
}
|
|
|
|
static void testFixedSaltRandomization() {
|
|
// Store the same password for two users
|
|
FixedCredentialStore store = new FixedCredentialStore();
|
|
store.storePassword("u1", "shared-password");
|
|
store.storePassword("u2", "shared-password");
|
|
|
|
byte[] hash1 = store.rawHash("u1");
|
|
byte[] hash2 = store.rawHash("u2");
|
|
byte[] salt1 = store.rawSalt("u1");
|
|
byte[] salt2 = store.rawSalt("u2");
|
|
|
|
// FIX: different salts → different hashes — rainbow table attack fails
|
|
assertNotEqualBytes("fixed: same password produces different hashes (random salt)",
|
|
hash1, hash2);
|
|
assertNotEqualBytes("fixed: unique salt per user", salt1, salt2);
|
|
}
|
|
|
|
static void testFixedHashNotEqualToPassword() {
|
|
FixedCredentialStore store = new FixedCredentialStore();
|
|
String password = "mypassword";
|
|
store.storePassword("user", password);
|
|
byte[] hash = store.rawHash("user");
|
|
// Hash bytes should not equal password bytes
|
|
byte[] passwordBytes = password.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
assertFalseBytes("fixed: stored hash does not equal password bytes", hash, passwordBytes);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
static void assertEqual(String label, String expected, String actual) {
|
|
if (expected == null ? actual == null : expected.equals(actual)) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertTrue(String label, boolean condition) {
|
|
if (condition) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s%n", label);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertFalse(String label, boolean condition) {
|
|
assertTrue(label, !condition);
|
|
}
|
|
|
|
static void assertNotNull(String label, Object obj) {
|
|
assertTrue(label + " (not null)", obj != null);
|
|
}
|
|
|
|
static void assertNotContains(String label, String haystack, String needle) {
|
|
if (!haystack.contains(needle)) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — found '%s' in '%s'%n", label, needle, haystack);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertNotEqualBytes(String label, byte[] a, byte[] b) {
|
|
if (!java.util.Arrays.equals(a, b)) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — byte arrays are equal when they should differ%n", label);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertFalseBytes(String label, byte[] a, byte[] b) {
|
|
assertNotEqualBytes(label, a, b);
|
|
}
|
|
|
|
static boolean hasExtractMethod(FixedCredentialStore store) {
|
|
try {
|
|
store.getClass().getMethod("extractPassword", String.class);
|
|
return true;
|
|
} catch (NoSuchMethodException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static String bytesToHex(byte[] bytes) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (byte b : bytes) sb.append(String.format("%02x", b));
|
|
return sb.toString();
|
|
}
|
|
}
|