1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt.ct; 18 19 import java.io.File; 20 import java.io.FileNotFoundException; 21 import java.io.IOException; 22 import java.io.PrintWriter; 23 import java.io.StringBufferInputStream; 24 import java.security.PublicKey; 25 import junit.framework.TestCase; 26 import org.conscrypt.OpenSSLKey; 27 28 public class CTLogStoreImplTest extends TestCase { 29 private static final String[] LOG_KEYS = new String[] { 30 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U" + 31 "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==", 32 33 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErEULmlBnX9L/+AK20hLYzPMFozYx" + 34 "pP0Wm1ylqGkPEwuDKn9DSpNSOym49SN77BLGuAXu9twOW/qT+ddIYVBEIw==", 35 36 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEP6PGcXmjlyCBz2ZFUuUjrgbZLaEF" + 37 "gfLUkt2cEqlSbb4vTuB6WWmgC9h0L6PN6JF0CPcajpBKGlTI15242a8d4g==", 38 39 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER3qB0NADsP1szXxe4EagrD/ryPVh" + 40 "Y/azWbKyXcK12zhXnO8WH2U4QROVUMctFXLflIzw0EivdRN9t7UH1Od30w==", 41 42 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY0ww9JqeJvzVtKNTPVb3JZa7s0ZV" + 43 "duH3PpshpMS5XVoPRSjSQCph6f3HjUcM3c4N2hpa8OFbrFFy37ttUrgD+A==" 44 }; 45 private static final String[] LOG_FILENAMES = new String[] { 46 "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764", 47 "84f8ae3f613b13407a75fa2893b93ab03b18d86c455fe7c241ae020033216446", 48 "89baa01a445100009d8f9a238947115b30702275aafee675a7d94b6b09287619", 49 "57456bffe268e49a190dce4318456034c2b4958f3c0201bed5a366737d1e74ca", 50 "896c898ced4b8e6547fa351266caae4ca304f1c1ec2b623c2ee259c5452147b0" 51 }; 52 53 private static final CTLogInfo[] LOGS; 54 private static final String[] LOGS_SERIALIZED; 55 56 static { 57 try { 58 int logCount = LOG_KEYS.length; 59 LOGS = new CTLogInfo[logCount]; 60 LOGS_SERIALIZED = new String[logCount]; 61 for (int i = 0; i < logCount; i++) { 62 PublicKey key = OpenSSLKey.fromPublicKeyPemInputStream(new StringBufferInputStream( 63 "-----BEGIN PUBLIC KEY-----\n" + 64 LOG_KEYS[i] + "\n" + 65 "-----END PUBLIC KEY-----\n")).getPublicKey(); 66 String description = String.format("Test Log %d", i); 67 String url = String.format("log%d.example.com", i); 68 LOGS[i] = new CTLogInfo(key, description, url); 69 LOGS_SERIALIZED[i] = String.format("description:%s\nurl:%s\nkey:%s", 70 description, url, LOG_KEYS[i]); 71 } 72 } catch (Exception e) { 73 throw new RuntimeException(e); 74 } 75 } 76 77 /* CTLogStoreImpl loads the list of logs lazily when they are first needed 78 * to avoid any overhead when CT is disabled. 79 * This test simply forces the logs to be loaded to make sure it doesn't 80 * fail, as all of the other tests use a different log store. 81 */ 82 public void test_getDefaultFallbackLogs() { 83 CTLogInfo[] knownLogs = CTLogStoreImpl.getDefaultFallbackLogs(); 84 assertEquals(KnownLogs.LOG_COUNT, knownLogs.length); 85 } 86 87 public void test_loadLog() throws Exception { 88 CTLogInfo log = CTLogStoreImpl.loadLog(new StringBufferInputStream(LOGS_SERIALIZED[0])); 89 assertEquals(LOGS[0], log); 90 91 File testFile = writeFile(LOGS_SERIALIZED[0]); 92 log = CTLogStoreImpl.loadLog(testFile); 93 assertEquals(LOGS[0], log); 94 95 // Empty log file, used to mask fallback logs 96 assertEquals(null, CTLogStoreImpl.loadLog(new StringBufferInputStream(""))); 97 try { 98 CTLogStoreImpl.loadLog(new StringBufferInputStream("randomgarbage")); 99 fail("InvalidLogFileException not thrown"); 100 } catch (CTLogStoreImpl.InvalidLogFileException e) {} 101 102 try { 103 CTLogStoreImpl.loadLog(new File("/nonexistent")); 104 fail("FileNotFoundException not thrown"); 105 } catch (FileNotFoundException e) {} 106 } 107 108 public void test_getKnownLog() throws Exception { 109 File userDir = createTempDirectory(); 110 userDir.deleteOnExit(); 111 112 File systemDir = createTempDirectory(); 113 systemDir.deleteOnExit(); 114 115 CTLogInfo[] fallback = new CTLogInfo[] { LOGS[2], LOGS[3] }; 116 117 CTLogStore store = new CTLogStoreImpl(userDir, systemDir, fallback); 118 119 /* Add logs 0 and 1 to the user and system directories respectively 120 * Log 2 & 3 are part of the fallbacks 121 * But mask log 3 with an empty file in the user directory. 122 * Log 4 is not in the store 123 */ 124 File log0File = new File(userDir, LOG_FILENAMES[0]); 125 File log1File = new File(systemDir, LOG_FILENAMES[1]); 126 File log3File = new File(userDir, LOG_FILENAMES[3]); 127 File log4File = new File(userDir, LOG_FILENAMES[4]); 128 129 writeFile(log0File, LOGS_SERIALIZED[0]); 130 writeFile(log1File, LOGS_SERIALIZED[1]); 131 writeFile(log3File, ""); 132 133 // Logs 01 are present, log 2 is in the fallback and unused, log 3 is present but masked, 134 // log 4 is missing 135 assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID())); 136 assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID())); 137 // Fallback logs are not used if the userDir is present. 138 assertEquals(null, store.getKnownLog(LOGS[2].getID())); 139 assertEquals(null, store.getKnownLog(LOGS[3].getID())); 140 assertEquals(null, store.getKnownLog(LOGS[4].getID())); 141 142 /* Test whether CTLogStoreImpl caches properly 143 * Modify the files on the disk, the result of the store should not change 144 * Delete log 0, mask log 1, add log 4 145 */ 146 log0File.delete(); 147 writeFile(log1File, ""); 148 writeFile(log4File, LOGS_SERIALIZED[4]); 149 150 assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID())); 151 assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID())); 152 assertEquals(null, store.getKnownLog(LOGS[4].getID())); 153 154 // Test that fallback logs are used when the userDir doesn't exist. 155 File doesntExist = new File("/doesnt/exist/"); 156 store = new CTLogStoreImpl(doesntExist, doesntExist, fallback); 157 assertEquals(LOGS[2], store.getKnownLog(LOGS[2].getID())); 158 assertEquals(LOGS[3], store.getKnownLog(LOGS[3].getID())); 159 } 160 161 /** 162 * Create a temporary file and write to it. 163 * The file will be deleted on exit. 164 * @param contents The data to be written to the file 165 * @return A reference to the temporary file 166 */ 167 private File writeFile(String contents) throws IOException { 168 File file = File.createTempFile("test", null); 169 file.deleteOnExit(); 170 writeFile(file, contents); 171 return file; 172 } 173 174 private static void writeFile(File file, String contents) throws FileNotFoundException { 175 PrintWriter writer = new PrintWriter(file); 176 try { 177 writer.write(contents); 178 } finally { 179 writer.close(); 180 } 181 } 182 183 /* 184 * This is NOT safe, as another process could create a file between delete() and mkdir() 185 * It should be fine for tests though 186 */ 187 private static File createTempDirectory() throws IOException { 188 File folder = File.createTempFile("test", ""); 189 folder.delete(); 190 folder.mkdir(); 191 return folder; 192 } 193 } 194 195