aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVikram Gaur <vikramgaur@google.com>2023-02-02 01:08:17 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-02-02 01:08:17 +0000
commitcf70492e95158c27da030143eeeb9a923f504394 (patch)
treef5a0010f5a0b52c3d01fe95aced95145cd3fc9a7
parent959072d698de0c448b0e413459f0ece9d0d6c580 (diff)
parent1f9c2b1497a72d2b95796b39cb0d151f82d838be (diff)
downloadRemoteProvisioner-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>
-rw-r--r--src/com/android/remoteprovisioner/Provisioner.java9
-rw-r--r--src/com/android/remoteprovisioner/X509Utils.java64
-rw-r--r--tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java6
-rw-r--r--tests/unittests/src/com/android/remoteprovisioner/unittest/X509UtilsTest.java81
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);
}
}