diff options
author | Vikram Gaur <vikramgaur@google.com> | 2023-02-02 01:08:17 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-02-02 01:08:17 +0000 |
commit | cf70492e95158c27da030143eeeb9a923f504394 (patch) | |
tree | f5a0010f5a0b52c3d01fe95aced95145cd3fc9a7 | |
parent | 959072d698de0c448b0e413459f0ece9d0d6c580 (diff) | |
parent | 1f9c2b1497a72d2b95796b39cb0d151f82d838be (diff) | |
download | RemoteProvisioner-cf70492e95158c27da030143eeeb9a923f504394.tar.gz |
Verify that certificates are valid before storing in database. am: 1f9c2b1497
Original change: https://android-review.googlesource.com/c/platform/packages/apps/RemoteProvisioner/+/2410936
Change-Id: I441bafd3c22cfeec47e6fb2a660b6b131ce4b17c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
4 files changed, 150 insertions, 10 deletions
diff --git a/src/com/android/remoteprovisioner/Provisioner.java b/src/com/android/remoteprovisioner/Provisioner.java index 77505da..b24124f 100644 --- a/src/com/android/remoteprovisioner/Provisioner.java +++ b/src/com/android/remoteprovisioner/Provisioner.java @@ -22,8 +22,12 @@ import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.ProtectedData; import android.security.IGenerateRkpKeyService; import android.security.remoteprovisioning.IRemoteProvisioning; +import android.util.Base64; import android.util.Log; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -131,7 +135,10 @@ public class Provisioner { X509Certificate cert; try { cert = X509Utils.formatX509Certs(certChain)[0]; - } catch (CertificateException e) { + } catch (CertificateException | NoSuchAlgorithmException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { + Log.e(TAG, "Unable to parse certificate chain: " + + Base64.encodeToString(certChain, Base64.DEFAULT), e); throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR, "Failed to interpret DER encoded certificate chain", e); } diff --git a/src/com/android/remoteprovisioner/X509Utils.java b/src/com/android/remoteprovisioner/X509Utils.java index d33d573..f4a0868 100644 --- a/src/com/android/remoteprovisioner/X509Utils.java +++ b/src/com/android/remoteprovisioner/X509Utils.java @@ -20,13 +20,24 @@ import android.util.Log; import java.io.ByteArrayInputStream; import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; /** * Provides convenience methods for parsing certificates and extracting information. @@ -40,11 +51,17 @@ public class X509Utils { * contained within as an X509Certificate array. */ public static X509Certificate[] formatX509Certs(byte[] certStream) - throws CertificateException { + throws CertificateException, InvalidAlgorithmParameterException, + NoSuchAlgorithmException, NoSuchProviderException { CertificateFactory fact = CertificateFactory.getInstance("X.509"); ByteArrayInputStream in = new ByteArrayInputStream(certStream); ArrayList<Certificate> certs = new ArrayList<Certificate>(fact.generateCertificates(in)); - return certs.toArray(new X509Certificate[certs.size()]); + X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); + if (isCertChainValid(certChain)) { + return certChain; + } else { + throw new CertificateException("Could not validate certificate chain."); + } } /** @@ -88,4 +105,47 @@ public class X509Utils { } return formattedKey; } + + /** + * Validates the X509 certificate chain and returns appropriate boolean result. + */ + public static boolean isCertChainValid(X509Certificate[] certChain) + throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidAlgorithmParameterException { + X509Certificate rootCert = certChain[certChain.length - 1]; + return isSelfSignedCertificate(rootCert) && verifyCertChain(rootCert, certChain); + } + + private static boolean verifyCertChain(X509Certificate rootCert, X509Certificate[] certChain) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + try { + // Only add the self-signed root certificate as trust anchor. + // All the other certificates in the chain should be signed by the previous cert's key. + Set<TrustAnchor> trustedAnchors = Set.of(new TrustAnchor(rootCert, null)); + + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + CertPathValidator validator = CertPathValidator.getInstance("PKIX"); + PKIXParameters parameters = new PKIXParameters(trustedAnchors); + parameters.setRevocationEnabled(false); + validator.validate(fact.generateCertPath(Arrays.asList(certChain)), parameters); + return true; + } catch (CertificateException | CertPathValidatorException e) { + Log.e(TAG, "Certificate chain validation failed.", e); + return false; + } + } + + /** + * Verifies whether an X509Certificate is a self-signed certificate. + */ + public static boolean isSelfSignedCertificate(X509Certificate certificate) + throws NoSuchAlgorithmException, NoSuchProviderException { + try { + certificate.verify(certificate.getPublicKey()); + return true; + } catch (SignatureException | InvalidKeyException | CertificateException e) { + Log.e(TAG, "Error verifying self signed certificate.", e); + return false; + } + } } diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java index c5be182..f9f5fa7 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java @@ -63,6 +63,7 @@ import com.android.remoteprovisioner.ProvisionerMetrics; import com.android.remoteprovisioner.RemoteProvisioningException; import com.android.remoteprovisioner.ServerInterface; import com.android.remoteprovisioner.SettingsManager; +import com.android.remoteprovisioner.X509Utils; import org.junit.After; import org.junit.Assert; @@ -82,6 +83,7 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.ProviderException; import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Arrays; import java.util.concurrent.Executors; @@ -188,6 +190,10 @@ public class ServerToSystemTest { keyPairGenerator.initialize(spec); keyPairGenerator.generateKeyPair(); Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias()); + X509Certificate[] x509Certificates = Arrays.stream(certs) + .map(x -> (X509Certificate) x) + .toArray(X509Certificate[]::new); + assertThat(X509Utils.isCertChainValid(x509Certificates)).isTrue(); keyStore.deleteEntry(alias); return certs; } diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/X509UtilsTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/X509UtilsTest.java index 23ba642..edca70c 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/X509UtilsTest.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/X509UtilsTest.java @@ -20,12 +20,12 @@ import static com.android.remoteprovisioner.unittest.Utils.generateEcdsaKeyPair; import static com.android.remoteprovisioner.unittest.Utils.getP256PubKeyFromBytes; import static com.android.remoteprovisioner.unittest.Utils.signPublicKey; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; +import android.util.Base64; import androidx.test.runner.AndroidJUnit4; @@ -36,8 +36,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.security.KeyPair; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @RunWith(AndroidJUnit4.class) @@ -64,10 +68,10 @@ public class X509UtilsTest { os.write(certs[i].getEncoded()); } X509Certificate[] roundTrip = X509Utils.formatX509Certs(os.toByteArray()); - assertEquals(certs.length, roundTrip.length); + assertThat(certs.length).isEqualTo(roundTrip.length); for (int i = 0; i < certs.length; i++) { - assertArrayEquals("Failed on index " + i, - certs[i].getEncoded(), roundTrip[i].getEncoded()); + assertWithMessage("Failed on index " + i) + .that(certs[i].getEncoded()).isEqualTo(roundTrip[i].getEncoded()); } } @@ -81,6 +85,69 @@ public class X509UtilsTest { byte[] yPoint = new byte[32]; System.arraycopy(formattedKey, 0 /* offset */, xPoint, 0 /* offset */, 32 /* length */); System.arraycopy(formattedKey, 32 /* offset */, yPoint, 0 /* offset */, 32 /* length */); - assertTrue(testKey.getPublic().equals(getP256PubKeyFromBytes(xPoint, yPoint))); + assertThat(testKey.getPublic()).isEqualTo(getP256PubKeyFromBytes(xPoint, yPoint)); + } + + @Test + public void testCertificateChains() throws Exception { + String encodedTestCert = "MIIBvTCCAWOgAwIBAgIRAKrDc87UaGSeFTRzF4vz0IcwCgYIKoZIzj0EAwIwIDEN" + + "MAsGA1UECgwERmFrZTEPMA0GA1UEAwwGSXNzdWVyMCAXDTIzMDIwMTE1MzExMVoYDzIxMjMwMTA4MTU" + + "zMTExWjA5MQwwCgYDVQQKDANURUUxKTAnBgNVBAMMIGFhYzM3M2NlZDQ2ODY0OWUxNTM0NzMxNzhiZj" + + "NkMDg3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcC8SjTKkEqpPGQMXiZMC1/Dk3Fo/PsCZBI0E8" + + "N4zXhBHJJZdT4LnYUNQXhSndDhrPO/x0MSySnz+hDZiRlRdzKNjMGEwHQYDVR0OBBYEFMcyyg91rTsG" + + "QxM2hY2dfrmcYNIoMB8GA1UdIwQYMBaAFN2wvxbmHbqJicPAK1Ce+692JkfcMA8GA1UdEwEB/wQFMAM" + + "BAf8wDgYDVR0PAQH/BAQDAgIEMAoGCCqGSM49BAMCA0gAMEUCIQD/ZJAabKvYlyuL6Ehc7bZMZFn9e7" + + "Gu8f+QTA2fPjN/EQIgUeJPlHjNhoiu0QPpAoRbd4idOLyf5pqNEiXt7n8VDe0="; + String encodedRootCert = "MIIBpDCCAUmgAwIBAgIQf7TE7zQ0iDLyiZIIpqKCvjAKBggqhkjOPQQDAjAgMQ0w" + + "CwYDVQQKDARGYWtlMQ8wDQYDVQQDDAZJc3N1ZXIwIBcNMjMwMjAxMTUxMDM0WhgPMjEyMzAxMDgxNTE" + + "wMzRaMCAxDTALBgNVBAoMBEZha2UxDzANBgNVBAMMBklzc3VlcjBZMBMGByqGSM49AgEGCCqGSM49Aw" + + "EHA0IABNh7P0mPpgFdSw9pC+aDMDRWnZa6g7H+jdy/a4V+erKJ+lDqdsV4Ao+2+vt2WelEP0DIZl51U" + + "CaS8CKqZtRGLB6jYzBhMB0GA1UdDgQWBBTdsL8W5h26iYnDwCtQnvuvdiZH3DAfBgNVHSMEGDAWgBTd" + + "sL8W5h26iYnDwCtQnvuvdiZH3DAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAKBggqhkj" + + "OPQQDAgNJADBGAiEAm9Y2YGYe/2RqI6xMGq2IFJzeJ0qjfQzBLg6KjRLiJ10CIQCxpJCHRN4Gj17/ON" + + "JGL2npbIsQVpSn1M5xPsY+9/qB1g=="; + + X509Certificate rootCert = generateCertificateFromEncodedBytes(encodedRootCert); + X509Certificate testCert = generateCertificateFromEncodedBytes(encodedTestCert); + X509Certificate[] validCertChain = new X509Certificate[]{testCert, rootCert}; + X509Certificate[] invalidCertChain = new X509Certificate[]{rootCert, testCert}; + + assertThat(X509Utils.isCertChainValid(validCertChain)).isTrue(); + assertThat(X509Utils.isCertChainValid(invalidCertChain)).isFalse(); + } + + @Test + public void testCertChainSwapOAndCN() throws Exception { + String encodedTestCert = "MIIBvTCCAWOgAwIBAgIRAKrDc87UaGSeFTRzF4vz0IcwCgYIKoZIzj0EAwIwIDEP" + + "MA0GA1UEAwwGSXNzdWVyMQ0wCwYDVQQKDARGYWtlMCAXDTIzMDIwMTE1MzExMVoYDzIxMjMwMTA4MTU" + + "zMTExWjA5MQwwCgYDVQQKDANURUUxKTAnBgNVBAMMIGFhYzM3M2NlZDQ2ODY0OWUxNTM0NzMxNzhiZj" + + "NkMDg3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcC8SjTKkEqpPGQMXiZMC1/Dk3Fo/PsCZBI0E8" + + "N4zXhBHJJZdT4LnYUNQXhSndDhrPO/x0MSySnz+hDZiRlRdzKNjMGEwHQYDVR0OBBYEFMcyyg91rTsG" + + "QxM2hY2dfrmcYNIoMB8GA1UdIwQYMBaAFN2wvxbmHbqJicPAK1Ce+692JkfcMA8GA1UdEwEB/wQFMAM" + + "BAf8wDgYDVR0PAQH/BAQDAgIEMAoGCCqGSM49BAMCA0gAMEUCIQD/ZJAabKvYlyuL6Ehc7bZMZFn9e7" + + "Gu8f+QTA2fPjN/EQIgUeJPlHjNhoiu0QPpAoRbd4idOLyf5pqNEiXt7n8VDe0="; + String encodedRootCert = "MIIBpDCCAUmgAwIBAgIQf7TE7zQ0iDLyiZIIpqKCvjAKBggqhkjOPQQDAjAgMQ0w" + + "CwYDVQQKDARGYWtlMQ8wDQYDVQQDDAZJc3N1ZXIwIBcNMjMwMjAxMTUxMDM0WhgPMjEyMzAxMDgxNTE" + + "wMzRaMCAxDTALBgNVBAoMBEZha2UxDzANBgNVBAMMBklzc3VlcjBZMBMGByqGSM49AgEGCCqGSM49Aw" + + "EHA0IABNh7P0mPpgFdSw9pC+aDMDRWnZa6g7H+jdy/a4V+erKJ+lDqdsV4Ao+2+vt2WelEP0DIZl51U" + + "CaS8CKqZtRGLB6jYzBhMB0GA1UdDgQWBBTdsL8W5h26iYnDwCtQnvuvdiZH3DAfBgNVHSMEGDAWgBTd" + + "sL8W5h26iYnDwCtQnvuvdiZH3DAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAKBggqhkj" + + "OPQQDAgNJADBGAiEAm9Y2YGYe/2RqI6xMGq2IFJzeJ0qjfQzBLg6KjRLiJ10CIQCxpJCHRN4Gj17/ON" + + "JGL2npbIsQVpSn1M5xPsY+9/qB1g=="; + + X509Certificate rootCert = generateCertificateFromEncodedBytes(encodedRootCert); + X509Certificate testCert = generateCertificateFromEncodedBytes(encodedTestCert); + X509Certificate[] certChain = new X509Certificate[]{testCert, rootCert}; + + assertThat(X509Utils.isSelfSignedCertificate(rootCert)).isTrue(); + assertThat(X509Utils.isSelfSignedCertificate(testCert)).isFalse(); + assertThat(X509Utils.isCertChainValid(certChain)).isFalse(); + } + + private X509Certificate generateCertificateFromEncodedBytes(String encodedCert) + throws CertificateException { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + InputStream in = new ByteArrayInputStream(Base64.decode(encodedCert, Base64.DEFAULT)); + return (X509Certificate) certFactory.generateCertificate(in); } } |