Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f414d94 authored by Bo Zhu's avatar Bo Zhu
Browse files

Check the public-key signature of the whole certificate file before

accepting the certificates

This change requires an additional param to the initRecoveryService()
API to take in the public-key signature.

Bug: 73904566
Test: adb shell am instrument -w -e package \
com.android.server.locksettings.recoverablekeystore \
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: I2aeead1fda51b6cd8df71ed3b5066342ebc8d5ea
parent b1d5004c
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -4310,7 +4310,8 @@ package android.security.keystore.recovery {
    method public int[] getRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException;
    method public deprecated int getRecoveryStatus(java.lang.String, java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
    method public int getRecoveryStatus(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
    method public void initRecoveryService(java.lang.String, byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
    method public deprecated void initRecoveryService(java.lang.String, byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
    method public void initRecoveryService(java.lang.String, byte[], byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
    method public void recoverySecretAvailable(android.security.keystore.recovery.KeyChainProtectionParams) throws android.security.keystore.recovery.InternalRecoveryServiceException;
    method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
    method public void setRecoverySecretTypes(int[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+64 −17
Original line number Diff line number Diff line
@@ -99,7 +99,11 @@ public class RecoveryController {
    public static final int ERROR_SESSION_EXPIRED = 24;

    /**
     * Failed because the provided certificate was not a valid X509 certificate.
     * Failed because the format of the provided certificate is incorrect, e.g., cannot be decoded
     * properly or misses necessary fields.
     *
     * <p>Note that this is different from {@link #ERROR_INVALID_CERTIFICATE}, which implies the
     * certificate has a correct format but cannot be validated.
     *
     * @hide
     */
@@ -121,6 +125,16 @@ public class RecoveryController {
     */
    public static final int ERROR_INVALID_KEY_FORMAT = 27;

    /**
     * Failed because the provided certificate cannot be validated, e.g., is expired or has invalid
     * signatures.
     *
     * <p>Note that this is different from {@link #ERROR_BAD_CERTIFICATE_FORMAT}, which denotes
     * incorrect certificate formats, e.g., due to wrong encoding or structure.
     *
     * @hide
     */
    public static final int ERROR_INVALID_CERTIFICATE = 28;

    private final ILockSettings mBinder;
    private final KeyStore mKeyStore;
@@ -149,23 +163,9 @@ public class RecoveryController {
    }

    /**
     * Initializes key recovery service for the calling application. RecoveryController
     * randomly chooses one of the keys from the list and keeps it to use for future key export
     * operations. Collection of all keys in the list must be signed by the provided {@code
     * rootCertificateAlias}, which must also be present in the list of root certificates
     * preinstalled on the device. The random selection allows RecoveryController to select
     * which of a set of remote recovery service devices will be used.
     *
     * <p>In addition, RecoveryController enforces a delay of three months between
     * consecutive initialization attempts, to limit the ability of an attacker to often switch
     * remote recovery devices and significantly increase number of recovery attempts.
     *
     * @param rootCertificateAlias alias of a root certificate preinstalled on the device
     * @param signedPublicKeyList binary blob a list of X509 certificates and signature
     * @throws CertificateException if the {@code signedPublicKeyList} is in a bad format.
     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
     *     service.
     * @deprecated Use {@link #initRecoveryService(String, byte[], byte[])} instead.
     */
    @Deprecated
    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
    public void initRecoveryService(
            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
@@ -175,7 +175,54 @@ public class RecoveryController {
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
            if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT
                    || e.errorCode == ERROR_INVALID_CERTIFICATE) {
                throw new CertificateException(e.getMessage());
            }
            throw wrapUnexpectedServiceSpecificException(e);
        }
    }

    /**
     * Initializes the recovery service for the calling application. The detailed steps should be:
     * <ol>
     *     <li>Parse {@code signatureFile} to get relevant information.
     *     <li>Validate the signer's X509 certificate, contained in {@code signatureFile}, against
     *         the root certificate pre-installed in the OS and chosen by {@code
     *         rootCertificateAlias}.
     *     <li>Verify the public-key signature, contained in {@code signatureFile}, and verify it
     *         against the entire {@code certificateFile}.
     *     <li>Parse {@code certificateFile} to get relevant information.
     *     <li>Check the serial number, contained in {@code certificateFile}, and skip the following
     *         steps if the serial number is not larger than the one previously stored.
     *     <li>Randomly choose a X509 certificate from the endpoint X509 certificates, contained in
     *         {@code certificateFile}, and validate it against the root certificate pre-installed
     *         in the OS and chosen by {@code rootCertificateAlias}.
     *     <li>Store the chosen X509 certificate and the serial in local database for later use.
     * </ol>
     *
     * @param rootCertificateAlias the alias of a root certificate pre-installed in the OS
     * @param certificateFile the binary content of the XML file containing a list of recovery
     *     service X509 certificates, and other metadata including the serial number
     * @param signatureFile the binary content of the XML file containing the public-key signature
     *     of the entire certificate file, and a signer's X509 certificate
     * @throws CertificateException if the given certificate files cannot be parsed or validated
     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
     *     service.
     */
    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
    public void initRecoveryService(
            @NonNull String rootCertificateAlias, @NonNull byte[] certificateFile,
            @NonNull byte[] signatureFile)
            throws CertificateException, InternalRecoveryServiceException {
        try {
            mBinder.initRecoveryServiceWithSigFile(
                    rootCertificateAlias, certificateFile, signatureFile);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT
                    || e.errorCode == ERROR_INVALID_CERTIFICATE) {
                throw new CertificateException(e.getMessage());
            }
            throw wrapUnexpectedServiceSpecificException(e);
+4 −2
Original line number Diff line number Diff line
@@ -94,7 +94,8 @@ public class RecoverySession implements AutoCloseable {
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (ServiceSpecificException e) {
            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) {
            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
                throw new CertificateException(e.getMessage());
            }
            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
@@ -143,7 +144,8 @@ public class RecoverySession implements AutoCloseable {
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (ServiceSpecificException e) {
            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) {
            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
                throw new CertificateException(e.getMessage());
            }
            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
+2 −0
Original line number Diff line number Diff line
@@ -65,6 +65,8 @@ interface ILockSettings {
    // {@code ServiceSpecificException} may be thrown to signal an error, which caller can
    // convert to  {@code RecoveryManagerException}.
    void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
    void initRecoveryServiceWithSigFile(in String rootCertificateAlias,
            in byte[] recoveryServiceCertFile, in byte[] recoveryServiceSigFile);
    KeyChainSnapshot getKeyChainSnapshot();
    byte[] generateAndStoreKey(String alias);
    String generateKey(String alias);
+8 −0
Original line number Diff line number Diff line
@@ -1982,6 +1982,14 @@ public class LockSettingsService extends ILockSettings.Stub {
                signedPublicKeyList);
    }

    @Override
    public void initRecoveryServiceWithSigFile(@NonNull String rootCertificateAlias,
            @NonNull byte[] recoveryServiceCertFile, @NonNull byte[] recoveryServiceSigFile)
            throws RemoteException {
        mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(rootCertificateAlias,
                recoveryServiceCertFile, recoveryServiceSigFile);
    }

    @Override
    public KeyChainSnapshot getKeyChainSnapshot() throws RemoteException {
        return mRecoverableKeyStoreManager.getKeyChainSnapshot();
Loading