Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c1a45cb5 authored by Sandro Montanari's avatar Sandro Montanari Committed by Android (Google) Code Review
Browse files

Merge "Remove single log extraction from log_list.json" into main

parents 77b2e9ab aeed3727
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+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",
}
+44 −68
Original line number Diff line number Diff line
@@ -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;

@@ -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 {

@@ -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;
@@ -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.
@@ -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 {
@@ -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)) {
+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"
}