Merge "Implement killswitch mechanism for Android CT" into main
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 502c449..002ad9a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -64,6 +64,8 @@
private final CertificateTransparencyInstaller mInstaller;
private final CertificateTransparencyLogger mLogger;
+ private boolean started = false;
+
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
@@ -79,15 +81,32 @@
mLogger = logger;
}
- void initialize() {
+ void start() {
+ if (started) {
+ return;
+ }
mInstaller.addCompatibilityVersion(Config.COMPATIBILITY_VERSION);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
- mContext.registerReceiver(this, intentFilter, Context.RECEIVER_EXPORTED);
+ mContext.registerReceiver(
+ this,
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+ Context.RECEIVER_EXPORTED);
+ started = true;
if (Config.DEBUG) {
- Log.d(TAG, "CertificateTransparencyDownloader initialized successfully");
+ Log.d(TAG, "CertificateTransparencyDownloader started.");
+ }
+ }
+
+ void stop() {
+ if (!started) {
+ return;
+ }
+ mContext.unregisterReceiver(this);
+ mInstaller.removeCompatibilityVersion(Config.COMPATIBILITY_VERSION);
+ started = false;
+
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyDownloader stopped.");
}
}
@@ -246,8 +265,9 @@
String version = null;
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
- version = new JSONObject(new String(inputStream.readAllBytes(), UTF_8))
- .getString("version");
+ 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;
@@ -272,7 +292,7 @@
mLogger.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
}
}
@@ -281,30 +301,28 @@
Log.e(TAG, "Download failed with " + status);
if (updateFailureCount()) {
- int failureCount = mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
+ int failureCount =
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
// HTTP Error
if (400 <= status.reason() && status.reason() <= 600) {
mLogger.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_HTTP_ERROR,
failureCount,
- status.reason()
- );
+ status.reason());
} else {
// TODO(b/384935059): handle blocked domain logging
// TODO(b/384936292): add additionalchecks for pending wifi status
mLogger.logCTLogListUpdateFailedEvent(
- downloadStatusToFailureReason(status.reason()),
- failureCount
- );
+ downloadStatusToFailureReason(status.reason()), failureCount);
}
}
}
/** Converts DownloadStatus reason into failure reason to log. */
private int downloadStatusToFailureReason(int downloadStatusReason) {
- switch(downloadStatusReason) {
+ switch (downloadStatusReason) {
case DownloadManager.PAUSED_WAITING_TO_RETRY:
case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_DEVICE_OFFLINE;
@@ -330,8 +348,9 @@
* @return whether the failure count exceeds the threshold and should be logged.
*/
private boolean updateFailureCount() {
- int failure_count = mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
+ int failure_count =
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
int new_failure_count = failure_count + 1;
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count);
@@ -339,8 +358,7 @@
boolean shouldReport = new_failure_count >= Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD;
if (shouldReport) {
- Log.d(TAG,
- "Log list update failure count exceeds threshold: " + new_failure_count);
+ Log.d(TAG, "Log list update failure count exceeds threshold: " + new_failure_count);
}
return shouldReport;
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
deleted file mode 100644
index 3138ea7..0000000
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ /dev/null
@@ -1,128 +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.annotation.RequiresApi;
-import android.os.Build;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.security.GeneralSecurityException;
-import java.util.concurrent.Executors;
-
-/** Listener class for the Certificate Transparency Phenotype flags. */
-@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
-class CertificateTransparencyFlagsListener implements DeviceConfig.OnPropertiesChangedListener {
-
- private static final String TAG = "CertificateTransparencyFlagsListener";
-
- private final DataStore mDataStore;
- private final SignatureVerifier mSignatureVerifier;
- private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
-
- CertificateTransparencyFlagsListener(
- DataStore dataStore,
- SignatureVerifier signatureVerifier,
- CertificateTransparencyDownloader certificateTransparencyDownloader) {
- mDataStore = dataStore;
- mSignatureVerifier = signatureVerifier;
- mCertificateTransparencyDownloader = certificateTransparencyDownloader;
- }
-
- void initialize() {
- mDataStore.load();
- mCertificateTransparencyDownloader.initialize();
- DeviceConfig.addOnPropertiesChangedListener(
- Config.NAMESPACE_NETWORK_SECURITY, Executors.newSingleThreadExecutor(), this);
- if (Config.DEBUG) {
- Log.d(TAG, "CertificateTransparencyFlagsListener initialized successfully");
- }
- // TODO: handle property changes triggering on boot before registering this listener.
- }
-
- @Override
- public void onPropertiesChanged(Properties properties) {
- if (!Config.NAMESPACE_NETWORK_SECURITY.equals(properties.getNamespace())) {
- return;
- }
-
- String newPublicKey =
- DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY,
- Config.FLAG_PUBLIC_KEY,
- /* defaultValue= */ "");
- String newVersion =
- DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY,
- Config.FLAG_VERSION,
- /* defaultValue= */ "");
- String newContentUrl =
- DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY,
- Config.FLAG_CONTENT_URL,
- /* defaultValue= */ "");
- String newMetadataUrl =
- DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY,
- Config.FLAG_METADATA_URL,
- /* defaultValue= */ "");
- if (TextUtils.isEmpty(newPublicKey)
- || TextUtils.isEmpty(newVersion)
- || TextUtils.isEmpty(newContentUrl)
- || TextUtils.isEmpty(newMetadataUrl)) {
- return;
- }
-
- if (Config.DEBUG) {
- Log.d(TAG, "newPublicKey=" + newPublicKey);
- Log.d(TAG, "newVersion=" + newVersion);
- Log.d(TAG, "newContentUrl=" + newContentUrl);
- Log.d(TAG, "newMetadataUrl=" + newMetadataUrl);
- }
-
- String oldVersion = mDataStore.getProperty(Config.VERSION);
- String oldContentUrl = mDataStore.getProperty(Config.CONTENT_URL);
- String oldMetadataUrl = mDataStore.getProperty(Config.METADATA_URL);
-
- if (TextUtils.equals(newVersion, oldVersion)
- && TextUtils.equals(newContentUrl, oldContentUrl)
- && TextUtils.equals(newMetadataUrl, oldMetadataUrl)) {
- Log.i(TAG, "No flag changed, ignoring update");
- return;
- }
-
- try {
- mSignatureVerifier.setPublicKey(newPublicKey);
- } catch (GeneralSecurityException | IllegalArgumentException e) {
- Log.e(TAG, "Error setting the public Key", e);
- return;
- }
-
- // TODO: handle the case where there is already a pending download.
-
- mDataStore.setProperty(Config.CONTENT_URL, newContentUrl);
- mDataStore.setProperty(Config.METADATA_URL, newMetadataUrl);
- mDataStore.store();
-
- if (mCertificateTransparencyDownloader.startMetadataDownload() == -1) {
- Log.e(TAG, "Metadata download not started.");
- } else if (Config.DEBUG) {
- Log.d(TAG, "Metadata download started successfully.");
- }
- }
-}
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 abede87..baca2e3 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -37,6 +37,7 @@
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private final AlarmManager mAlarmManager;
+ private final PendingIntent mPendingIntent;
private boolean mDependenciesReady = false;
@@ -49,9 +50,15 @@
mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
mAlarmManager = context.getSystemService(AlarmManager.class);
+ mPendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ /* requestCode= */ 0,
+ new Intent(ConfigUpdate.ACTION_UPDATE_CT_LOGS),
+ PendingIntent.FLAG_IMMUTABLE);
}
- void initialize() {
+ void schedule() {
mContext.registerReceiver(
this,
new IntentFilter(ConfigUpdate.ACTION_UPDATE_CT_LOGS),
@@ -60,14 +67,21 @@
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime(), // schedule first job at earliest convenient time.
AlarmManager.INTERVAL_DAY,
- PendingIntent.getBroadcast(
- mContext,
- 0,
- new Intent(ConfigUpdate.ACTION_UPDATE_CT_LOGS),
- PendingIntent.FLAG_IMMUTABLE));
+ mPendingIntent);
if (Config.DEBUG) {
- Log.d(TAG, "CertificateTransparencyJob scheduled successfully.");
+ Log.d(TAG, "CertificateTransparencyJob scheduled.");
+ }
+ }
+
+ void cancel() {
+ mContext.unregisterReceiver(this);
+ mAlarmManager.cancel(mPendingIntent);
+ mCertificateTransparencyDownloader.stop();
+ mDependenciesReady = false;
+
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyJob canceled.");
}
}
@@ -82,7 +96,7 @@
}
if (!mDependenciesReady) {
mDataStore.load();
- mCertificateTransparencyDownloader.initialize();
+ mCertificateTransparencyDownloader.start();
mDependenciesReady = true;
}
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 2a27204..4569628 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -25,27 +25,29 @@
import android.content.Context;
import android.net.ct.ICertificateTransparencyManager;
import android.os.Build;
+import android.util.Log;
import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import com.android.server.SystemService;
+import java.util.concurrent.Executors;
/** Implementation of the Certificate Transparency service. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
-public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub {
+public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub
+ implements DeviceConfig.OnPropertiesChangedListener {
- private final CertificateTransparencyFlagsListener mFlagsListener;
+ private static final String TAG = "CertificateTransparencyService";
+
private final CertificateTransparencyJob mCertificateTransparencyJob;
+ private boolean started = false;
+
/**
* @return true if the CertificateTransparency service is enabled.
*/
public static boolean enabled(Context context) {
- return DeviceConfig.getBoolean(
- Config.NAMESPACE_NETWORK_SECURITY,
- Config.FLAG_SERVICE_ENABLED,
- /* defaultValue= */ true)
- && certificateTransparencyService()
- && certificateTransparencyConfiguration();
+ return certificateTransparencyService() && certificateTransparencyConfiguration();
}
/** Creates a new {@link CertificateTransparencyService} object. */
@@ -61,8 +63,6 @@
signatureVerifier,
new CertificateTransparencyInstaller(),
new CertificateTransparencyLogger());
- mFlagsListener =
- new CertificateTransparencyFlagsListener(dataStore, signatureVerifier, downloader);
mCertificateTransparencyJob =
new CertificateTransparencyJob(context, dataStore, downloader);
}
@@ -75,13 +75,50 @@
public void onBootPhase(int phase) {
switch (phase) {
case SystemService.PHASE_BOOT_COMPLETED:
- if (certificateTransparencyJob()) {
- mCertificateTransparencyJob.initialize();
- } else {
- mFlagsListener.initialize();
- }
+ DeviceConfig.addOnPropertiesChangedListener(
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Executors.newSingleThreadExecutor(),
+ this);
+ onPropertiesChanged(
+ new Properties.Builder(Config.NAMESPACE_NETWORK_SECURITY).build());
break;
default:
}
}
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ if (!Config.NAMESPACE_NETWORK_SECURITY.equals(properties.getNamespace())) {
+ return;
+ }
+
+ if (DeviceConfig.getBoolean(
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_SERVICE_ENABLED,
+ /* defaultValue= */ true)) {
+ startService();
+ } else {
+ stopService();
+ }
+ }
+
+ private void startService() {
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyService start");
+ }
+ if (!started) {
+ mCertificateTransparencyJob.schedule();
+ started = true;
+ }
+ }
+
+ private void stopService() {
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyService stop");
+ }
+ if (started) {
+ mCertificateTransparencyJob.cancel();
+ started = false;
+ }
+ }
}