RecoverySession.java revision 5240541e0173116bf0a088241057ad2e63fc9113
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.CertPath; 28import java.security.cert.CertificateException; 29import java.util.List; 30import java.util.Map; 31 32/** 33 * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a 34 * recovery agent. 35 * 36 * @hide 37 */ 38@SystemApi 39public class RecoverySession implements AutoCloseable { 40 private static final String TAG = "RecoverySession"; 41 42 private static final int SESSION_ID_LENGTH_BYTES = 16; 43 44 private final String mSessionId; 45 private final RecoveryController mRecoveryController; 46 47 private RecoverySession(RecoveryController recoveryController, String sessionId) { 48 mRecoveryController = recoveryController; 49 mSessionId = sessionId; 50 } 51 52 /** 53 * A new session, started by the {@link RecoveryController}. 54 */ 55 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 56 static RecoverySession newInstance(RecoveryController recoveryController) { 57 return new RecoverySession(recoveryController, newSessionId()); 58 } 59 60 /** 61 * Returns a new random session ID. 62 */ 63 private static String newSessionId() { 64 SecureRandom secureRandom = new SecureRandom(); 65 byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES]; 66 secureRandom.nextBytes(sessionId); 67 StringBuilder sb = new StringBuilder(); 68 for (byte b : sessionId) { 69 sb.append(Byte.toHexString(b, /*upperCase=*/ false)); 70 } 71 return sb.toString(); 72 } 73 74 /** 75 * @deprecated Use {@link #start(CertPath, byte[], byte[], List)} instead. 76 */ 77 @Deprecated 78 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 79 @NonNull public byte[] start( 80 @NonNull byte[] verifierPublicKey, 81 @NonNull byte[] vaultParams, 82 @NonNull byte[] vaultChallenge, 83 @NonNull List<KeyChainProtectionParams> secrets) 84 throws CertificateException, InternalRecoveryServiceException { 85 try { 86 byte[] recoveryClaim = 87 mRecoveryController.getBinder().startRecoverySession( 88 mSessionId, 89 verifierPublicKey, 90 vaultParams, 91 vaultChallenge, 92 secrets); 93 return recoveryClaim; 94 } catch (RemoteException e) { 95 throw e.rethrowFromSystemServer(); 96 } catch (ServiceSpecificException e) { 97 if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) { 98 throw new CertificateException(e.getMessage()); 99 } 100 throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); 101 } 102 } 103 104 /** 105 * Starts a recovery session and returns a blob with proof of recovery secret possession. 106 * The method generates a symmetric key for a session, which trusted remote device can use to 107 * return recovery key. 108 * 109 * @param verifierCertPath The certificate path used to create the recovery blob on the source 110 * device. Keystore will verify the certificate path by using the root of trust. 111 * @param vaultParams Must match the parameters in the corresponding field in the recovery blob. 112 * Used to limit number of guesses. 113 * @param vaultChallenge Data passed from server for this recovery session and used to prevent 114 * replay attacks. 115 * @param secrets Secrets provided by user, the method only uses type and secret fields. 116 * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is 117 * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric 118 * key and parameters necessary to identify the counter with the number of failed recovery 119 * attempts. 120 * @throws CertificateException if the {@code verifierCertPath} is invalid. 121 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 122 * service. 123 */ 124 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 125 @NonNull public byte[] start( 126 @NonNull CertPath verifierCertPath, 127 @NonNull byte[] vaultParams, 128 @NonNull byte[] vaultChallenge, 129 @NonNull List<KeyChainProtectionParams> secrets) 130 throws CertificateException, InternalRecoveryServiceException { 131 // Wrap the CertPath in a Parcelable so it can be passed via Binder calls. 132 RecoveryCertPath recoveryCertPath = 133 RecoveryCertPath.createRecoveryCertPath(verifierCertPath); 134 try { 135 byte[] recoveryClaim = 136 mRecoveryController.getBinder().startRecoverySessionWithCertPath( 137 mSessionId, 138 recoveryCertPath, 139 vaultParams, 140 vaultChallenge, 141 secrets); 142 return recoveryClaim; 143 } catch (RemoteException e) { 144 throw e.rethrowFromSystemServer(); 145 } catch (ServiceSpecificException e) { 146 if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) { 147 throw new CertificateException(e.getMessage()); 148 } 149 throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); 150 } 151 } 152 153 /** 154 * Imports keys. 155 * 156 * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. 157 * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob 158 * and session. KeyStore only uses package names from the application info in {@link 159 * WrappedApplicationKey}. Caller is responsibility to perform certificates check. 160 * @return Map from alias to raw key material. 161 * @throws SessionExpiredException if {@code session} has since been closed. 162 * @throws DecryptionFailedException if unable to decrypt the snapshot. 163 * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service. 164 */ 165 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 166 public Map<String, byte[]> recoverKeys( 167 @NonNull byte[] recoveryKeyBlob, 168 @NonNull List<WrappedApplicationKey> applicationKeys) 169 throws SessionExpiredException, DecryptionFailedException, 170 InternalRecoveryServiceException { 171 try { 172 return (Map<String, byte[]>) mRecoveryController.getBinder().recoverKeys( 173 mSessionId, recoveryKeyBlob, applicationKeys); 174 } catch (RemoteException e) { 175 throw e.rethrowFromSystemServer(); 176 } catch (ServiceSpecificException e) { 177 if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) { 178 throw new DecryptionFailedException(e.getMessage()); 179 } 180 if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) { 181 throw new SessionExpiredException(e.getMessage()); 182 } 183 throw mRecoveryController.wrapUnexpectedServiceSpecificException(e); 184 } 185 } 186 187 /** 188 * An internal session ID, used by the framework to match recovery claims to snapshot responses. 189 * 190 * @hide 191 */ 192 String getSessionId() { 193 return mSessionId; 194 } 195 196 /** 197 * Deletes all data associated with {@code session}. Should not be invoked directly but via 198 * {@link RecoverySession#close()}. 199 */ 200 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 201 @Override 202 public void close() { 203 try { 204 mRecoveryController.getBinder().closeSession(mSessionId); 205 } catch (RemoteException | ServiceSpecificException e) { 206 Log.e(TAG, "Unexpected error trying to close session", e); 207 } 208 } 209} 210