Add logging when signature is either not found or doesn't pass
verification Note: InvalidKeyExceptions may be thrown for other reasons (e.g. signature algorithm not matching), hence the addition of a new Exception to specify when the public key is missing. Flag: com.android.net.ct.flags.certificate_transparency_service Fixes: 384931263 Bug: 378626065 Test: atest NetworkSecurityUnitTests Change-Id: I67341b1e2d169cf634e45ca84eb24e3a1f6d887e
This commit is contained in:
parent
06a516c8ae
commit
558445cc9a
4 changed files with 209 additions and 9 deletions
|
|
@ -20,8 +20,10 @@ import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFIC
|
|||
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_SIGNATURE_NOT_FOUND;
|
||||
import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION;
|
||||
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_UNKNOWN;
|
||||
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;
|
||||
|
||||
|
|
@ -47,6 +49,7 @@ import org.json.JSONObject;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
/** Helper class to download certificate transparency log files. */
|
||||
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
|
|
@ -202,15 +205,42 @@ class CertificateTransparencyDownloader extends BroadcastReceiver {
|
|||
}
|
||||
|
||||
boolean success = false;
|
||||
boolean failureLogged = false;
|
||||
|
||||
try {
|
||||
success = mSignatureVerifier.verify(contentUri, metadataUri);
|
||||
} catch (MissingPublicKeyException e) {
|
||||
if (updateFailureCount()) {
|
||||
failureLogged = true;
|
||||
mLogger.logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
|
||||
mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0)
|
||||
);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
if (updateFailureCount()) {
|
||||
failureLogged = true;
|
||||
mLogger.logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
|
||||
mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0)
|
||||
);
|
||||
}
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
Log.e(TAG, "Could not verify new log list", e);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Log.w(TAG, "Log list did not pass verification");
|
||||
|
||||
// TODO(b/384931263): add logging for failed signature verification
|
||||
// Avoid logging failure twice
|
||||
if (!failureLogged && updateFailureCount()) {
|
||||
mLogger.logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
|
||||
mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An exception thrown when the public key is missing for CT signature verification.
|
||||
*/
|
||||
public class MissingPublicKeyException extends Exception {
|
||||
|
||||
public MissingPublicKeyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ import androidx.annotation.VisibleForTesting;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
|
|
@ -74,9 +73,10 @@ public class SignatureVerifier {
|
|||
mPublicKey = Optional.of(publicKey);
|
||||
}
|
||||
|
||||
boolean verify(Uri file, Uri signature) throws GeneralSecurityException, IOException {
|
||||
boolean verify(Uri file, Uri signature)
|
||||
throws GeneralSecurityException, IOException, MissingPublicKeyException {
|
||||
if (!mPublicKey.isPresent()) {
|
||||
throw new InvalidKeyException("Missing public key for signature verification");
|
||||
throw new MissingPublicKeyException("Missing public key for signature verification");
|
||||
}
|
||||
Signature verifier = Signature.getInstance("SHA256withRSA");
|
||||
verifier.initVerify(mPublicKey.get());
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
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_SIGNATURE_NOT_FOUND;
|
||||
import static com.android.server.net.ct.CertificateTransparencyStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION;
|
||||
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;
|
||||
|
|
@ -220,7 +222,7 @@ public class CertificateTransparencyDownloaderTest {
|
|||
public void testDownloader_publicKeyDownloadFail_failureThresholdNotMet_doesNotLog()
|
||||
throws Exception {
|
||||
long publicKeyId = mCertificateTransparencyDownloader.startPublicKeyDownload();
|
||||
// Set the failure count to just below the threshold
|
||||
// Set the failure count to well below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
|
||||
setFailedDownload(
|
||||
publicKeyId, // Failure cases where we give up on the download.
|
||||
|
|
@ -294,7 +296,7 @@ public class CertificateTransparencyDownloaderTest {
|
|||
public void testDownloader_metadataDownloadFail_failureThresholdNotMet_doesNotLog()
|
||||
throws Exception {
|
||||
long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
|
||||
// Set the failure count to just below the threshold
|
||||
// Set the failure count to well below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
|
||||
setFailedDownload(
|
||||
metadataId,
|
||||
|
|
@ -379,7 +381,7 @@ public class CertificateTransparencyDownloaderTest {
|
|||
public void testDownloader_contentDownloadFail_failureThresholdNotMet_doesNotLog()
|
||||
throws Exception {
|
||||
long contentId = mCertificateTransparencyDownloader.startContentDownload();
|
||||
// Set the failure count to just below the threshold
|
||||
// Set the failure count to well below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
|
||||
setFailedDownload(
|
||||
contentId,
|
||||
|
|
@ -417,6 +419,148 @@ public class CertificateTransparencyDownloaderTest {
|
|||
assertNoVersionIsInstalled();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testDownloader_contentDownloadSuccess_noSignatureFound_failureThresholdExceeded_logsSingleFailure()
|
||||
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.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
|
||||
|
||||
// Set the public key to be missing
|
||||
mSignatureVerifier.resetPublicKey();
|
||||
mCertificateTransparencyDownloader.onReceive(
|
||||
mContext, makeDownloadCompleteIntent(contentId));
|
||||
|
||||
assertThat(mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
|
||||
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
|
||||
verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
|
||||
);
|
||||
verify(mLogger, never()).logCTLogListUpdateFailedEvent(
|
||||
eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
|
||||
anyInt()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testDownloader_contentDownloadSuccess_wrongSignatureAlgo_failureThresholdExceeded_logsSingleFailure()
|
||||
throws Exception {
|
||||
// Arrange
|
||||
File logListFile = makeLogListFile("456");
|
||||
File metadataFile = sign(logListFile);
|
||||
|
||||
// Set the key to be deliberately wrong by using diff algorithm
|
||||
KeyPairGenerator instance = KeyPairGenerator.getInstance("EC");
|
||||
mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
|
||||
|
||||
long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
|
||||
setSuccessfulDownload(metadataId, metadataFile);
|
||||
long contentId = mCertificateTransparencyDownloader.startContentDownload();
|
||||
setSuccessfulDownload(contentId, logListFile);
|
||||
|
||||
// Set the failure count to just below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
|
||||
|
||||
// Act
|
||||
mCertificateTransparencyDownloader.onReceive(
|
||||
mContext, makeDownloadCompleteIntent(contentId));
|
||||
|
||||
// Assert
|
||||
assertThat(mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
|
||||
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
|
||||
verify(mLogger, never()).logCTLogListUpdateFailedEvent(
|
||||
eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
|
||||
anyInt()
|
||||
);
|
||||
verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testDownloader_contentDownloadSuccess_signatureNotVerified_failureThresholdExceeded_logsSingleFailure()
|
||||
throws Exception {
|
||||
// Arrange
|
||||
File logListFile = makeLogListFile("456");
|
||||
File metadataFile = sign(logListFile);
|
||||
|
||||
// Set the key to be deliberately wrong by using diff key pair
|
||||
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
|
||||
mSignatureVerifier.setPublicKey(instance.generateKeyPair().getPublic());
|
||||
|
||||
long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
|
||||
setSuccessfulDownload(metadataId, metadataFile);
|
||||
long contentId = mCertificateTransparencyDownloader.startContentDownload();
|
||||
setSuccessfulDownload(contentId, logListFile);
|
||||
|
||||
// Set the failure count to just below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
|
||||
|
||||
// Act
|
||||
mCertificateTransparencyDownloader.onReceive(
|
||||
mContext, makeDownloadCompleteIntent(contentId));
|
||||
|
||||
// Assert
|
||||
assertThat(mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
|
||||
.isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
|
||||
verify(mLogger, never()).logCTLogListUpdateFailedEvent(
|
||||
eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
|
||||
anyInt()
|
||||
);
|
||||
verify(mLogger, times(1)).logCTLogListUpdateFailedEvent(
|
||||
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
|
||||
Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testDownloader_contentDownloadSuccess_wrongSignature_failureThresholdNotMet_doesNotLog()
|
||||
throws Exception {
|
||||
File logListFile = makeLogListFile("456");
|
||||
File metadataFile = sign(logListFile);
|
||||
// Set the public key wrong, so signature verification fails
|
||||
mSignatureVerifier.setPublicKey(mPublicKey);
|
||||
long metadataId = mCertificateTransparencyDownloader.startMetadataDownload();
|
||||
setSuccessfulDownload(metadataId, metadataFile);
|
||||
long contentId = mCertificateTransparencyDownloader.startContentDownload();
|
||||
setSuccessfulDownload(contentId, logListFile);
|
||||
// Set the failure count to well below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
|
||||
|
||||
mCertificateTransparencyDownloader.onReceive(
|
||||
mContext, makeDownloadCompleteIntent(contentId));
|
||||
|
||||
assertThat(mDataStore.getPropertyInt(
|
||||
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
|
||||
.isEqualTo(1);
|
||||
verify(mLogger, never()).logCTLogListUpdateFailedEvent(
|
||||
eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND),
|
||||
anyInt()
|
||||
);
|
||||
verify(mLogger, never()).logCTLogListUpdateFailedEvent(
|
||||
eq(CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION),
|
||||
anyInt()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testDownloader_contentDownloadSuccess_installFail_failureThresholdExceeded_logsFailure()
|
||||
|
|
@ -458,7 +602,7 @@ public class CertificateTransparencyDownloaderTest {
|
|||
setSuccessfulDownload(metadataId, metadataFile);
|
||||
long contentId = mCertificateTransparencyDownloader.startContentDownload();
|
||||
setSuccessfulDownload(contentId, logListFile);
|
||||
// Set the failure count to just below the threshold
|
||||
// Set the failure count to well below the threshold
|
||||
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, 0);
|
||||
when(mCertificateTransparencyInstaller.install(
|
||||
eq(Config.COMPATIBILITY_VERSION), any(), anyString()))
|
||||
|
|
|
|||
Loading…
Reference in a new issue