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