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 c9694d1878..c679ab858a 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.CERTIFIC 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 androidx.annotation.VisibleForTesting; 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { private final DataStore mDataStore; private final DownloadHelper mDownloadHelper; private final SignatureVerifier mSignatureVerifier; - private final CertificateTransparencyInstaller mInstaller; private final CertificateTransparencyLogger mLogger; + private final List mCompatVersions = new ArrayList<>(); + private boolean started = false; CertificateTransparencyDownloader( @@ -71,25 +69,27 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { return; } mContext.unregisterReceiver(this); - mInstaller.removeCompatibilityVersion(Config.COMPATIBILITY_VERSION); + mDataStore.delete(); started = false; if (Config.DEBUG) { @@ -111,7 +111,7 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { } 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { 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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { @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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { @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 9970667b79..0000000000 --- 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 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 9c92d14eb5..a8acc60d15 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 @@ public class CertificateTransparencyJob extends BroadcastReceiver { 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 @@ public class CertificateTransparencyJob extends BroadcastReceiver { /** 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 @@ public class CertificateTransparencyJob extends BroadcastReceiver { mContext.unregisterReceiver(this); mAlarmManager.cancel(mPendingIntent); mCertificateTransparencyDownloader.stop(); + mCompatVersion.delete(); mDependenciesReady = false; if (Config.DEBUG) { @@ -102,16 +107,10 @@ public class CertificateTransparencyJob extends BroadcastReceiver { 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 eb245674a5..ed980567c7 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 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana 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 27488b59b6..fdeb746738 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 @@ class CompatibilityVersion { } 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 @@ class CompatibilityVersion { 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 @@ class CompatibilityVersion { } 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 aafee60f91..592bc4ed5e 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 @@ final class Config { // 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 377926922d..8180316795 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 @@ class DataStore extends Properties { } } + 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 ba42a821c4..54e277a764 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 @@ import java.io.IOException; 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 25f0dc17bf..555268996d 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 com.google.common.truth.Truth.assertThat; 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 @@ import java.util.Base64; 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { @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 @@ public class CertificateTransparencyDownloaderTest { @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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { assertNoVersionIsInstalled(); // 1. Start download of public key. - long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload(); - - // 2. On successful public key download, set the key and start the metatadata download. - setSuccessfulDownload(publicKeyId, publicKeyFile); + mCertificateTransparencyDownloader.startPublicKeyDownload(); + // 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)); - - // 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); + mContext, makeMetadataDownloadCompleteIntent(mCompatVersion, metadataFile)); + // 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 @@ public class CertificateTransparencyDownloaderTest { .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 @@ public class CertificateTransparencyDownloaderTest { 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 @@ public class CertificateTransparencyDownloaderTest { 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 50d3f23743..0000000000 --- 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 0000000000..38fff48bca --- /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( + // /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( + // /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()); + } + } +}