17689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker/* 27689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * Copyright (C) 2016 The Android Open Source Project 37689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * 47689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * Licensed under the Apache License, Version 2.0 (the "License"); 57689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * you may not use this file except in compliance with the License. 67689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * You may obtain a copy of the License at 77689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * 87689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * http://www.apache.org/licenses/LICENSE-2.0 97689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * 107689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * Unless required by applicable law or agreed to in writing, software 117689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * distributed under the License is distributed on an "AS IS" BASIS, 127689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * See the License for the specific language governing permissions and 147689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * limitations under the License. 157689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker */ 167689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 177689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerpackage com.android.server.updates; 187689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 197689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport com.android.internal.util.HexDump; 207689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport android.os.FileUtils; 217689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport android.system.Os; 227689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport android.system.ErrnoException; 237689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport android.util.Base64; 247689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport android.util.Slog; 257689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.File; 267689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.FileFilter; 277689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.FileOutputStream; 287689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.IOException; 297689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.OutputStreamWriter; 307689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.io.StringBufferInputStream; 317689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.nio.charset.StandardCharsets; 327689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.security.MessageDigest; 337689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.security.PublicKey; 347689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport java.security.NoSuchAlgorithmException; 357689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport org.json.JSONArray; 367689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport org.json.JSONException; 377689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerimport org.json.JSONObject; 387689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 397689446208ae41ecf54431778647fdc83ee16fcaChad Brubakerpublic class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver { 407689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 417689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private static final String TAG = "CTLogInstallReceiver"; 427689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private static final String LOGDIR_PREFIX = "logs-"; 437689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 447689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker public CertificateTransparencyLogInstallReceiver() { 457689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker super("/data/misc/keychain/trusted_ct_logs/", "ct_logs", "metadata/", "version"); 467689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 477689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 487689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker @Override 497689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker protected void install(byte[] content, int version) throws IOException { 507689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker /* Install is complicated here because we translate the input, which is a JSON file 517689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * containing log information to a directory with a file per log. To support atomically 527689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * replacing the old configuration directory with the new there's a bunch of steps. We 537689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * create a new directory with the logs and then do an atomic update of the current symlink 547689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * to point to the new directory. 557689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker */ 567689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 577689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 1. Ensure that the update dir exists and is readable 587689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker updateDir.mkdir(); 597689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!updateDir.isDirectory()) { 607689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Unable to make directory " + updateDir.getCanonicalPath()); 617689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 627689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!updateDir.setReadable(true, false)) { 637689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Unable to set permissions on " + 647689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker updateDir.getCanonicalPath()); 657689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 667689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File currentSymlink = new File(updateDir, "current"); 677689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version)); 687689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File oldDirectory; 697689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 2. Handle the corner case where the new directory already exists. 707689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (newVersion.exists()) { 717689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // If the symlink has already been updated then the update died between steps 7 and 8 727689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // and so we cannot delete the directory since its in use. Instead just bump the version 737689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // and return. 747689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) { 757689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes()); 767689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker deleteOldLogDirectories(); 777689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker return; 787689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } else { 797689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker FileUtils.deleteContentsAndDir(newVersion); 807689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 817689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 827689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try { 837689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 3. Create /data/misc/keychain/trusted_ct_logs/<new_version>/ . 847689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker newVersion.mkdir(); 857689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!newVersion.isDirectory()) { 867689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Unable to make directory " + newVersion.getCanonicalPath()); 877689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 887689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!newVersion.setReadable(true, false)) { 897689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Failed to set " +newVersion.getCanonicalPath() + 907689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker " readable"); 917689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 927689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 937689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 4. For each log in the log file create the corresponding file in <new_version>/ . 947689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try { 957689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8)); 967689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker JSONArray logs = json.getJSONArray("logs"); 977689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker for (int i = 0; i < logs.length(); i++) { 987689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker JSONObject log = logs.getJSONObject(i); 997689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker installLog(newVersion, log); 1007689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1017689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } catch (JSONException e) { 1027689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Failed to parse logs", e); 1037689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1047689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1057689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic 1067689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // update. 1077689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File tempSymlink = new File(updateDir, "new_symlink"); 1087689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try { 1097689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker Os.symlink(newVersion.getCanonicalPath(), tempSymlink.getCanonicalPath()); 1107689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } catch (ErrnoException e) { 1117689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Failed to create symlink", e); 1127689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1137689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1147689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 6. Update the symlink target, this is the actual update step. 1157689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker tempSymlink.renameTo(currentSymlink.getAbsoluteFile()); 1167689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } catch (IOException | RuntimeException e) { 1177689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker FileUtils.deleteContentsAndDir(newVersion); 1187689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw e; 1197689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1207689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath()); 1217689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 7. Update the current version information 1227689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes()); 1237689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // 8. Cleanup 1247689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker deleteOldLogDirectories(); 1257689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1267689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1277689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private void installLog(File directory, JSONObject logObject) throws IOException { 1287689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try { 1297689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker String logFilename = getLogFileName(logObject.getString("key")); 1307689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File file = new File(directory, logFilename); 1317689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try (OutputStreamWriter out = 1327689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { 1337689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker writeLogEntry(out, "key", logObject.getString("key")); 1347689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker writeLogEntry(out, "url", logObject.getString("url")); 1357689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker writeLogEntry(out, "description", logObject.getString("description")); 1367689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1377689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!file.setReadable(true, false)) { 1387689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); 1397689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1407689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } catch (JSONException e) { 1417689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new IOException("Failed to parse log", e); 1427689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1437689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1447689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1457689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1467689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker /** 1477689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * Get the filename for a log based on its public key. This must be kept in sync with 1487689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker * org.conscrypt.ct.CTLogStoreImpl. 1497689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker */ 1507689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private String getLogFileName(String base64PublicKey) { 1517689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT); 1527689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker try { 1537689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes); 1547689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker return HexDump.toHexString(id, false); 1557689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } catch (NoSuchAlgorithmException e) { 1567689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker // SHA-256 is guaranteed to be available. 1577689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throw new RuntimeException(e); 1587689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1597689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1607689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1617689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private void writeLogEntry(OutputStreamWriter out, String key, String value) 1627689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker throws IOException { 1637689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker out.write(key + ":" + value + "\n"); 1647689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1657689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker 1667689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker private void deleteOldLogDirectories() throws IOException { 1677689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker if (!updateDir.exists()) { 1687689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker return; 1697689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1707689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker File currentTarget = new File(updateDir, "current").getCanonicalFile(); 1717689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker FileFilter filter = new FileFilter() { 1727689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker @Override 1737689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker public boolean accept(File file) { 1747689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX); 1757689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1767689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker }; 1777689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker for (File f : updateDir.listFiles(filter)) { 1787689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker FileUtils.deleteContentsAndDir(f); 1797689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1807689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker } 1817689446208ae41ecf54431778647fdc83ee16fcaChad Brubaker} 182