Add a consecutive failure count to the CT DataStore

We plan on only logging failure metrics after 10
consecutive failures, so we need some way of storing it.

Fixes: 384473786
Test: atest NetworkSecurityUnitTests
Change-Id: Ib316309864c536893d08be2dc59b8a70965ebde6
This commit is contained in:
Bessie Jiang 2024-12-16 18:27:45 +00:00
commit 6ae4c31703
3 changed files with 219 additions and 4 deletions

View file

@ -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) {

View file

@ -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;
}

View file

@ -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 {