Merge "Add CompatibilityVersion v2 to CT downloader" into main

This commit is contained in:
Treehugger Robot 2025-03-14 09:26:07 -07:00 • committed by Gerrit Code Review
commit d64a649df9
7 changed files with 85 additions and 77 deletions

View file

@ -33,8 +33,7 @@ import com.android.server.net.ct.DownloadHelper.DownloadStatus;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@ -48,28 +47,21 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
private final DownloadHelper mDownloadHelper;
private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyLogger mLogger;
private final List<CompatibilityVersion> mCompatVersions = new ArrayList<>();
private final Collection<CompatibilityVersion> mCompatVersions;
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
SignatureVerifier signatureVerifier,
CertificateTransparencyLogger logger) {
CertificateTransparencyLogger logger,
Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mLogger = logger;
}
void addCompatibilityVersion(CompatibilityVersion compatVersion) {
mCompatVersions.add(compatVersion);
}
void clearCompatibilityVersions() {
mCompatVersions.clear();
mCompatVersions = compatVersions;
}
long startPublicKeyDownload() {

View file

@ -28,6 +28,8 @@ import android.os.ConfigUpdate;
import android.os.SystemClock;
import android.util.Log;
import java.util.Collection;
/** Implementation of the Certificate Transparency job */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class CertificateTransparencyJob extends BroadcastReceiver {
@ -37,8 +39,8 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
private final Context mContext;
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private final CompatibilityVersion mCompatVersion;
private final SignatureVerifier mSignatureVerifier;
private final Collection<CompatibilityVersion> mCompatVersions;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
@ -50,13 +52,13 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
Context context,
DataStore dataStore,
CertificateTransparencyDownloader certificateTransparencyDownloader,
CompatibilityVersion compatVersion,
SignatureVerifier signatureVerifier) {
SignatureVerifier signatureVerifier,
Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
mCompatVersion = compatVersion;
mSignatureVerifier = signatureVerifier;
mCompatVersions = compatVersions;
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
@ -99,7 +101,9 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
}
mDependenciesReady = false;
mCompatVersion.delete();
for (CompatibilityVersion compatVersion : mCompatVersions) {
compatVersion.delete();
}
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyJob canceled.");
@ -129,7 +133,6 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
private void startDependencies() {
mDataStore.load();
mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
mSignatureVerifier.loadAllowedKeys();
mContext.registerReceiver(
mCertificateTransparencyDownloader,
@ -144,7 +147,6 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
private void stopDependencies() {
mContext.unregisterReceiver(mCertificateTransparencyDownloader);
mSignatureVerifier.clearAllowedKeys();
mCertificateTransparencyDownloader.clearCompatibilityVersions();
mDataStore.delete();
if (Config.DEBUG) {

View file

@ -30,6 +30,8 @@ import android.util.Log;
import com.android.server.SystemService;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executors;
/** Implementation of the Certificate Transparency service. */
@ -51,8 +53,18 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana
/** Creates a new {@link CertificateTransparencyService} object. */
public CertificateTransparencyService(Context context) {
DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
SignatureVerifier signatureVerifier = new SignatureVerifier(context);
Collection<CompatibilityVersion> compatVersions =
Arrays.asList(
new CompatibilityVersion(
Config.COMPATIBILITY_VERSION_V1,
Config.URL_SIGNATURE_V1,
Config.URL_LOG_LIST_V1),
new CompatibilityVersion(
Config.COMPATIBILITY_VERSION_V2,
Config.URL_SIGNATURE_V2,
Config.URL_LOG_LIST_V2));
mCertificateTransparencyJob =
new CertificateTransparencyJob(
context,
@ -62,13 +74,10 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana
dataStore,
new DownloadHelper(context),
signatureVerifier,
new CertificateTransparencyLoggerImpl(dataStore)),
new CompatibilityVersion(
Config.COMPATIBILITY_VERSION,
Config.URL_SIGNATURE,
Config.URL_LOG_LIST,
Config.CT_ROOT_DIRECTORY_PATH),
signatureVerifier);
new CertificateTransparencyLoggerImpl(dataStore),
compatVersions),
signatureVerifier,
compatVersions);
}
/**

View file

@ -23,6 +23,8 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState;
import org.json.JSONException;
@ -40,6 +42,8 @@ class CompatibilityVersion {
private static final String TAG = "CompatibilityVersion";
private static File sRootDirectory = new File(Config.CT_ROOT_DIRECTORY_PATH);
static final String LOGS_DIR_PREFIX = "logs-";
static final String LOGS_LIST_FILE_NAME = "log_list.json";
static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
@ -48,23 +52,21 @@ class CompatibilityVersion {
private final String mMetadataUrl;
private final String mContentUrl;
private final File mRootDirectory;
private final File mVersionDirectory;
private final File mCurrentLogsDirSymlink;
CompatibilityVersion(
String compatVersion, String metadataUrl, String contentUrl, File rootDirectory) {
String compatVersion, String metadataUrl, String contentUrl) {
mCompatVersion = compatVersion;
mMetadataUrl = metadataUrl;
mContentUrl = contentUrl;
mRootDirectory = rootDirectory;
mVersionDirectory = new File(rootDirectory, compatVersion);
mVersionDirectory = new File(sRootDirectory, compatVersion);
mCurrentLogsDirSymlink = new File(mVersionDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
}
CompatibilityVersion(
String compatVersion, String metadataUrl, String contentUrl, String rootDirectoryPath) {
this(compatVersion, metadataUrl, contentUrl, new File(rootDirectoryPath));
@VisibleForTesting
static void setRootDirectoryForTesting(File rootDirectory) {
sRootDirectory = rootDirectory;
}
/**
@ -75,8 +77,8 @@ class CompatibilityVersion {
* @return true if the log list was installed successfully, false otherwise.
* @throws IOException if the list cannot be saved in the CT directory.
*/
LogListUpdateStatus install(
InputStream newContent, LogListUpdateStatus.Builder statusBuilder) throws IOException {
LogListUpdateStatus install(InputStream newContent, LogListUpdateStatus.Builder statusBuilder)
throws IOException {
String content = new String(newContent.readAllBytes(), UTF_8);
try {
JSONObject contentJson = new JSONObject(content);
@ -98,7 +100,7 @@ class CompatibilityVersion {
// 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 the path to the root and version directories exist and are readable.
DirectoryUtils.makeDir(mRootDirectory);
DirectoryUtils.makeDir(sRootDirectory);
DirectoryUtils.makeDir(mVersionDirectory);
File newLogsDir = new File(mVersionDirectory, LOGS_DIR_PREFIX + version);

View file

@ -33,18 +33,14 @@ final class Config {
private static final String PREFERENCES_FILE_NAME = "ct.preferences";
static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
// CT directory
// CT paths
static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/";
static final String COMPATIBILITY_VERSION = "v1";
static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
// Phenotype flags
static final String NAMESPACE_NETWORK_SECURITY = "network_security";
private static final String FLAGS_PREFIX = "CertificateTransparencyLogList__";
static final String FLAG_SERVICE_ENABLED = FLAGS_PREFIX + "service_enabled";
static final String FLAG_CONTENT_URL = FLAGS_PREFIX + "content_url";
static final String FLAG_METADATA_URL = FLAGS_PREFIX + "metadata_url";
static final String FLAG_VERSION = FLAGS_PREFIX + "version";
static final String FLAG_PUBLIC_KEY = FLAGS_PREFIX + "public_key";
// properties
static final String VERSION = "version";
@ -53,9 +49,18 @@ final class Config {
static final String PUBLIC_KEY_DOWNLOAD_ID = "public_key_download_id";
static final String LOG_LIST_UPDATE_FAILURE_COUNT = "log_list_update_failure_count";
// URLs
static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
static final String URL_LOG_LIST = URL_PREFIX + "log_list.json";
static final String URL_SIGNATURE = URL_PREFIX + "log_list.sig";
// Public Key URLs
static final String URL_PUBLIC_KEY = URL_PREFIX + "log_list.pub";
// Compatibility Version v1
static final String COMPATIBILITY_VERSION_V1 = "v1";
static final String URL_PREFIX_V1 = URL_PREFIX;
static final String URL_LOG_LIST_V1 = URL_PREFIX_V1 + "log_list.json";
static final String URL_SIGNATURE_V1 = URL_PREFIX_V1 + "log_list.sig";
// Compatibility Version v2
static final String COMPATIBILITY_VERSION_V2 = "v2";
static final String URL_PREFIX_V2 = URL_PREFIX + COMPATIBILITY_VERSION_V2 + "/";
static final String URL_LOG_LIST_V2 = URL_PREFIX_V2 + "log_list.json";
static final String URL_SIGNATURE_V2 = URL_PREFIX_V2 + "log_list.sig";
}

View file

@ -60,6 +60,7 @@ import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
@ -94,24 +95,25 @@ public class CertificateTransparencyDownloaderTest {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mDataStore = new DataStore(File.createTempFile("datastore-test", ".properties"));
mSignatureVerifier = new SignatureVerifier(mContext);
CompatibilityVersion.setRootDirectoryForTesting(mContext.getFilesDir());
mCompatVersion =
new CompatibilityVersion(
/* compatVersion= */ "v666",
Config.URL_SIGNATURE_V1,
Config.URL_LOG_LIST_V1);
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
mContext,
mDataStore,
new DownloadHelper(mDownloadManager),
mSignatureVerifier,
mLogger);
mCompatVersion =
new CompatibilityVersion(
/* compatVersion= */ "v666",
Config.URL_SIGNATURE,
Config.URL_LOG_LIST,
mContext.getFilesDir());
mLogger,
Arrays.asList(mCompatVersion));
prepareDownloadManager();
mSignatureVerifier.addAllowedKey(mPublicKey);
mDataStore.load();
mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
}
@After

View file

@ -27,6 +27,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@ -47,9 +48,16 @@ public class CompatibilityVersionTest {
private final File mTestDir =
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir();
private final CompatibilityVersion mCompatVersion =
new CompatibilityVersion(
TEST_VERSION, Config.URL_SIGNATURE, Config.URL_LOG_LIST, mTestDir);
private CompatibilityVersion mCompatVersion;
@Before
public void setUp() {
CompatibilityVersion.setRootDirectoryForTesting(mTestDir);
mCompatVersion =
new CompatibilityVersion(
TEST_VERSION, Config.URL_SIGNATURE_V1, Config.URL_LOG_LIST_V1);
}
@After
public void tearDown() {
@ -111,9 +119,7 @@ public class CompatibilityVersionTest {
JSONObject logList = makeLogList(version, "i_am_content");
try (InputStream inputStream = asStream(logList)) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@ -142,9 +148,7 @@ public class CompatibilityVersionTest {
@Test
public void testCompatibilityVersion_deleteSuccessfully() throws Exception {
try (InputStream inputStream = asStream(makeLogList(/* version= */ "123"))) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@ -156,9 +160,7 @@ public class CompatibilityVersionTest {
@Test
public void testCompatibilityVersion_invalidLogList() throws Exception {
try (InputStream inputStream = new ByteArrayInputStream(("not_a_valid_list".getBytes()))) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(LogListUpdateStatus.builder().setState(LOG_LIST_INVALID).build());
}
@ -179,9 +181,7 @@ public class CompatibilityVersionTest {
JSONObject newLogList = makeLogList(existingVersion, "i_am_the_real_content");
try (InputStream inputStream = asStream(newLogList)) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@ -193,16 +193,12 @@ public class CompatibilityVersionTest {
String existingVersion = "666";
JSONObject existingLogList = makeLogList(existingVersion, "i_was_installed_successfully");
try (InputStream inputStream = asStream(existingLogList)) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
try (InputStream inputStream = asStream(makeLogList(existingVersion, "i_am_ignored"))) {
assertThat(
mCompatVersion.install(
inputStream, LogListUpdateStatus.builder()))
assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(
LogListUpdateStatus.builder()
.setState(VERSION_ALREADY_EXISTS)