Add logging of cause of failure when the log list fails to update
Bug: 378626065
Flag: com.android.net.ct.flags.certificate_transparency_service
Test: atest NetworkSecurityUnitTests
Change-Id: I4c11d7b09ae0f32fccbdf4085192a0f04cd2321e
diff --git a/networksecurity/service/Android.bp b/networksecurity/service/Android.bp
index f27acb7..d7aacdb 100644
--- a/networksecurity/service/Android.bp
+++ b/networksecurity/service/Android.bp
@@ -24,12 +24,14 @@
srcs: [
"src/**/*.java",
+ ":statslog-certificate-transparency-java-gen",
],
libs: [
"framework-configinfrastructure.stubs.module_lib",
"framework-connectivity-pre-jarjar",
"service-connectivity-pre-jarjar",
+ "framework-statsd.stubs.module_lib",
],
static_libs: [
@@ -49,3 +51,10 @@
sdk_version: "system_server_current",
apex_available: ["com.android.tethering"],
}
+
+genrule {
+ name: "statslog-certificate-transparency-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module certificate_transparency --javaPackage com.android.server.net.ct --javaClass CertificateTransparencyStatsLog",
+ out: ["com/android/server/net/ct/CertificateTransparencyStatsLog.java"],
+}
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 d16a760..79123ee 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -16,6 +16,15 @@
package com.android.server.net.ct;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_DEVICE_OFFLINE;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_DOWNLOAD_CANNOT_RESUME;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_HTTP_ERROR;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_UNKNOWN;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_TOO_MANY_REDIRECTS;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__PENDING_WAITING_FOR_WIFI;
+
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.RequiresApi;
@@ -50,18 +59,21 @@
private final DownloadHelper mDownloadHelper;
private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyInstaller mInstaller;
+ private final CertificateTransparencyLogger mLogger;
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
SignatureVerifier signatureVerifier,
- CertificateTransparencyInstaller installer) {
+ CertificateTransparencyInstaller installer,
+ CertificateTransparencyLogger logger) {
mContext = context;
mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mInstaller = installer;
+ mLogger = logger;
}
void initialize() {
@@ -197,6 +209,8 @@
}
if (!success) {
Log.w(TAG, "Log list did not pass verification");
+
+ // TODO(b/384931263): add logging for failed signature verification
return;
}
@@ -225,7 +239,10 @@
mDataStore.store();
} else {
if (updateFailureCount()) {
- // TODO(378626065): Report FAILURE_VERSION_ALREADY_EXISTS failure via statsd.
+ mLogger.logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
}
}
@@ -234,7 +251,46 @@
Log.e(TAG, "Download failed with " + status);
if (updateFailureCount()) {
- // TODO(378626065): Report download failure via statsd.
+ 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()
+ );
+ } else {
+ // TODO(b/384935059): handle blocked domain logging
+ // TODO(b/384936292): add additionalchecks for pending wifi status
+ mLogger.logCTLogListUpdateFailedEvent(
+ downloadStatusToFailureReason(status.reason()),
+ failureCount
+ );
+ }
+ }
+ }
+
+ /** Converts DownloadStatus reason into failure reason to log. */
+ private int downloadStatusToFailureReason(int 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;
+ case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
+ case DownloadManager.ERROR_HTTP_DATA_ERROR:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_HTTP_ERROR;
+ case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_TOO_MANY_REDIRECTS;
+ case DownloadManager.ERROR_CANNOT_RESUME:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_DOWNLOAD_CANNOT_RESUME;
+ case DownloadManager.ERROR_INSUFFICIENT_SPACE:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE;
+ case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__PENDING_WAITING_FOR_WIFI;
+ default:
+ return CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_UNKNOWN;
}
}
@@ -253,7 +309,7 @@
boolean shouldReport = new_failure_count >= Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD;
if (shouldReport) {
- Log.e(TAG,
+ 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/CertificateTransparencyLogger.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
new file mode 100644
index 0000000..93493c2
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
@@ -0,0 +1,52 @@
+/*
+ * 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 static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED;
+
+/** Helper class to interface with logging to statsd. */
+public class CertificateTransparencyLogger {
+
+ public CertificateTransparencyLogger() {}
+
+ /**
+ * Logs a CTLogListUpdateFailed event to statsd, when no HTTP error status code is present.
+ *
+ * @param failureReason reason why the log list wasn't updated (e.g. DownloadManager failures)
+ * @param failureCount number of consecutive log list update failures
+ */
+ public void logCTLogListUpdateFailedEvent(int failureReason, int failureCount) {
+ logCTLogListUpdateFailedEvent(failureReason, failureCount, /* httpErrorStatusCode= */ 0);
+ }
+
+ /**
+ * Logs a CTLogListUpdateFailed event to statsd, when an HTTP error status code is provided.
+ *
+ * @param failureReason reason why the log list wasn't updated (e.g. DownloadManager failures)
+ * @param failureCount number of consecutive log list update failures
+ * @param httpErrorStatusCode if relevant, the HTTP error status code from DownloadManager
+ */
+ public void logCTLogListUpdateFailedEvent(
+ int failureReason, int failureCount, int httpErrorStatusCode) {
+ CertificateTransparencyStatsLog.write(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED,
+ failureReason,
+ failureCount,
+ httpErrorStatusCode
+ );
+ }
+}
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 6151727..2a27204 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -59,7 +59,8 @@
dataStore,
downloadHelper,
signatureVerifier,
- new CertificateTransparencyInstaller());
+ new CertificateTransparencyInstaller(),
+ new CertificateTransparencyLogger());
mFlagsListener =
new CertificateTransparencyFlagsListener(dataStore, signatureVerifier, downloader);
mCertificateTransparencyJob =
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index 4748043..3a359f4 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -15,12 +15,17 @@
*/
package com.android.server.net.ct;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE;
+import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -67,6 +72,7 @@
@Mock private DownloadManager mDownloadManager;
@Mock private CertificateTransparencyInstaller mCertificateTransparencyInstaller;
+ @Mock private CertificateTransparencyLogger mLogger;
private PrivateKey mPrivateKey;
private PublicKey mPublicKey;
@@ -96,7 +102,8 @@
mDataStore,
new DownloadHelper(mDownloadManager),
mSignatureVerifier,
- mCertificateTransparencyInstaller);
+ mCertificateTransparencyInstaller,
+ mLogger);
prepareDataStore();
prepareDownloadManager();
@@ -203,7 +210,10 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- // TODO(378626065): Verify logged failure via statsd.
+ verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
+ );
}
@Test
@@ -223,7 +233,7 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(1);
- // TODO(378626065): Verify no failure logged via statsd.
+ verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
@@ -274,7 +284,10 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- // TODO(378626065): Verify logged failure via statsd.
+ verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
+ );
}
@Test
@@ -295,7 +308,7 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(1);
- // TODO(378626065): Verify no failure logged via statsd.
+ verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
@@ -356,7 +369,10 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- // TODO(378626065): Verify logged failure via statsd.
+ verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_NO_DISK_SPACE,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
+ );
}
@Test
@@ -377,7 +393,7 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(1);
- // TODO(378626065): Verify no failure logged via statsd.
+ verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test
@@ -425,7 +441,10 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
- // TODO(378626065): Verify logged failure via statsd.
+ verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
+ CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
+ Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
+ );
}
@Test
@@ -451,7 +470,7 @@
assertThat(mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(1);
- // TODO(378626065): Verify no failure logged via statsd.
+ verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
}
@Test