import java.util.*; import java.util.logging.*; import java.io.*; /** * DigiKamOAuthSecretLogTest — digikam-0004 * * Reproduces the CWE-312 defect in O2::onVerificationReceived: * when GrantFlowAuthorizationCode completes, the full token exchange * POST body — including client_secret — is logged verbatim via qDebug(). * * Models the defective (full body logged) vs fixed (body redacted) behavior, * verifying that the fixed version does not emit the secret. */ public class DigiKamOAuthSecretLogTest { // Simulates building the OAuth2 token exchange body (like buildRequestBody) static String buildRequestBody(Map params) { StringBuilder sb = new StringBuilder(); for (Map.Entry e : params.entrySet()) { if (sb.length() > 0) sb.append("&"); sb.append(e.getKey()).append("=").append(e.getValue()); } return sb.toString(); } // Defective: logs full body including client_secret (mirrors o2.cpp line 284) static String defectiveLog(String clientSecret, String code, String clientId, String redirectUri) { Map parameters = new LinkedHashMap<>(); parameters.put("code", code); parameters.put("client_id", clientId); parameters.put("client_secret", clientSecret); parameters.put("redirect_uri", redirectUri); parameters.put("grant_type", "authorization_code"); String data = buildRequestBody(parameters); // Simulates: qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(data) return "O2::onVerificationReceived: Exchange access code data:\n" + data; } // Fixed: logs redacted message (mirrors patch) static String fixedLog(String clientSecret, String code, String clientId, String redirectUri) { // Simulates: qDebug() << "O2::onVerificationReceived: Sending token exchange request (body redacted)" return "O2::onVerificationReceived: Sending token exchange request (body redacted)"; } public static void main(String[] args) { System.out.println("DigiKamOAuthSecretLogTest — digikam-0004 CWE-312"); System.out.println("O2::onVerificationReceived logs client_secret in full POST body"); System.out.println(); String clientSecret = "super_secret_oauth2_client_credential_abc123xyz"; String code = "auth_code_from_callback"; String clientId = "digikam-app-client-id"; String redirectUri = "http://localhost:8080/callback"; // Defective: secret appears in log output String defectiveOutput = defectiveLog(clientSecret, code, clientId, redirectUri); boolean defectiveLeaks = defectiveOutput.contains(clientSecret); System.out.println("Defective log output:"); System.out.println(" " + defectiveOutput.replace("\n", "\n ")); System.out.println(" Contains secret: " + defectiveLeaks); assert defectiveLeaks : "Defective variant should contain secret in log (test setup error)"; System.out.println("PASS: defective variant confirmed to leak secret"); // Fixed: secret must NOT appear in log output String fixedOutput = fixedLog(clientSecret, code, clientId, redirectUri); boolean fixedLeaks = fixedOutput.contains(clientSecret); System.out.println("\nFixed log output:"); System.out.println(" " + fixedOutput); System.out.println(" Contains secret: " + fixedLeaks); assert !fixedLeaks : "Fixed variant must not contain secret in log, but found: " + fixedOutput; System.out.println("PASS: fixed variant does not leak secret"); // Verify fixed log still contains useful diagnostic info assert fixedOutput.contains("O2::onVerificationReceived") : "Fixed log should still identify the function"; assert fixedOutput.contains("redacted") : "Fixed log should indicate body was redacted"; System.out.println("PASS: fixed log retains useful diagnostic context"); // Edge: verify no partial secret leak (first 3 chars truncation check) String secretPrefix3 = clientSecret.substring(0, 3); assert !fixedOutput.contains(secretPrefix3) : "Fixed log should not contain even first 3 chars of secret"; System.out.println("PASS: fixed log contains no partial secret fragment"); System.out.println("\nALL PASS"); } }