Home | History | Annotate | Download | only in ct
      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      */
     87     public void test_getDefaultFallbackLogs() {
     88         CTLogInfo[] knownLogs = CTLogStoreImpl.getDefaultFallbackLogs();
     89         assertEquals(KnownLogs.LOG_COUNT, knownLogs.length);
     90     }
     91 
     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 
    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      */
    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 
    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      */
    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