Loading services/core/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -215,6 +215,7 @@ java_library_static { "power_hint_flags_lib", "biometrics_flags_lib", "am_flags_lib", "updates_flags_lib", "com_android_server_accessibility_flags_lib", "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "com_android_wm_shell_flags_lib", Loading services/core/java/com/android/server/updates/Android.bp 0 → 100644 +11 −0 Original line number Diff line number Diff line aconfig_declarations { name: "updates_flags", package: "com.android.server.updates", container: "system", srcs: ["*.aconfig"], } java_aconfig_library { name: "updates_flags_lib", aconfig_declarations: "updates_flags", } services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java +44 −68 Original line number Diff line number Diff line Loading @@ -16,17 +16,15 @@ package com.android.server.updates; import android.content.Context; import android.content.Intent; import android.os.FileUtils; import android.system.ErrnoException; import android.system.Os; import android.util.Base64; import android.util.Slog; import com.android.internal.util.HexDump; import libcore.io.Streams; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; Loading @@ -36,10 +34,7 @@ import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver { Loading @@ -52,31 +47,31 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta @Override protected void install(InputStream inputStream, int version) throws IOException { /* Install is complicated here because we translate the input, which is a JSON file * containing log information to a directory with a file per log. To support atomically * replacing the old configuration directory with the new there's a bunch of steps. We * create a new directory with the logs and then do an atomic update of the current symlink * to point to the new directory. */ if (!Flags.certificateTransparencyInstaller()) { return; } // To support atomically replacing the old configuration directory with the new there's a // bunch of steps. We create a new directory with the logs and then do an atomic update of // the current symlink to point to the new directory. // 1. Ensure that the update dir exists and is readable updateDir.mkdir(); if (!updateDir.isDirectory()) { throw new IOException("Unable to make directory " + updateDir.getCanonicalPath()); } if (!updateDir.setReadable(true, false)) { throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); } File currentSymlink = new File(updateDir, "current"); File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version)); File oldDirectory; // 2. Handle the corner case where the new directory already exists. if (newVersion.exists()) { // If the symlink has already been updated then the update died between steps 7 and 8 // and so we cannot delete the directory since its in use. Instead just bump the version // and return. if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) { writeUpdate(updateDir, updateVersion, writeUpdate( updateDir, updateVersion, new ByteArrayInputStream(Long.toString(version).getBytes())); deleteOldLogDirectories(); return; Loading @@ -91,22 +86,12 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta throw new IOException("Unable to make directory " + newVersion.getCanonicalPath()); } if (!newVersion.setReadable(true, false)) { throw new IOException("Failed to set " +newVersion.getCanonicalPath() + " readable"); throw new IOException( "Failed to set " + newVersion.getCanonicalPath() + " readable"); } // 4. For each log in the log file create the corresponding file in <new_version>/ . try { byte[] content = Streams.readFullyNoClose(inputStream); JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8)); JSONArray logs = json.getJSONArray("logs"); for (int i = 0; i < logs.length(); i++) { JSONObject log = logs.getJSONObject(i); installLog(newVersion, log); } } catch (JSONException e) { throw new IOException("Failed to parse logs", e); } // 4. Validate the log list json and move the file in <new_version>/ . installLogList(newVersion, inputStream); // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic // update. Loading @@ -125,49 +110,38 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta } Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath()); // 7. Update the current version information writeUpdate(updateDir, updateVersion, writeUpdate( updateDir, updateVersion, new ByteArrayInputStream(Long.toString(version).getBytes())); // 8. Cleanup deleteOldLogDirectories(); } private void installLog(File directory, JSONObject logObject) throws IOException { try { String logFilename = getLogFileName(logObject.getString("key")); File file = new File(directory, logFilename); try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { writeLogEntry(out, "key", logObject.getString("key")); writeLogEntry(out, "url", logObject.getString("url")); writeLogEntry(out, "description", logObject.getString("description")); } if (!file.setReadable(true, false)) { throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); @Override protected void postInstall(Context context, Intent intent) { if (!Flags.certificateTransparencyInstaller()) { return; } } catch (JSONException e) { throw new IOException("Failed to parse log", e); } private void installLogList(File directory, InputStream inputStream) throws IOException { try { byte[] content = Streams.readFullyNoClose(inputStream); if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) { throw new IOException("Log list data not valid"); } /** * Get the filename for a log based on its public key. This must be kept in sync with * org.conscrypt.ct.CTLogStoreImpl. */ private String getLogFileName(String base64PublicKey) { byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT); try { byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes); return HexDump.toHexString(id, false); } catch (NoSuchAlgorithmException e) { // SHA-256 is guaranteed to be available. throw new RuntimeException(e); File file = new File(directory, "log_list.json"); try (FileOutputStream outputStream = new FileOutputStream(file)) { outputStream.write(content); } if (!file.setReadable(true, false)) { throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); } } catch (JSONException e) { throw new IOException("Malformed json in log list", e); } private void writeLogEntry(OutputStreamWriter out, String key, String value) throws IOException { out.write(key + ":" + value + "\n"); } private void deleteOldLogDirectories() throws IOException { Loading @@ -175,10 +149,12 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta return; } File currentTarget = new File(updateDir, "current").getCanonicalFile(); FileFilter filter = new FileFilter() { FileFilter filter = new FileFilter() { @Override public boolean accept(File file) { return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX); return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX); } }; for (File f : updateDir.listFiles(filter)) { Loading services/core/java/com/android/server/updates/flags.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.server.updates" container: "system" flag { name: "certificate_transparency_installer" is_exported: true namespace: "network_security" description: "Enable certificate transparency installer for log list data" bug: "319829948" } Loading
services/core/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -215,6 +215,7 @@ java_library_static { "power_hint_flags_lib", "biometrics_flags_lib", "am_flags_lib", "updates_flags_lib", "com_android_server_accessibility_flags_lib", "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "com_android_wm_shell_flags_lib", Loading
services/core/java/com/android/server/updates/Android.bp 0 → 100644 +11 −0 Original line number Diff line number Diff line aconfig_declarations { name: "updates_flags", package: "com.android.server.updates", container: "system", srcs: ["*.aconfig"], } java_aconfig_library { name: "updates_flags_lib", aconfig_declarations: "updates_flags", }
services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java +44 −68 Original line number Diff line number Diff line Loading @@ -16,17 +16,15 @@ package com.android.server.updates; import android.content.Context; import android.content.Intent; import android.os.FileUtils; import android.system.ErrnoException; import android.system.Os; import android.util.Base64; import android.util.Slog; import com.android.internal.util.HexDump; import libcore.io.Streams; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; Loading @@ -36,10 +34,7 @@ import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver { Loading @@ -52,31 +47,31 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta @Override protected void install(InputStream inputStream, int version) throws IOException { /* Install is complicated here because we translate the input, which is a JSON file * containing log information to a directory with a file per log. To support atomically * replacing the old configuration directory with the new there's a bunch of steps. We * create a new directory with the logs and then do an atomic update of the current symlink * to point to the new directory. */ if (!Flags.certificateTransparencyInstaller()) { return; } // To support atomically replacing the old configuration directory with the new there's a // bunch of steps. We create a new directory with the logs and then do an atomic update of // the current symlink to point to the new directory. // 1. Ensure that the update dir exists and is readable updateDir.mkdir(); if (!updateDir.isDirectory()) { throw new IOException("Unable to make directory " + updateDir.getCanonicalPath()); } if (!updateDir.setReadable(true, false)) { throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); } File currentSymlink = new File(updateDir, "current"); File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version)); File oldDirectory; // 2. Handle the corner case where the new directory already exists. if (newVersion.exists()) { // If the symlink has already been updated then the update died between steps 7 and 8 // and so we cannot delete the directory since its in use. Instead just bump the version // and return. if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) { writeUpdate(updateDir, updateVersion, writeUpdate( updateDir, updateVersion, new ByteArrayInputStream(Long.toString(version).getBytes())); deleteOldLogDirectories(); return; Loading @@ -91,22 +86,12 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta throw new IOException("Unable to make directory " + newVersion.getCanonicalPath()); } if (!newVersion.setReadable(true, false)) { throw new IOException("Failed to set " +newVersion.getCanonicalPath() + " readable"); throw new IOException( "Failed to set " + newVersion.getCanonicalPath() + " readable"); } // 4. For each log in the log file create the corresponding file in <new_version>/ . try { byte[] content = Streams.readFullyNoClose(inputStream); JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8)); JSONArray logs = json.getJSONArray("logs"); for (int i = 0; i < logs.length(); i++) { JSONObject log = logs.getJSONObject(i); installLog(newVersion, log); } } catch (JSONException e) { throw new IOException("Failed to parse logs", e); } // 4. Validate the log list json and move the file in <new_version>/ . installLogList(newVersion, inputStream); // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic // update. Loading @@ -125,49 +110,38 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta } Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath()); // 7. Update the current version information writeUpdate(updateDir, updateVersion, writeUpdate( updateDir, updateVersion, new ByteArrayInputStream(Long.toString(version).getBytes())); // 8. Cleanup deleteOldLogDirectories(); } private void installLog(File directory, JSONObject logObject) throws IOException { try { String logFilename = getLogFileName(logObject.getString("key")); File file = new File(directory, logFilename); try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { writeLogEntry(out, "key", logObject.getString("key")); writeLogEntry(out, "url", logObject.getString("url")); writeLogEntry(out, "description", logObject.getString("description")); } if (!file.setReadable(true, false)) { throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); @Override protected void postInstall(Context context, Intent intent) { if (!Flags.certificateTransparencyInstaller()) { return; } } catch (JSONException e) { throw new IOException("Failed to parse log", e); } private void installLogList(File directory, InputStream inputStream) throws IOException { try { byte[] content = Streams.readFullyNoClose(inputStream); if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) { throw new IOException("Log list data not valid"); } /** * Get the filename for a log based on its public key. This must be kept in sync with * org.conscrypt.ct.CTLogStoreImpl. */ private String getLogFileName(String base64PublicKey) { byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT); try { byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes); return HexDump.toHexString(id, false); } catch (NoSuchAlgorithmException e) { // SHA-256 is guaranteed to be available. throw new RuntimeException(e); File file = new File(directory, "log_list.json"); try (FileOutputStream outputStream = new FileOutputStream(file)) { outputStream.write(content); } if (!file.setReadable(true, false)) { throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); } } catch (JSONException e) { throw new IOException("Malformed json in log list", e); } private void writeLogEntry(OutputStreamWriter out, String key, String value) throws IOException { out.write(key + ":" + value + "\n"); } private void deleteOldLogDirectories() throws IOException { Loading @@ -175,10 +149,12 @@ public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInsta return; } File currentTarget = new File(updateDir, "current").getCanonicalFile(); FileFilter filter = new FileFilter() { FileFilter filter = new FileFilter() { @Override public boolean accept(File file) { return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX); return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX); } }; for (File f : updateDir.listFiles(filter)) { Loading
services/core/java/com/android/server/updates/flags.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.server.updates" container: "system" flag { name: "certificate_transparency_installer" is_exported: true namespace: "network_security" description: "Enable certificate transparency installer for log list data" bug: "319829948" }