1 /*
2  * Copyright (C) 2022 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.rkp.service;
18 
19 import static android.annotation.SystemApi.Client.SYSTEM_SERVER;
20 
21 import static java.util.concurrent.TimeUnit.MILLISECONDS;
22 
23 import android.annotation.CallbackExecutor;
24 import android.annotation.NonNull;
25 import android.annotation.SystemApi;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.ServiceConnection;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.ResolveInfoFlags;
32 import android.content.pm.ResolveInfo;
33 import android.content.pm.ServiceInfo;
34 import android.os.CancellationSignal;
35 import android.os.IBinder;
36 import android.os.OperationCanceledException;
37 import android.os.OutcomeReceiver;
38 import android.os.RemoteException;
39 import android.os.UserHandle;
40 import android.util.Log;
41 
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.rkpdapp.IGetKeyCallback;
44 import com.android.rkpdapp.IGetRegistrationCallback;
45 import com.android.rkpdapp.IRegistration;
46 import com.android.rkpdapp.IRemoteProvisioning;
47 import com.android.rkpdapp.IStoreUpgradedKeyCallback;
48 
49 import java.time.Duration;
50 import java.util.List;
51 import java.util.concurrent.CountDownLatch;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.TimeoutException;
54 import java.util.concurrent.atomic.AtomicBoolean;
55 import java.util.stream.Collectors;
56 
57 /**
58  * Proxy object for calling into com.android.rkpdapp, which is responsible for remote key
59  * provisioning. System server cannot call into rkpdapp directly, as it is contained in a mainline
60  * module, and as such cannot expose stable AIDL. Instead, this wrapper code is exposed as a
61  * stable SYSTEM_SERVER API. The AIDL details are hidden by this class.
62  *
63  * @hide
64  */
65 @SystemApi(client = SYSTEM_SERVER)
66 public class RegistrationProxy {
67     static final String TAG = "RegistrationProxy";
68     IRegistration mBinder;
69 
70     /** Deals with the {@code ServiceConnection} lifetime for the rkpd bound service. */
71     private static class IRemoteProvisioningConnection implements ServiceConnection {
72         @GuardedBy("this")
73         IRemoteProvisioning mRemoteProvisioningService;
74         RemoteException mRemoteException;
75         CountDownLatch mLatch = new CountDownLatch(1);
76 
IRemoteProvisioningConnection(Context context)77         IRemoteProvisioningConnection(Context context) throws RemoteException {
78             final String serviceName = IRemoteProvisioning.class.getName();
79             final Intent intent = new Intent(serviceName);
80 
81             // Look for the bound service hosted by a system package. There should only ever be one.
82             final ResolveInfoFlags flags = ResolveInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY);
83             final List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(
84                     intent, flags);
85             if (resolveInfos == null || resolveInfos.size() == 0) {
86                 throw new IllegalStateException(
87                         "No system services were found hosting " + serviceName);
88             }
89             if (resolveInfos.size() > 1) {
90                 throw new IllegalStateException(
91                         "Multiple system packages found hosting " + serviceName + ": "
92                                 + resolveInfos.stream().map(
93                                     r -> r.serviceInfo.applicationInfo.packageName).collect(
94                                         Collectors.joining(", ")));
95             }
96 
97             // At this point, we're sure there's one system service hosting the binder, so bind it
98             final ServiceInfo serviceInfo = resolveInfos.get(0).serviceInfo;
99             intent.setComponent(
100                     new ComponentName(serviceInfo.applicationInfo.packageName, serviceInfo.name));
101             if (!context.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
102                     UserHandle.SYSTEM)) {
103                 throw new RemoteException("Failed to bind to IRemoteProvisioning service");
104             }
105         }
106 
waitForRemoteProvisioningService(Duration bindTimeout)107         public IRemoteProvisioning waitForRemoteProvisioningService(Duration bindTimeout)
108                 throws RemoteException, TimeoutException {
109             try {
110                 if (!mLatch.await(bindTimeout.toMillis(), MILLISECONDS)) {
111                     throw new TimeoutException("Timed out waiting on service connection to rkpd");
112                 }
113             } catch (InterruptedException e) {
114                 Log.e(TAG, "Wait for binder was interrupted", e);
115             }
116             synchronized (this) {
117                 if (mRemoteException != null) {
118                     throw mRemoteException;
119                 }
120                 return mRemoteProvisioningService;
121             }
122         }
123 
124         @Override
onServiceConnected(ComponentName name, IBinder binder)125         public void onServiceConnected(ComponentName name, IBinder binder) {
126             Log.i(TAG, "onServiceConnected: " + name.getClassName());
127             synchronized (this) {
128                 mRemoteException = null;
129                 mRemoteProvisioningService = IRemoteProvisioning.Stub.asInterface(binder);
130             }
131             mLatch.countDown();
132         }
133 
134         @Override
onNullBinding(ComponentName name)135         public void onNullBinding(ComponentName name) {
136             Log.i(TAG, "onNullBinding: " + name.getClassName());
137             mRemoteException = new RemoteException("Received null binding from rkpd service.");
138             mLatch.countDown();
139         }
140 
141         @Override
onServiceDisconnected(ComponentName name)142         public void onServiceDisconnected(ComponentName name) {
143             Log.i(TAG, "onServiceDisconnected: " + name.getClassName());
144             synchronized (this) {
145                 mRemoteException = new RemoteException("rkpd service disconnected");
146                 mRemoteProvisioningService = null;
147             }
148         }
149 
150         @Override
onBindingDied(ComponentName name)151         public void onBindingDied(ComponentName name) {
152             Log.i(TAG, "onBindingDied: " + name.getClassName());
153             synchronized (this) {
154                 mRemoteException = new RemoteException("rkpd service binding died");
155                 mRemoteProvisioningService = null;
156             }
157         }
158     }
159 
160     /**
161      * Factory method for creating a registration for an IRemotelyProvisionedComponent.
162      * RegistrationProxy objects may not be directly constructed via new.
163      *
164      * @param context The application Context
165      * @param callerUid The UID of the caller into system server for whom we need to fetch a
166      *                  registration. Remotely provisioned keys are partitioned by client UID,
167      *                  so each UID passed here will result in a unique registration that returns
168      *                  keys that are only for the given client UID.
169      * @param irpcName The name of the IRemotelyProvisionedComponent HAL for which a registration
170      *                 is requested.
171      * @param bindTimeout How long to wait for the underlying service binding. If the service is
172      *                    not available within this time limit, the receiver is notified with a
173      *                    TimeoutException.
174      * @param executor Callbacks to the receiver are performed using this executor.
175      * @param receiver Asynchronously receives either a registration or some exception indicating
176      *                 why the registration could not be created.
177      */
createAsync(@onNull Context context, int callerUid, @NonNull String irpcName, @NonNull Duration bindTimeout, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<RegistrationProxy, Exception> receiver)178     public static void createAsync(@NonNull Context context, int callerUid,
179             @NonNull String irpcName, @NonNull Duration bindTimeout,
180             @NonNull @CallbackExecutor Executor executor,
181             @NonNull OutcomeReceiver<RegistrationProxy, Exception> receiver) {
182         try {
183             // The connection object is used to get exactly one IRegistration binder. Once we
184             // get it, we unbind the connection. This allows the bound service to be terminated
185             // under memory pressure.
186             final IRemoteProvisioningConnection connection = new IRemoteProvisioningConnection(
187                     context);
188             IGetRegistrationCallback.Stub callbackHandler = new IGetRegistrationCallback.Stub() {
189                 @Override
190                 public void onSuccess(IRegistration registration) {
191                     Log.i(TAG, "IGetRegistrationCallback.onSuccess");
192                     context.unbindService(connection);
193                     executor.execute(() -> receiver.onResult(new RegistrationProxy(registration)));
194                 }
195 
196                 @Override
197                 public void onCancel() {
198                     Log.i(TAG, "IGetRegistrationCallback.onCancel");
199                     context.unbindService(connection);
200                 }
201 
202                 @Override
203                 public void onError(String error) {
204                     Log.i(TAG, "IGetRegistrationCallback.onError:" + error);
205                     context.unbindService(connection);
206                     executor.execute(() -> receiver.onError(new RemoteException(error)));
207                 }
208             };
209 
210             final IRemoteProvisioning remoteProvisioningService =
211                     connection.waitForRemoteProvisioningService(bindTimeout);
212             remoteProvisioningService.getRegistration(callerUid, irpcName, callbackHandler);
213         } catch (Exception e) {
214             Log.e(TAG, "Error getting registration", e);
215             executor.execute(() -> receiver.onError(e));
216         }
217     }
218 
RegistrationProxy(IRegistration binder)219     private RegistrationProxy(IRegistration binder) {
220         mBinder = binder;
221     }
222 
223     /**
224      * Begins an async operation to fetch a key from rkp. The receiver will be notified on
225      * completion or error. Cancellation is reported as an error, passing OperationCanceledException
226      * to the receiver.
227      * @param keyId An arbitrary, caller-chosen identifier for the key. If a key has previously
228      *              been assigned this id, then that key will be returned. Else, an unassigned key
229      *              is chosen and keyId is assigned to that key.
230      * @param cancellationSignal Signal object used to indicate to the asynchronous code that a
231      *                           pending call should be cancelled.
232      * @param executor The executor on which to call receiver.
233      * @param receiver Asynchronously receives a RemotelyProvisionedKey on success, else an
234      *                 exception is received.
235      */
getKeyAsync(int keyId, @NonNull CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<RemotelyProvisionedKey, Exception> receiver)236     public void getKeyAsync(int keyId,
237             @NonNull CancellationSignal cancellationSignal,
238             @NonNull @CallbackExecutor Executor executor,
239             @NonNull OutcomeReceiver<RemotelyProvisionedKey, Exception> receiver) {
240         final AtomicBoolean operationComplete = new AtomicBoolean(false);
241 
242         final var callback = new IGetKeyCallback.Stub() {
243             @Override
244             public void onSuccess(com.android.rkpdapp.RemotelyProvisionedKey key) {
245                 if (operationComplete.compareAndSet(false, true)) {
246                     executor.execute(() -> receiver.onResult(new RemotelyProvisionedKey(key)));
247                 } else {
248                     Log.w(TAG, "Ignoring extra success for " + this);
249                 }
250             }
251 
252             @Override
253             public void onProvisioningNeeded() {
254                 Log.i(TAG, "Provisioning required before keys are available for " + this);
255             }
256 
257             @Override
258             public void onCancel() {
259                 if (operationComplete.compareAndSet(false, true)) {
260                     executor.execute(() -> receiver.onError(new OperationCanceledException()));
261                 } else {
262                     Log.w(TAG, "Ignoring extra cancel for " + this);
263                 }
264             }
265 
266             @Override
267             public void onError(byte error, String description) {
268                 if (operationComplete.compareAndSet(false, true)) {
269                     executor.execute(() -> receiver.onError(
270                             new RkpProxyException(convertGetKeyError(error), description)));
271                 } else {
272                     Log.w(TAG, "Ignoring extra error (" + error + ") for " + this);
273                 }
274             }
275         };
276 
277         cancellationSignal.setOnCancelListener(() -> {
278             if (operationComplete.get()) {
279                 Log.w(TAG,
280                         "Ignoring cancel call after operation complete for " + callback.hashCode());
281             } else {
282                 try {
283                     Log.i(TAG, "Attempting to cancel getKeyAsync for " + callback.hashCode());
284                     mBinder.cancelGetKey(callback);
285                 } catch (RemoteException e) {
286                     Log.e(TAG, "Error cancelling getKey operation", e);
287                 }
288             }
289         });
290 
291         try {
292             Log.i(TAG, "getKeyAsync operation started with callback " + callback.hashCode());
293             mBinder.getKey(keyId, callback);
294         } catch (RemoteException e) {
295             throw e.rethrowAsRuntimeException();
296         }
297     }
298 
299     /**
300      * Begins an async operation to store an upgraded key blob in the rkpd database. This is part
301      * of the anti-rollback protections for keys. If a system has a security patch applied, then
302      * key blobs may need to be upgraded so that the keys cannot be used if the system is rolled
303      * back to the vulnerable version of code.
304      *
305      * The anti-rollback mechanism requires the consumer of the key blob (e.g. KeyMint) to return
306      * an error indicating that the key needs to be upgraded. The client (e.g. keystore2) is then
307      * responsible for calling the upgrade method, then asking rkpd to store the upgraded blob.
308      * This way, if the system is ever downgraded, the key blobs can no longer be used.
309      *
310      * @param oldKeyBlob The key to be upgraded.
311      * @param newKeyBlob The new key, replacing oldKeyBlob in the database.
312      * @param executor The executor on which to call receiver.
313      * @param receiver Asynchronously receives success callback, else an exception is received.
314      */
storeUpgradedKeyAsync(@onNull byte[] oldKeyBlob, @NonNull byte[] newKeyBlob, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> receiver)315     public void storeUpgradedKeyAsync(@NonNull byte[] oldKeyBlob, @NonNull byte[] newKeyBlob,
316             @NonNull @CallbackExecutor Executor executor,
317             @NonNull OutcomeReceiver<Void, Exception> receiver) {
318         final var callback = new IStoreUpgradedKeyCallback.Stub() {
319             @Override
320             public void onSuccess() {
321                 Log.e(TAG, "upgrade key succeeded for callback " + hashCode());
322                 executor.execute(() -> receiver.onResult(null));
323             }
324 
325             @Override
326             public void onError(String error) {
327                 Log.e(TAG, "upgrade key failed: " + error + ", callback: " + hashCode());
328                 executor.execute(() -> receiver.onError(new RemoteException(error)));
329             }
330         };
331 
332         try {
333             Log.i(TAG, "storeUpgradedKeyAsync operation started with callback "
334                     + callback.hashCode());
335             mBinder.storeUpgradedKeyAsync(oldKeyBlob, newKeyBlob, callback);
336         } catch (RemoteException e) {
337             throw e.rethrowAsRuntimeException();
338         }
339     }
340 
341     /** Converts an IGetKeyCallback.Error code into an RkpProxyException error code. */
convertGetKeyError(byte error)342     private static int convertGetKeyError(byte error) {
343         switch (error) {
344             case IGetKeyCallback.Error.ERROR_REQUIRES_SECURITY_PATCH:
345                 return RkpProxyException.ERROR_REQUIRES_SECURITY_PATCH;
346             case IGetKeyCallback.Error.ERROR_PENDING_INTERNET_CONNECTIVITY:
347                 return RkpProxyException.ERROR_PENDING_INTERNET_CONNECTIVITY;
348             case IGetKeyCallback.Error.ERROR_PERMANENT:
349                 return RkpProxyException.ERROR_PERMANENT;
350             case IGetKeyCallback.Error.ERROR_UNKNOWN:
351                 return RkpProxyException.ERROR_UNKNOWN;
352             default:
353                 Log.e(TAG, "Undefined error from rkpd: " + error);
354                 return RkpProxyException.ERROR_UNKNOWN;
355         }
356     }
357 }
358