diff options
| author | 2018-02-28 15:14:49 +0000 | |
|---|---|---|
| committer | 2018-02-28 15:14:49 +0000 | |
| commit | 3e58039f874e4a20ccc164e6351c04c83b3cf689 (patch) | |
| tree | 2645b1141490b3e0586fb4751d3dd540c1c5e7a3 | |
| parent | 9371be3b88e2bcd6da6223b4cf59a00d32147133 (diff) | |
| parent | 7ce4ea52b356c2c7e1e65f5d484b3b641d06343e (diff) | |
Merge "Check the given CertPath against the root of trust during recovery"
4 files changed, 109 insertions, 7 deletions
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index da0b0d03b54d..1e0703a3994b 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -43,6 +43,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; +import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; @@ -446,12 +447,14 @@ public class RecoverableKeyStoreManager { "Failed decode the certificate path"); } - // TODO: Validate the cert path according to the root of trust - - if (certPath.getCertificates().isEmpty()) { - throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, - "The given CertPath is empty"); + try { + CertUtils.validateCertPath(TrustedRootCert.TRUSTED_ROOT_CERT, certPath); + } catch (CertValidationException e) { + Log.e(TAG, "Failed to validate the given cert path", e); + // TODO: Change this to ERROR_INVALID_CERTIFICATE once ag/3666620 is submitted + throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); } + byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded(); if (verifierPublicKey == null) { Log.e(TAG, "Failed to encode verifierPublicKey"); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java index 09ec5ad1d5dd..6e08949b634e 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java @@ -40,6 +40,7 @@ import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.CertStore; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; @@ -292,6 +293,42 @@ public final class CertUtils { return certPath; } + /** + * Validates a given {@code CertPath} against the trusted root certificate. + * + * @param trustedRoot the trusted root certificate + * @param certPath the certificate path to be validated + * @throws CertValidationException if the given certificate path is invalid, e.g., is expired, + * or does not have a valid signature + */ + public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath) + throws CertValidationException { + validateCertPath(/*validationDate=*/ null, trustedRoot, certPath); + } + + /** + * Validates a given {@code CertPath} against a given {@code validationDate}. If the given + * validation date is null, the current date will be used. + */ + @VisibleForTesting + static void validateCertPath(@Nullable Date validationDate, X509Certificate trustedRoot, + CertPath certPath) throws CertValidationException { + if (certPath.getCertificates().isEmpty()) { + throw new CertValidationException("The given certificate path is empty"); + } + if (!(certPath.getCertificates().get(0) instanceof X509Certificate)) { + throw new CertValidationException( + "The given certificate path does not contain X509 certificates"); + } + + List<X509Certificate> certificates = (List<X509Certificate>) certPath.getCertificates(); + X509Certificate leafCert = certificates.get(0); + List<X509Certificate> intermediateCerts = + certificates.subList(/*fromIndex=*/ 1, certificates.size()); + + validateCert(validationDate, trustedRoot, intermediateCerts, leafCert); + } + @VisibleForTesting static CertPath buildCertPath(PKIXParameters pkixParams) throws CertValidationException { CertPathBuilder certPathBuilder; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 199410c42b0e..e6a36c6776da 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -277,7 +277,7 @@ public class RecoverableKeyStoreManagerTest { } @Test - public void initRecoveryService_succeeds() throws Exception { + public void initRecoveryService_succeedsWithCertFile() throws Exception { int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); long certSerial = 1000L; @@ -566,7 +566,31 @@ public class RecoverableKeyStoreManagerTest { TEST_SECRET))); fail("should have thrown"); } catch (ServiceSpecificException e) { - assertThat(e.getMessage()).contains("CertPath is empty"); + assertThat(e.getMessage()).contains("empty"); + } + } + + @Test + public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + CertPath shortCertPath = certFactory.generateCertPath( + TestData.CERT_PATH_1.getCertificates() + .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1)); + try { + mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( + TEST_SESSION_ID, + RecoveryCertPath.createRecoveryCertPath(shortCertPath), + TEST_VAULT_PARAMS, + TEST_VAULT_CHALLENGE, + ImmutableList.of( + new KeyChainProtectionParams( + TYPE_LOCKSCREEN, + UI_FORMAT_PASSWORD, + KeyDerivationParams.createSha256Params(TEST_SALT), + TEST_SECRET))); + fail("should have thrown"); + } catch (ServiceSpecificException e) { + // expected } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java index d08dab4752d5..9279698c6004 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertUtilsTest.java @@ -30,8 +30,10 @@ import java.io.InputStream; import java.security.KeyPairGenerator; import java.security.PublicKey; import java.security.cert.CertPath; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.ECGenParameterSpec; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; @@ -317,6 +319,42 @@ public final class CertUtilsTest { } @Test + public void validateCertPath_succeeds() throws Exception { + X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; + List<X509Certificate> intermediateCerts = + Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2); + X509Certificate leafCert = TestData.LEAF_CERT_2; + CertPath certPath = + CertUtils.buildCertPath( + CertUtils.buildPkixParams( + TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts, + leafCert)); + CertUtils.validateCertPath( + TestData.DATE_ALL_CERTS_VALID, TestData.ROOT_CA_TRUSTED, certPath); + } + + @Test + public void validateCertPath_throwsIfEmptyCertPath() throws Exception { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>()); + CertValidationException expected = + expectThrows( + CertValidationException.class, + () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID, + TestData.ROOT_CA_TRUSTED, emptyCertPath)); + assertThat(expected.getMessage()).contains("empty"); + } + + @Test + public void validateCertPath_throwsIfNotValidated() throws Exception { + assertThrows( + CertValidationException.class, + () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID, + TestData.ROOT_CA_DIFFERENT_COMMON_NAME, + com.android.server.locksettings.recoverablekeystore.TestData.CERT_PATH_1)); + } + + @Test public void validateCert_succeeds() throws Exception { X509Certificate rootCert = TestData.ROOT_CA_TRUSTED; List<X509Certificate> intermediateCerts = |