1 /*
2  * Copyright (C) 2020 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 
17 package android.security;
18 
19 import android.annotation.NonNull;
20 import android.app.compat.CompatChanges;
21 import android.hardware.security.keymint.KeyParameter;
22 import android.os.Binder;
23 import android.os.RemoteException;
24 import android.os.ServiceSpecificException;
25 import android.os.StrictMode;
26 import android.security.keystore.BackendBusyException;
27 import android.security.keystore.KeyStoreConnectException;
28 import android.system.keystore2.AuthenticatorSpec;
29 import android.system.keystore2.CreateOperationResponse;
30 import android.system.keystore2.IKeystoreSecurityLevel;
31 import android.system.keystore2.KeyDescriptor;
32 import android.system.keystore2.KeyMetadata;
33 import android.system.keystore2.ResponseCode;
34 import android.util.Log;
35 
36 import java.util.Calendar;
37 import java.util.Collection;
38 
39 /**
40  * This is a shim around the security level specific interface of Keystore 2.0. Services with
41  * this interface are instantiated per KeyMint backend, each having there own security level.
42  * Thus this object representation of a security level.
43  * @hide
44  */
45 public class KeyStoreSecurityLevel {
46     private static final String TAG = "KeyStoreSecurityLevel";
47     private final IKeystoreSecurityLevel mSecurityLevel;
48 
KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel)49     public KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel) {
50         Binder.allowBlocking(securityLevel.asBinder());
51         this.mSecurityLevel = securityLevel;
52     }
53 
handleExceptions(CheckedRemoteRequest<R> request)54     private <R> R handleExceptions(CheckedRemoteRequest<R> request) throws KeyStoreException {
55         try {
56             return request.execute();
57         } catch (ServiceSpecificException e) {
58             throw KeyStore2.getKeyStoreException(e.errorCode, e.getMessage());
59         } catch (RemoteException e) {
60             // Log exception and report invalid operation handle.
61             // This should prompt the caller drop the reference to this operation and retry.
62             Log.e(TAG, "Could not connect to Keystore.", e);
63             throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, "", e.getMessage());
64         }
65     }
66 
67     /**
68      * Creates a new keystore operation.
69      * @see IKeystoreSecurityLevel#createOperation(KeyDescriptor, KeyParameter[], boolean) for more
70      * details.
71      * @param keyDescriptor
72      * @param args
73      * @return
74      * @throws KeyStoreException
75      * @hide
76      */
createOperation(@onNull KeyDescriptor keyDescriptor, Collection<KeyParameter> args)77     public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor,
78             Collection<KeyParameter> args) throws KeyStoreException {
79         StrictMode.noteDiskWrite();
80         while (true) {
81             try {
82                 CreateOperationResponse createOperationResponse =
83                         mSecurityLevel.createOperation(
84                                 keyDescriptor,
85                                 args.toArray(new KeyParameter[args.size()]),
86                                 false /* forced */
87                         );
88                 Long challenge = null;
89                 if (createOperationResponse.operationChallenge != null) {
90                     challenge = createOperationResponse.operationChallenge.challenge;
91                 }
92                 KeyParameter[] parameters = null;
93                 if (createOperationResponse.parameters != null) {
94                     parameters = createOperationResponse.parameters.keyParameter;
95                 }
96                 return new KeyStoreOperation(
97                         createOperationResponse.iOperation,
98                         challenge,
99                         parameters);
100             } catch (ServiceSpecificException e) {
101                 switch (e.errorCode) {
102                     case ResponseCode.BACKEND_BUSY: {
103                         long backOffHint = (long) (Math.random() * 80 + 20);
104                         if (CompatChanges.isChangeEnabled(
105                                 KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) {
106                             // Starting with Android S we inform the caller about the
107                             // backend being busy.
108                             throw new BackendBusyException(backOffHint);
109                         } else {
110                             // Before Android S operation creation must always succeed. So we
111                             // just have to retry. We do so with a randomized back-off between
112                             // 20 and 100ms.
113                             // It is a little awkward that we cannot break out of this loop
114                             // by interrupting this thread. But that is the expected behavior.
115                             // There is some comfort in the fact that interrupting a thread
116                             // also does not unblock a thread waiting for a binder transaction.
117                             interruptedPreservingSleep(backOffHint);
118                         }
119                         break;
120                     }
121                     default:
122                         throw KeyStore2.getKeyStoreException(e.errorCode, e.getMessage());
123                 }
124             } catch (RemoteException e) {
125                 Log.w(TAG, "Cannot connect to keystore", e);
126                 throw new KeyStoreConnectException();
127             }
128         }
129     }
130 
131     /**
132      * Generates a new key in Keystore.
133      * @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
134      * byte[]) for more details.
135      * @param descriptor
136      * @param attestationKey
137      * @param args
138      * @param flags
139      * @param entropy
140      * @return
141      * @throws KeyStoreException
142      * @hide
143      */
generateKey(@onNull KeyDescriptor descriptor, KeyDescriptor attestationKey, Collection<KeyParameter> args, int flags, byte[] entropy)144     public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
145             Collection<KeyParameter> args, int flags, byte[] entropy)
146             throws KeyStoreException {
147         StrictMode.noteDiskWrite();
148 
149         return handleExceptions(() -> mSecurityLevel.generateKey(
150                 descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
151                 flags, entropy));
152     }
153 
154     /**
155      * Imports a key into Keystore.
156      * @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
157      * byte[]) for more details.
158      * @param descriptor
159      * @param attestationKey
160      * @param args
161      * @param flags
162      * @param keyData
163      * @return
164      * @throws KeyStoreException
165      * @hide
166      */
importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey, Collection<KeyParameter> args, int flags, byte[] keyData)167     public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
168             Collection<KeyParameter> args, int flags, byte[] keyData)
169             throws KeyStoreException {
170         StrictMode.noteDiskWrite();
171 
172         return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
173                 args.toArray(new KeyParameter[args.size()]), flags, keyData));
174     }
175 
176     /**
177      * Imports a wrapped key into Keystore.
178      * @see IKeystoreSecurityLevel#importWrappedKey(KeyDescriptor, KeyDescriptor, byte[],
179      * KeyParameter[], AuthenticatorSpec[]) for more details.
180      * @param wrappedKeyDescriptor
181      * @param wrappingKeyDescriptor
182      * @param wrappedKey
183      * @param maskingKey
184      * @param args
185      * @param authenticatorSpecs
186      * @return
187      * @throws KeyStoreException
188      * @hide
189      */
importWrappedKey(@onNull KeyDescriptor wrappedKeyDescriptor, @NonNull KeyDescriptor wrappingKeyDescriptor, @NonNull byte[] wrappedKey, byte[] maskingKey, Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)190     public KeyMetadata importWrappedKey(@NonNull KeyDescriptor wrappedKeyDescriptor,
191             @NonNull KeyDescriptor wrappingKeyDescriptor,
192             @NonNull byte[] wrappedKey, byte[] maskingKey,
193             Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)
194             throws KeyStoreException {
195         StrictMode.noteDiskWrite();
196         KeyDescriptor keyDescriptor = new KeyDescriptor();
197         keyDescriptor.alias = wrappedKeyDescriptor.alias;
198         keyDescriptor.nspace = wrappedKeyDescriptor.nspace;
199         keyDescriptor.blob = wrappedKey;
200         keyDescriptor.domain = wrappedKeyDescriptor.domain;
201 
202         return handleExceptions(() -> mSecurityLevel.importWrappedKey(keyDescriptor,
203                 wrappingKeyDescriptor, maskingKey,
204                 args.toArray(new KeyParameter[args.size()]), authenticatorSpecs));
205     }
206 
interruptedPreservingSleep(long millis)207     protected static void interruptedPreservingSleep(long millis) {
208         boolean wasInterrupted = false;
209         Calendar calendar = Calendar.getInstance();
210         long target = calendar.getTimeInMillis() + millis;
211         while (true) {
212             try {
213                 Thread.sleep(target - calendar.getTimeInMillis());
214                 break;
215             } catch (InterruptedException e) {
216                 wasInterrupted = true;
217             } catch (IllegalArgumentException e) {
218                 // This means that the argument to sleep was negative.
219                 // So we are done sleeping.
220                 break;
221             }
222         }
223         if (wasInterrupted) {
224             Thread.currentThread().interrupt();
225         }
226     }
227 }
228