From 70c4de8edca49d75bf91eed22fe3db211028640b Mon Sep 17 00:00:00 2001 From: Sandro Montanari Date: Tue, 19 Nov 2024 16:03:54 +0000 Subject: [PATCH] Add daily CertificateTransparencyJob Following a conversation with the Experiments team (b/374692404), it appears we will not be able to use device config flags to trigger the download of CT log lists. This CL is the first of a series that will allow us to move away from flags and instead rely on a daily job execution. For scheduling we rely on the Android AlarmManager. I previously attempted to schedule the daily job using the Android JobScheduler. However, those attempts were unsuccessful beacause the Tethering apex is not on the system_server classpath, so the system would crash when attempting to load the class to execute the job. Flag: com.android.net.ct.flags.certificate_transparency_job Bug: 374692404 Test: NetworkSecurityUnitTests Change-Id: I6ce44dcea464cab20d2df73b065e35c0f76dcbab --- common/networksecurity_flags.aconfig | 9 ++ .../ct/CertificateTransparencyDownloader.java | 17 ++-- .../CertificateTransparencyFlagsListener.java | 10 +-- .../net/ct/CertificateTransparencyJob.java | 87 +++++++++++++++++++ .../ct/CertificateTransparencyService.java | 19 +++- 5 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java diff --git a/common/networksecurity_flags.aconfig b/common/networksecurity_flags.aconfig index 6438ba4fd6..4a83af438e 100644 --- a/common/networksecurity_flags.aconfig +++ b/common/networksecurity_flags.aconfig @@ -8,3 +8,12 @@ flag { bug: "319829948" is_fixed_read_only: true } + +flag { + name: "certificate_transparency_job" + is_exported: true + namespace: "network_security" + description: "Enable daily job service for certificate transparency instead of flags listener" + bug: "319829948" + is_fixed_read_only: true +} 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 f86d1274c0..d53f00793f 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java @@ -88,12 +88,17 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { } void setPublicKey(String publicKey) throws GeneralSecurityException { - mPublicKey = - Optional.of( - KeyFactory.getInstance("RSA") - .generatePublic( - new X509EncodedKeySpec( - Base64.getDecoder().decode(publicKey)))); + try { + mPublicKey = + Optional.of( + KeyFactory.getInstance("RSA") + .generatePublic( + new X509EncodedKeySpec( + Base64.getDecoder().decode(publicKey)))); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid public key Base64 encoding", e); + mPublicKey = Optional.empty(); + } } @VisibleForTesting 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 0ae982d3bf..93a706421f 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java @@ -16,7 +16,6 @@ package com.android.server.net.ct; import android.annotation.RequiresApi; -import android.content.Context; import android.os.Build; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; @@ -35,10 +34,11 @@ class CertificateTransparencyFlagsListener implements DeviceConfig.OnPropertiesC private final DataStore mDataStore; private final CertificateTransparencyDownloader mCertificateTransparencyDownloader; - CertificateTransparencyFlagsListener(Context context) { - mDataStore = new DataStore(Config.PREFERENCES_FILE); - mCertificateTransparencyDownloader = - new CertificateTransparencyDownloader(context, mDataStore); + CertificateTransparencyFlagsListener( + DataStore dataStore, + CertificateTransparencyDownloader certificateTransparencyDownloader) { + mDataStore = dataStore; + mCertificateTransparencyDownloader = certificateTransparencyDownloader; } void initialize() { diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java new file mode 100644 index 0000000000..6fbf0bab82 --- /dev/null +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java @@ -0,0 +1,87 @@ +/* + * 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.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.SystemClock; +import android.provider.DeviceConfig; +import android.util.Log; + +import java.util.HashMap; + +/** Implementation of the Certificate Transparency job */ +@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +public class CertificateTransparencyJob extends BroadcastReceiver { + + private static final String TAG = "CertificateTransparencyJob"; + + private static final String ACTION_JOB_START = "com.android.server.net.ct.action.JOB_START"; + + private final Context mContext; + private final DataStore mDataStore; + private final CertificateTransparencyDownloader mCertificateTransparencyDownloader; + // TODO(b/374692404): remove dependency to flags. + private final CertificateTransparencyFlagsListener mFlagsListener; + private final AlarmManager mAlarmManager; + + /** Creates a new {@link CertificateTransparencyJob} object. */ + public CertificateTransparencyJob( + Context context, + DataStore dataStore, + CertificateTransparencyDownloader certificateTransparencyDownloader, + CertificateTransparencyFlagsListener flagsListener) { + mContext = context; + mFlagsListener = flagsListener; + mDataStore = dataStore; + mCertificateTransparencyDownloader = certificateTransparencyDownloader; + mAlarmManager = context.getSystemService(AlarmManager.class); + } + + void initialize() { + mDataStore.load(); + mCertificateTransparencyDownloader.initialize(); + + mContext.registerReceiver( + this, new IntentFilter(ACTION_JOB_START), Context.RECEIVER_EXPORTED); + mAlarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime(), // schedule first job at earliest convenient time. + AlarmManager.INTERVAL_DAY, + PendingIntent.getBroadcast( + mContext, 0, new Intent(ACTION_JOB_START), PendingIntent.FLAG_IMMUTABLE)); + + if (Config.DEBUG) { + Log.d(TAG, "CertificateTransparencyJob scheduled successfully."); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!ACTION_JOB_START.equals(intent.getAction())) { + Log.w(TAG, "Received unexpected broadcast with action " + intent); + return; + } + mFlagsListener.onPropertiesChanged( + new DeviceConfig.Properties(Config.NAMESPACE_NETWORK_SECURITY, new HashMap<>())); + } +} 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 edf7c565fd..ac55e44887 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java @@ -28,7 +28,10 @@ import com.android.server.SystemService; @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub { + private final DataStore mDataStore; + private final CertificateTransparencyDownloader mCertificateTransparencyDownloader; private final CertificateTransparencyFlagsListener mFlagsListener; + private final CertificateTransparencyJob mCertificateTransparencyJob; /** * @return true if the CertificateTransparency service is enabled. @@ -41,7 +44,15 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana /** Creates a new {@link CertificateTransparencyService} object. */ public CertificateTransparencyService(Context context) { - mFlagsListener = new CertificateTransparencyFlagsListener(context); + mDataStore = new DataStore(Config.PREFERENCES_FILE); + mCertificateTransparencyDownloader = + new CertificateTransparencyDownloader(context, mDataStore); + mFlagsListener = + new CertificateTransparencyFlagsListener( + mDataStore, mCertificateTransparencyDownloader); + mCertificateTransparencyJob = + new CertificateTransparencyJob( + context, mDataStore, mCertificateTransparencyDownloader, mFlagsListener); } /** @@ -53,7 +64,11 @@ public class CertificateTransparencyService extends ICertificateTransparencyMana switch (phase) { case SystemService.PHASE_BOOT_COMPLETED: - mFlagsListener.initialize(); + if (Flags.certificateTransparencyJob()) { + mCertificateTransparencyJob.initialize(); + } else { + mFlagsListener.initialize(); + } break; default: }