RecoverySession.java revision f8ae5deba2911b7bc8441df31c0504eaaa687add
1/* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.security.keystore.recovery; 18 19import android.annotation.NonNull; 20import android.annotation.RequiresPermission; 21import android.annotation.SystemApi; 22import android.os.RemoteException; 23import android.os.ServiceSpecificException; 24import android.util.Log; 25 26import java.security.SecureRandom; 27import java.security.cert.CertificateException; 28import java.util.List; 29import java.util.Map; 30 31/** 32 * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a 33 * recovery agent. 34 * 35 * @hide 36 */ 37@SystemApi 38public class RecoverySession implements AutoCloseable { 39 private static final String TAG = "RecoverySession"; 40 41 private static final int SESSION_ID_LENGTH_BYTES = 16; 42 43 private final String mSessionId; 44 private final RecoveryController mRecoveryController; 45 46 private RecoverySession(RecoveryController recoveryController, String sessionId) { 47 mRecoveryController = recoveryController; 48 mSessionId = sessionId; 49 } 50 51 /** 52 * A new session, started by {@code recoveryManager}. 53 */ 54 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 55 static RecoverySession newInstance(RecoveryController recoveryController) { 56 return new RecoverySession(recoveryController, newSessionId()); 57 } 58 59 /** 60 * Returns a new random session ID. 61 */ 62 private static String newSessionId() { 63 SecureRandom secureRandom = new SecureRandom(); 64 byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES]; 65 secureRandom.nextBytes(sessionId); 66 StringBuilder sb = new StringBuilder(); 67 for (byte b : sessionId) { 68 sb.append(Byte.toHexString(b, /*upperCase=*/ false)); 69 } 70 return sb.toString(); 71 } 72 73 /** 74 * Starts a recovery session and returns a blob with proof of recovery secret possession. 75 * The method generates a symmetric key for a session, which trusted remote device can use to 76 * return recovery key. 77 * 78 * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key 79 * used to create the recovery blob on the source device. 80 * Keystore will verify the certificate using root of trust. 81 * @param vaultParams Must match the parameters in the corresponding field in the recovery blob. 82 * Used to limit number of guesses. 83 * @param vaultChallenge Data passed from server for this recovery session and used to prevent 84 * replay attacks 85 * @param secrets Secrets provided by user, the method only uses type and secret fields. 86 * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is 87 * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric 88 * key and parameters necessary to identify the counter with the number of failed recovery 89 * attempts. 90 * @throws CertificateException if the {@code verifierPublicKey} is in an incorrect 91 * format. 92 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 93 * service. 94 */ 95 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 96 @NonNull public byte[] start( 97 @NonNull byte[] verifierPublicKey, 98 @NonNull byte[] vaultParams, 99 @NonNull byte[] vaultChallenge, 100 @NonNull List<KeyChainProtectionParams> secrets) 101 throws CertificateException, InternalRecoveryServiceException { 102 try { 103 byte[] recoveryClaim = 104 mRecoveryController.getBinder().startRecoverySession( 105 mSessionId, 106 verifierPublicKey, 107 vaultParams, 108 vaultChallenge, 109 secrets); 110 return recoveryClaim; 111 } catch (RemoteException e) { 112 throw e.rethrowFromSystemServer(); 113 } catch (ServiceSpecificException e) { 114 if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) { 115 throw new CertificateException(e.getMessage()); 116 } 117 throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); 118 } 119 } 120 121 /** 122 * Imports keys. 123 * 124 * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. 125 * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob 126 * and session. KeyStore only uses package names from the application info in {@link 127 * WrappedApplicationKey}. Caller is responsibility to perform certificates check. 128 * @return Map from alias to raw key material. 129 * @throws SessionExpiredException if {@code session} has since been closed. 130 * @throws DecryptionFailedException if unable to decrypt the snapshot. 131 * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. 132 */ 133 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 134 public Map<String, byte[]> recoverKeys( 135 @NonNull byte[] recoveryKeyBlob, 136 @NonNull List<WrappedApplicationKey> applicationKeys) 137 throws SessionExpiredException, DecryptionFailedException, 138 InternalRecoveryServiceException { 139 try { 140 return (Map<String, byte[]>) mRecoveryController.getBinder().recoverKeys( 141 mSessionId, recoveryKeyBlob, applicationKeys); 142 } catch (RemoteException e) { 143 throw e.rethrowFromSystemServer(); 144 } catch (ServiceSpecificException e) { 145 if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) { 146 throw new DecryptionFailedException(e.getMessage()); 147 } 148 if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) { 149 throw new SessionExpiredException(e.getMessage()); 150 } 151 throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); 152 } 153 } 154 155 /** 156 * An internal session ID, used by the framework to match recovery claims to snapshot responses. 157 * 158 * @hide 159 */ 160 String getSessionId() { 161 return mSessionId; 162 } 163 164 /** 165 * Deletes all data associated with {@code session}. Should not be invoked directly but via 166 * {@link RecoverySession#close()}. 167 */ 168 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 169 @Override 170 public void close() { 171 try { 172 mRecoveryController.getBinder().closeSession(mSessionId); 173 } catch (RemoteException | ServiceSpecificException e) { 174 Log.e(TAG, "Unexpected error trying to close session", e); 175 } 176 } 177} 178