Add support for Base64 encoded signature

To simplify debugging, we are planning to publish the log list signature
as a Base64 encoded string.

It might take a while for the changes to the signature publishing pipeline
to reach prod, and we do not know exactly when that will happen. To avoid
accidental crashes, I added a fallback path to the verifier code that
reverts to use the signature as raw (not Base64 encoded) bytes. I will
remove the fallback path once the signature publishing changes are
finalized.

Bug: 374719543
Test: atest NetworkSecurityUnitTests
Change-Id: Ia785b554b840940dc81b47ac9bc2121c0b410ab0
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 96488fc..67ef63f 100644
--- a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
+++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -62,10 +63,15 @@
     }
 
     void setPublicKey(String publicKey) throws GeneralSecurityException {
+        byte[] decodedPublicKey = null;
+        try {
+            decodedPublicKey = Base64.getDecoder().decode(publicKey);
+        } catch (IllegalArgumentException e) {
+            throw new GeneralSecurityException("Invalid public key base64 encoding", e);
+        }
         setPublicKey(
                 KeyFactory.getInstance("RSA")
-                        .generatePublic(
-                                new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))));
+                        .generatePublic(new X509EncodedKeySpec(decodedPublicKey)));
     }
 
     @VisibleForTesting
@@ -82,10 +88,21 @@
         verifier.initVerify(mPublicKey.get());
         ContentResolver contentResolver = mContext.getContentResolver();
 
+        boolean success = false;
         try (InputStream fileStream = contentResolver.openInputStream(file);
                 InputStream signatureStream = contentResolver.openInputStream(signature)) {
             verifier.update(fileStream.readAllBytes());
-            return verifier.verify(signatureStream.readAllBytes());
+
+            byte[] signatureBytes = signatureStream.readAllBytes();
+            try {
+                success = verifier.verify(Base64.getDecoder().decode(signatureBytes));
+            } 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);
+            }
         }
+        return success;
     }
 }
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 2f57fc9..78e7272 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
@@ -235,8 +235,8 @@
                                 Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
                 .isEqualTo(1);
         verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
-        verify(mLogger, never()).logCTLogListUpdateFailedEventWithDownloadStatus(
-                anyInt(), anyInt());
+        verify(mLogger, never())
+                .logCTLogListUpdateFailedEventWithDownloadStatus(anyInt(), anyInt());
     }
 
     @Test
@@ -309,8 +309,8 @@
                                 Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
                 .isEqualTo(1);
         verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
-        verify(mLogger, never()).logCTLogListUpdateFailedEventWithDownloadStatus(
-                anyInt(), anyInt());
+        verify(mLogger, never())
+                .logCTLogListUpdateFailedEventWithDownloadStatus(anyInt(), anyInt());
     }
 
     @Test
@@ -387,8 +387,8 @@
                                 Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
                 .isEqualTo(1);
         verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
-        verify(mLogger, never()).logCTLogListUpdateFailedEventWithDownloadStatus(
-                anyInt(), anyInt());
+        verify(mLogger, never())
+                .logCTLogListUpdateFailedEventWithDownloadStatus(anyInt(), anyInt());
     }
 
     @Test
@@ -606,8 +606,8 @@
                                 Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
                 .isEqualTo(1);
         verify(mLogger, never()).logCTLogListUpdateFailedEvent(anyInt(), anyInt());
-        verify(mLogger, never()).logCTLogListUpdateFailedEventWithDownloadStatus(
-                anyInt(), anyInt());
+        verify(mLogger, never())
+                .logCTLogListUpdateFailedEventWithDownloadStatus(anyInt(), anyInt());
     }
 
     @Test
@@ -793,7 +793,7 @@
         try (InputStream fileStream = new FileInputStream(file);
                 OutputStream outputStream = new FileOutputStream(signatureFile)) {
             signer.update(fileStream.readAllBytes());
-            outputStream.write(signer.sign());
+            outputStream.write(Base64.getEncoder().encode(signer.sign()));
         }
 
         return signatureFile;