java-topology/defects/esp-idf-0002/test/EspIdf0002Test.java

146 lines
5.3 KiB
Java

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