Merge "Implement killswitch mechanism for Android CT" into main
This commit is contained in:
commit
1df64f7cf6
4 changed files with 112 additions and 171 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue