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 502c449e32..002ad9af6e 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 @@ 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; } 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 3138ea7a83..0000000000 --- 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 abede878eb..baca2e3e69 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 @@ 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; } 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 2a27204416..4569628a46 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.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; + } + } }