| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.conscrypt.ct; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.StringBufferInputStream; |
| import java.security.PublicKey; |
| import junit.framework.TestCase; |
| import org.conscrypt.OpenSSLKey; |
| |
| public class CTLogStoreImplTest extends TestCase { |
| private static final String[] LOG_KEYS = new String[] { |
| "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U" + |
| "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==", |
| |
| "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErEULmlBnX9L/+AK20hLYzPMFozYx" + |
| "pP0Wm1ylqGkPEwuDKn9DSpNSOym49SN77BLGuAXu9twOW/qT+ddIYVBEIw==", |
| |
| "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEP6PGcXmjlyCBz2ZFUuUjrgbZLaEF" + |
| "gfLUkt2cEqlSbb4vTuB6WWmgC9h0L6PN6JF0CPcajpBKGlTI15242a8d4g==", |
| |
| "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER3qB0NADsP1szXxe4EagrD/ryPVh" + |
| "Y/azWbKyXcK12zhXnO8WH2U4QROVUMctFXLflIzw0EivdRN9t7UH1Od30w==", |
| |
| "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY0ww9JqeJvzVtKNTPVb3JZa7s0ZV" + |
| "duH3PpshpMS5XVoPRSjSQCph6f3HjUcM3c4N2hpa8OFbrFFy37ttUrgD+A==" |
| }; |
| private static final String[] LOG_FILENAMES = new String[] { |
| "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764", |
| "84f8ae3f613b13407a75fa2893b93ab03b18d86c455fe7c241ae020033216446", |
| "89baa01a445100009d8f9a238947115b30702275aafee675a7d94b6b09287619", |
| "57456bffe268e49a190dce4318456034c2b4958f3c0201bed5a366737d1e74ca", |
| "896c898ced4b8e6547fa351266caae4ca304f1c1ec2b623c2ee259c5452147b0" |
| }; |
| |
| private static final CTLogInfo[] LOGS; |
| private static final String[] LOGS_SERIALIZED; |
| |
| static { |
| try { |
| int logCount = LOG_KEYS.length; |
| LOGS = new CTLogInfo[logCount]; |
| LOGS_SERIALIZED = new String[logCount]; |
| for (int i = 0; i < logCount; i++) { |
| PublicKey key = OpenSSLKey.fromPublicKeyPemInputStream(new StringBufferInputStream( |
| "-----BEGIN PUBLIC KEY-----\n" + |
| LOG_KEYS[i] + "\n" + |
| "-----END PUBLIC KEY-----\n")).getPublicKey(); |
| String description = String.format("Test Log %d", i); |
| String url = String.format("log%d.example.com", i); |
| LOGS[i] = new CTLogInfo(key, description, url); |
| LOGS_SERIALIZED[i] = String.format("description:%s\nurl:%s\nkey:%s", |
| description, url, LOG_KEYS[i]); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /* CTLogStoreImpl loads the list of logs lazily when they are first needed |
| * to avoid any overhead when CT is disabled. |
| * This test simply forces the logs to be loaded to make sure it doesn't |
| * fail, as all of the other tests use a different log store. |
| */ |
| public void test_getDefaultFallbackLogs() { |
| CTLogInfo[] knownLogs = CTLogStoreImpl.getDefaultFallbackLogs(); |
| assertEquals(KnownLogs.LOG_COUNT, knownLogs.length); |
| } |
| |
| public void test_loadLog() throws Exception { |
| CTLogInfo log = CTLogStoreImpl.loadLog(new StringBufferInputStream(LOGS_SERIALIZED[0])); |
| assertEquals(LOGS[0], log); |
| |
| File testFile = writeFile(LOGS_SERIALIZED[0]); |
| log = CTLogStoreImpl.loadLog(testFile); |
| assertEquals(LOGS[0], log); |
| |
| // Empty log file, used to mask fallback logs |
| assertEquals(null, CTLogStoreImpl.loadLog(new StringBufferInputStream(""))); |
| try { |
| CTLogStoreImpl.loadLog(new StringBufferInputStream("randomgarbage")); |
| fail("InvalidLogFileException not thrown"); |
| } catch (CTLogStoreImpl.InvalidLogFileException e) {} |
| |
| try { |
| CTLogStoreImpl.loadLog(new File("/nonexistent")); |
| fail("FileNotFoundException not thrown"); |
| } catch (FileNotFoundException e) {} |
| } |
| |
| public void test_getKnownLog() throws Exception { |
| File userDir = createTempDirectory(); |
| userDir.deleteOnExit(); |
| |
| File systemDir = createTempDirectory(); |
| systemDir.deleteOnExit(); |
| |
| CTLogInfo[] fallback = new CTLogInfo[] { LOGS[2], LOGS[3] }; |
| |
| CTLogStore store = new CTLogStoreImpl(userDir, systemDir, fallback); |
| |
| /* Add logs 0 and 1 to the user and system directories respectively |
| * Log 2 & 3 are part of the fallbacks |
| * But mask log 3 with an empty file in the user directory. |
| * Log 4 is not in the store |
| */ |
| File log0File = new File(userDir, LOG_FILENAMES[0]); |
| File log1File = new File(systemDir, LOG_FILENAMES[1]); |
| File log3File = new File(userDir, LOG_FILENAMES[3]); |
| File log4File = new File(userDir, LOG_FILENAMES[4]); |
| |
| writeFile(log0File, LOGS_SERIALIZED[0]); |
| writeFile(log1File, LOGS_SERIALIZED[1]); |
| writeFile(log3File, ""); |
| |
| // Logs 01 are present, log 2 is in the fallback and unused, log 3 is present but masked, |
| // log 4 is missing |
| assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID())); |
| assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID())); |
| // Fallback logs are not used if the userDir is present. |
| assertEquals(null, store.getKnownLog(LOGS[2].getID())); |
| assertEquals(null, store.getKnownLog(LOGS[3].getID())); |
| assertEquals(null, store.getKnownLog(LOGS[4].getID())); |
| |
| /* Test whether CTLogStoreImpl caches properly |
| * Modify the files on the disk, the result of the store should not change |
| * Delete log 0, mask log 1, add log 4 |
| */ |
| log0File.delete(); |
| writeFile(log1File, ""); |
| writeFile(log4File, LOGS_SERIALIZED[4]); |
| |
| assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID())); |
| assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID())); |
| assertEquals(null, store.getKnownLog(LOGS[4].getID())); |
| |
| // Test that fallback logs are used when the userDir doesn't exist. |
| File doesntExist = new File("/doesnt/exist/"); |
| store = new CTLogStoreImpl(doesntExist, doesntExist, fallback); |
| assertEquals(LOGS[2], store.getKnownLog(LOGS[2].getID())); |
| assertEquals(LOGS[3], store.getKnownLog(LOGS[3].getID())); |
| } |
| |
| /** |
| * Create a temporary file and write to it. |
| * The file will be deleted on exit. |
| * @param contents The data to be written to the file |
| * @return A reference to the temporary file |
| */ |
| private File writeFile(String contents) throws IOException { |
| File file = File.createTempFile("test", null); |
| file.deleteOnExit(); |
| writeFile(file, contents); |
| return file; |
| } |
| |
| private static void writeFile(File file, String contents) throws FileNotFoundException { |
| PrintWriter writer = new PrintWriter( |
| new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF_8)), |
| false); |
| try { |
| writer.write(contents); |
| } finally { |
| writer.close(); |
| } |
| } |
| |
| /* |
| * This is NOT safe, as another process could create a file between delete() and mkdir() |
| * It should be fine for tests though |
| */ |
| private static File createTempDirectory() throws IOException { |
| File folder = File.createTempFile("test", ""); |
| folder.delete(); |
| folder.mkdir(); |
| return folder; |
| } |
| } |
| |