Merge "Add logging of the log list signature" into main
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 45871de..a4eff0f 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -34,7 +34,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.List;
@@ -226,40 +225,24 @@
return;
}
- boolean success = false;
- CTLogListUpdateState failureReason = CTLogListUpdateState.UNKNOWN_STATE;
+ LogListUpdateStatus updateStatus = mSignatureVerifier.verify(contentUri, metadataUri);
+ // TODO(b/391327942): parse file and log the timestamp of the log list
- try {
- success = mSignatureVerifier.verify(contentUri, metadataUri);
- } catch (MissingPublicKeyException e) {
+ if (!updateStatus.isSignatureVerified()) {
updateFailureCount();
- failureReason = CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
- Log.e(TAG, "No public key found for log list verification", e);
- } catch (InvalidKeyException e) {
- updateFailureCount();
- failureReason = CTLogListUpdateState.SIGNATURE_INVALID;
- Log.e(TAG, "Signature invalid for log list verification", e);
- } 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");
- // Avoid logging failure twice
- if (failureReason == CTLogListUpdateState.UNKNOWN_STATE) {
- updateFailureCount();
- failureReason = CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
- }
-
mLogger.logCTLogListUpdateStateChangedEvent(
- failureReason,
+ updateStatus.state(),
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0),
+ updateStatus.signature());
return;
}
+ boolean success = false;
+
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
success = compatVersion.install(inputStream);
} catch (IOException e) {
@@ -276,7 +259,8 @@
mLogger.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.VERSION_ALREADY_EXISTS,
mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0),
+ updateStatus.signature());
}
}
@@ -288,6 +272,7 @@
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
+ // Unable to log the signature, as that is dependent on successful downloads
if (status.isHttpError()) {
mLogger.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.HTTP_ERROR,
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
index 8d53983..0b415f0 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
@@ -33,8 +33,10 @@
*
* @param failureReason reason why the log list wasn't updated
* @param failureCount number of consecutive log list update failures
+ * @param logListSignature signature used during log list verification
*/
- void logCTLogListUpdateStateChangedEvent(CTLogListUpdateState failureReason, int failureCount);
+ void logCTLogListUpdateStateChangedEvent(
+ CTLogListUpdateState failureReason, int failureCount, String logListSignature);
/**
* Logs a CTLogListUpdateStateChanged event to statsd with an HTTP error status code.
@@ -44,7 +46,9 @@
* @param httpErrorStatusCode if relevant, the HTTP error status code from DownloadManager
*/
void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount, int httpErrorStatusCode);
+ CTLogListUpdateState failureReason,
+ int failureCount,
+ int httpErrorStatusCode);
/**
* Intermediate enum for use with CertificateTransparencyStatsLog.
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
index 6accdf8..4a0689a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
@@ -41,33 +41,40 @@
logCTLogListUpdateStateChangedEvent(
downloadStatusToFailureReason(downloadStatus),
failureCount,
- /* httpErrorStatusCode= */ 0);
+ /* httpErrorStatusCode= */ 0,
+ /* signature= */ "");
}
@Override
public void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount) {
+ CTLogListUpdateState failureReason, int failureCount, String signature) {
logCTLogListUpdateStateChangedEvent(
localEnumToStatsLogEnum(failureReason),
failureCount,
- /* httpErrorStatusCode= */ 0);
+ /* httpErrorStatusCode= */ 0,
+ signature);
}
@Override
public void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount, int httpErrorStatusCode) {
+ CTLogListUpdateState failureReason,
+ int failureCount,
+ int httpErrorStatusCode) {
logCTLogListUpdateStateChangedEvent(
- localEnumToStatsLogEnum(failureReason), failureCount, httpErrorStatusCode);
+ localEnumToStatsLogEnum(failureReason),
+ failureCount,
+ httpErrorStatusCode,
+ /* signature= */ "");
}
private void logCTLogListUpdateStateChangedEvent(
- int failureReason, int failureCount, int httpErrorStatusCode) {
+ int failureReason, int failureCount, int httpErrorStatusCode, String signature) {
CertificateTransparencyStatsLog.write(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED,
failureReason,
failureCount,
httpErrorStatusCode,
- /* signature= */ "",
+ signature,
/* logListTimestampMs= */ 0);
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java
new file mode 100644
index 0000000..0c75120
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java
@@ -0,0 +1,68 @@
+/*
+ * 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.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
+
+import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState;
+
+import com.google.auto.value.AutoValue;
+
+/** Class to represent the signature verification status for Certificate Transparency. */
+@AutoValue
+public abstract class LogListUpdateStatus {
+
+ abstract CTLogListUpdateState state();
+
+ abstract String signature();
+
+ abstract long logListTimestamp();
+
+ boolean isSignatureVerified() {
+ // Check that none of the signature verification failures have been set as the state
+ return state() != PUBLIC_KEY_NOT_FOUND
+ && state() != SIGNATURE_INVALID
+ && state() != SIGNATURE_NOT_FOUND
+ && state() != SIGNATURE_VERIFICATION_FAILED;
+ }
+
+ boolean hasSignature() {
+ return signature() != null && signature().length() > 0;
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setState(CTLogListUpdateState updateState);
+
+ abstract Builder setSignature(String signature);
+
+ abstract Builder setLogListTimestamp(long timestamp);
+
+ abstract LogListUpdateStatus build();
+ }
+
+ abstract LogListUpdateStatus.Builder toBuilder();
+
+ static Builder builder() {
+ return new AutoValue_LogListUpdateStatus.Builder()
+ .setState(CTLogListUpdateState.UNKNOWN_STATE)
+ .setSignature("")
+ .setLogListTimestamp(0L);
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java b/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java
deleted file mode 100644
index 80607f6..0000000
--- a/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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);
- }
-}
diff --git a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
index 67ef63f..3ba56db 100644
--- a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
+++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
@@ -15,6 +15,11 @@
*/
package com.android.server.net.ct;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
+
import android.annotation.NonNull;
import android.annotation.RequiresApi;
import android.content.ContentResolver;
@@ -27,7 +32,9 @@
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
@@ -40,6 +47,7 @@
public class SignatureVerifier {
private final Context mContext;
+ private static final String TAG = "SignatureVerifier";
@NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
@@ -79,30 +87,54 @@
mPublicKey = Optional.of(publicKey);
}
- boolean verify(Uri file, Uri signature)
- throws GeneralSecurityException, IOException, MissingPublicKeyException {
+ LogListUpdateStatus verify(Uri file, Uri signature) {
+ LogListUpdateStatus.Builder statusBuilder = LogListUpdateStatus.builder();
+
if (!mPublicKey.isPresent()) {
- throw new MissingPublicKeyException("Missing public key for signature verification");
+ statusBuilder.setState(PUBLIC_KEY_NOT_FOUND);
+ Log.e(TAG, "No public key found for log list verification");
+ return statusBuilder.build();
}
- Signature verifier = Signature.getInstance("SHA256withRSA");
- verifier.initVerify(mPublicKey.get());
+
ContentResolver contentResolver = mContext.getContentResolver();
- boolean success = false;
try (InputStream fileStream = contentResolver.openInputStream(file);
InputStream signatureStream = contentResolver.openInputStream(signature)) {
+ Signature verifier = Signature.getInstance("SHA256withRSA");
+ verifier.initVerify(mPublicKey.get());
verifier.update(fileStream.readAllBytes());
byte[] signatureBytes = signatureStream.readAllBytes();
try {
- success = verifier.verify(Base64.getDecoder().decode(signatureBytes));
+ byte[] decodedSigBytes = Base64.getDecoder().decode(signatureBytes);
+ statusBuilder.setSignature(new String(decodedSigBytes, StandardCharsets.UTF_8));
+
+ if (!verifier.verify(decodedSigBytes)) {
+ // Leave the UpdateState as UNKNOWN_STATE if successful as there are other
+ // potential failures past the signature verification step
+ statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
+ }
} catch (IllegalArgumentException e) {
- Log.w("CertificateTransparencyDownloader", "Invalid signature base64 encoding", e);
- // TODO: remove the fallback once the signature base64 is published
- Log.i("CertificateTransparencyDownloader", "Signature verification as raw bytes");
- success = verifier.verify(signatureBytes);
+ Log.w(TAG, "Invalid signature base64 encoding", e);
+ statusBuilder.setSignature(new String(signatureBytes, StandardCharsets.UTF_8));
+ statusBuilder.setState(SIGNATURE_INVALID);
+ return statusBuilder.build();
}
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Signature invalid for log list verification", e);
+ statusBuilder.setState(SIGNATURE_INVALID);
+ return statusBuilder.build();
+ } catch (IOException | GeneralSecurityException e) {
+ Log.e(TAG, "Could not verify new log list", e);
+ statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
+ return statusBuilder.build();
}
- return success;
+
+ // Double check if the signature is empty that we set the state correctly
+ if (!statusBuilder.build().hasSignature()) {
+ statusBuilder.setState(SIGNATURE_NOT_FOUND);
+ }
+
+ return statusBuilder.build();
}
}
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 ec4d6be..be3273f 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
@@ -16,10 +16,12 @@
package com.android.server.net.ct;
+import static com.google.common.io.Files.toByteArray;
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;
@@ -57,6 +59,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -335,7 +338,7 @@
@Test
public void
- testDownloader_contentDownloadSuccess_noSignatureFound_logsSingleFailure()
+ testDownloader_contentDownloadSuccess_noPublicKeyFound_logsSingleFailure()
throws Exception {
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
@@ -356,19 +359,23 @@
verify(mLogger, times(1))
.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND,
- /* failureCount= */ 1);
+ /* failureCount= */ 1,
+ "");
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_INVALID),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED),
- anyInt());
+ anyInt(),
+ anyString());
}
@Test
@@ -398,19 +405,23 @@
verify(mLogger, times(1))
.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.SIGNATURE_INVALID,
- /* failureCount= */ 1);
+ /* failureCount= */ 1,
+ ""); // Should be empty b/c invalid key exception thrown
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
+ anyInt(),
+ anyString());
}
@Test
@@ -440,19 +451,24 @@
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.SIGNATURE_INVALID),
- anyInt());
+ anyInt(),
+ anyString());
verify(mLogger, never())
.logCTLogListUpdateStateChangedEvent(
eq(CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND),
- anyInt());
+ anyInt(),
+ anyString());
+ byte[] signatureBytes = Base64.getDecoder().decode(toByteArray(metadataFile));
verify(mLogger, times(1))
.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED,
- /* failureCount= */ 1);
+ /* failureCount= */ 1,
+ new String(signatureBytes, StandardCharsets.UTF_8));
}
@Test
@@ -473,10 +489,12 @@
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
.isEqualTo(1);
+ byte[] signatureBytes = Base64.getDecoder().decode(toByteArray(metadataFile));
verify(mLogger, times(1))
.logCTLogListUpdateStateChangedEvent(
CTLogListUpdateState.VERSION_ALREADY_EXISTS,
- /* failureCount= */ 1);
+ /* failureCount= */ 1,
+ new String(signatureBytes, StandardCharsets.UTF_8));
}
@Test