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 56a5ee5df7..ef0d0eae27 100644 --- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java +++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.server.net.ct; import static java.nio.charset.StandardCharsets.UTF_8; @@ -201,9 +202,8 @@ 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; @@ -219,13 +219,44 @@ class CertificateTransparencyDownloader extends BroadcastReceiver { if (success) { // Update information about the stored version on successful install. mDataStore.setProperty(Config.VERSION, version); + + // Reset the number of consecutive log list failure updates back to zero. + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0L); mDataStore.store(); + } else { + if (updateFailureCount()) { + // TODO(378626065): Report FAILURE_VERSION_ALREADY_EXISTS failure via statsd. + } } } private void handleDownloadFailed(DownloadStatus status) { Log.e(TAG, "Download failed with " + status); - // TODO(378626065): Report failure via statsd. + + if (updateFailureCount()) { + // TODO(378626065): Report download failure via statsd. + } + } + + /** + * Updates the data store with the current number of consecutive log list update failures. + * + * @return whether the failure count exceeds the threshold and should be logged. + */ + private boolean updateFailureCount() { + long failure_count = mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L); + long new_failure_count = failure_count + 1L; + + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count); + mDataStore.store(); + + boolean shouldReport = new_failure_count >= Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD; + if (shouldReport) { + Log.e(TAG, + "Log list update failure count exceeds threshold: " + new_failure_count); + } + return shouldReport; } private long download(String url) { diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java index 70d8e42280..edd6a805d0 100644 --- a/networksecurity/service/src/com/android/server/net/ct/Config.java +++ b/networksecurity/service/src/com/android/server/net/ct/Config.java @@ -54,10 +54,14 @@ final class Config { static final String METADATA_DOWNLOAD_ID = "metadata_download_id"; static final String PUBLIC_KEY_URL = "public_key_url"; static final String PUBLIC_KEY_DOWNLOAD_ID = "public_key_download_id"; + static final String LOG_LIST_UPDATE_FAILURE_COUNT = "log_list_update_failure_count"; // URLs static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/"; static final String URL_LOG_LIST = URL_PREFIX + "log_list.json"; static final String URL_SIGNATURE = URL_PREFIX + "log_list.sig"; static final String URL_PUBLIC_KEY = URL_PREFIX + "log_list.pub"; + + // Threshold amounts + static final long LOG_LIST_UPDATE_FAILURE_THRESHOLD = 10L; } 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 ffa12838c9..5c4a4e5416 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 @@ -185,6 +185,47 @@ public class CertificateTransparencyDownloaderTest { assertThat(mCertificateTransparencyDownloader.hasMetadataDownloadId()).isFalse(); } + @Test + public void testDownloader_publicKeyDownloadFail_failureThresholdExceeded_logsFailure() + throws Exception { + long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, + Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1); + setFailedDownload( + publicKeyId, // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD); + // TODO(378626065): Verify logged failure via statsd. + } + + @Test + public void testDownloader_publicKeyDownloadFail_failureThresholdNotMet_doesNotLog() + throws Exception { + long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0); + setFailedDownload( + publicKeyId, // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(publicKeyId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(1); + // TODO(378626065): Verify no failure logged via statsd. + } + @Test public void testDownloader_metadataDownloadSuccess_startContentDownload() { long metadataId = mCertificateTransparencyDownloader.startMetadataDownload(); @@ -214,6 +255,49 @@ public class CertificateTransparencyDownloaderTest { assertThat(mCertificateTransparencyDownloader.hasContentDownloadId()).isFalse(); } + @Test + public void testDownloader_metadataDownloadFail_failureThresholdExceeded_logsFailure() + throws Exception { + long metadataId = mCertificateTransparencyDownloader.startMetadataDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, + Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1); + setFailedDownload( + metadataId, + // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD); + // TODO(378626065): Verify logged failure via statsd. + } + + @Test + public void testDownloader_metadataDownloadFail_failureThresholdNotMet_doesNotLog() + throws Exception { + long metadataId = mCertificateTransparencyDownloader.startMetadataDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0); + setFailedDownload( + metadataId, + // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(metadataId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(1); + // TODO(378626065): Verify no failure logged via statsd. + } + @Test public void testDownloader_contentDownloadSuccess_installSuccess_updateDataStore() throws Exception { @@ -253,6 +337,49 @@ public class CertificateTransparencyDownloaderTest { assertNoVersionIsInstalled(); } + @Test + public void testDownloader_contentDownloadFail_failureThresholdExceeded_logsFailure() + throws Exception { + long contentId = mCertificateTransparencyDownloader.startContentDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, + Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1); + setFailedDownload( + contentId, + // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD); + // TODO(378626065): Verify logged failure via statsd. + } + + @Test + public void testDownloader_contentDownloadFail_failureThresholdNotMet_doesNotLog() + throws Exception { + long contentId = mCertificateTransparencyDownloader.startContentDownload(); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0); + setFailedDownload( + contentId, + // Failure cases where we give up on the download. + DownloadManager.ERROR_INSUFFICIENT_SPACE, + DownloadManager.ERROR_HTTP_DATA_ERROR); + Intent downloadCompleteIntent = makeDownloadCompleteIntent(contentId); + + mCertificateTransparencyDownloader.onReceive(mContext, downloadCompleteIntent); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(1); + // TODO(378626065): Verify no failure logged via statsd. + } + @Test public void testDownloader_contentDownloadSuccess_installFail_doNotUpdateDataStore() throws Exception { @@ -274,6 +401,59 @@ public class CertificateTransparencyDownloaderTest { assertNoVersionIsInstalled(); } + @Test + public void + testDownloader_contentDownloadSuccess_installFail_failureThresholdExceeded_logsFailure() + throws Exception { + File logListFile = makeLogListFile("456"); + File metadataFile = sign(logListFile); + mSignatureVerifier.setPublicKey(mPublicKey); + long metadataId = mCertificateTransparencyDownloader.startMetadataDownload(); + setSuccessfulDownload(metadataId, metadataFile); + long contentId = mCertificateTransparencyDownloader.startContentDownload(); + setSuccessfulDownload(contentId, logListFile); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, + Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1); + when(mCertificateTransparencyInstaller.install( + eq(Config.COMPATIBILITY_VERSION), any(), anyString())) + .thenReturn(false); + + mCertificateTransparencyDownloader.onReceive( + mContext, makeDownloadCompleteIntent(contentId)); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD); + // TODO(378626065): Verify logged failure via statsd. + } + + @Test + public void + testDownloader_contentDownloadSuccess_installFail_failureThresholdNotMet_doesNotLog() + throws Exception { + File logListFile = makeLogListFile("456"); + File metadataFile = sign(logListFile); + mSignatureVerifier.setPublicKey(mPublicKey); + long metadataId = mCertificateTransparencyDownloader.startMetadataDownload(); + setSuccessfulDownload(metadataId, metadataFile); + long contentId = mCertificateTransparencyDownloader.startContentDownload(); + setSuccessfulDownload(contentId, logListFile); + // Set the failure count to just below the threshold + mDataStore.setPropertyLong(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0); + when(mCertificateTransparencyInstaller.install( + eq(Config.COMPATIBILITY_VERSION), any(), anyString())) + .thenReturn(false); + + mCertificateTransparencyDownloader.onReceive( + mContext, makeDownloadCompleteIntent(contentId)); + + assertThat(mDataStore.getPropertyLong( + Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0L)) + .isEqualTo(1); + // TODO(378626065): Verify no failure logged via statsd. + } + @Test public void testDownloader_contentDownloadSuccess_verificationFail_doNotInstall() throws Exception {