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(); } }