Loading core/java/android/security/keystore/recovery/RecoverySession.java +57 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,63 @@ public class RecoverySession implements AutoCloseable { 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, Loading core/java/android/security/keystore/recovery/TrustedRootCertificates.java +17 −0 Original line number Diff line number Diff line Loading @@ -77,10 +77,27 @@ public class TrustedRootCertificates { 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( Loading core/java/com/android/internal/widget/ILockSettings.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -78,7 +78,7 @@ interface ILockSettings { 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, Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −3 Original line number Diff line number Diff line Loading @@ -2051,11 +2051,13 @@ public class LockSettingsService extends ILockSettings.Stub { @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 { Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +27 −6 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.security.keystore.recovery.KeyChainProtectionParams; 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; Loading @@ -50,7 +51,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKe 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; Loading @@ -64,6 +64,7 @@ import java.security.UnrecoverableKeyException; 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; Loading Loading @@ -200,15 +201,19 @@ public class RecoverableKeyStoreManager { } 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) { Loading Loading @@ -253,8 +258,9 @@ public class RecoverableKeyStoreManager { 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) Loading Loading @@ -479,6 +485,7 @@ public class RecoverableKeyStoreManager { */ public @NonNull byte[] startRecoverySessionWithCertPath( @NonNull String sessionId, @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, Loading @@ -495,11 +502,10 @@ public class RecoverableKeyStoreManager { } 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(); Loading Loading @@ -837,6 +843,21 @@ public class RecoverableKeyStoreManager { } } 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, Loading Loading
core/java/android/security/keystore/recovery/RecoverySession.java +57 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,63 @@ public class RecoverySession implements AutoCloseable { 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, Loading
core/java/android/security/keystore/recovery/TrustedRootCertificates.java +17 −0 Original line number Diff line number Diff line Loading @@ -77,10 +77,27 @@ public class TrustedRootCertificates { 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( Loading
core/java/com/android/internal/widget/ILockSettings.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -78,7 +78,7 @@ interface ILockSettings { 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, Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +5 −3 Original line number Diff line number Diff line Loading @@ -2051,11 +2051,13 @@ public class LockSettingsService extends ILockSettings.Stub { @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 { Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +27 −6 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.security.keystore.recovery.KeyChainProtectionParams; 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; Loading @@ -50,7 +51,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKe 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; Loading @@ -64,6 +64,7 @@ import java.security.UnrecoverableKeyException; 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; Loading Loading @@ -200,15 +201,19 @@ public class RecoverableKeyStoreManager { } 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) { Loading Loading @@ -253,8 +258,9 @@ public class RecoverableKeyStoreManager { 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) Loading Loading @@ -479,6 +485,7 @@ public class RecoverableKeyStoreManager { */ public @NonNull byte[] startRecoverySessionWithCertPath( @NonNull String sessionId, @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, Loading @@ -495,11 +502,10 @@ public class RecoverableKeyStoreManager { } 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(); Loading Loading @@ -837,6 +843,21 @@ public class RecoverableKeyStoreManager { } } 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, Loading