diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java index fb42c03de7..41b58fac59 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java @@ -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 mCompatVersions = new ArrayList<>(); + private final Collection mCompatVersions; CertificateTransparencyDownloader( Context context, DataStore dataStore, DownloadHelper downloadHelper, SignatureVerifier signatureVerifier, - CertificateTransparencyLogger logger) { + CertificateTransparencyLogger logger, + Collection 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() { diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java index f1b9a4f827..286f3268bf 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java @@ -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 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 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) { diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java index 2e910b2b2f..5e530c74a6 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java @@ -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 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); } /** diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java index e8a6e6433d..0a91963ce4 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java +++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java @@ -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); diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java index 5fdba0919c..72b715aefe 100644 --- a/networksecurity/service/src/com/android/server/net/ct/Config.java +++ b/networksecurity/service/src/com/android/server/net/ct/Config.java @@ -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"; } diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java index 22dc6ab9a8..956bad57cd 100644 --- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java +++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java @@ -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 diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java index 2b8b3cd4ca..0d15183140 100644 --- a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java +++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java @@ -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)