Add compatibility version to log lists installed by CT
Introduce a CompatibilityVersion class to group installing operations on
log list related to the same compatibility version (e.g, v3).
Add support for multiple compatibility versions to the
CertificateTransparencyInstaller, although at the moment of writing
there exists only one compatibility version.
Test: atest NetworkSecurityUnitTests
Bug: 319829948
Change-Id: I503dd30cea93d2b1ba57e5ac91b8427394c453f8
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 fd73b29..16f32c4 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -73,7 +73,9 @@
new CertificateTransparencyInstaller());
}
- void registerReceiver() {
+ void initialize() {
+ mInstaller.addCompatibilityVersion(Config.COMPATIBILITY_VERSION);
+
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
mContext.registerReceiver(this, intentFilter, Context.RECEIVER_EXPORTED);
@@ -185,7 +187,7 @@
String contentUrl = mDataStore.getProperty(Config.CONTENT_URL_PENDING);
String metadataUrl = mDataStore.getProperty(Config.METADATA_URL_PENDING);
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
- success = mInstaller.install(inputStream, version);
+ success = mInstaller.install(Config.COMPATIBILITY_VERSION, inputStream, version);
} catch (IOException e) {
Log.e(TAG, "Could not install new content", e);
return;
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
index 914af06..0ae982d 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -43,7 +43,7 @@
void initialize() {
mDataStore.load();
- mCertificateTransparencyDownloader.registerReceiver();
+ mCertificateTransparencyDownloader.initialize();
DeviceConfig.addOnPropertiesChangedListener(
Config.NAMESPACE_NETWORK_SECURITY, Executors.newSingleThreadExecutor(), this);
if (Config.DEBUG) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
index 82dcadf..4ca97eb 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
@@ -15,148 +15,78 @@
*/
package com.android.server.net.ct;
-import android.annotation.SuppressLint;
-import android.system.ErrnoException;
-import android.system.Os;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
/** Installer of CT log lists. */
public class CertificateTransparencyInstaller {
private static final String TAG = "CertificateTransparencyInstaller";
- private static final String CT_DIR_NAME = "/data/misc/keychain/ct/";
- static final String LOGS_DIR_PREFIX = "logs-";
- static final String LOGS_LIST_FILE_NAME = "log_list.json";
- static final String CURRENT_DIR_SYMLINK_NAME = "current";
+ private final Map<String, CompatibilityVersion> mCompatVersions = new HashMap<>();
- private final File mCertificateTransparencyDir;
- private final File mCurrentDirSymlink;
+ // The CT root directory.
+ private final File mRootDirectory;
- CertificateTransparencyInstaller(File certificateTransparencyDir) {
- mCertificateTransparencyDir = certificateTransparencyDir;
- mCurrentDirSymlink = new File(certificateTransparencyDir, CURRENT_DIR_SYMLINK_NAME);
+ public CertificateTransparencyInstaller(File rootDirectory) {
+ mRootDirectory = rootDirectory;
}
- CertificateTransparencyInstaller() {
- this(new File(CT_DIR_NAME));
+ 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 version of the new 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(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 update dir exists and is readable.
- makeDir(mCertificateTransparencyDir);
-
- File newLogsDir = new File(mCertificateTransparencyDir, 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 (newLogsDir.getCanonicalPath().equals(mCurrentDirSymlink.getCanonicalPath())) {
- 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.
- deleteContentsAndDir(newLogsDir);
- }
- try {
- // 3. Create /data/misc/keychain/ct/logs-<new_version>/ .
- makeDir(newLogsDir);
-
- // 4. Move the log list json file in logs-<new_version>/ .
- File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME);
- if (Files.copy(newContent, logListFile.toPath()) == 0) {
- throw new IOException("The log list appears empty");
- }
- setWorldReadable(logListFile);
-
- // 5. Create temp symlink. We rename this to the target symlink to get an atomic update.
- File tempSymlink = new File(mCertificateTransparencyDir, "new_symlink");
- try {
- Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
- } catch (ErrnoException e) {
- throw new IOException("Failed to create symlink", e);
- }
-
- // 6. Update the symlink target, this is the actual update step.
- tempSymlink.renameTo(mCurrentDirSymlink.getAbsoluteFile());
- } catch (IOException | RuntimeException e) {
- deleteContentsAndDir(newLogsDir);
- throw e;
- }
- Log.i(TAG, "CT log directory updated to " + newLogsDir.getAbsolutePath());
- // 7. Cleanup
- deleteOldLogDirectories();
- return true;
- }
-
- private void makeDir(File dir) throws IOException {
- dir.mkdir();
- if (!dir.isDirectory()) {
- throw new IOException("Unable to make directory " + dir.getCanonicalPath());
- }
- setWorldReadable(dir);
- }
-
- // CT files and directories are readable by all apps.
- @SuppressLint("SetWorldReadable")
- private void setWorldReadable(File file) throws IOException {
- if (!file.setReadable(true, false)) {
- throw new IOException("Failed to set " + file.getCanonicalPath() + " readable");
- }
- }
-
- private void deleteOldLogDirectories() throws IOException {
- if (!mCertificateTransparencyDir.exists()) {
- return;
- }
- File currentTarget = mCurrentDirSymlink.getCanonicalFile();
- for (File file : mCertificateTransparencyDir.listFiles()) {
- if (!currentTarget.equals(file.getCanonicalFile())
- && file.getName().startsWith(LOGS_DIR_PREFIX)) {
- deleteContentsAndDir(file);
- }
- }
- }
-
- static boolean deleteContentsAndDir(File dir) {
- if (deleteContents(dir)) {
- return dir.delete();
- } else {
+ 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);
- private static boolean deleteContents(File dir) {
- File[] files = dir.listFiles();
- boolean success = true;
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- success &= deleteContents(file);
- }
- if (!file.delete()) {
- Log.w(TAG, "Failed to delete " + file);
- success = false;
- }
- }
+ if (!compatVersion.install(newContent, version)) {
+ Log.e(TAG, "Failed to install logs for compatibility version " + compatibilityVersion);
+ return false;
}
- return success;
+ Log.i(TAG, "New logs installed at " + compatVersion.getLogsDir());
+ return true;
}
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
new file mode 100644
index 0000000..27488b5
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -0,0 +1,135 @@
+/*
+ * 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.system.ErrnoException;
+import android.system.Os;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+/** Represents a compatibility version directory. */
+class CompatibilityVersion {
+
+ static final String LOGS_DIR_PREFIX = "logs-";
+ static final String LOGS_LIST_FILE_NAME = "log_list.json";
+
+ private static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
+
+ private final File mRootDirectory;
+ private final File mCurrentLogsDirSymlink;
+
+ private File mCurrentLogsDir = null;
+
+ CompatibilityVersion(File rootDirectory) {
+ mRootDirectory = rootDirectory;
+ mCurrentLogsDirSymlink = new File(mRootDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
+ }
+
+ /**
+ * 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);
+
+ File newLogsDir = new File(mRootDirectory, 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 (newLogsDir.getCanonicalPath().equals(mCurrentLogsDirSymlink.getCanonicalPath())) {
+ 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.
+ DirectoryUtils.removeDir(newLogsDir);
+ }
+ try {
+ // 3. Create a new logs-<new_version>/ directory to store the new list.
+ DirectoryUtils.makeDir(newLogsDir);
+
+ // 4. Move the log list json file in logs-<new_version>/ .
+ File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME);
+ if (Files.copy(newContent, logListFile.toPath()) == 0) {
+ throw new IOException("The log list appears empty");
+ }
+ 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");
+ try {
+ Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to create symlink", e);
+ }
+
+ // 6. Update the symlink target, this is the actual update step.
+ tempSymlink.renameTo(mCurrentLogsDirSymlink.getAbsoluteFile());
+ } catch (IOException | RuntimeException e) {
+ DirectoryUtils.removeDir(newLogsDir);
+ throw e;
+ }
+ // 7. Cleanup
+ mCurrentLogsDir = newLogsDir;
+ deleteOldLogDirectories();
+ return true;
+ }
+
+ File getRootDir() {
+ return mRootDirectory;
+ }
+
+ File getLogsDir() {
+ return mCurrentLogsDir;
+ }
+
+ File getLogsDirSymlink() {
+ return mCurrentLogsDirSymlink;
+ }
+
+ File getLogsFile() {
+ return new File(mCurrentLogsDir, LOGS_LIST_FILE_NAME);
+ }
+
+ boolean delete() {
+ return DirectoryUtils.removeDir(mRootDirectory);
+ }
+
+ private void deleteOldLogDirectories() throws IOException {
+ if (!mRootDirectory.exists()) {
+ return;
+ }
+ File currentTarget = mCurrentLogsDirSymlink.getCanonicalFile();
+ for (File file : mRootDirectory.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 611a5c7..242f13a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -33,6 +33,10 @@
private static final String PREFERENCES_FILE_NAME = "ct.preferences";
static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
+ // CT directory
+ static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/";
+ static final String COMPATIBILITY_VERSION = "v1";
+
// Phenotype flags
static final String NAMESPACE_NETWORK_SECURITY = "network_security";
private static final String FLAGS_PREFIX = "CertificateTransparencyLogList__";
diff --git a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
new file mode 100644
index 0000000..e3b4124
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.annotation.SuppressLint;
+
+import java.io.File;
+import java.io.IOException;
+
+/** Utility class to manipulate CT directories. */
+class DirectoryUtils {
+
+ static void makeDir(File dir) throws IOException {
+ dir.mkdir();
+ if (!dir.isDirectory()) {
+ throw new IOException("Unable to make directory " + dir.getCanonicalPath());
+ }
+ setWorldReadable(dir);
+ }
+
+ // CT files and directories are readable by all apps.
+ @SuppressLint("SetWorldReadable")
+ static void setWorldReadable(File file) throws IOException {
+ if (!file.setReadable(true, false)) {
+ throw new IOException("Failed to set " + file.getCanonicalPath() + " readable");
+ }
+ }
+
+ static boolean removeDir(File dir) {
+ return deleteContentsAndDir(dir);
+ }
+
+ private static boolean deleteContentsAndDir(File dir) {
+ if (deleteContents(dir)) {
+ return dir.delete();
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ boolean success = true;
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ success &= deleteContents(file);
+ }
+ if (!file.delete()) {
+ success = false;
+ }
+ }
+ }
+ return success;
+ }
+}
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 1aad028..df02446 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
@@ -159,7 +159,9 @@
Base64.getEncoder().encodeToString(mPublicKey.getEncoded()));
setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
- when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(true);
+ when(mCertificateTransparencyInstaller.install(
+ eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ .thenReturn(true);
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
@@ -168,7 +170,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, times(1)).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, times(1))
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isEqualTo(contentUri.toString());
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isEqualTo(metadataUri.toString());
@@ -185,7 +188,9 @@
Uri metadataUri = Uri.fromFile(metadataFile);
setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
- when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(false);
+ when(mCertificateTransparencyInstaller.install(
+ eq(Config.COMPATIBILITY_VERSION), any(), eq(version)))
+ .thenReturn(false);
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
@@ -208,7 +213,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, never()).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, never())
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
@@ -230,7 +236,8 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeDownloadCompleteIntent(contentId));
- verify(mCertificateTransparencyInstaller, never()).install(any(), eq(version));
+ verify(mCertificateTransparencyInstaller, never())
+ .install(eq(Config.COMPATIBILITY_VERSION), any(), eq(version));
assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
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
index bfb8bdf..50d3f23 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
@@ -17,11 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
-import android.system.ErrnoException;
-import android.system.Os;
-
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,98 +37,134 @@
@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 File mTestSymlink =
- new File(mTestDir, CertificateTransparencyInstaller.CURRENT_DIR_SYMLINK_NAME);
private CertificateTransparencyInstaller mCertificateTransparencyInstaller =
new CertificateTransparencyInstaller(mTestDir);
@Before
public void setUp() {
- CertificateTransparencyInstaller.deleteContentsAndDir(mTestDir);
+ 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";
- boolean success = false;
try (InputStream inputStream = asStream(content)) {
- success = mCertificateTransparencyInstaller.install(inputStream, version);
+ assertThat(
+ mCertificateTransparencyInstaller.install(
+ TEST_VERSION, inputStream, version))
+ .isTrue();
}
- assertThat(success).isTrue();
assertThat(mTestDir.exists()).isTrue();
assertThat(mTestDir.isDirectory()).isTrue();
- assertThat(mTestSymlink.exists()).isTrue();
- assertThat(mTestSymlink.isDirectory()).isTrue();
-
- File logsDir =
- new File(mTestDir, CertificateTransparencyInstaller.LOGS_DIR_PREFIX + version);
+ CompatibilityVersion compatVersion =
+ mCertificateTransparencyInstaller.getCompatibilityVersion(TEST_VERSION);
+ File logsDir = compatVersion.getLogsDir();
assertThat(logsDir.exists()).isTrue();
assertThat(logsDir.isDirectory()).isTrue();
- assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(logsDir.getCanonicalPath());
-
- File logsListFile = new File(logsDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
+ 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, ErrnoException {
+ throws IOException {
String existingVersion = "666";
String existingContent = "i_was_already_installed_successfully";
- File existingLogDir =
- new File(
- mTestDir,
- CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
- assertThat(mTestDir.mkdir()).isTrue();
- assertThat(existingLogDir.mkdir()).isTrue();
- Os.symlink(existingLogDir.getCanonicalPath(), mTestSymlink.getCanonicalPath());
- File logsListFile =
- new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
- logsListFile.createNewFile();
- writeToFile(logsListFile, existingContent);
- boolean success = false;
+ 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")) {
- success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
+ assertThat(
+ mCertificateTransparencyInstaller.install(
+ TEST_VERSION, inputStream, existingVersion))
+ .isFalse();
}
- assertThat(success).isFalse();
- assertThat(readAsString(logsListFile)).isEqualTo(existingContent);
- }
-
- @Test
- public void testCertificateTransparencyInstaller_versionInstalledFailed()
- throws IOException, ErrnoException {
- String existingVersion = "666";
- String existingContent = "somebody_tried_to_install_me_but_failed_halfway_through";
- String newContent = "i_am_the_real_certificate";
- File existingLogDir =
- new File(
- mTestDir,
- CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
- assertThat(mTestDir.mkdir()).isTrue();
- assertThat(existingLogDir.mkdir()).isTrue();
- File logsListFile =
- new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
- logsListFile.createNewFile();
- writeToFile(logsListFile, existingContent);
- boolean success = false;
-
- try (InputStream inputStream = asStream(newContent)) {
- success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
- }
-
- assertThat(success).isTrue();
- assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(existingLogDir.getCanonicalPath());
- assertThat(readAsString(logsListFile)).isEqualTo(newContent);
+ assertThat(readAsString(compatVersion.getLogsFile())).isEqualTo(existingContent);
}
private static InputStream asStream(String string) throws IOException {