blob: 7d148f6225be028119d20c82bd38643f8dcab0a3 [file] [log] [blame]
/*
* 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.IBinder;
import android.os.OperationCanceledException;
import android.os.OutcomeReceiver;
import android.security.rkp.IGetKeyCallback;
import android.security.rkp.IRegistration;
import android.security.rkp.IStoreUpgradedKeyCallback;
import android.security.rkp.service.RegistrationProxy;
import android.security.rkp.service.RemotelyProvisionedKey;
import android.security.rkp.service.RkpProxyException;
import android.util.Log;
import java.util.Set;
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<IBinder, CancellationSignal> mGetKeyOperations =
new ConcurrentHashMap<>();
private final Set<IBinder> mStoreUpgradedKeyOperations = ConcurrentHashMap.newKeySet();
private final RegistrationProxy mRegistration;
private final Executor mExecutor;
private class GetKeyReceiver implements OutcomeReceiver<RemotelyProvisionedKey, Exception> {
IGetKeyCallback mCallback;
GetKeyReceiver(IGetKeyCallback callback) {
mCallback = callback;
}
@Override
public void onResult(RemotelyProvisionedKey result) {
mGetKeyOperations.remove(mCallback.asBinder());
Log.i(TAG, "Successfully fetched key for client " + mCallback.asBinder().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) {
mGetKeyOperations.remove(mCallback.asBinder());
if (e instanceof OperationCanceledException) {
Log.i(TAG, "Operation cancelled for client " + mCallback.asBinder().hashCode());
wrapCallback(mCallback::onCancel);
} else if (e instanceof RkpProxyException) {
Log.e(TAG, "RKP error fetching key for client " + mCallback.asBinder().hashCode()
+ ": "
+ e.getMessage());
RkpProxyException rkpException = (RkpProxyException) e;
wrapCallback(() -> mCallback.onError(toGetKeyError(rkpException),
e.getMessage()));
} else {
Log.e(TAG,
"Unknown error fetching key for client " + mCallback.asBinder().hashCode()
+ ": " + e.getMessage());
wrapCallback(() -> mCallback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN,
e.getMessage()));
}
}
}
private byte toGetKeyError(RkpProxyException exception) {
switch (exception.getError()) {
case RkpProxyException.ERROR_UNKNOWN:
return IGetKeyCallback.ErrorCode.ERROR_UNKNOWN;
case RkpProxyException.ERROR_REQUIRES_SECURITY_PATCH:
return IGetKeyCallback.ErrorCode.ERROR_REQUIRES_SECURITY_PATCH;
case RkpProxyException.ERROR_PENDING_INTERNET_CONNECTIVITY:
return IGetKeyCallback.ErrorCode.ERROR_PENDING_INTERNET_CONNECTIVITY;
case RkpProxyException.ERROR_PERMANENT:
return IGetKeyCallback.ErrorCode.ERROR_PERMANENT;
default:
Log.e(TAG, "Unexpected error code in RkpProxyException", exception);
return IGetKeyCallback.ErrorCode.ERROR_UNKNOWN;
}
}
RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) {
mRegistration = registration;
mExecutor = executor;
}
@Override
public void getKey(int keyId, IGetKeyCallback callback) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (mGetKeyOperations.putIfAbsent(callback.asBinder(), cancellationSignal) != null) {
Log.e(TAG,
"Client can only request one call at a time " + callback.asBinder().hashCode());
throw new IllegalArgumentException(
"Callback is already associated with an existing operation: "
+ callback.asBinder().hashCode());
}
try {
Log.i(TAG, "Fetching key " + keyId + " for client " + callback.asBinder().hashCode());
mRegistration.getKeyAsync(keyId, cancellationSignal, mExecutor,
new GetKeyReceiver(callback));
} catch (Exception e) {
Log.e(TAG,
"getKeyAsync threw an exception for client " + callback.asBinder().hashCode(),
e);
mGetKeyOperations.remove(callback.asBinder());
wrapCallback(() -> callback.onError(IGetKeyCallback.ErrorCode.ERROR_UNKNOWN,
e.getMessage()));
}
}
@Override
public void cancelGetKey(IGetKeyCallback callback) {
CancellationSignal cancellationSignal = mGetKeyOperations.remove(callback.asBinder());
if (cancellationSignal == null) {
throw new IllegalArgumentException(
"Invalid client in cancelGetKey: " + callback.asBinder().hashCode());
}
Log.i(TAG, "Requesting cancellation for client " + callback.asBinder().hashCode());
cancellationSignal.cancel();
}
@Override
public void storeUpgradedKeyAsync(byte[] oldKeyBlob, byte[] newKeyBlob,
IStoreUpgradedKeyCallback callback) {
if (!mStoreUpgradedKeyOperations.add(callback.asBinder())) {
throw new IllegalArgumentException(
"Callback is already associated with an existing operation: "
+ callback.asBinder().hashCode());
}
try {
mRegistration.storeUpgradedKeyAsync(oldKeyBlob, newKeyBlob, mExecutor,
new OutcomeReceiver<>() {
@Override
public void onResult(Void result) {
mStoreUpgradedKeyOperations.remove(callback.asBinder());
wrapCallback(callback::onSuccess);
}
@Override
public void onError(Exception e) {
mStoreUpgradedKeyOperations.remove(callback.asBinder());
wrapCallback(() -> callback.onError(e.getMessage()));
}
});
} catch (Exception e) {
Log.e(TAG, "storeUpgradedKeyAsync threw an exception for client "
+ callback.asBinder().hashCode(), e);
mStoreUpgradedKeyOperations.remove(callback.asBinder());
wrapCallback(() -> callback.onError(e.getMessage()));
}
}
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);
}
}
}