package unit; import java.util.HashMap; /** * mbedtls-0002 unit test * * Models the O(S×C×D) cipher suite selection in ssl_tls12_server.c * and the O(S+C) fixed version using a hash set of client-offered IDs. * * Compile: javac -d . MbedTlsCipherSelectTest.java * Run: java unit.MbedTlsCipherSelectTest */ public class MbedTlsCipherSelectTest { // Simulated ciphersuite_definitions table (D entries) static final int[] CIPHER_DEFINITIONS; static { // Representative TLS 1.2 cipher suite IDs (a subset of the ~70 in mbedTLS) CIPHER_DEFINITIONS = new int[]{ 0xC02B, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02C, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0xC02F, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC030, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0xCCA9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 0xCCA8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 0xC009, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC013, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0x002F, // TLS_RSA_WITH_AES_128_CBC_SHA 0x0035, // TLS_RSA_WITH_AES_256_CBC_SHA 0x003C, // TLS_RSA_WITH_AES_128_CBC_SHA256 0x003D, // TLS_RSA_WITH_AES_256_CBC_SHA256 0x009C, // TLS_RSA_WITH_AES_128_GCM_SHA256 0x009D, // TLS_RSA_WITH_AES_256_GCM_SHA384 0xC023, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC024, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 }; } // O(D) linear lookup — mirrors mbedtls_ssl_ciphersuite_from_id static boolean cipherDefinitionExists(int id) { for (int def : CIPHER_DEFINITIONS) { if (def == id) return true; } return false; } // ------------------------------------------------------------------ // // DEFECTIVE: O(S × C × D) // // server-preference mode (most common): for each server suite, // // scan all client suites; on match, call ciphersuite_match (O(D)) // // ------------------------------------------------------------------ // static int defectiveSelect(int[] serverSuites, int[] clientSuites) { for (int sid : serverSuites) { for (int cid : clientSuites) { if (cid == sid) { // ssl_ciphersuite_match calls mbedtls_ssl_ciphersuite_from_id (O(D)) if (cipherDefinitionExists(sid)) return sid; } } } return -1; } // ------------------------------------------------------------------ // // FIXED: O(S + C) — hash set of client IDs, O(1) membership test // // ------------------------------------------------------------------ // static int fixedSelect(int[] serverSuites, int[] clientSuites) { // Build hash set of client IDs — O(C) HashMap clientSet = new HashMap<>(); for (int cid : clientSuites) clientSet.put(cid, Boolean.TRUE); // Iterate server list — O(S), each lookup O(1) for (int sid : serverSuites) { if (clientSet.containsKey(sid)) { // ciphersuite_match still needed, but only on actual matches if (cipherDefinitionExists(sid)) return sid; } } return -1; } // ------------------------------------------------------------------ // // Counting versions // // ------------------------------------------------------------------ // static long[] ops = new long[2]; // [defective, fixed] static int defectiveCounting(int[] server, int[] client) { for (int sid : server) { for (int cid : client) { ops[0]++; // inner comparison if (cid == sid) { for (int def : CIPHER_DEFINITIONS) { ops[0]++; // O(D) lookup if (def == sid) return sid; } } } } return -1; } static int fixedCounting(int[] server, int[] client) { HashMap cs = new HashMap<>(); for (int cid : client) { ops[1]++; cs.put(cid, Boolean.TRUE); } for (int sid : server) { ops[1]++; if (cs.containsKey(sid)) { for (int def : CIPHER_DEFINITIONS) { ops[1]++; if (def == sid) return sid; } } } return -1; } static int pass = 0, fail = 0; static void check(String label, boolean cond) { if (cond) { System.out.println(" PASS " + label); pass++; } else { System.out.println(" FAIL " + label); fail++; } } public static void main(String[] args) { System.out.println("=== mbedtls-0002: TLS 1.2 cipher selection O(S×C×D) defect ===\n"); // Server prefers ECDHE-ECDSA-AES128-GCM, then ECDHE-RSA-AES128-GCM int[] serverList = {0xC02B, 0xC02F, 0x002F}; // Test 1: first server suite matches int[] client1 = {0xC02F, 0xC02B, 0x0000}; check("defective: picks server-preferred 0xC02B", defectiveSelect(serverList, client1) == 0xC02B); check("fixed: picks server-preferred 0xC02B", fixedSelect(serverList, client1) == 0xC02B); // Test 2: only second server suite matches int[] client2 = {0xC02F, 0x0035}; check("defective: picks 0xC02F (second server entry)", defectiveSelect(serverList, client2) == 0xC02F); check("fixed: picks 0xC02F (second server entry)", fixedSelect(serverList, client2) == 0xC02F); // Test 3: no common suite int[] client3 = {0x0004, 0x0005, 0x000A}; check("defective: no match → -1", defectiveSelect(serverList, client3) == -1); check("fixed: no match → -1", fixedSelect(serverList, client3) == -1); // Test 4: complexity — adversarial large client list with no match // (no-match is worst case: defective must scan all S*C combinations) int S = 20; int C = 300; int D = CIPHER_DEFINITIONS.length; int[] bigServer = new int[S]; for (int i = 0; i < S; i++) bigServer[i] = 0x1000 + i; // server suites NOT in CIPHER_DEFINITIONS // client sends C unknown IDs → no match, full O(S*C) scan int[] bigClient = new int[C]; for (int i = 0; i < C; i++) bigClient[i] = 0x9000 + i; ops[0] = 0; ops[1] = 0; int rd = defectiveCounting(bigServer, bigClient); int rf = fixedCounting(bigServer, bigClient); System.out.println("\n Complexity comparison (S=" + S + ", C=" + C + ", D=" + D + ", no-match):"); System.out.println(" Defective ops: " + ops[0] + " (expected ~" + ((long)S * C) + ")"); System.out.println(" Fixed ops: " + ops[1] + " (expected ~" + (S + C) + ")"); System.out.printf(" Speedup: %.1fx%n", (double) ops[0] / ops[1]); check("defective and fixed agree on result (both -1)", rd == rf && rd == -1); check("defective ops > 5x fixed ops (quadratic vs linear)", ops[0] > 5 * ops[1]); System.out.println("\n--- " + (pass + fail) + " tests: " + pass + " passed, " + fail + " failed ---"); if (fail > 0) System.exit(1); } }