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
diff --git a/common/networksecurity_flags.aconfig b/common/networksecurity_flags.aconfig
index 6438ba4..4a83af4 100644
--- a/common/networksecurity_flags.aconfig
+++ b/common/networksecurity_flags.aconfig
@@ -8,3 +8,12 @@
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 f86d127..d53f007 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 @@
}
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 0ae982d..93a7064 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 @@
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 0000000..6fbf0ba
--- /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 edf7c56..ac55e44 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 @@
@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 @@
/** 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 @@
switch (phase) {
case SystemService.PHASE_BOOT_COMPLETED:
- mFlagsListener.initialize();
+ if (Flags.certificateTransparencyJob()) {
+ mCertificateTransparencyJob.initialize();
+ } else {
+ mFlagsListener.initialize();
+ }
break;
default:
}