RecoverySession.java revision 86f5bb1a8cfe2d169767fb723d315955dda3a0e6
181ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry/*
281ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * Copyright (C) 2018 The Android Open Source Project
381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry *
481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * Licensed under the Apache License, Version 2.0 (the "License");
581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * you may not use this file except in compliance with the License.
681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * You may obtain a copy of the License at
781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry *
881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry *      http://www.apache.org/licenses/LICENSE-2.0
981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry *
1081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * Unless required by applicable law or agreed to in writing, software
1181ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * distributed under the License is distributed on an "AS IS" BASIS,
1281ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * See the License for the specific language governing permissions and
1481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * limitations under the License.
1581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry */
1681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
1781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berrypackage android.security.keystore.recovery;
1881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
194a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berryimport android.Manifest;
200916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport android.annotation.NonNull;
21f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyevimport android.annotation.RequiresPermission;
22f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyevimport android.annotation.SystemApi;
230916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport android.os.RemoteException;
240916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport android.os.ServiceSpecificException;
254a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berryimport android.util.ArrayMap;
260916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport android.util.Log;
270916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev
284a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berryimport java.security.Key;
2981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berryimport java.security.SecureRandom;
304a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berryimport java.security.UnrecoverableKeyException;
317c1972ff71080568b7288197e96e163d5a469e5fBo Zhuimport java.security.cert.CertPath;
320916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport java.security.cert.CertificateException;
330916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport java.util.List;
344a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berryimport java.util.Locale;
350916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyevimport java.util.Map;
3681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
3781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry/**
380916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a
3981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * recovery agent.
4081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry *
4181ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry * @hide
4281ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry */
43f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyev@SystemApi
4481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berrypublic class RecoverySession implements AutoCloseable {
450916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    private static final String TAG = "RecoverySession";
4681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
4781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    private static final int SESSION_ID_LENGTH_BYTES = 16;
4881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
4981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    private final String mSessionId;
5081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    private final RecoveryController mRecoveryController;
5181ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
521e6a9dcecb92b4a9a8d3c60372821ba7cd830873Dmitry Dementyev    private RecoverySession(@NonNull RecoveryController recoveryController,
531e6a9dcecb92b4a9a8d3c60372821ba7cd830873Dmitry Dementyev            @NonNull String sessionId) {
5481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        mRecoveryController = recoveryController;
5581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        mSessionId = sessionId;
5681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    }
5781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
5881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    /**
59beafcb50d4f963421bac7e84a4f47f68a8b5e4b6Robert Berry     * A new session, started by the {@link RecoveryController}.
6081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry     */
61f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyev    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
621e6a9dcecb92b4a9a8d3c60372821ba7cd830873Dmitry Dementyev    static @NonNull RecoverySession newInstance(RecoveryController recoveryController) {
6381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        return new RecoverySession(recoveryController, newSessionId());
6481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    }
6581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
6681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    /**
6781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry     * Returns a new random session ID.
6881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry     */
691e6a9dcecb92b4a9a8d3c60372821ba7cd830873Dmitry Dementyev    private static @NonNull String newSessionId() {
7081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        SecureRandom secureRandom = new SecureRandom();
7181ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES];
7281ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        secureRandom.nextBytes(sessionId);
7381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        StringBuilder sb = new StringBuilder();
7481ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        for (byte b : sessionId) {
7581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry            sb.append(Byte.toHexString(b, /*upperCase=*/ false));
7681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        }
7781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        return sb.toString();
7881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    }
7981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
8081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    /**
81e7997a3ea7c5dea839220ae832ea5ff7a7dc7742Bo Zhu     * @deprecated Use {@link #start(String, CertPath, byte[], byte[], List)} instead.
820916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev     */
837c1972ff71080568b7288197e96e163d5a469e5fBo Zhu    @Deprecated
84f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyev    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
850916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    @NonNull public byte[] start(
860916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull byte[] verifierPublicKey,
870916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull byte[] vaultParams,
880916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull byte[] vaultChallenge,
890916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull List<KeyChainProtectionParams> secrets)
900916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throws CertificateException, InternalRecoveryServiceException {
910916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        try {
920916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            byte[] recoveryClaim =
930916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                    mRecoveryController.getBinder().startRecoverySession(
940916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                            mSessionId,
950916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                            verifierPublicKey,
960916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                            vaultParams,
970916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                            vaultChallenge,
980916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                            secrets);
990916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            return recoveryClaim;
1000916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        } catch (RemoteException e) {
1010916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throw e.rethrowFromSystemServer();
1020916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        } catch (ServiceSpecificException e) {
1037f414d94fc4f6bd34325f3865b51e8d11acb52adBo Zhu            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
1047f414d94fc4f6bd34325f3865b51e8d11acb52adBo Zhu                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
1050916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                throw new CertificateException(e.getMessage());
1060916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            }
1070916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
1080916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        }
1090916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    }
1100916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev
1110916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    /**
112e7997a3ea7c5dea839220ae832ea5ff7a7dc7742Bo Zhu     * @deprecated Use {@link #start(String, CertPath, byte[], byte[], List)} instead.
1137c1972ff71080568b7288197e96e163d5a469e5fBo Zhu     */
114e7997a3ea7c5dea839220ae832ea5ff7a7dc7742Bo Zhu    @Deprecated
1157c1972ff71080568b7288197e96e163d5a469e5fBo Zhu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
1167c1972ff71080568b7288197e96e163d5a469e5fBo Zhu    @NonNull public byte[] start(
1177c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            @NonNull CertPath verifierCertPath,
1187c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            @NonNull byte[] vaultParams,
1197c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            @NonNull byte[] vaultChallenge,
1207c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            @NonNull List<KeyChainProtectionParams> secrets)
1217c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            throws CertificateException, InternalRecoveryServiceException {
1227c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        // Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
1237c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        RecoveryCertPath recoveryCertPath =
1247c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
1257c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        try {
1267c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            byte[] recoveryClaim =
1277c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                    mRecoveryController.getBinder().startRecoverySessionWithCertPath(
1287c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                            mSessionId,
129b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            /*rootCertificateAlias=*/ "",  // Use the default root cert
130b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            recoveryCertPath,
131b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            vaultParams,
132b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            vaultChallenge,
133b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            secrets);
134b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            return recoveryClaim;
135b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        } catch (RemoteException e) {
136b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            throw e.rethrowFromSystemServer();
137b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        } catch (ServiceSpecificException e) {
138b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
139b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
140b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                throw new CertificateException(e.getMessage());
141b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            }
142b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
143b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        }
144b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu    }
145b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu
146b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu    /**
147b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * Starts a recovery session and returns a blob with proof of recovery secret possession.
148b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * The method generates a symmetric key for a session, which trusted remote device can use to
149b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * return recovery key.
150b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *
151b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @param rootCertificateAlias The alias of the root certificate that is already in the Android
152b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     OS. The root certificate will be used for validating {@code verifierCertPath}.
153b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @param verifierCertPath The certificate path used to create the recovery blob on the source
154b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     device. Keystore will verify the certificate path by using the root of trust.
155b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
156b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     Used to limit number of guesses.
157b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @param vaultChallenge Data passed from server for this recovery session and used to prevent
158b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     replay attacks.
159b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @param secrets Secrets provided by user, the method only uses type and secret fields.
16086f5bb1a8cfe2d169767fb723d315955dda3a0e6Dmitry Dementyev     * @return The binary blob with recovery claim. It is encrypted with verifierPublicKey
16186f5bb1a8cfe2d169767fb723d315955dda3a0e6Dmitry Dementyev     * and contains a proof of user secrets possession, session symmetric
162b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     key and parameters necessary to identify the counter with the number of failed recovery
163b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     attempts.
164b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @throws CertificateException if the {@code verifierCertPath} is invalid.
165b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
166b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     *     service.
167b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu     */
168b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
169b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu    @NonNull public byte[] start(
170b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            @NonNull String rootCertificateAlias,
171b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            @NonNull CertPath verifierCertPath,
172b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            @NonNull byte[] vaultParams,
173b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            @NonNull byte[] vaultChallenge,
174b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            @NonNull List<KeyChainProtectionParams> secrets)
175b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            throws CertificateException, InternalRecoveryServiceException {
176b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        // Wrap the CertPath in a Parcelable so it can be passed via Binder calls.
177b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        RecoveryCertPath recoveryCertPath =
178b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                RecoveryCertPath.createRecoveryCertPath(verifierCertPath);
179b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu        try {
180b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu            byte[] recoveryClaim =
181b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                    mRecoveryController.getBinder().startRecoverySessionWithCertPath(
182b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            mSessionId,
183b31ab6740d66b21a74ffa77b753ea3364288254eBo Zhu                            rootCertificateAlias,
1847c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                            recoveryCertPath,
1857c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                            vaultParams,
1867c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                            vaultChallenge,
1877c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                            secrets);
1887c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            return recoveryClaim;
1897c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        } catch (RemoteException e) {
1907c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            throw e.rethrowFromSystemServer();
1917c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        } catch (ServiceSpecificException e) {
1927f414d94fc4f6bd34325f3865b51e8d11acb52adBo Zhu            if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT
1937f414d94fc4f6bd34325f3865b51e8d11acb52adBo Zhu                    || e.errorCode == RecoveryController.ERROR_INVALID_CERTIFICATE) {
1947c1972ff71080568b7288197e96e163d5a469e5fBo Zhu                throw new CertificateException(e.getMessage());
1957c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            }
1967c1972ff71080568b7288197e96e163d5a469e5fBo Zhu            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
1977c1972ff71080568b7288197e96e163d5a469e5fBo Zhu        }
1987c1972ff71080568b7288197e96e163d5a469e5fBo Zhu    }
1997c1972ff71080568b7288197e96e163d5a469e5fBo Zhu
2007c1972ff71080568b7288197e96e163d5a469e5fBo Zhu    /**
201750b71c6512dad08e9c8eb59c2ad3c0d4fcfe79fRobert Berry     * @deprecated Use {@link #recoverKeyChainSnapshot(byte[], List)} instead.
2020916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev     */
203750b71c6512dad08e9c8eb59c2ad3c0d4fcfe79fRobert Berry    @Deprecated
204f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyev    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
2050916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    public Map<String, byte[]> recoverKeys(
2060916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull byte[] recoveryKeyBlob,
2070916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            @NonNull List<WrappedApplicationKey> applicationKeys)
2080916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throws SessionExpiredException, DecryptionFailedException,
2090916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            InternalRecoveryServiceException {
2100916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        try {
2110916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            return (Map<String, byte[]>) mRecoveryController.getBinder().recoverKeys(
2120916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                    mSessionId, recoveryKeyBlob, applicationKeys);
2130916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        } catch (RemoteException e) {
2140916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throw e.rethrowFromSystemServer();
2150916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        } catch (ServiceSpecificException e) {
2160916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
2170916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                throw new DecryptionFailedException(e.getMessage());
2180916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            }
2190916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
2200916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev                throw new SessionExpiredException(e.getMessage());
2210916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            }
2220916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
2230916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        }
2240916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    }
2250916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev
2260916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    /**
2274a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * Imports key chain snapshot recovered from a remote vault.
2284a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     *
2294a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
2304a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
23186f5bb1a8cfe2d169767fb723d315955dda3a0e6Dmitry Dementyev     *     and session key generated by {@link #start}.
23286f5bb1a8cfe2d169767fb723d315955dda3a0e6Dmitry Dementyev     * @return {@code Map} from recovered keys aliases to their references.
2334a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * @throws SessionExpiredException if {@code session} has since been closed.
2344a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * @throws DecryptionFailedException if unable to decrypt the snapshot.
2354a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
2364a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry     */
2374a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    @RequiresPermission(Manifest.permission.RECOVER_KEYSTORE)
238fd4ae0b2ddd58f6acbb19632f20e40024e3d85b1Dmitry Dementyev    @NonNull public Map<String, Key> recoverKeyChainSnapshot(
2394a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            @NonNull byte[] recoveryKeyBlob,
2404a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            @NonNull List<WrappedApplicationKey> applicationKeys
2414a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    ) throws SessionExpiredException, DecryptionFailedException, InternalRecoveryServiceException {
2424a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        try {
2434a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            Map<String, String> grantAliases = mRecoveryController
2444a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                    .getBinder()
2454a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                    .recoverKeyChainSnapshot(mSessionId, recoveryKeyBlob, applicationKeys);
2464a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            return getKeysFromGrants(grantAliases);
2474a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        } catch (RemoteException e) {
2484a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            throw e.rethrowFromSystemServer();
2494a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        } catch (ServiceSpecificException e) {
2504a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
2514a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                throw new DecryptionFailedException(e.getMessage());
2524a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            }
2534a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
2544a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                throw new SessionExpiredException(e.getMessage());
2554a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            }
2564a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
2574a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        }
2584a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    }
2594a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry
2604a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    /** Given a map from alias to grant alias, returns a map from alias to a {@link Key} handle. */
2610bbaf189c259f7d3154737c4284023921dc821b0Dmitry Dementyev    private @NonNull Map<String, Key> getKeysFromGrants(@NonNull Map<String, String> grantAliases)
2624a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            throws InternalRecoveryServiceException {
2634a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        ArrayMap<String, Key> keysByAlias = new ArrayMap<>(grantAliases.size());
2644a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        for (String alias : grantAliases.keySet()) {
2654a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            String grantAlias = grantAliases.get(alias);
2664a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            Key key;
2674a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            try {
2684a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                key = mRecoveryController.getKeyFromGrant(grantAlias);
2694a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            } catch (UnrecoverableKeyException e) {
2704a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                throw new InternalRecoveryServiceException(
2714a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                        String.format(
2724a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                                Locale.US,
2734a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                                "Failed to get key '%s' from grant '%s'",
2744a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                                alias,
2754a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry                                grantAlias), e);
2764a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            }
2774a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry            keysByAlias.put(alias, key);
2784a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        }
2794a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry        return keysByAlias;
2804a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    }
2814a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry
2824a5c87def075c805d4fcae7ff01dd2e78ec27b1aRobert Berry    /**
28381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry     * An internal session ID, used by the framework to match recovery claims to snapshot responses.
2840916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev     *
2850916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev     * @hide
28681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry     */
28781ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    String getSessionId() {
28881ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry        return mSessionId;
28981ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    }
29081ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry
2910916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev    /**
29286f5bb1a8cfe2d169767fb723d315955dda3a0e6Dmitry Dementyev     * Deletes all data associated with {@code session}.
2930916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev     */
294f8ae5deba2911b7bc8441df31c0504eaaa687addDmitry Dementyev    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
29581ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    @Override
29681ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    public void close() {
2970916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        try {
2980916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            mRecoveryController.getBinder().closeSession(mSessionId);
2990916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        } catch (RemoteException | ServiceSpecificException e) {
3000916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev            Log.e(TAG, "Unexpected error trying to close session", e);
3010916e7ca44aba5e6c89d75007da805697fdace9eDmitry Dementyev        }
30281ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry    }
30381ee34bf957dffe020442e3f0c6c06817397ebf0Robert Berry}
304