Merge "Add CompatibilityVersion v2 to CT downloader" into main
This commit is contained in:
commit
d64a649df9
7 changed files with 85 additions and 77 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue