import java.util.ArrayList; import java.util.List; /** * Test for esp-idf-0002: CWE-312 HTTP Digest auth password logged verbatim * in components/esp_http_client/lib/http_auth.c http_auth_digest(). * * Pattern: * ESP_LOGD(TAG, "%s %s %s %s", "Digest", username, auth_data->realm, password) * * During HTTP Digest authentication setup the plaintext password is emitted * to serial/JTAG when debug logging is active. Unlike Basic Auth the password * should never leave the chip except as part of the one-way digest hash. * * Fix: remove the offending ESP_LOGD line. Logging the digest hash result * (ha1/response) would be safe; logging the raw password is not. * * Compile and run (no build tool required): * javac defects/esp-idf-0002/test/EspIdf0002Test.java -d /tmp/esp-idf-0002 * java -cp /tmp/esp-idf-0002 EspIdf0002Test */ public class EspIdf0002Test { private static int passed = 0; private static int failed = 0; // --- Simulated log collector --- static class LogCollector { private final List lines = new ArrayList<>(); void logd(String tag, String fmt, Object... args) { lines.add(String.format(fmt, args)); } boolean containsText(String text) { for (String line : lines) { if (line.contains(text)) return true; } return false; } int size() { return lines.size(); } } /** Trivial stand-in for MD5 hex — actual hash value irrelevant for this test. */ static String mockMd5(String input) { return Integer.toHexString(input.hashCode() & 0x7fffffff); } // --- Defective: logs username + realm + password --- static String computeDigestDefective( String username, String realm, String password, LogCollector log) { String ha1 = mockMd5(username + ":" + realm + ":" + password); log.logd("HTTP_AUTH", "%s %s %s %s", "Digest", username, realm, password); // CWE-312 String ha2 = mockMd5("GET:/api/data"); return mockMd5(ha1 + ":nonce123:" + ha2); } // --- Fixed: no password in any log statement --- static String computeDigestFixed( String username, String realm, String password, LogCollector log) { String ha1 = mockMd5(username + ":" + realm + ":" + password); // ESP_LOGD with password removed — fix for esp-idf-0002 String ha2 = mockMd5("GET:/api/data"); return mockMd5(ha1 + ":nonce123:" + ha2); } // --- Tests --- static void testDefectiveLogsPassword() { LogCollector log = new LogCollector(); computeDigestDefective("alice", "testrealm@host.com", "hunter2", log); check("defective must log the password (confirms CWE-312 site is present)", log.containsText("hunter2")); } static void testFixedDoesNotLogPassword() { LogCollector log = new LogCollector(); computeDigestFixed("alice", "testrealm@host.com", "hunter2", log); check("fixed must NOT log the HTTP auth password", !log.containsText("hunter2")); } static void testDefectiveAlsoExposesUsername() { LogCollector log = new LogCollector(); computeDigestDefective("alice", "testrealm@host.com", "hunter2", log); // The combined log line is the real risk (username + realm + password together) check("defective log line includes username (combined exposure risk)", log.containsText("alice")); } static void testFixedProducesNoLogLines() { LogCollector log = new LogCollector(); computeDigestFixed("alice", "testrealm@host.com", "hunter2", log); check("fixed digest computation emits no log lines", log.size() == 0); } static void testBothReturnSameDigest() { LogCollector log1 = new LogCollector(); LogCollector log2 = new LogCollector(); String d1 = computeDigestDefective("bob", "realm", "s3cr3t", log1); String d2 = computeDigestFixed("bob", "realm", "s3cr3t", log2); check("fixed and defective produce identical digest response (fix is behaviour-neutral)", d1.equals(d2)); } static void testPasswordNotLeakedForMultipleRealms() { String[] realms = {"api.example.com", "admin.local", "iot-gateway"}; for (String realm : realms) { LogCollector log = new LogCollector(); computeDigestFixed("user", realm, "TopSecretPW", log); check("password not logged for realm '" + realm + "'", !log.containsText("TopSecretPW")); } } // --- Harness --- static void check(String desc, boolean cond) { if (cond) { System.out.println(" PASS: " + desc); passed++; } else { System.out.println(" FAIL: " + desc); failed++; } } public static void main(String[] args) { System.out.println("=== EspIdf0002Test (CWE-312 HTTP auth password logged) ===\n"); testDefectiveLogsPassword(); testFixedDoesNotLogPassword(); testDefectiveAlsoExposesUsername(); testFixedProducesNoLogLines(); testBothReturnSameDigest(); testPasswordNotLeakedForMultipleRealms(); System.out.println("\n--- " + passed + " passed, " + failed + " failed ---"); if (failed > 0) System.exit(1); } }