From 7ceb8aab0f711dd20f67dd7050e0bf3f0e4c3620 Mon Sep 17 00:00:00 2001 From: Seth Moore Date: Tue, 13 Dec 2022 10:10:21 -0800 Subject: [PATCH] Implement IRegistration interface for RKP Bug: 261888917 Test: RemoteProvisioningRegistrationTest Change-Id: Ide9eee760efade9603346c2144490c20c4e0dd8a --- .../security/rkp/IRemoteProvisioning.aidl | 7 - .../rkp/RemoteProvisioningRegistration.java | 132 ++++++++++++++++++ .../rkp/RemoteProvisioningService.java | 93 +++++------- 3 files changed, 166 insertions(+), 66 deletions(-) create mode 100644 services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl index 23d8159f7b280..0efaa89e5dd6a 100644 --- a/core/java/android/security/rkp/IRemoteProvisioning.aidl +++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl @@ -58,11 +58,4 @@ oneway interface IRemoteProvisioning { * */ void getRegistration(String irpcName, IGetRegistrationCallback callback); - - /** - * Cancel any active {@link getRegistration} call associated with the given - * callback. If no getRegistration call is currently active, this function is - * a noop. - */ - void cancelGetRegistration(IGetRegistrationCallback callback); } diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java new file mode 100644 index 0000000000000..868f34bf45ce9 --- /dev/null +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.security.rkp; + +import android.os.CancellationSignal; +import android.os.OperationCanceledException; +import android.os.OutcomeReceiver; +import android.security.rkp.IGetKeyCallback; +import android.security.rkp.IRegistration; +import android.security.rkp.service.RegistrationProxy; +import android.security.rkp.service.RemotelyProvisionedKey; +import android.util.Log; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +/** + * Implements android.security.rkp.IRegistration as a thin wrapper around the java code + * exported by com.android.rkp. + * + * @hide + */ +final class RemoteProvisioningRegistration extends IRegistration.Stub { + static final String TAG = RemoteProvisioningService.TAG; + private final ConcurrentHashMap mOperations = + new ConcurrentHashMap<>(); + private final RegistrationProxy mRegistration; + private final Executor mExecutor; + + private class GetKeyReceiver implements OutcomeReceiver { + IGetKeyCallback mCallback; + GetKeyReceiver(IGetKeyCallback callback) { + mCallback = callback; + } + + @Override + public void onResult(RemotelyProvisionedKey result) { + mOperations.remove(mCallback); + Log.i(TAG, "Successfully fetched key for client " + mCallback.hashCode()); + android.security.rkp.RemotelyProvisionedKey parcelable = + new android.security.rkp.RemotelyProvisionedKey(); + parcelable.keyBlob = result.getKeyBlob(); + parcelable.encodedCertChain = result.getEncodedCertChain(); + wrapCallback(() -> mCallback.onSuccess(parcelable)); + } + + @Override + public void onError(Exception e) { + mOperations.remove(mCallback); + if (e instanceof OperationCanceledException) { + Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode()); + wrapCallback(mCallback::onCancel); + } else { + Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e); + wrapCallback(() -> mCallback.onError(e.getMessage())); + } + } + } + + RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) { + mRegistration = registration; + mExecutor = executor; + } + + @Override + public void getKey(int keyId, IGetKeyCallback callback) { + CancellationSignal cancellationSignal = new CancellationSignal(); + if (mOperations.putIfAbsent(callback, cancellationSignal) != null) { + Log.e(TAG, "Client can only request one call at a time " + callback.hashCode()); + throw new IllegalArgumentException( + "Callback is already associated with an existing operation: " + + callback.hashCode()); + } + + try { + Log.i(TAG, "Fetching key " + keyId + " for client " + callback.hashCode()); + mRegistration.getKeyAsync(keyId, cancellationSignal, mExecutor, + new GetKeyReceiver(callback)); + } catch (Exception e) { + Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e); + mOperations.remove(callback); + wrapCallback(() -> callback.onError(e.getMessage())); + } + } + + @Override + public void cancelGetKey(IGetKeyCallback callback) { + CancellationSignal cancellationSignal = mOperations.remove(callback); + if (cancellationSignal == null) { + throw new IllegalArgumentException( + "Invalid client in cancelGetKey: " + callback.hashCode()); + } + + Log.i(TAG, "Requesting cancellation for client " + callback.hashCode()); + cancellationSignal.cancel(); + } + + @Override + public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) { + // TODO(b/262748535) + Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED"); + } + + interface CallbackRunner { + void run() throws Exception; + } + + private void wrapCallback(CallbackRunner callback) { + // Exceptions resulting from notifications to IGetKeyCallback objects can only be logged, + // since getKey execution is asynchronous, and there's no way for an exception to be + // properly handled up the stack. + try { + callback.run(); + } catch (Exception e) { + Log.e(TAG, "Error invoking callback on client binder", e); + } + } +} diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java index 65a4b38629b69..cd1a968a8e890 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java @@ -20,9 +20,7 @@ import android.content.Context; import android.os.Binder; import android.os.OutcomeReceiver; import android.os.RemoteException; -import android.security.rkp.IGetKeyCallback; import android.security.rkp.IGetRegistrationCallback; -import android.security.rkp.IRegistration; import android.security.rkp.IRemoteProvisioning; import android.security.rkp.service.RegistrationProxy; import android.util.Log; @@ -30,6 +28,7 @@ import android.util.Log; import com.android.server.SystemService; import java.time.Duration; +import java.util.concurrent.Executor; /** * Implements the remote provisioning system service. This service is backed by a mainline @@ -43,6 +42,35 @@ public class RemoteProvisioningService extends SystemService { private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10); private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl(); + private static class RegistrationReceiver implements + OutcomeReceiver { + private final Executor mExecutor; + private final IGetRegistrationCallback mCallback; + + RegistrationReceiver(Executor executor, IGetRegistrationCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResult(RegistrationProxy registration) { + try { + mCallback.onSuccess(new RemoteProvisioningRegistration(registration, mExecutor)); + } catch (RemoteException e) { + Log.e(TAG, "Error calling success callback " + mCallback.hashCode(), e); + } + } + + @Override + public void onError(Exception error) { + try { + mCallback.onError(error.toString()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling error callback " + mCallback.hashCode(), e); + } + } + } + /** @hide */ public RemoteProvisioningService(Context context) { super(context); @@ -54,73 +82,20 @@ public class RemoteProvisioningService extends SystemService { } private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub { - - final class RegistrationBinder extends IRegistration.Stub { - static final String TAG = RemoteProvisioningService.TAG; - private final RegistrationProxy mRegistration; - - RegistrationBinder(RegistrationProxy registration) { - mRegistration = registration; - } - - @Override - public void getKey(int keyId, IGetKeyCallback callback) { - Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED"); - } - - @Override - public void cancelGetKey(IGetKeyCallback callback) { - Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED"); - } - - @Override - public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) { - Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED"); - } - } - @Override public void getRegistration(String irpcName, IGetRegistrationCallback callback) throws RemoteException { final int callerUid = Binder.getCallingUidOrThrow(); final long callingIdentity = Binder.clearCallingIdentity(); + final Executor executor = getContext().getMainExecutor(); try { Log.i(TAG, "getRegistration(" + irpcName + ")"); - RegistrationProxy.createAsync( - getContext(), - callerUid, - irpcName, - CREATE_REGISTRATION_TIMEOUT, - getContext().getMainExecutor(), - new OutcomeReceiver<>() { - @Override - public void onResult(RegistrationProxy registration) { - try { - callback.onSuccess(new RegistrationBinder(registration)); - } catch (RemoteException e) { - Log.e(TAG, "Error calling success callback", e); - } - } - - @Override - public void onError(Exception error) { - try { - callback.onError(error.toString()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling error callback", e); - } - } - }); + RegistrationProxy.createAsync(getContext(), callerUid, irpcName, + CREATE_REGISTRATION_TIMEOUT, executor, + new RegistrationReceiver(executor, callback)); } finally { Binder.restoreCallingIdentity(callingIdentity); } } - - @Override - public void cancelGetRegistration(IGetRegistrationCallback callback) - throws RemoteException { - Log.i(TAG, "cancelGetRegistration()"); - callback.onError("cancelGetRegistration not yet implemented"); - } } }