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