Merge "Use the new root cert file under the core/ folder" into pi-dev
diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java
index 137dd89..b44c0e1 100644
--- a/core/java/android/security/keystore/recovery/RecoverySession.java
+++ b/core/java/android/security/keystore/recovery/RecoverySession.java
@@ -136,6 +136,63 @@
byte[] recoveryClaim =
mRecoveryController.getBinder().startRecoverySessionWithCertPath(
mSessionId,
+ /*rootCertificateAlias=*/ "", // Use the default root cert
+ recoveryCertPath,
+ vaultParams,
+ vaultChallenge,
+ secrets);
+ return recoveryClaim;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
+ || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
+ throw new CertificateException(e.getMessage());
+ }
+ throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Starts a recovery session and returns a blob with proof of recovery secret possession.
+ * The method generates a symmetric key for a session, which trusted remote device can use to
+ * return recovery key.
+ *
+ * @param rootCertificateAlias The alias of the root certificate that is already in the Android
+ * OS. The root certificate will be used for validating {@code verifierCertPath}.
+ * @param verifierCertPath The certificate path used to create the recovery blob on the source
+ * device. Keystore will verify the certificate path by using the root of trust.
+ * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
+ * Used to limit number of guesses.
+ * @param vaultChallenge Data passed from server for this recovery session and used to prevent
+ * replay attacks.
+ * @param secrets Secrets provided by user, the method only uses type and secret fields.
+ * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
+ * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
+ * key and parameters necessary to identify the counter with the number of failed recovery
+ * attempts.
+ * @throws CertificateException if the {@code verifierCertPath} is invalid.
+ * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+ * service.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ @NonNull public byte[] start(
+ @NonNull String rootCertificateAlias,
+ @NonNull CertPath verifierCertPath,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyChainProtectionParams> secrets)
+ throws CertificateException, InternalRecoveryServiceException {
+ // Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
+ RecoveryCertPath recoveryCertPath =
+ RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
+ try {
+ byte[] recoveryClaim =
+ mRecoveryController.getBinder().startRecoverySessionWithCertPath(
+ mSessionId,
+ rootCertificateAlias,
recoveryCertPath,
vaultParams,
vaultChallenge,
diff --git a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java
index 27c6522..4bdde8a 100644
--- a/core/java/android/security/keystore/recovery/TrustedRootCertificates.java
+++ b/core/java/android/security/keystore/recovery/TrustedRootCertificates.java
@@ -77,10 +77,27 @@
private static final int NUMBER_OF_ROOT_CERTIFICATES = 1;
+ private static final ArrayMap<String, X509Certificate> ALL_ROOT_CERTIFICATES =
+ constructRootCertificateMap();
+
/**
* Returns all available root certificates, keyed by alias.
*/
public static Map<String, X509Certificate> listRootCertificates() {
+ return new ArrayMap(ALL_ROOT_CERTIFICATES);
+ }
+
+ /**
+ * Gets a root certificate referenced by the given {@code alias}.
+ *
+ * @param alias the alias of the certificate
+ * @return the certificate referenced by the alias, or null if such a certificate doesn't exist.
+ */
+ public static X509Certificate getRootCertificate(String alias) {
+ return ALL_ROOT_CERTIFICATES.get(alias);
+ }
+
+ private static ArrayMap<String, X509Certificate> constructRootCertificateMap() {
ArrayMap<String, X509Certificate> certificates =
new ArrayMap<>(NUMBER_OF_ROOT_CERTIFICATES);
certificates.put(
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 25e1589..e572b0e 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -78,7 +78,7 @@
byte[] startRecoverySession(in String sessionId,
in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
in List<KeyChainProtectionParams> secrets);
- byte[] startRecoverySessionWithCertPath(in String sessionId,
+ byte[] startRecoverySessionWithCertPath(in String sessionId, in String rootCertificateAlias,
in RecoveryCertPath verifierCertPath, in byte[] vaultParams, in byte[] vaultChallenge,
in List<KeyChainProtectionParams> secrets);
Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 74ebf3e4..3bda585 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2051,11 +2051,13 @@
@Override
public byte[] startRecoverySessionWithCertPath(@NonNull String sessionId,
- @NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)
+ @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath,
+ @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyChainProtectionParams> secrets)
throws RemoteException {
return mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
- sessionId, verifierCertPath, vaultParams, vaultChallenge, secrets);
+ sessionId, rootCertificateAlias, verifierCertPath, vaultParams, vaultChallenge,
+ secrets);
}
public void closeSession(@NonNull String sessionId) throws RemoteException {
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 20f3403..06aa0ba 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -38,6 +38,7 @@
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.RecoveryController;
+import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.KeyStore;
import android.util.Log;
@@ -50,7 +51,6 @@
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
-import com.android.server.locksettings.recoverablekeystore.certificate.TrustedRootCert;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -64,6 +64,7 @@
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@@ -200,15 +201,19 @@
}
Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
+ // Randomly choose and validate an endpoint certificate from the list
CertPath certPath;
+ X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
try {
Log.d(TAG, "Getting and validating a random endpoint certificate");
- certPath = certXml.getRandomEndpointCert(TrustedRootCert.TRUSTED_ROOT_CERT);
+ certPath = certXml.getRandomEndpointCert(rootCert);
} catch (CertValidationException e) {
Log.e(TAG, "Invalid endpoint cert", e);
throw new ServiceSpecificException(
ERROR_INVALID_CERTIFICATE, "Failed to validate certificate.");
}
+
+ // Save the chosen and validated certificate into database
try {
Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
@@ -253,8 +258,9 @@
ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file.");
}
+ X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
try {
- sigXml.verifyFileSignature(TrustedRootCert.TRUSTED_ROOT_CERT, recoveryServiceCertFile);
+ sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
} catch (CertValidationException e) {
Log.d(TAG, "The signature over the cert file is invalid."
+ " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
@@ -479,6 +485,7 @@
*/
public @NonNull byte[] startRecoverySessionWithCertPath(
@NonNull String sessionId,
+ @NonNull String rootCertificateAlias,
@NonNull RecoveryCertPath verifierCertPath,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
@@ -495,11 +502,10 @@
}
try {
- CertUtils.validateCertPath(TrustedRootCert.TRUSTED_ROOT_CERT, certPath);
+ CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), 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());
+ throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
}
byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
@@ -837,6 +843,21 @@
}
}
+ private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
+ if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
+ // Use the default Google Key Vault Service CA certificate if the alias is not provided
+ rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
+ }
+
+ X509Certificate rootCertificate =
+ TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
+ if (rootCertificate == null) {
+ throw new ServiceSpecificException(
+ ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
+ }
+ return rootCertificate;
+ }
+
private void checkRecoverKeyStorePermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.RECOVER_KEYSTORE,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
deleted file mode 100644
index 7195d62..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore.certificate;
-
-import java.security.cert.X509Certificate;
-
-/**
- * Holds the X509 certificate of the trusted root CA cert for the recoverable key store service.
- *
- * TODO: Read the certificate from a PEM file directly and remove this class.
- */
-public final class TrustedRootCert {
-
- private static final String TRUSTED_ROOT_CERT_BASE64 = ""
- + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV"
- + "BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDIxOTM5MTRaFw0zODAx"
- + "MjgxOTM5MTRaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCAiIw"
- + "DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2OT5i40/H7LINg/lq/0G0hR65P"
- + "Q4Mud3OnuVt6UIYV2T18+v6qW1yJd5FcnND/ZKPau4aUAYklqJuSVjOXQD0BjgS2"
- + "98Xa4dSn8Ci1rUR+5tdmrxqbYUdT2ZvJIUMMR6fRoqi+LlAbKECrV+zYQTyLU68w"
- + "V66hQpAButjJKiZzkXjmKLfJ5IWrNEn17XM988rk6qAQn/BYCCQGf3rQuJeksGmA"
- + "N1lJOwNYxmWUyouVwqwZthNEWqTuEyBFMkAT+99PXW7oVDc7oU5cevuihxQWNTYq"
- + "viGB8cck6RW3cmqrDSaJF/E+N0cXFKyYC7FDcggt6k3UrxNKTuySdDEa8+2RTQqU"
- + "Y9npxBlQE+x9Ig56OI1BG3bSBsGdPgjpyHadZeh2tgk+oqlGsSsum24YxaxuSysT"
- + "Qfcu/XhyfUXavfmGrBOXerTzIl5oBh/F5aHTV85M2tYEG0qsPPvSpZAWtdJ/2rca"
- + "OxvhwOL+leZKr8McjXVR00lBsRuKXX4nTUMwya09CO3QHFPFZtZvqjy2HaMOnVLQ"
- + "I6b6dHEfmsHybzVOe3yPEoFQSU9UhUdmi71kwwoanPD3j9fJHmXTx4PzYYBRf1ZE"
- + "o+uPgMPk7CDKQFZLjnR40z1uzu3O8aZ3AKZzP+j7T4XQKJLQLmllKtPgLgNdJyib"
- + "2Glg7QhXH/jBTL6hAgMBAAGjYzBhMB0GA1UdDgQWBBSbZfrqOYH54EJpkdKMZjMc"
- + "z/Hp+DAfBgNVHSMEGDAWgBSbZfrqOYH54EJpkdKMZjMcz/Hp+DAPBgNVHRMBAf8E"
- + "BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0FAAOCAgEAKh9nm/vW"
- + "glMWp3vcCwWwJW286ecREDlI+CjGh5h+f2N4QRrXd/tKE3qQJWCqGx8sFfIUjmI7"
- + "KYdsC2gyQ2cA2zl0w7pB2QkuqE6zVbnh1D17Hwl19IMyAakFaM9ad4/EoH7oQmqX"
- + "nF/f5QXGZw4kf1HcgKgoCHWXjqR8MqHOcXR8n6WFqxjzJf1jxzi6Yo2dZ7PJbnE6"
- + "+kHIJuiCpiHL75v5g1HM41gT3ddFFSrn88ThNPWItT5Z8WpFjryVzank2Yt02LLl"
- + "WqZg9IC375QULc5B58NMnaiVJIDJQ8zoNgj1yaxqtUMnJX570lotO2OXe4ec9aCQ"
- + "DIJ84YLM/qStFdeZ9416E80dchskbDG04GuVJKlzWjxAQNMRFhyaPUSBTLLg+kwP"
- + "t9+AMmc+A7xjtFQLZ9fBYHOBsndJOmeSQeYeckl+z/1WQf7DdwXn/yijon7mxz4z"
- + "cCczfKwTJTwBh3wR5SQr2vQm7qaXM87qxF8PCAZrdZaw5I80QwkgTj0WTZ2/GdSw"
- + "d3o5SyzzBAjpwtG+4bO/BD9h9wlTsHpT6yWOZs4OYAKU5ykQrncI8OyavMggArh3"
- + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8"
- + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA=";
-
- /**
- * The X509 certificate of the trusted root CA cert for the recoverable key store service.
- *
- * TODO: Change it to the production certificate root CA before the final launch.
- */
- public static final X509Certificate TRUSTED_ROOT_CERT;
-
- static {
- try {
- TRUSTED_ROOT_CERT = CertUtils.decodeCert(
- CertUtils.decodeBase64(TRUSTED_ROOT_CERT_BASE64));
- } catch (CertParsingException e) {
- // Should not happen
- throw new RuntimeException(e);
- }
- }
-}
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 0ceb558..0f0ab9f 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
@@ -85,7 +85,7 @@
public class RecoverableKeyStoreManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
- private static final String ROOT_CERTIFICATE_ALIAS = "put_default_alias_here";
+ private static final String ROOT_CERTIFICATE_ALIAS = "";
private static final String TEST_SESSION_ID = "karlin";
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -139,6 +139,7 @@
private static final String KEY_ALGORITHM = "AES";
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey";
+ private static final String TEST_ROOT_CERT_ALIAS = "";
@Mock private Context mMockContext;
@Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
@@ -449,10 +450,13 @@
eq(Manifest.permission.RECOVER_KEYSTORE), any());
}
+ // TODO: Add tests for non-existing cert alias
+
@Test
public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -474,6 +478,7 @@
public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -591,6 +596,7 @@
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -609,6 +615,7 @@
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1),
vaultParams,
TEST_VAULT_CHALLENGE,
@@ -631,6 +638,7 @@
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(emptyCertPath),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
@@ -655,6 +663,7 @@
try {
mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
TEST_SESSION_ID,
+ TEST_ROOT_CERT_ALIAS,
RecoveryCertPath.createRecoveryCertPath(shortCertPath),
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,