RecoverySession.java revision 41d2dd2f266eb8dc50afcda253f04f1c7e9ccc0e
15778822d86b0337407514b9372562b86edfa91cdAndreas Huber/*
25778822d86b0337407514b9372562b86edfa91cdAndreas Huber * Copyright (C) 2018 The Android Open Source Project
35778822d86b0337407514b9372562b86edfa91cdAndreas Huber *
45778822d86b0337407514b9372562b86edfa91cdAndreas Huber * Licensed under the Apache License, Version 2.0 (the "License");
55778822d86b0337407514b9372562b86edfa91cdAndreas Huber * you may not use this file except in compliance with the License.
65778822d86b0337407514b9372562b86edfa91cdAndreas Huber * You may obtain a copy of the License at
75778822d86b0337407514b9372562b86edfa91cdAndreas Huber *
85778822d86b0337407514b9372562b86edfa91cdAndreas Huber *      http://www.apache.org/licenses/LICENSE-2.0
95778822d86b0337407514b9372562b86edfa91cdAndreas Huber *
105778822d86b0337407514b9372562b86edfa91cdAndreas Huber * Unless required by applicable law or agreed to in writing, software
115778822d86b0337407514b9372562b86edfa91cdAndreas Huber * distributed under the License is distributed on an "AS IS" BASIS,
125778822d86b0337407514b9372562b86edfa91cdAndreas Huber * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135778822d86b0337407514b9372562b86edfa91cdAndreas Huber * See the License for the specific language governing permissions and
145778822d86b0337407514b9372562b86edfa91cdAndreas Huber * limitations under the License.
155778822d86b0337407514b9372562b86edfa91cdAndreas Huber */
165778822d86b0337407514b9372562b86edfa91cdAndreas Huber
175778822d86b0337407514b9372562b86edfa91cdAndreas Huberpackage android.security.keystore.recovery;
185778822d86b0337407514b9372562b86edfa91cdAndreas Huber
19fc7fca77caa12993dd938d5ff43797d781291027Lajos Molnarimport android.Manifest;
205778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport android.annotation.NonNull;
212606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhangimport android.annotation.RequiresPermission;
225778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport android.annotation.SystemApi;
235778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport android.os.RemoteException;
24c481b5012a5f6cf72e5e93b36f1ed4c9169916f2Jeff Tinkerimport android.os.ServiceSpecificException;
2567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wuimport android.util.ArrayMap;
262606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhangimport android.util.Log;
27c481b5012a5f6cf72e5e93b36f1ed4c9169916f2Jeff Tinker
2879608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhangimport java.security.Key;
291a2952aee048ca7b1765e2bc09ebe9aeddaeafa3Mathias Agopianimport java.security.SecureRandom;
30ed3e3e046840d5bf1ca84a8c0cc097425e89d6d6Andreas Huberimport java.security.UnrecoverableKeyException;
31d291c222357303b9611cab89d0c3b047584ef377Chong Zhangimport java.security.cert.CertPath;
3267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wuimport java.security.cert.CertificateException;
335778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport java.util.List;
345778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport java.util.Locale;
355778822d86b0337407514b9372562b86edfa91cdAndreas Huberimport java.util.Map;
365b8987e7de9d04b09153f329c680d2316cdb44ecAndreas Huber
37ed3e3e046840d5bf1ca84a8c0cc097425e89d6d6Andreas Huber/**
385778822d86b0337407514b9372562b86edfa91cdAndreas Huber * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a
397cd58537932ef6f481f68be0b9c597a89cebdfecAndy McFadden * recovery agent.
402606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang *
416f9439efd2a6004b588605f6a9d4af20c98e8e80Marco Nelissen * @hide
42e96ee699aca0f711d41e6c0833e5de2341c4a36dAndreas Huber */
435778822d86b0337407514b9372562b86edfa91cdAndreas Huber@SystemApi
44744f5739019d1fd917f981e740b353c3d73fd1a8David Smithpublic class RecoverySession implements AutoCloseable {
455778822d86b0337407514b9372562b86edfa91cdAndreas Huber    private static final String TAG = "RecoverySession";
46d291c222357303b9611cab89d0c3b047584ef377Chong Zhang
47d291c222357303b9611cab89d0c3b047584ef377Chong Zhang    private static final int SESSION_ID_LENGTH_BYTES = 16;
488b23759763dbf11b0c628a7e62dc5b3dea7dc188Lajos Molnar
4999e69716215cd0665379bc90d708f2ea8689831dRuben Brunk    private final String mSessionId;
502606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang    private final RecoveryController mRecoveryController;
512606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang
522606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang    private RecoverySession(@NonNull RecoveryController recoveryController,
53e96ee699aca0f711d41e6c0833e5de2341c4a36dAndreas Huber            @NonNull String sessionId) {
545778822d86b0337407514b9372562b86edfa91cdAndreas Huber        mRecoveryController = recoveryController;
555778822d86b0337407514b9372562b86edfa91cdAndreas Huber        mSessionId = sessionId;
5667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    }
5767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu
5867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    /**
5967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * A new session, started by the {@link RecoveryController}.
6067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     */
6147a2e875bdd2bd25cb8500208940ff1488b01e08Ronghua Wu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
6267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    static @NonNull RecoverySession newInstance(RecoveryController recoveryController) {
6367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        return new RecoverySession(recoveryController, newSessionId());
6467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    }
654b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu
6667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    /**
6767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * Returns a new random session ID.
6867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     */
6967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    private static @NonNull String newSessionId() {
7067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        SecureRandom secureRandom = new SecureRandom();
7167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES];
7267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        secureRandom.nextBytes(sessionId);
7367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        StringBuilder sb = new StringBuilder();
7467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        for (byte b : sessionId) {
7567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            sb.append(Byte.toHexString(b, /*upperCase=*/ false));
7647a2e875bdd2bd25cb8500208940ff1488b01e08Ronghua Wu        }
774b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu        return sb.toString();
784b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu    }
794b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu
804b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu    /**
814b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu     * @deprecated Use {@link #start(String, CertPath, byte[], byte[], List)} instead.
824b710f086070fabe022b3a1f474bfcbec842b8fcRonghua Wu     */
8367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    @Deprecated
8467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
8567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    @NonNull public byte[] start(
8667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull byte[] verifierPublicKey,
8767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull byte[] vaultParams,
8867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull byte[] vaultChallenge,
898f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu            @NonNull List<KeyChainProtectionParams> secrets)
908f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu            throws CertificateException, InternalRecoveryServiceException {
918f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu        try {
928f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu            byte[] recoveryClaim =
938f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                    mRecoveryController.getBinder().startRecoverySession(
948f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                            mSessionId,
958f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                            verifierPublicKey,
968f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                            vaultParams,
978f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                            vaultChallenge,
988f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu                            secrets);
998f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu            return recoveryClaim;
1008f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu        } catch (RemoteException e) {
1018f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu            throw e.rethrowFromSystemServer();
1028f9dd872366f54b6260506c75c3d0cc3f9f73f81Ronghua Wu        } catch (ServiceSpecificException e) {
10367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
10467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
10567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                throw new CertificateException("Invalid certificate for recovery session", e);
10667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            }
10767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
10867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        }
10967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    }
11067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu
11167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    /**
11267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @deprecated Use {@link #start(String, CertPath, byte[], byte[], List)} instead.
11368845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu     */
11468845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu    @Deprecated
11568845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
11668845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu    @NonNull public byte[] start(
11768845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu            @NonNull CertPath verifierCertPath,
11867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull byte[] vaultParams,
11967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull byte[] vaultChallenge,
12067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull List<KeyChainProtectionParams> secrets)
12167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            throws CertificateException, InternalRecoveryServiceException {
12267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        // Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
12367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        RecoveryCertPath recoveryCertPath =
12467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
12567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        try {
12667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            byte[] recoveryClaim =
12767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                    mRecoveryController.getBinder().startRecoverySessionWithCertPath(
12867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            mSessionId,
12967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            /*rootCertificateAlias=*/ "",  // Use the default root cert
13067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            recoveryCertPath,
13167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            vaultParams,
13267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            vaultChallenge,
13367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                            secrets);
134e4237177a4a3eea059cd74247b2d770d301a8230Ronghua Wu            return recoveryClaim;
13567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        } catch (RemoteException e) {
13667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            throw e.rethrowFromSystemServer();
13767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        } catch (ServiceSpecificException e) {
13867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
13967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
14067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                throw new CertificateException("Invalid certificate for recovery session", e);
14167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            }
14267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
14367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu        }
14467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    }
14567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu
14667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    /**
14767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * Starts a recovery session and returns a blob with proof of recovery secret possession.
14867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * The method generates a symmetric key for a session, which trusted remote device can use to
14967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * return recovery key.
15067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *
15137c8924c508a7c9b8bd3c8ce80fc005070531902Ronghua Wu     * @param rootCertificateAlias The alias of the root certificate that is already in the Android
15267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     OS. The root certificate will be used for validating {@code verifierCertPath}.
15367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @param verifierCertPath The certificate path used to create the recovery blob on the source
15467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     device. Keystore will verify the certificate path by using the root of trust.
15567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
15667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     Used to limit number of guesses.
15767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @param vaultChallenge Data passed from server for this recovery session and used to prevent
15867e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     replay attacks.
15937c8924c508a7c9b8bd3c8ce80fc005070531902Ronghua Wu     * @param secrets Secrets provided by user, the method only uses type and secret fields.
16067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @return The binary blob with recovery claim. It is encrypted with verifierPublicKey
16167e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * and contains a proof of user secrets possession, session symmetric
16267e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     key and parameters necessary to identify the counter with the number of failed recovery
16337c8924c508a7c9b8bd3c8ce80fc005070531902Ronghua Wu     *     attempts.
16467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @throws CertificateException if the {@code verifierCertPath} is invalid.
16567e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
16667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     *     service.
16767e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     */
16837c8924c508a7c9b8bd3c8ce80fc005070531902Ronghua Wu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
16967e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu    @NonNull public byte[] start(
17067e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu            @NonNull String rootCertificateAlias,
1715778822d86b0337407514b9372562b86edfa91cdAndreas Huber            @NonNull CertPath verifierCertPath,
1725778822d86b0337407514b9372562b86edfa91cdAndreas Huber            @NonNull byte[] vaultParams,
1736fc17d1a7c5d2fb117491b2e9f66c6236b526508Lajos Molnar            @NonNull byte[] vaultChallenge,
17468845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu            @NonNull List<KeyChainProtectionParams> secrets)
1755778822d86b0337407514b9372562b86edfa91cdAndreas Huber            throws CertificateException, InternalRecoveryServiceException {
176251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung        // Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
177251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung        RecoveryCertPath recoveryCertPath =
178251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung                RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
179251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung        try {
180251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung            byte[] recoveryClaim =
1815778822d86b0337407514b9372562b86edfa91cdAndreas Huber                    mRecoveryController.getBinder().startRecoverySessionWithCertPath(
1825778822d86b0337407514b9372562b86edfa91cdAndreas Huber                            mSessionId,
1835778822d86b0337407514b9372562b86edfa91cdAndreas Huber                            rootCertificateAlias,
1845778822d86b0337407514b9372562b86edfa91cdAndreas Huber                            recoveryCertPath,
1856fc17d1a7c5d2fb117491b2e9f66c6236b526508Lajos Molnar                            vaultParams,
18668845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu                            vaultChallenge,
1875778822d86b0337407514b9372562b86edfa91cdAndreas Huber                            secrets);
188251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung            return recoveryClaim;
189251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung        } catch (RemoteException e) {
190251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung            throw e.rethrowFromSystemServer();
191251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung        } catch (ServiceSpecificException e) {
192251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
1935778822d86b0337407514b9372562b86edfa91cdAndreas Huber                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
1945778822d86b0337407514b9372562b86edfa91cdAndreas Huber                throw new CertificateException("Invalid certificate for recovery session", e);
195d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            }
1965b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
1975b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar        }
1985b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar    }
1995b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar
2005b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar    /**
2015b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar     * @deprecated Use {@link #recoverKeyChainSnapshot(byte[], List)} instead.
2025b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar     */
2035b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar    @Deprecated
2045b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
2055b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar    public Map<String, byte[]> recoverKeys(
2065b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            @NonNull byte[] recoveryKeyBlob,
2075b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            @NonNull List<WrappedApplicationKey> applicationKeys)
2085b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            throws SessionExpiredException, DecryptionFailedException,
2095b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            InternalRecoveryServiceException {
2105b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar        try {
2115b05e49e6550cb2abf1a88272d6cd460b8957176Lajos Molnar            return (Map<String, byte[]>) mRecoveryController.getBinder().recoverKeys(
212d291c222357303b9611cab89d0c3b047584ef377Chong Zhang                    mSessionId, recoveryKeyBlob, applicationKeys);
213d291c222357303b9611cab89d0c3b047584ef377Chong Zhang        } catch (RemoteException e) {
214d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            throw e.rethrowFromSystemServer();
215d291c222357303b9611cab89d0c3b047584ef377Chong Zhang        } catch (ServiceSpecificException e) {
216d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
21779608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang                throw new DecryptionFailedException(e.getMessage());
21879608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            }
21979608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
22079608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang                throw new SessionExpiredException(e.getMessage());
22179608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            }
22279608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
22379608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang        }
22479608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang    }
22579608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang
22679608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang    /**
22779608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * Imports key chain snapshot recovered from a remote vault.
22879608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     *
22979608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
23079608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
23179608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     *     and session key generated by {@link #start}.
23279608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @return {@code Map} from recovered keys aliases to their references.
23379608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @throws SessionExpiredException if {@code session} has since been closed.
23479608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @throws DecryptionFailedException if unable to decrypt the snapshot.
23579608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
23679608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang     */
23779608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang    @RequiresPermission(Manifest.permission.RECOVER_KEYSTORE)
238d291c222357303b9611cab89d0c3b047584ef377Chong Zhang    @NonNull public Map<String, Key> recoverKeyChainSnapshot(
239d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            @NonNull byte[] recoveryKeyBlob,
240d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            @NonNull List<WrappedApplicationKey> applicationKeys
241d291c222357303b9611cab89d0c3b047584ef377Chong Zhang    ) throws SessionExpiredException, DecryptionFailedException, InternalRecoveryServiceException {
242d291c222357303b9611cab89d0c3b047584ef377Chong Zhang        try {
243d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            Map<String, String> grantAliases = mRecoveryController
244d291c222357303b9611cab89d0c3b047584ef377Chong Zhang                    .getBinder()
245d291c222357303b9611cab89d0c3b047584ef377Chong Zhang                    .recoverKeyChainSnapshot(mSessionId, recoveryKeyBlob, applicationKeys);
246d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            return getKeysFromGrants(grantAliases);
247d291c222357303b9611cab89d0c3b047584ef377Chong Zhang        } catch (RemoteException e) {
248d291c222357303b9611cab89d0c3b047584ef377Chong Zhang            throw e.rethrowFromSystemServer();
24979608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang        } catch (ServiceSpecificException e) {
25079608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
25179608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang                throw new DecryptionFailedException(e.getMessage());
25279608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            }
25379608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
25479608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang                throw new SessionExpiredException(e.getMessage());
25579608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            }
25679608158c2254fe1357959157f2d0c1560a8a6c6Chong Zhang            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
257d291c222357303b9611cab89d0c3b047584ef377Chong Zhang        }
258d291c222357303b9611cab89d0c3b047584ef377Chong Zhang    }
259d291c222357303b9611cab89d0c3b047584ef377Chong Zhang
26068845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu    /** Given a map from alias to grant alias, returns a map from alias to a {@link Key} handle. */
2615778822d86b0337407514b9372562b86edfa91cdAndreas Huber    private @NonNull Map<String, Key> getKeysFromGrants(@NonNull Map<String, String> grantAliases)
26247a2e875bdd2bd25cb8500208940ff1488b01e08Ronghua Wu            throws InternalRecoveryServiceException {
2635778822d86b0337407514b9372562b86edfa91cdAndreas Huber        ArrayMap<String, Key> keysByAlias = new ArrayMap<>(grantAliases.size());
26492cd05b8f2e994aabcdda5d7454c96a707dc9579Lajos Molnar        for (String alias : grantAliases.keySet()) {
2657cd58537932ef6f481f68be0b9c597a89cebdfecAndy McFadden            String grantAlias = grantAliases.get(alias);
2665778822d86b0337407514b9372562b86edfa91cdAndreas Huber            Key key;
267251d4be8aa5ab80bc915a82a2420233bdc62018eAndy Hung            try {
2685778822d86b0337407514b9372562b86edfa91cdAndreas Huber                key = mRecoveryController.getKeyFromGrant(grantAlias);
269ee4e1b1a63758941460ae79a064249d3a5189443Lajos Molnar            } catch (UnrecoverableKeyException e) {
27068845c14ebf2c7282800b1abffde38d8e9a57aabRonghua Wu                throw new InternalRecoveryServiceException(
2712606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang                        String.format(
2722606b10d51c2dceb851a2ea63e803aba4134bf00Chong Zhang                                Locale.US,
27367e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                                "Failed to get key '%s' from grant '%s'",
27467e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu                                alias,
275505aab41c0e8e79a49d4506344fcd9d220d5965bChong Zhang                                grantAlias), e);
2765778822d86b0337407514b9372562b86edfa91cdAndreas Huber            }
2775778822d86b0337407514b9372562b86edfa91cdAndreas Huber            keysByAlias.put(alias, key);
2785778822d86b0337407514b9372562b86edfa91cdAndreas Huber        }
2796507d14c6d10f93d390de62b9eed267f9b544985Andy McFadden        return keysByAlias;
2803d66eb4128aebef31bb0fa44c4d53d6122294a26Chong Zhang    }
2813d66eb4128aebef31bb0fa44c4d53d6122294a26Chong Zhang
2825778822d86b0337407514b9372562b86edfa91cdAndreas Huber    /**
2835778822d86b0337407514b9372562b86edfa91cdAndreas Huber     * An internal session ID, used by the framework to match recovery claims to snapshot responses.
2845778822d86b0337407514b9372562b86edfa91cdAndreas Huber     *
2855778822d86b0337407514b9372562b86edfa91cdAndreas Huber     * @hide
28667e7f543c7f1c4fe4ee1989ceb0aebe44a63b49eRonghua Wu     */
2875778822d86b0337407514b9372562b86edfa91cdAndreas Huber    String getSessionId() {
2885778822d86b0337407514b9372562b86edfa91cdAndreas Huber        return mSessionId;
2895778822d86b0337407514b9372562b86edfa91cdAndreas Huber    }
2905778822d86b0337407514b9372562b86edfa91cdAndreas Huber
2915778822d86b0337407514b9372562b86edfa91cdAndreas Huber    /**
2925778822d86b0337407514b9372562b86edfa91cdAndreas Huber     * Deletes all data associated with {@code session}.
2935778822d86b0337407514b9372562b86edfa91cdAndreas Huber     */
2945778822d86b0337407514b9372562b86edfa91cdAndreas Huber    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
2955778822d86b0337407514b9372562b86edfa91cdAndreas Huber    @Override
2965778822d86b0337407514b9372562b86edfa91cdAndreas Huber    public void close() {
2975778822d86b0337407514b9372562b86edfa91cdAndreas Huber        try {
2985778822d86b0337407514b9372562b86edfa91cdAndreas Huber            mRecoveryController.getBinder().closeSession(mSessionId);
2995778822d86b0337407514b9372562b86edfa91cdAndreas Huber        } catch (RemoteException | ServiceSpecificException e) {
3005778822d86b0337407514b9372562b86edfa91cdAndreas Huber            Log.e(TAG, "Unexpected error trying to close session", e);
3015778822d86b0337407514b9372562b86edfa91cdAndreas Huber        }
3025778822d86b0337407514b9372562b86edfa91cdAndreas Huber    }
3035778822d86b0337407514b9372562b86edfa91cdAndreas Huber}
3045778822d86b0337407514b9372562b86edfa91cdAndreas Huber