1f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar/*
2f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * Copyright (C) 2015 The Android Open Source Project
3f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar *
4f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * Licensed under the Apache License, Version 2.0 (the "License");
5f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * you may not use this file except in compliance with the License.
6f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * You may obtain a copy of the License at
7f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar *
8f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar *      http://www.apache.org/licenses/LICENSE-2.0
9f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar *
10f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * Unless required by applicable law or agreed to in writing, software
11f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * distributed under the License is distributed on an "AS IS" BASIS,
12f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * See the License for the specific language governing permissions and
14f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar * limitations under the License.
15f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar */
16f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
17f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietarpackage org.conscrypt.ct;
18f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
192558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittlerimport java.io.ByteArrayInputStream;
202693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.io.File;
212693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.io.FileInputStream;
222693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.io.FileNotFoundException;
23d0687b8c0d5aa6e88853fff774afd24ec331964cKenny Rootimport java.io.InputStream;
242693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.nio.ByteBuffer;
252558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittlerimport java.nio.charset.Charset;
262693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.security.InvalidKeyException;
27f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietarimport java.security.NoSuchAlgorithmException;
28d0687b8c0d5aa6e88853fff774afd24ec331964cKenny Rootimport java.security.PublicKey;
29f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietarimport java.util.Arrays;
302693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.util.Collections;
31bea3563621a1b743812e387b3783b070fb9f9fbbKenny Rootimport java.util.HashMap;
322693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.util.HashSet;
332693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietarimport java.util.Scanner;
34d0687b8c0d5aa6e88853fff774afd24ec331964cKenny Rootimport java.util.Set;
3529916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittlerimport org.conscrypt.Internal;
3629916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittlerimport org.conscrypt.InternalUtil;
37f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
3829916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittler/**
3929916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittler * @hide
4029916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittler */
4129916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittler@Internal
42f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietarpublic class CTLogStoreImpl implements CTLogStore {
432558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler    private static final Charset US_ASCII = Charset.forName("US-ASCII");
442558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler
452693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    /**
462693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * Thrown when parsing of a log file fails.
472693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     */
482693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public static class InvalidLogFileException extends Exception {
492693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        public InvalidLogFileException() {
502693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
512693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
522693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        public InvalidLogFileException(String message) {
532693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            super(message);
542693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
552693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
562693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        public InvalidLogFileException(String message, Throwable cause) {
572693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            super(message, cause);
582693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
592693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
602693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        public InvalidLogFileException(Throwable cause) {
612693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            super(cause);
622693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
632693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
642693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
652693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private static final File defaultUserLogDir;
662693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private static final File defaultSystemLogDir;
672693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    // Lazy loaded by CTLogStoreImpl()
682693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private static volatile CTLogInfo[] defaultFallbackLogs = null;
692693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    static {
702693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        String ANDROID_DATA = System.getenv("ANDROID_DATA");
712693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
72518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker        defaultUserLogDir = new File(ANDROID_DATA + "/misc/keychain/trusted_ct_logs/current/");
732693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        defaultSystemLogDir = new File(ANDROID_ROOT + "/etc/security/ct_known_logs/");
742693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
752693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
76ca253dc11efbb040c39eaa49ef9e06c344d7e9f2Kenny Root    private File userLogDir;
77ca253dc11efbb040c39eaa49ef9e06c344d7e9f2Kenny Root    private File systemLogDir;
78ca253dc11efbb040c39eaa49ef9e06c344d7e9f2Kenny Root    private CTLogInfo[] fallbackLogs;
792693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
80bea3563621a1b743812e387b3783b070fb9f9fbbKenny Root    private HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
81bea3563621a1b743812e387b3783b070fb9f9fbbKenny Root    private Set<ByteBuffer> missingLogCache = Collections.synchronizedSet(new HashSet<ByteBuffer>());
822693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
832693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public CTLogStoreImpl() {
842693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        this(defaultUserLogDir,
852693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar             defaultSystemLogDir,
862693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar             getDefaultFallbackLogs());
872693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
882693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
892693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public CTLogStoreImpl(File userLogDir, File systemLogDir, CTLogInfo[] fallbackLogs) {
902693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        this.userLogDir = userLogDir;
912693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        this.systemLogDir = systemLogDir;
922693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        this.fallbackLogs = fallbackLogs;
932693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
94f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
95f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar    @Override
96f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar    public CTLogInfo getKnownLog(byte[] logId) {
972693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        ByteBuffer buf = ByteBuffer.wrap(logId);
982693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        CTLogInfo log = logCache.get(buf);
992693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        if (log != null) {
1002693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return log;
1012693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
1022693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        if (missingLogCache.contains(buf)) {
1032693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return null;
1042693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
1052693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1062693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        log = findKnownLog(logId);
1072693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        if (log != null) {
1082693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            logCache.put(buf, log);
1092693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } else {
1102693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            missingLogCache.add(buf);
111f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        }
1122693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1132693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        return log;
1142693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
1152693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1162693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private CTLogInfo findKnownLog(byte[] logId) {
1172693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        String filename = hexEncode(logId);
1182693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        try {
1192693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return loadLog(new File(userLogDir, filename));
1202693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (InvalidLogFileException e) {
1212693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return null;
1222693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (FileNotFoundException e) {}
1232693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1242693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        try {
1252693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return loadLog(new File(systemLogDir, filename));
1262693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (InvalidLogFileException e) {
1272693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            return null;
1282693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (FileNotFoundException e) {}
1292693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
130518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker        // If the updateable logs dont exist then use the fallback logs.
131518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker        if (!userLogDir.exists()) {
132518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker            for (CTLogInfo log: fallbackLogs) {
133518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker                if (Arrays.equals(logId, log.getID())) {
134518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker                    return log;
135518ca8c6c8d88daa55d4ea0d0ca83a4654bd1071Chad Brubaker                }
136f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar            }
137f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        }
138f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        return null;
139f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar    }
140f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
1412693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public static CTLogInfo[] getDefaultFallbackLogs() {
1422693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        CTLogInfo[] result = defaultFallbackLogs;
1432693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        if (result == null) {
1442693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            // single-check idiom
1452693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            defaultFallbackLogs = result = createDefaultFallbackLogs();
1462693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
1472693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        return result;
1482693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
1492693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1502693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private static CTLogInfo[] createDefaultFallbackLogs() {
1514dc3e45f010e805b5b7fb9ac2e40cc05244209b0Kenny Root        CTLogInfo[] logs = new CTLogInfo[KnownLogs.LOG_COUNT];
152f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        for (int i = 0; i < KnownLogs.LOG_COUNT; i++) {
153f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar            try {
15429916ef38dc9cb4e4c6e3fdb87d4e921546d3ef4Nathan Mittler                PublicKey key = InternalUtil.logKeyToPublicKey(KnownLogs.LOG_KEYS[i]);
155f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar
156f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar                logs[i] = new CTLogInfo(key,
157f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar                                        KnownLogs.LOG_DESCRIPTIONS[i],
158f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar                                        KnownLogs.LOG_URLS[i]);
159f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar            } catch (NoSuchAlgorithmException e) {
160f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar                throw new RuntimeException(e);
161f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar            }
162f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        }
1632693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1642693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        defaultFallbackLogs = logs;
165f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar        return logs;
166f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar    }
1672693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1682693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    /**
1692693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * Load a CTLogInfo from a file.
1702693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * @throws FileNotFoundException if the file does not exist
1712693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * @throws InvalidLogFileException if the file could not be parsed properly
1722693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * @return a CTLogInfo or null if the file is empty
1732693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     */
1742693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public static CTLogInfo loadLog(File file) throws FileNotFoundException,
1752693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar                                                      InvalidLogFileException {
1762693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        return loadLog(new FileInputStream(file));
1772693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
1782693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
1792693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    /**
180aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root     * Load a CTLogInfo from a textual representation. Closes {@code input} upon completion
181aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root     * of loading.
182aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root     *
1832693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * @throws InvalidLogFileException if the input could not be parsed properly
1842693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     * @return a CTLogInfo or null if the input is empty
1852693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar     */
1862693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    public static CTLogInfo loadLog(InputStream input) throws InvalidLogFileException {
187aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        final Scanner scan = new Scanner(input, "UTF-8");
188aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        scan.useDelimiter("\n");
1892693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
190aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        String description = null;
191aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        String url = null;
192aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        String key = null;
193aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        try {
194aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root            // If the scanner can't even read one token then the file must be empty/blank
195aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root            if (!scan.hasNext()) {
196aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                return null;
1972693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            }
1982693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
199aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root            while (scan.hasNext()) {
200aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                String[] parts = scan.next().split(":", 2);
201aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                if (parts.length < 2) {
202aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                    continue;
203aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                }
204aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root
205aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                String name = parts[0];
206aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                String value = parts[1];
207aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                switch (name) {
208aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                    case "description":
209aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        description = value;
210aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        break;
211aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                    case "url":
212aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        url = value;
213aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        break;
214aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                    case "key":
215aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        key = value;
216aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                        break;
217aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root                }
2182693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            }
219aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root        } finally {
220aab5adf03c8d67b19e43cdf2d7a76672032411f7Kenny Root            scan.close();
2212693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
2222693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
2232693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        if (description == null || url == null || key == null) {
2242693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            throw new InvalidLogFileException("Missing one of 'description', 'url' or 'key'");
2252693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
2262693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
2272693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        PublicKey pubkey;
2282693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        try {
2292558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler            pubkey = InternalUtil.readPublicKeyPem(new ByteArrayInputStream(
2302558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler                    ("-----BEGIN PUBLIC KEY-----\n" +
2312693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar                        key + "\n" +
2322558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler                        "-----END PUBLIC KEY-----").getBytes(US_ASCII)));
2332693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (InvalidKeyException e) {
2342693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            throw new InvalidLogFileException(e);
2352693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        } catch (NoSuchAlgorithmException e) {
2362693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            throw new InvalidLogFileException(e);
2372693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
2382693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
2392693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        return new CTLogInfo(pubkey, description, url);
2402693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
2412693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
2422693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private final static char[] HEX_DIGITS = new char[] {
2432693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        '0', '1', '2', '3', '4', '5', '6', '7',
2442693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
2452693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    };
2462693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar
2472693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    private static String hexEncode(byte[] data) {
2482558aec59ff6708b7e39cd9890db217cf9c043efNathan Mittler        StringBuilder sb = new StringBuilder(data.length * 2);
2492693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        for (byte b: data) {
2502693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            sb.append(HEX_DIGITS[(b >> 4) & 0x0f]);
2512693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar            sb.append(HEX_DIGITS[b & 0x0f]);
2522693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        }
2532693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar        return sb.toString();
2542693d203bf976ff2c5167df13e382d4e10024ad7Paul Lietar    }
255f714bf65e491155e7837ddb3242e3ee6be173943Paul Lietar}
256