Remove CertificateTransparencyInstaller
The task of installing a new log list is now performed by the
CompatibilityVersion class.
Also, each CompatibilityVersion provides the URLs hosting the signature
and log list files. We do not store that information in the DataStore.
Change-Id: I36b585dc6984278de1ab3e6f3dc872e22f06ebea
Flag: com.android.net.ct.flags.certificate_transparency_service
Bug: 377276991
Test: atest NetworkSecurityUnitTests
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 c9694d1..c679ab8 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -27,8 +27,6 @@
import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS;
import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__PENDING_WAITING_FOR_WIFI;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.RequiresApi;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
@@ -43,13 +41,12 @@
import com.android.server.net.ct.DownloadHelper.DownloadStatus;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
+import java.util.ArrayList;
+import java.util.List;
/** Helper class to download certificate transparency log files. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -61,9 +58,10 @@
private final DataStore mDataStore;
private final DownloadHelper mDownloadHelper;
private final SignatureVerifier mSignatureVerifier;
- private final CertificateTransparencyInstaller mInstaller;
private final CertificateTransparencyLogger mLogger;
+ private final List<CompatibilityVersion> mCompatVersions = new ArrayList<>();
+
private boolean started = false;
CertificateTransparencyDownloader(
@@ -71,25 +69,27 @@
DataStore dataStore,
DownloadHelper downloadHelper,
SignatureVerifier signatureVerifier,
- CertificateTransparencyInstaller installer,
CertificateTransparencyLogger logger) {
mContext = context;
mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
- mInstaller = installer;
mLogger = logger;
}
+ void addCompatibilityVersion(CompatibilityVersion compatVersion) {
+ mCompatVersions.add(compatVersion);
+ }
+
void start() {
if (started) {
return;
}
- mInstaller.addCompatibilityVersion(Config.COMPATIBILITY_VERSION);
mContext.registerReceiver(
this,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
Context.RECEIVER_EXPORTED);
+ mDataStore.load();
started = true;
if (Config.DEBUG) {
@@ -102,7 +102,7 @@
return;
}
mContext.unregisterReceiver(this);
- mInstaller.removeCompatibilityVersion(Config.COMPATIBILITY_VERSION);
+ mDataStore.delete();
started = false;
if (Config.DEBUG) {
@@ -111,7 +111,7 @@
}
long startPublicKeyDownload() {
- long downloadId = download(mDataStore.getProperty(Config.PUBLIC_KEY_URL));
+ long downloadId = download(Config.URL_PUBLIC_KEY);
if (downloadId != -1) {
mDataStore.setPropertyLong(Config.PUBLIC_KEY_DOWNLOAD_ID, downloadId);
mDataStore.store();
@@ -119,19 +119,31 @@
return downloadId;
}
- long startMetadataDownload() {
- long downloadId = download(mDataStore.getProperty(Config.METADATA_URL));
+ private long startMetadataDownload(CompatibilityVersion compatVersion) {
+ long downloadId = download(compatVersion.getMetadataUrl());
if (downloadId != -1) {
- mDataStore.setPropertyLong(Config.METADATA_DOWNLOAD_ID, downloadId);
+ mDataStore.setPropertyLong(compatVersion.getMetadataPropertyName(), downloadId);
mDataStore.store();
}
return downloadId;
}
- long startContentDownload() {
- long downloadId = download(mDataStore.getProperty(Config.CONTENT_URL));
+ @VisibleForTesting
+ void startMetadataDownload() {
+ for (CompatibilityVersion compatVersion : mCompatVersions) {
+ if (startMetadataDownload(compatVersion) == -1) {
+ Log.e(TAG, "Metadata download not started for " + compatVersion.getCompatVersion());
+ } else if (Config.DEBUG) {
+ Log.d(TAG, "Metadata download started for " + compatVersion.getCompatVersion());
+ }
+ }
+ }
+
+ @VisibleForTesting
+ long startContentDownload(CompatibilityVersion compatVersion) {
+ long downloadId = download(compatVersion.getContentUrl());
if (downloadId != -1) {
- mDataStore.setPropertyLong(Config.CONTENT_DOWNLOAD_ID, downloadId);
+ mDataStore.setPropertyLong(compatVersion.getContentPropertyName(), downloadId);
mDataStore.store();
}
return downloadId;
@@ -145,25 +157,28 @@
return;
}
- long completedId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
+ long completedId =
+ intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, /* defaultValue= */ -1);
if (completedId == -1) {
Log.e(TAG, "Invalid completed download Id");
return;
}
- if (isPublicKeyDownloadId(completedId)) {
+ if (getPublicKeyDownloadId() == completedId) {
handlePublicKeyDownloadCompleted(completedId);
return;
}
- if (isMetadataDownloadId(completedId)) {
- handleMetadataDownloadCompleted(completedId);
- return;
- }
+ for (CompatibilityVersion compatVersion : mCompatVersions) {
+ if (getMetadataDownloadId(compatVersion) == completedId) {
+ handleMetadataDownloadCompleted(compatVersion, completedId);
+ return;
+ }
- if (isContentDownloadId(completedId)) {
- handleContentDownloadCompleted(completedId);
- return;
+ if (getContentDownloadId(compatVersion) == completedId) {
+ handleContentDownloadCompleted(compatVersion, completedId);
+ return;
+ }
}
Log.i(TAG, "Download id " + completedId + " is not recognized.");
@@ -189,35 +204,33 @@
return;
}
- if (startMetadataDownload() == -1) {
- Log.e(TAG, "Metadata download not started.");
- } else if (Config.DEBUG) {
- Log.d(TAG, "Metadata download started successfully.");
- }
+ startMetadataDownload();
}
- private void handleMetadataDownloadCompleted(long downloadId) {
+ private void handleMetadataDownloadCompleted(
+ CompatibilityVersion compatVersion, long downloadId) {
DownloadStatus status = mDownloadHelper.getDownloadStatus(downloadId);
if (!status.isSuccessful()) {
handleDownloadFailed(status);
return;
}
- if (startContentDownload() == -1) {
- Log.e(TAG, "Content download not started.");
+ if (startContentDownload(compatVersion) == -1) {
+ Log.e(TAG, "Content download failed for" + compatVersion.getCompatVersion());
} else if (Config.DEBUG) {
- Log.d(TAG, "Content download started successfully.");
+ Log.d(TAG, "Content download started for" + compatVersion.getCompatVersion());
}
}
- private void handleContentDownloadCompleted(long downloadId) {
+ private void handleContentDownloadCompleted(
+ CompatibilityVersion compatVersion, long downloadId) {
DownloadStatus status = mDownloadHelper.getDownloadStatus(downloadId);
if (!status.isSuccessful()) {
handleDownloadFailed(status);
return;
}
- Uri contentUri = getContentDownloadUri();
- Uri metadataUri = getMetadataDownloadUri();
+ Uri contentUri = getContentDownloadUri(compatVersion);
+ Uri metadataUri = getMetadataDownloadUri(compatVersion);
if (contentUri == null || metadataUri == null) {
Log.e(TAG, "Invalid URIs");
return;
@@ -234,8 +247,7 @@
mLogger.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0)
- );
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
} catch (InvalidKeyException e) {
if (updateFailureCount()) {
@@ -243,8 +255,7 @@
mLogger.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0)
- );
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
} catch (IOException | GeneralSecurityException e) {
Log.e(TAG, "Could not verify new log list", e);
@@ -258,32 +269,19 @@
mLogger.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
return;
}
- String version = null;
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
- version =
- new JSONObject(new String(inputStream.readAllBytes(), UTF_8))
- .getString("version");
- } catch (JSONException | IOException e) {
- Log.e(TAG, "Could not extract version from log list", e);
- return;
- }
-
- try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
- success = mInstaller.install(Config.COMPATIBILITY_VERSION, inputStream, version);
+ success = compatVersion.install(inputStream);
} catch (IOException e) {
Log.e(TAG, "Could not install new content", e);
return;
}
if (success) {
- // Update information about the stored version on successful install.
- mDataStore.setProperty(Config.VERSION, version);
-
// Reset the number of consecutive log list failure updates back to zero.
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0);
mDataStore.store();
@@ -373,17 +371,19 @@
@VisibleForTesting
long getPublicKeyDownloadId() {
- return mDataStore.getPropertyLong(Config.PUBLIC_KEY_DOWNLOAD_ID, -1);
+ return mDataStore.getPropertyLong(Config.PUBLIC_KEY_DOWNLOAD_ID, /* defaultValue= */ -1);
}
@VisibleForTesting
- long getMetadataDownloadId() {
- return mDataStore.getPropertyLong(Config.METADATA_DOWNLOAD_ID, -1);
+ long getMetadataDownloadId(CompatibilityVersion compatVersion) {
+ return mDataStore.getPropertyLong(
+ compatVersion.getMetadataPropertyName(), /* defaultValue */ -1);
}
@VisibleForTesting
- long getContentDownloadId() {
- return mDataStore.getPropertyLong(Config.CONTENT_DOWNLOAD_ID, -1);
+ long getContentDownloadId(CompatibilityVersion compatVersion) {
+ return mDataStore.getPropertyLong(
+ compatVersion.getContentPropertyName(), /* defaultValue= */ -1);
}
@VisibleForTesting
@@ -393,38 +393,27 @@
@VisibleForTesting
boolean hasMetadataDownloadId() {
- return getMetadataDownloadId() != -1;
+ return mCompatVersions.stream()
+ .map(this::getMetadataDownloadId)
+ .anyMatch(downloadId -> downloadId != -1);
}
@VisibleForTesting
boolean hasContentDownloadId() {
- return getContentDownloadId() != -1;
- }
-
- @VisibleForTesting
- boolean isPublicKeyDownloadId(long downloadId) {
- return getPublicKeyDownloadId() == downloadId;
- }
-
- @VisibleForTesting
- boolean isMetadataDownloadId(long downloadId) {
- return getMetadataDownloadId() == downloadId;
- }
-
- @VisibleForTesting
- boolean isContentDownloadId(long downloadId) {
- return getContentDownloadId() == downloadId;
+ return mCompatVersions.stream()
+ .map(this::getContentDownloadId)
+ .anyMatch(downloadId -> downloadId != -1);
}
private Uri getPublicKeyDownloadUri() {
return mDownloadHelper.getUri(getPublicKeyDownloadId());
}
- private Uri getMetadataDownloadUri() {
- return mDownloadHelper.getUri(getMetadataDownloadId());
+ private Uri getMetadataDownloadUri(CompatibilityVersion compatVersion) {
+ return mDownloadHelper.getUri(getMetadataDownloadId(compatVersion));
}
- private Uri getContentDownloadUri() {
- return mDownloadHelper.getUri(getContentDownloadId());
+ private Uri getContentDownloadUri(CompatibilityVersion compatVersion) {
+ return mDownloadHelper.getUri(getContentDownloadId(compatVersion));
}
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
deleted file mode 100644
index 9970667..0000000
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.net.ct;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-/** Installer of CT log lists. */
-public class CertificateTransparencyInstaller {
-
- private static final String TAG = "CertificateTransparencyInstaller";
-
- private final Map<String, CompatibilityVersion> mCompatVersions = new HashMap<>();
-
- // The CT root directory.
- private final File mRootDirectory;
-
- public CertificateTransparencyInstaller(File rootDirectory) {
- mRootDirectory = rootDirectory;
- }
-
- public CertificateTransparencyInstaller(String rootDirectoryPath) {
- this(new File(rootDirectoryPath));
- }
-
- public CertificateTransparencyInstaller() {
- this(Config.CT_ROOT_DIRECTORY_PATH);
- }
-
- void addCompatibilityVersion(String versionName) {
- removeCompatibilityVersion(versionName);
- CompatibilityVersion newCompatVersion =
- new CompatibilityVersion(new File(mRootDirectory, versionName));
- mCompatVersions.put(versionName, newCompatVersion);
- }
-
- void removeCompatibilityVersion(String versionName) {
- CompatibilityVersion compatVersion = mCompatVersions.remove(versionName);
- if (compatVersion != null && !compatVersion.delete()) {
- Log.w(TAG, "Could not delete compatibility version directory.");
- }
- }
-
- CompatibilityVersion getCompatibilityVersion(String versionName) {
- return mCompatVersions.get(versionName);
- }
-
- /**
- * Install a new log list to use during SCT verification.
- *
- * @param compatibilityVersion the compatibility version of the new log list
- * @param newContent an input stream providing the log list
- * @param version the minor version of the new log list
- * @return true if the log list was installed successfully, false otherwise.
- * @throws IOException if the list cannot be saved in the CT directory.
- */
- public boolean install(String compatibilityVersion, InputStream newContent, String version)
- throws IOException {
- CompatibilityVersion compatVersion = mCompatVersions.get(compatibilityVersion);
- if (compatVersion == null) {
- Log.e(TAG, "No compatibility version for " + compatibilityVersion);
- return false;
- }
- // Ensure root directory exists and is readable.
- DirectoryUtils.makeDir(mRootDirectory);
-
- if (!compatVersion.install(newContent, version)) {
- Log.e(TAG, "Failed to install logs version " + version);
- return false;
- }
- Log.i(TAG, "New logs installed at " + compatVersion.getLogsDir());
- return true;
- }
-}
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 9c92d14..a8acc60 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -36,7 +36,7 @@
private static final String UPDATE_CONFIG_PERMISSION = "android.permission.UPDATE_CONFIG";
private final Context mContext;
- private final DataStore mDataStore;
+ private final CompatibilityVersion mCompatVersion;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
@@ -45,12 +45,16 @@
/** Creates a new {@link CertificateTransparencyJob} object. */
public CertificateTransparencyJob(
- Context context,
- DataStore dataStore,
- CertificateTransparencyDownloader certificateTransparencyDownloader) {
+ Context context, CertificateTransparencyDownloader certificateTransparencyDownloader) {
mContext = context;
- mDataStore = dataStore;
+ mCompatVersion =
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION,
+ Config.URL_SIGNATURE,
+ Config.URL_LOG_LIST,
+ Config.CT_ROOT_DIRECTORY_PATH);
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
+ mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
PendingIntent.getBroadcast(
@@ -80,6 +84,7 @@
mContext.unregisterReceiver(this);
mAlarmManager.cancel(mPendingIntent);
mCertificateTransparencyDownloader.stop();
+ mCompatVersion.delete();
mDependenciesReady = false;
if (Config.DEBUG) {
@@ -102,16 +107,10 @@
Log.d(TAG, "Starting CT daily job.");
}
if (!mDependenciesReady) {
- mDataStore.load();
mCertificateTransparencyDownloader.start();
mDependenciesReady = true;
}
- mDataStore.setProperty(Config.CONTENT_URL, Config.URL_LOG_LIST);
- mDataStore.setProperty(Config.METADATA_URL, Config.URL_SIGNATURE);
- mDataStore.setProperty(Config.PUBLIC_KEY_URL, Config.URL_PUBLIC_KEY);
- mDataStore.store();
-
if (mCertificateTransparencyDownloader.startPublicKeyDownload() == -1) {
Log.e(TAG, "Public key download not started.");
} else 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 eb24567..ed98056 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -61,10 +61,8 @@
dataStore,
downloadHelper,
signatureVerifier,
- new CertificateTransparencyInstaller(),
new CertificateTransparencyLoggerImpl());
- mCertificateTransparencyJob =
- new CertificateTransparencyJob(context, dataStore, downloader);
+ mCertificateTransparencyJob = new CertificateTransparencyJob(context, downloader);
}
/**
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 27488b5..fdeb746 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -15,58 +15,92 @@
*/
package com.android.server.net.ct;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.RequiresApi;
+import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.Log;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/** Represents a compatibility version directory. */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
class CompatibilityVersion {
+ private static final String TAG = "CompatibilityVersion";
+
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";
- private static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
+ private final String mCompatVersion;
- private final File mRootDirectory;
+ private final String mMetadataUrl;
+ private final String mContentUrl;
+ private final File mVersionDirectory;
private final File mCurrentLogsDirSymlink;
- private File mCurrentLogsDir = null;
+ CompatibilityVersion(
+ String compatVersion, String metadataUrl, String contentUrl, File rootDirectory) {
+ mCompatVersion = compatVersion;
+ mMetadataUrl = metadataUrl;
+ mContentUrl = contentUrl;
+ mVersionDirectory = new File(rootDirectory, compatVersion);
+ mCurrentLogsDirSymlink = new File(mVersionDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
+ }
- CompatibilityVersion(File rootDirectory) {
- mRootDirectory = rootDirectory;
- mCurrentLogsDirSymlink = new File(mRootDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
+ CompatibilityVersion(
+ String compatVersion, String metadataUrl, String contentUrl, String rootDirectoryPath) {
+ this(compatVersion, metadataUrl, contentUrl, new File(rootDirectoryPath));
}
/**
* Installs a log list within this compatibility version directory.
*
* @param newContent an input stream providing the log list
- * @param version the version number of the log list
* @return true if the log list was installed successfully, false otherwise.
* @throws IOException if the list cannot be saved in the CT directory.
*/
- boolean install(InputStream newContent, String version) throws IOException {
- // 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 root directory exists and is readable.
- DirectoryUtils.makeDir(mRootDirectory);
+ boolean install(InputStream newContent) throws IOException {
+ String content = new String(newContent.readAllBytes(), UTF_8);
+ try {
+ return install(
+ new ByteArrayInputStream(content.getBytes()),
+ new JSONObject(content).getString("version"));
+ } catch (JSONException e) {
+ Log.e(TAG, "invalid log list format", e);
+ return false;
+ }
+ }
- File newLogsDir = new File(mRootDirectory, LOGS_DIR_PREFIX + version);
+ private boolean install(InputStream newContent, String version) throws IOException {
+ // 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 the path to the root directory exists and is readable.
+ DirectoryUtils.makeDir(mVersionDirectory);
+
+ File newLogsDir = new File(mVersionDirectory, LOGS_DIR_PREFIX + version);
// 2. Handle the corner case where the new directory already exists.
if (newLogsDir.exists()) {
- // If the symlink has already been updated then the update died between steps 6 and 7
- // and so we cannot delete the directory since it is in use.
+ // If the symlink has already been updated then the update died between steps 6
+ // and 7 and so we cannot delete the directory since it is in use.
if (newLogsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) {
+ Log.i(TAG, newLogsDir + " already exists, skipping install.");
deleteOldLogDirectories();
return false;
}
- // If the symlink has not been updated then the previous installation failed and this is
- // a re-attempt. Clean-up leftover files and try again.
+ // If the symlink has not been updated then the previous installation failed and
+ // this is a re-attempt. Clean-up leftover files and try again.
DirectoryUtils.removeDir(newLogsDir);
}
try {
@@ -80,8 +114,8 @@
}
DirectoryUtils.setWorldReadable(logListFile);
- // 5. Create temp symlink. We rename this to the target symlink to get an atomic update.
- File tempSymlink = new File(mRootDirectory, "new_symlink");
+ // 5. Create temp symlink. We rename to the target symlink for an atomic update.
+ File tempSymlink = new File(mVersionDirectory, "new_symlink");
try {
Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
} catch (ErrnoException e) {
@@ -95,17 +129,33 @@
throw e;
}
// 7. Cleanup
- mCurrentLogsDir = newLogsDir;
+ Log.i(TAG, "New logs installed at " + newLogsDir);
deleteOldLogDirectories();
return true;
}
- File getRootDir() {
- return mRootDirectory;
+ String getCompatVersion() {
+ return mCompatVersion;
}
- File getLogsDir() {
- return mCurrentLogsDir;
+ String getMetadataUrl() {
+ return mMetadataUrl;
+ }
+
+ String getMetadataPropertyName() {
+ return mCompatVersion + "_" + Config.METADATA_DOWNLOAD_ID;
+ }
+
+ String getContentUrl() {
+ return mContentUrl;
+ }
+
+ String getContentPropertyName() {
+ return mCompatVersion + "_" + Config.CONTENT_DOWNLOAD_ID;
+ }
+
+ File getVersionDir() {
+ return mVersionDirectory;
}
File getLogsDirSymlink() {
@@ -113,19 +163,21 @@
}
File getLogsFile() {
- return new File(mCurrentLogsDir, LOGS_LIST_FILE_NAME);
+ return new File(mCurrentLogsDirSymlink, LOGS_LIST_FILE_NAME);
}
- boolean delete() {
- return DirectoryUtils.removeDir(mRootDirectory);
+ void delete() {
+ if (!DirectoryUtils.removeDir(mVersionDirectory)) {
+ Log.w(TAG, "Could not delete compatibility version directory " + mVersionDirectory);
+ }
}
private void deleteOldLogDirectories() throws IOException {
- if (!mRootDirectory.exists()) {
+ if (!mVersionDirectory.exists()) {
return;
}
File currentTarget = mCurrentLogsDirSymlink.getCanonicalFile();
- for (File file : mRootDirectory.listFiles()) {
+ for (File file : mVersionDirectory.listFiles()) {
if (!currentTarget.equals(file.getCanonicalFile())
&& file.getName().startsWith(LOGS_DIR_PREFIX)) {
DirectoryUtils.removeDir(file);
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 aafee60..592bc4e 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -48,11 +48,8 @@
// properties
static final String VERSION = "version";
- static final String CONTENT_URL = "content_url";
static final String CONTENT_DOWNLOAD_ID = "content_download_id";
- static final String METADATA_URL = "metadata_url";
static final String METADATA_DOWNLOAD_ID = "metadata_download_id";
- static final String PUBLIC_KEY_URL = "public_key_url";
static final String PUBLIC_KEY_DOWNLOAD_ID = "public_key_download_id";
static final String LOG_LIST_UPDATE_FAILURE_COUNT = "log_list_update_failure_count";
diff --git a/networksecurity/service/src/com/android/server/net/ct/DataStore.java b/networksecurity/service/src/com/android/server/net/ct/DataStore.java
index 3779269..8180316 100644
--- a/networksecurity/service/src/com/android/server/net/ct/DataStore.java
+++ b/networksecurity/service/src/com/android/server/net/ct/DataStore.java
@@ -57,6 +57,11 @@
}
}
+ boolean delete() {
+ clear();
+ return mPropertyFile.delete();
+ }
+
long getPropertyLong(String key, long defaultValue) {
return Optional.ofNullable(getProperty(key)).map(Long::parseLong).orElse(defaultValue);
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
index ba42a82..54e277a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
+++ b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
@@ -25,7 +25,7 @@
class DirectoryUtils {
static void makeDir(File dir) throws IOException {
- dir.mkdir();
+ dir.mkdirs();
if (!dir.isDirectory()) {
throw new IOException("Unable to make directory " + dir.getCanonicalPath());
}
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 25f0dc1..5552689 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
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.server.net.ct;
import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE;
@@ -24,7 +25,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -73,15 +73,14 @@
public class CertificateTransparencyDownloaderTest {
@Mock private DownloadManager mDownloadManager;
- @Mock private CertificateTransparencyInstaller mCertificateTransparencyInstaller;
@Mock private CertificateTransparencyLogger mLogger;
private PrivateKey mPrivateKey;
private PublicKey mPublicKey;
private Context mContext;
- private File mTempFile;
private DataStore mDataStore;
private SignatureVerifier mSignatureVerifier;
+ private CompatibilityVersion mCompatVersion;
private CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private long mNextDownloadId = 666;
@@ -95,8 +94,7 @@
mPublicKey = keyPair.getPublic();
mContext = InstrumentationRegistry.getInstrumentation().getContext();
- mTempFile = File.createTempFile("datastore-test", ".properties");
- mDataStore = new DataStore(mTempFile);
+ mDataStore = new DataStore(File.createTempFile("datastore-test", ".properties"));
mSignatureVerifier = new SignatureVerifier(mContext);
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
@@ -104,56 +102,64 @@
mDataStore,
new DownloadHelper(mDownloadManager),
mSignatureVerifier,
- mCertificateTransparencyInstaller,
mLogger);
+ mCompatVersion =
+ new CompatibilityVersion(
+ /* compatVersion= */ "v666",
+ Config.URL_SIGNATURE,
+ Config.URL_LOG_LIST,
+ mContext.getFilesDir());
- prepareDataStore();
prepareDownloadManager();
+ mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
+ mCertificateTransparencyDownloader.start();
}
@After
public void tearDown() {
- mTempFile.delete();
mSignatureVerifier.resetPublicKey();
+ mCertificateTransparencyDownloader.stop();
+ mCompatVersion.delete();
}
@Test
public void testDownloader_startPublicKeyDownload() {
assertThat(mCertificateTransparencyDownloader.hasPublicKeyDownloadId()).isFalse();
+
long downloadId = mCertificateTransparencyDownloader.startPublicKeyDownload();
assertThat(mCertificateTransparencyDownloader.hasPublicKeyDownloadId()).isTrue();
- assertThat(mCertificateTransparencyDownloader.isPublicKeyDownloadId(downloadId)).isTrue();
+ assertThat(mCertificateTransparencyDownloader.getPublicKeyDownloadId())
+ .isEqualTo(downloadId);
}
@Test
public void testDownloader_startMetadataDownload() {
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
- long downloadId = mCertificateTransparencyDownloader.startMetadataDownload();
+
+ mCertificateTransparencyDownloader.startMetadataDownload();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isTrue();
- assertThat(mCertificateTransparencyDownloader.isMetadataDownloadId(downloadId)).isTrue();
}
@Test
public void testDownloader_startContentDownload() {
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isFalse();
- long downloadId = mCertificateTransparencyDownloader.startContentDownload();
+
+ mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isTrue();
- assertThat(mCertificateTransparencyDownloader.isContentDownloadId(downloadId)).isTrue();
}
@Test
public void testDownloader_publicKeyDownloadSuccess_updatePublicKey_startMetadataDownload()
throws Exception {
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
- setSuccessfulDownload(publicKeyId, writePublicKeyToFile(mPublicKey));
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(publicKeyId));
+ mContext, makePublicKeyDownloadCompleteIntent(writePublicKeyToFile(mPublicKey)));
assertThat(mSignatureVerifier.getPublicKey()).hasValue(mPublicKey);
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isTrue();
@@ -163,14 +169,14 @@
public void
testDownloader_publicKeyDownloadSuccess_updatePublicKeyFail_doNotStartMetadataDownload()
throws Exception {
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
- setSuccessfulDownload(
- publicKeyId, writeToFile("i_am_not_a_base64_encoded_public_key".getBytes()));
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(publicKeyId));
+ mContext,
+ makePublicKeyDownloadCompleteIntent(
+ writeToFile("i_am_not_a_base64_encoded_public_key".getBytes())));
assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
@@ -178,17 +184,15 @@
@Test
public void testDownloader_publicKeyDownloadFail_doNotUpdatePublicKey() throws Exception {
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
- setFailedDownload(
- publicKeyId, // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId);
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makePublicKeyDownloadFailedIntent(DownloadManager.ERROR_INSUFFICIENT_SPACE));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makePublicKeyDownloadFailedIntent(DownloadManager.ERROR_HTTP_DATA_ERROR));
assertThat(mSignatureVerifier.getPublicKey()).isEmpty();
assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse();
@@ -196,225 +200,202 @@
@Test
public void testDownloader_publicKeyDownloadFail_failureThresholdExceeded_logsFailure()
- throws Exception {
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- setFailedDownload(
- publicKeyId, // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makePublicKeyDownloadFailedIntent(DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
public void testDownloader_publicKeyDownloadFail_failureThresholdNotMet_doesNotLog()
- throws Exception {
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
// Set the failure count to well below the threshold
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
- setFailedDownload(
- publicKeyId, // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makePublicKeyDownloadFailedIntent(DownloadManager.ERROR_HTTP_DATA_ERROR));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(1);
verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
public void testDownloader_metadataDownloadSuccess_startContentDownload() {
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, new File("log_list.sig"));
+ mCertificateTransparencyDownloader.startMetadataDownload();
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isFalse();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(metadataId));
+ mContext,
+ makeMetadataDownloadCompleteIntent(mCompatVersion, new File("log_list.sig")));
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isTrue();
}
@Test
public void testDownloader_metadataDownloadFail_doNotStartContentDownload() {
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setFailedDownload(
- metadataId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId);
+ mCertificateTransparencyDownloader.startMetadataDownload();
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isFalse();
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeMetadataDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeMetadataDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_HTTP_DATA_ERROR));
assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isFalse();
}
@Test
public void testDownloader_metadataDownloadFail_failureThresholdExceeded_logsFailure()
- throws Exception {
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startMetadataDownload();
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- setFailedDownload(
- metadataId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeMetadataDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
public void testDownloader_metadataDownloadFail_failureThresholdNotMet_doesNotLog()
- throws Exception {
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startMetadataDownload();
// Set the failure count to well below the threshold
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
- setFailedDownload(
- metadataId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeMetadataDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(1);
verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
- public void testDownloader_contentDownloadSuccess_installSuccess_updateDataStore()
- throws Exception {
+ public void testDownloader_contentDownloadSuccess_installSuccess() throws Exception {
String newVersion = "456";
File logListFile = makeLogListFile(newVersion);
File metadataFile = sign(logListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
- when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
- .thenReturn(true);
+ mCertificateTransparencyDownloader.startMetadataDownload();
assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
assertInstallSuccessful(newVersion);
}
@Test
public void testDownloader_contentDownloadFail_doNotInstall() throws Exception {
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setFailedDownload(
- contentId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId);
+ mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeContentDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeContentDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_HTTP_DATA_ERROR));
- verify(mCertificateTransparencyInstaller, never()).install(any(), any(), any());
assertNoVersionIsInstalled();
}
@Test
public void testDownloader_contentDownloadFail_failureThresholdExceeded_logsFailure()
- throws Exception {
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- setFailedDownload(
- contentId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeContentDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
public void testDownloader_contentDownloadFail_failureThresholdNotMet_doesNotLog()
- throws Exception {
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
+ throws Exception {
+ mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
// Set the failure count to well below the threshold
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
- setFailedDownload(
- contentId,
- // Failure cases where we give up on the download.
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- DownloadManager.ERROR_HTTP_DATA_ERROR);
- Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId);
- mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent);
+ mCertificateTransparencyDownloader.onReceive(
+ mContext,
+ makeContentDownloadFailedIntent(
+ mCompatVersion, DownloadManager.ERROR_HTTP_DATA_ERROR));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(1);
verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
- public void testDownloader_contentDownloadSuccess_installFail_doNotUpdateDataStore()
+ public void testDownloader_contentDownloadSuccess_invalidLogList_installFails()
throws Exception {
- File logListFile = makeLogListFile("456");
- File metadataFile = sign(logListFile);
+ File invalidLogListFile = writeToFile("not_a_json_log_list".getBytes());
+ File metadataFile = sign(invalidLogListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
- when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
- .thenReturn(false);
+ mCertificateTransparencyDownloader.startMetadataDownload();
assertNoVersionIsInstalled();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, invalidLogListFile));
assertNoVersionIsInstalled();
}
@@ -426,30 +407,31 @@
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
+ mCertificateTransparencyDownloader.startMetadataDownload();
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Set the public key to be missing
mSignatureVerifier.resetPublicKey();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
- verify(mLogger, never()).logCTLogListUpdateFailedEvent(
- eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
- anyInt()
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, never())
+ .logCTLogListUpdateFailedEvent(
+ eq(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
+ anyInt());
}
@Test
@@ -464,31 +446,31 @@
KeyPairGenerator instance = KeyPairGenerator.getInstance("EC");
mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
-
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Act
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
// Assert
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, never()).logCTLogListUpdateFailedEvent(
- eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
- anyInt()
- );
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, never())
+ .logCTLogListUpdateFailedEvent(
+ eq(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
+ anyInt());
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -503,31 +485,31 @@
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
-
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Act
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
// Assert
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, never()).logCTLogListUpdateFailedEvent(
- eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
- anyInt()
- );
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, never())
+ .logCTLogListUpdateFailedEvent(
+ eq(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
+ anyInt());
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -536,84 +518,81 @@
throws Exception {
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
- // Set the public key wrong, so signature verification fails
- mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
+ // Set the key to be deliberately wrong by using diff key pair
+ KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
+ mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
// Set the failure count to well below the threshold
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
- verify(mLogger, never()).logCTLogListUpdateFailedEvent(
- eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
- anyInt()
- );
- verify(mLogger, never()).logCTLogListUpdateFailedEvent(
- eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
- anyInt()
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(1);
+ verify(mLogger, never())
+ .logCTLogListUpdateFailedEvent(
+ eq(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
+ anyInt());
+ verify(mLogger, never())
+ .logCTLogListUpdateFailedEvent(
+ eq(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
+ anyInt());
}
@Test
public void
testDownloader_contentDownloadSuccess_installFail_failureThresholdExceeded_logsFailure()
throws Exception {
- File logListFile = makeLogListFile("456");
- File metadataFile = sign(logListFile);
+ File invalidLogListFile = writeToFile("not_a_json_log_list".getBytes());
+ File metadataFile = sign(invalidLogListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
// Set the failure count to just below the threshold
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
- when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
- .thenReturn(false);
+ mDataStore.setPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, invalidLogListFile));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
- );
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ verify(mLogger, times(1))
+ .logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
public void
testDownloader_contentDownloadSuccess_installFail_failureThresholdNotMet_doesNotLog()
throws Exception {
- File logListFile = makeLogListFile("456");
- File metadataFile = sign(logListFile);
+ File invalidLogListFile = writeToFile("not_a_json_log_list".getBytes());
+ File metadataFile = sign(invalidLogListFile);
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
// Set the failure count to well below the threshold
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
- when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
- .thenReturn(false);
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, invalidLogListFile));
- assertThat(mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
+ assertThat(
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
+ .isEqualTo(1);
verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@@ -623,17 +602,14 @@
File logListFile = makeLogListFile("456");
File metadataFile = File.createTempFile("log_list-wrong_metadata", "sig");
mSignatureVerifier.setPublicKey(mPublicKey);
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
assertNoVersionIsInstalled();
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
- verify(mCertificateTransparencyInstaller, never())
- .install(eq(Config.COMPATIBILITY_VERSION), any(), anyString());
assertNoVersionIsInstalled();
}
@@ -643,17 +619,14 @@
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
mSignatureVerifier.resetPublicKey();
- long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
- setSuccessfulDownload(metadataId, metadataFile);
- long contentId = mCertificateTransparencyDownloader.startContentDownload();
- setSuccessfulDownload(contentId, logListFile);
assertNoVersionIsInstalled();
+ mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
- verify(mCertificateTransparencyInstaller, never())
- .install(eq(Config.COMPATIBILITY_VERSION), any(), anyString());
assertNoVersionIsInstalled();
}
@@ -667,52 +640,37 @@
assertNoVersionIsInstalled();
// 1. Start download of public key.
- long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
+ mCertificateTransparencyDownloader.startPublicKeyDownload();
- // 2. On successful public key download, set the key and start the metatadata download.
- setSuccessfulDownload(publicKeyId, publicKeyFile);
-
+ // 2. On successful public key download, set the key and start the metatadata
+ // download.
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(publicKeyId));
+ mContext, makePublicKeyDownloadCompleteIntent(publicKeyFile));
// 3. On successful metadata download, start the content download.
- long metadataId = mCertificateTransparencyDownloader.getMetadataDownloadId();
- setSuccessfulDownload(metadataId, metadataFile);
-
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(metadataId));
+ mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile));
- // 4. On successful content download, verify the signature and install the new version.
- long contentId = mCertificateTransparencyDownloader.getContentDownloadId();
- setSuccessfulDownload(contentId, logListFile);
- when(mCertificateTransparencyInstaller.install(
- eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
- .thenReturn(true);
-
+ // 4. On successful content download, verify the signature and install the new
+ // version.
mCertificateTransparencyDownloader.onReceive(
- mContext, makeDownloadCompleteIntent(contentId));
+ mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
assertInstallSuccessful(newVersion);
}
private void assertNoVersionIsInstalled() {
- assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
+ assertThat(mCompatVersion.getVersionDir().exists()).isFalse();
}
private void assertInstallSuccessful(String version) {
- assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
- }
-
- private Intent makeDownloadCompleteIntent(long downloadId) {
- return new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
- .putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
- }
-
- private void prepareDataStore() {
- mDataStore.load();
- mDataStore.setProperty(Config.CONTENT_URL, Config.URL_LOG_LIST);
- mDataStore.setProperty(Config.METADATA_URL, Config.URL_SIGNATURE);
- mDataStore.setProperty(Config.PUBLIC_KEY_URL, Config.URL_PUBLIC_KEY);
+ File logsDir =
+ new File(
+ mCompatVersion.getVersionDir(),
+ CompatibilityVersion.LOGS_DIR_PREFIX + version);
+ assertThat(logsDir.exists()).isTrue();
+ File logsFile = new File(logsDir, CompatibilityVersion.LOGS_LIST_FILE_NAME);
+ assertThat(logsFile.exists()).isTrue();
}
private void prepareDownloadManager() {
@@ -720,6 +678,32 @@
.thenAnswer(invocation -> mNextDownloadId++);
}
+ private Intent makePublicKeyDownloadCompleteIntent(File publicKeyfile) {
+ return makeDownloadCompleteIntent(
+ mCertificateTransparencyDownloader.getPublicKeyDownloadId(), publicKeyfile);
+ }
+
+ private Intent makeMetadataDownloadCompleteIntent(
+ CompatibilityVersion compatVersion, File signatureFile) {
+ return makeDownloadCompleteIntent(
+ mCertificateTransparencyDownloader.getMetadataDownloadId(compatVersion),
+ signatureFile);
+ }
+
+ private Intent makeContentDownloadCompleteIntent(
+ CompatibilityVersion compatVersion, File logListFile) {
+ return makeDownloadCompleteIntent(
+ mCertificateTransparencyDownloader.getContentDownloadId(compatVersion),
+ logListFile);
+ }
+
+ private Intent makeDownloadCompleteIntent(long downloadId, File file) {
+ when(mDownloadManager.query(any(Query.class))).thenReturn(makeSuccessfulDownloadCursor());
+ when(mDownloadManager.getUriForDownloadedFile(downloadId)).thenReturn(Uri.fromFile(file));
+ return new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
+ .putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
+ }
+
private Cursor makeSuccessfulDownloadCursor() {
MatrixCursor cursor =
new MatrixCursor(
@@ -730,9 +714,26 @@
return cursor;
}
- private void setSuccessfulDownload(long downloadId, File file) {
- when(mDownloadManager.query(any(Query.class))).thenReturn(makeSuccessfulDownloadCursor());
- when(mDownloadManager.getUriForDownloadedFile(downloadId)).thenReturn(Uri.fromFile(file));
+ private Intent makePublicKeyDownloadFailedIntent(int error) {
+ return makeDownloadFailedIntent(
+ mCertificateTransparencyDownloader.getPublicKeyDownloadId(), error);
+ }
+
+ private Intent makeMetadataDownloadFailedIntent(CompatibilityVersion compatVersion, int error) {
+ return makeDownloadFailedIntent(
+ mCertificateTransparencyDownloader.getMetadataDownloadId(compatVersion), error);
+ }
+
+ private Intent makeContentDownloadFailedIntent(CompatibilityVersion compatVersion, int error) {
+ return makeDownloadFailedIntent(
+ mCertificateTransparencyDownloader.getContentDownloadId(compatVersion), error);
+ }
+
+ private Intent makeDownloadFailedIntent(long downloadId, int error) {
+ when(mDownloadManager.query(any(Query.class))).thenReturn(makeFailedDownloadCursor(error));
+ when(mDownloadManager.getUriForDownloadedFile(downloadId)).thenReturn(null);
+ return new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
+ .putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
}
private Cursor makeFailedDownloadCursor(int error) {
@@ -745,16 +746,6 @@
return cursor;
}
- private void setFailedDownload(long downloadId, int... downloadManagerErrors) {
- Cursor first = makeFailedDownloadCursor(downloadManagerErrors[0]);
- Cursor[] others = new Cursor[downloadManagerErrors.length - 1];
- for (int i = 1; i < downloadManagerErrors.length; i++) {
- others[i - 1] = makeFailedDownloadCursor(downloadManagerErrors[i]);
- }
- when(mDownloadManager.query(any())).thenReturn(first, others);
- when(mDownloadManager.getUriForDownloadedFile(downloadId)).thenReturn(null);
- }
-
private File writePublicKeyToFile(PublicKey publicKey)
throws IOException, GeneralSecurityException {
return writeToFile(Base64.getEncoder().encode(publicKey.getEncoded()));
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
deleted file mode 100644
index 50d3f23..0000000
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.net.ct;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/** Tests for the {@link CertificateTransparencyInstaller}. */
-@RunWith(JUnit4.class)
-public class CertificateTransparencyInstallerTest {
-
- private static final String TEST_VERSION = "test-v1";
-
- private File mTestDir =
- new File(
- InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
- "test-dir");
- private CertificateTransparencyInstaller mCertificateTransparencyInstaller =
- new CertificateTransparencyInstaller(mTestDir);
-
- @Before
- public void setUp() {
- mCertificateTransparencyInstaller.addCompatibilityVersion(TEST_VERSION);
- }
-
- @After
- public void tearDown() {
- mCertificateTransparencyInstaller.removeCompatibilityVersion(TEST_VERSION);
- DirectoryUtils.removeDir(mTestDir);
- }
-
- @Test
- public void testCompatibilityVersion_installSuccessful() throws IOException {
- assertThat(mTestDir.mkdir()).isTrue();
- String content = "i_am_compatible";
- String version = "i_am_version";
- CompatibilityVersion compatVersion =
- mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
-
- try (InputStream inputStream = asStream(content)) {
- assertThat(compatVersion.install(inputStream, version)).isTrue();
- }
- File logsDir = compatVersion.getLogsDir();
- assertThat(logsDir.exists()).isTrue();
- assertThat(logsDir.isDirectory()).isTrue();
- assertThat(logsDir.getAbsolutePath())
- .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION);
- File logsListFile = compatVersion.getLogsFile();
- assertThat(logsListFile.exists()).isTrue();
- assertThat(logsListFile.getAbsolutePath()).startsWith(logsDir.getAbsolutePath());
- assertThat(readAsString(logsListFile)).isEqualTo(content);
- File logsSymlink = compatVersion.getLogsDirSymlink();
- assertThat(logsSymlink.exists()).isTrue();
- assertThat(logsSymlink.isDirectory()).isTrue();
- assertThat(logsSymlink.getAbsolutePath())
- .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION + "/current");
- assertThat(logsSymlink.getCanonicalPath()).isEqualTo(logsDir.getCanonicalPath());
-
- assertThat(compatVersion.delete()).isTrue();
- assertThat(logsDir.exists()).isFalse();
- assertThat(logsSymlink.exists()).isFalse();
- assertThat(logsListFile.exists()).isFalse();
- }
-
- @Test
- public void testCompatibilityVersion_versionInstalledFailed() throws IOException {
- assertThat(mTestDir.mkdir()).isTrue();
-
- CompatibilityVersion compatVersion =
- mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
- File rootDir = compatVersion.getRootDir();
- assertThat(rootDir.mkdir()).isTrue();
-
- String existingVersion = "666";
- File existingLogDir =
- new File(rootDir, CompatibilityVersion.LOGS_DIR_PREFIX + existingVersion);
- assertThat(existingLogDir.mkdir()).isTrue();
-
- String existingContent = "somebody_tried_to_install_me_but_failed_halfway_through";
- File logsListFile = new File(existingLogDir, CompatibilityVersion.LOGS_LIST_FILE_NAME);
- assertThat(logsListFile.createNewFile()).isTrue();
- writeToFile(logsListFile, existingContent);
-
- String newContent = "i_am_the_real_content";
- try (InputStream inputStream = asStream(newContent)) {
- assertThat(compatVersion.install(inputStream, existingVersion)).isTrue();
- }
-
- assertThat(readAsString(logsListFile)).isEqualTo(newContent);
- }
-
- @Test
- public void testCertificateTransparencyInstaller_installSuccessfully() throws IOException {
- String content = "i_am_a_certificate_and_i_am_transparent";
- String version = "666";
-
- try (InputStream inputStream = asStream(content)) {
- assertThat(
- mCertificateTransparencyInstaller.install(
- TEST_VERSION, inputStream, version))
- .isTrue();
- }
-
- assertThat(mTestDir.exists()).isTrue();
- assertThat(mTestDir.isDirectory()).isTrue();
- CompatibilityVersion compatVersion =
- mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
- File logsDir = compatVersion.getLogsDir();
- assertThat(logsDir.exists()).isTrue();
- assertThat(logsDir.isDirectory()).isTrue();
- assertThat(logsDir.getAbsolutePath())
- .startsWith(mTestDir.getAbsolutePath() + "/" + TEST_VERSION);
- File logsListFile = compatVersion.getLogsFile();
- assertThat(logsListFile.exists()).isTrue();
- assertThat(logsListFile.getAbsolutePath()).startsWith(logsDir.getAbsolutePath());
- assertThat(readAsString(logsListFile)).isEqualTo(content);
- }
-
- @Test
- public void testCertificateTransparencyInstaller_versionIsAlreadyInstalled()
- throws IOException {
- String existingVersion = "666";
- String existingContent = "i_was_already_installed_successfully";
- CompatibilityVersion compatVersion =
- mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
-
- DirectoryUtils.makeDir(mTestDir);
- try (InputStream inputStream = asStream(existingContent)) {
- assertThat(compatVersion.install(inputStream, existingVersion)).isTrue();
- }
-
- try (InputStream inputStream = asStream("i_will_be_ignored")) {
- assertThat(
- mCertificateTransparencyInstaller.install(
- TEST_VERSION, inputStream, existingVersion))
- .isFalse();
- }
-
- assertThat(readAsString(compatVersion.getLogsFile())).isEqualTo(existingContent);
- }
-
- private static InputStream asStream(String string) throws IOException {
- return new ByteArrayInputStream(string.getBytes());
- }
-
- private static String readAsString(File file) throws IOException {
- return new String(new FileInputStream(file).readAllBytes());
- }
-
- private static void writeToFile(File file, String string) throws IOException {
- try (OutputStream out = new FileOutputStream(file)) {
- out.write(string.getBytes());
- }
- }
-}
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
new file mode 100644
index 0000000..38fff48
--- /dev/null
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net.ct;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Tests for the {@link CompatibilityVersion}. */
+@RunWith(JUnit4.class)
+public class CompatibilityVersionTest {
+
+ private static final String TEST_VERSION = "v123";
+
+ private final File mTestDir =
+ InstrumentationRegistry.getInstrumentation().getContext().getFilesDir();
+ private final CompatibilityVersion mCompatVersion =
+ new CompatibilityVersion(
+ TEST_VERSION, Config.URL_SIGNATURE, Config.URL_LOG_LIST, mTestDir);
+
+ @After
+ public void tearDown() {
+ mCompatVersion.delete();
+ }
+
+ @Test
+ public void testCompatibilityVersion_versionDirectory_setupSuccessful() {
+ File versionDir = mCompatVersion.getVersionDir();
+ assertThat(versionDir.exists()).isFalse();
+ assertThat(versionDir.getAbsolutePath()).startsWith(mTestDir.getAbsolutePath());
+ assertThat(versionDir.getAbsolutePath()).endsWith(TEST_VERSION);
+ }
+
+ @Test
+ public void testCompatibilityVersion_symlink_setupSuccessful() {
+ File dirSymlink = mCompatVersion.getLogsDirSymlink();
+ assertThat(dirSymlink.exists()).isFalse();
+ assertThat(dirSymlink.getAbsolutePath())
+ .startsWith(mCompatVersion.getVersionDir().getAbsolutePath());
+ }
+
+ @Test
+ public void testCompatibilityVersion_logsFile_setupSuccessful() {
+ File logsFile = mCompatVersion.getLogsFile();
+ assertThat(logsFile.exists()).isFalse();
+ assertThat(logsFile.getAbsolutePath())
+ .startsWith(mCompatVersion.getLogsDirSymlink().getAbsolutePath());
+ }
+
+ @Test
+ public void testCompatibilityVersion_installSuccessful() throws Exception {
+ String version = "i_am_version";
+ JSONObject logList = makeLogList(version, "i_am_content");
+
+ try (InputStream inputStream = asStream(logList)) {
+ assertThat(mCompatVersion.install(inputStream)).isTrue();
+ }
+
+ File logListFile = mCompatVersion.getLogsFile();
+ assertThat(logListFile.exists()).isTrue();
+ assertThat(logListFile.getCanonicalPath())
+ .isEqualTo(
+ // <path-to-test-files>/v123/logs-i_am_version/log_list.json
+ new File(
+ new File(
+ mCompatVersion.getVersionDir(),
+ CompatibilityVersion.LOGS_DIR_PREFIX + version),
+ CompatibilityVersion.LOGS_LIST_FILE_NAME)
+ .getCanonicalPath());
+ assertThat(logListFile.getAbsolutePath())
+ .isEqualTo(
+ // <path-to-test-files>/v123/current/log_list.json
+ new File(
+ new File(
+ mCompatVersion.getVersionDir(),
+ CompatibilityVersion.CURRENT_LOGS_DIR_SYMLINK_NAME),
+ CompatibilityVersion.LOGS_LIST_FILE_NAME)
+ .getAbsolutePath());
+ }
+
+ @Test
+ public void testCompatibilityVersion_deleteSuccessfully() throws Exception {
+ try (InputStream inputStream = asStream(makeLogList(/* version= */ "123"))) {
+ assertThat(mCompatVersion.install(inputStream)).isTrue();
+ }
+
+ mCompatVersion.delete();
+
+ assertThat(mCompatVersion.getLogsFile().exists()).isFalse();
+ }
+
+ @Test
+ public void testCompatibilityVersion_invalidLogList() throws Exception {
+ try (InputStream inputStream = new ByteArrayInputStream(("not_a_valid_list".getBytes()))) {
+ assertThat(mCompatVersion.install(inputStream)).isFalse();
+ }
+
+ assertThat(mCompatVersion.getLogsFile().exists()).isFalse();
+ }
+
+ @Test
+ public void testCompatibilityVersion_incompleteVersionExists_replacesOldVersion()
+ throws Exception {
+ String existingVersion = "666";
+ File existingLogDir =
+ new File(
+ mCompatVersion.getVersionDir(),
+ CompatibilityVersion.LOGS_DIR_PREFIX + existingVersion);
+ assertThat(existingLogDir.mkdirs()).isTrue();
+ File logsListFile = new File(existingLogDir, CompatibilityVersion.LOGS_LIST_FILE_NAME);
+ assertThat(logsListFile.createNewFile()).isTrue();
+
+ JSONObject newLogList = makeLogList(existingVersion, "i_am_the_real_content");
+ try (InputStream inputStream = asStream(newLogList)) {
+ assertThat(mCompatVersion.install(inputStream)).isTrue();
+ }
+
+ assertThat(readAsString(logsListFile)).isEqualTo(newLogList.toString());
+ }
+
+ @Test
+ public void testCompatibilityVersion_versionAlreadyExists_installFails() throws Exception {
+ String existingVersion = "666";
+ JSONObject existingLogList = makeLogList(existingVersion, "i_was_installed_successfully");
+ try (InputStream inputStream = asStream(existingLogList)) {
+ assertThat(mCompatVersion.install(inputStream)).isTrue();
+ }
+
+ try (InputStream inputStream = asStream(makeLogList(existingVersion, "i_am_ignored"))) {
+ assertThat(mCompatVersion.install(inputStream)).isFalse();
+ }
+
+ assertThat(readAsString(mCompatVersion.getLogsFile()))
+ .isEqualTo(existingLogList.toString());
+ }
+
+ private static InputStream asStream(JSONObject logList) throws IOException {
+ return new ByteArrayInputStream(logList.toString().getBytes());
+ }
+
+ private static JSONObject makeLogList(String version) throws JSONException {
+ return new JSONObject().put("version", version);
+ }
+
+ private static JSONObject makeLogList(String version, String content) throws JSONException {
+ return makeLogList(version).put("content", content);
+ }
+
+ private static String readAsString(File file) throws IOException {
+ try (InputStream in = new FileInputStream(file)) {
+ return new String(in.readAllBytes());
+ }
+ }
+}