• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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