Merge "Implement killswitch mechanism for Android CT" into main

This commit is contained in:
Treehugger Robot 2024-12-27 10:15:04 -08:00 • committed by Gerrit Code Review
commit 1df64f7cf6
4 changed files with 112 additions and 171 deletions

View file

@ -64,6 +64,8 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
private final CertificateTransparencyInstaller mInstaller;
private final CertificateTransparencyLogger mLogger;
private boolean started = false;
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
@ -79,15 +81,32 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
* @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 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
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;
}

View file

@ -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.");
}
}
}

View file

@ -37,6 +37,7 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
private boolean mDependenciesReady = false;
@ -49,9 +50,15 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
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 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
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 @@ public class CertificateTransparencyJob extends BroadcastReceiver {
}
if (!mDependenciesReady) {
mDataStore.load();
mCertificateTransparencyDownloader.initialize();
mCertificateTransparencyDownloader.start();
mDependenciesReady = true;
}

View file

@ -25,27 +25,29 @@ import android.annotation.RequiresApi;
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 static final String TAG = "CertificateTransparencyService";
private final CertificateTransparencyFlagsListener mFlagsListener;
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 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana
signatureVerifier,
new CertificateTransparencyInstaller(),
new CertificateTransparencyLogger());
mFlagsListener =
new CertificateTransparencyFlagsListener(dataStore, signatureVerifier, downloader);
mCertificateTransparencyJob =
new CertificateTransparencyJob(context, dataStore, downloader);
}
@ -75,13 +75,50 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana
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;
}
}
}