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
This commit is contained in:
Sandro Montanari 2025-01-21 17:37:35 +00:00
commit ed61bfc39d
2 changed files with 29 additions and 12 deletions

View file

@ -21,6 +21,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@ -62,10 +63,15 @@ public class SignatureVerifier {
}
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 @@ public class SignatureVerifier {
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;
}
}

View file

@ -235,8 +235,8 @@ public class CertificateTransparencyDownloaderTest {
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 @@ public class CertificateTransparencyDownloaderTest {
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 @@ public class CertificateTransparencyDownloaderTest {
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 @@ public class CertificateTransparencyDownloaderTest {
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 @@ public class CertificateTransparencyDownloaderTest {
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;