Merge "[CDM perm sync] Send and receive messages integration"

This commit is contained in:
TreeHugger Robot
2022-05-11 05:45:29 +00:00
committed by Android (Google) Code Review
7 changed files with 176 additions and 74 deletions

View File

@@ -103,7 +103,10 @@ public class CompanionApplicationController {
mCompanionServicesRegister.invalidate(userId);
}
void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
/**
* CDM binds to the companion app.
*/
public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
boolean bindImportant) {
if (DEBUG) {
Log.i(TAG, "bind() u" + userId + "/" + packageName
@@ -143,7 +146,10 @@ public class CompanionApplicationController {
}
}
void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
/**
* CDM unbinds the companion app.
*/
public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
final List<CompanionDeviceServiceConnector> serviceConnectors;
@@ -237,9 +243,9 @@ public class CompanionApplicationController {
primaryServiceConnector.postOnDeviceDisappeared(association);
}
/** Pass an encryped secure message to the companion application for transporting. */
/** Pass an encrypted secure message to the companion application for transporting. */
public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
int associationId, @NonNull byte[] message) {
int associationId, int messageId, @NonNull byte[] message) {
if (DEBUG) {
Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
+ " associationId=" + associationId);
@@ -256,7 +262,8 @@ public class CompanionApplicationController {
return;
}
primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, message);
primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
message);
}
private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {

View File

@@ -615,7 +615,15 @@ public class CompanionDeviceManagerService extends SystemService {
+ " message(Base64)=" + Base64.encodeToString(message, 0));
}
mSecureCommsManager.receiveSecureMessage(associationId, message);
AssociationInfo association = getAssociationWithCallerChecks(associationId);
if (association == null) {
throw new IllegalArgumentException("Association with ID " + associationId + " "
+ "does not exist "
+ "or belongs to a different package "
+ "or belongs to a different user");
}
mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
}
@Override

View File

@@ -98,15 +98,10 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
}
void postOnMessageDispatchedFromSystem(int associationId, @NonNull byte[] message) {
// We always use messageId 0 (at least for now).
// Unlike the message itself, the messageId is not encoded, which means that the CDM on the
// other (receiving) end CAN NOT and MUST NOT trust this messageId.
// If CDM needs to pass messageId around to the other side - it should embed it in the
// message body.
void postOnMessageDispatchedFromSystem(int associationId, int messageId,
@NonNull byte[] message) {
post(companionService ->
companionService.onMessageDispatchedFromSystem(
/* messageId*/ 0, associationId, message));
companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
}
/**

View File

@@ -113,7 +113,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
message = sb.toString().getBytes(UTF_8);
}
mSecureCommsManager.sendSecureMessage(associationId, message);
mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
message);
break;
case "simulate-device-appeared":

View File

@@ -16,6 +16,8 @@
package com.android.server.companion.datatransfer;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -39,6 +41,12 @@ public class CompanionMessageProcessor {
private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();
/** Listener for incoming complete messages. */
interface Listener {
/** When a complete message is received from the companion app. */
void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
}
// Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
// too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
// the WCS data processing layer. Refer to
@@ -48,6 +56,9 @@ public class CompanionMessageProcessor {
private final CompanionSecureCommunicationsManager mSecureCommsManager;
@Nullable
private Listener mListener;
// Association id -> (parent id -> received messages)
private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
new HashMap<>();
@@ -56,6 +67,11 @@ public class CompanionMessageProcessor {
public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
mSecureCommsManager = secureCommsManager;
mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
}
public void setListener(@NonNull Listener listener) {
mListener = listener;
}
/**
@@ -72,7 +88,8 @@ public class CompanionMessageProcessor {
for (int i = 0; i < totalMessageCount; i++) {
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(CompanionMessage.ID, parentMessageId + i + 1);
int messageId = parentMessageId + i + 1;
proto.write(CompanionMessage.ID, messageId);
long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
@@ -87,29 +104,68 @@ public class CompanionMessageProcessor {
Slog.i(LOG_TAG, "Sending " + currentData.length + " bytes to " + packageName);
mSecureCommsManager.sendSecureMessage(associationId, proto.getBytes());
mSecureCommsManager.sendSecureMessage(associationId, messageId, proto.getBytes());
}
}
/**
* Process message and store it. If all the messages with the same parent id have been received,
* return the message with combined message data. Otherwise, return null if there's still data
* parts missing.
* Process the message and store it. If all the messages with the same parent id have been
* received, return the message with combined message data. Otherwise, return null if there's
* still data parts missing.
*/
public CompanionMessageInfo processMessage(int messageId, int associationId, byte[] message) {
public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
byte[] message) {
ProtoInputStream proto = new ProtoInputStream(message);
try {
int id = proto.readInt(CompanionMessage.ID);
if (id == messageId) {
// Read proto data
long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
int parentId = proto.readInt(CompanionMessage.PaginationInfo.PARENT_ID);
int page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
int total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
proto.end(paginationToken);
int type = proto.readInt(CompanionMessage.TYPE);
byte[] data = proto.readBytes(CompanionMessage.DATA);
int id = 0;
int parentId = 0;
int page = 0;
int total = 0;
int type = CompanionMessage.UNKNOWN;
byte[] data = null;
// Read proto data
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (proto.getFieldNumber()) {
case (int) CompanionMessage.ID:
id = proto.readInt(CompanionMessage.ID);
break;
case (int) CompanionMessage.PAGINATION_INFO:
long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (proto.getFieldNumber()) {
case (int) CompanionMessage.PaginationInfo.PARENT_ID:
parentId = proto.readInt(
CompanionMessage.PaginationInfo.PARENT_ID);
break;
case (int) CompanionMessage.PaginationInfo.PAGE:
page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
break;
case (int) CompanionMessage.PaginationInfo.TOTAL:
total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
break;
default:
Slog.e(LOG_TAG, "Unexpected field id "
+ proto.getFieldNumber() + " for PaginationInfo.");
break;
}
}
proto.end(paginationToken);
break;
case (int) CompanionMessage.TYPE:
type = proto.readInt(CompanionMessage.TYPE);
break;
case (int) CompanionMessage.DATA:
data = proto.readBytes(CompanionMessage.DATA);
break;
default:
Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
+ " for CompanionMessage.");
break;
}
}
if (id == messageId) {
CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
data);
// Add the message into mAssociationsMessagesMap
@@ -122,28 +178,35 @@ public class CompanionMessageProcessor {
mAssociationsMessagesMap.put(associationId, associationMessages);
// Check if all the messages with the same parentId are received.
if (childMessages.size() == total) {
Slog.i(LOG_TAG, "All [" + total + "] messages are received. Processing.");
childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (int i = 0; i < childMessages.size(); i++) {
stream.write(childMessages.get(i).getData());
}
mAssociationsMessagesMap.remove(parentId);
return new CompanionMessageInfo(parentId, 0, total, type, stream.toByteArray());
mListener.onCompleteMessageReceived(
new CompanionMessageInfo(parentId, 0, total, type,
stream.toByteArray()));
} else {
Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
+ "] messages are received for parentId [" + parentId + "]");
}
} else {
Slog.e(LOG_TAG, "Message id mismatch.");
return null;
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Can't read proto message id: " + messageId + ", message: "
+ new String(message) + ".");
Slog.e(LOG_TAG, "Can't read proto from the message.");
return null;
}
return null;
}
/**
* Find the next parent id. The parent and child ids are incremental.
* Find the next parent id from [1, Integer.MAX_VALUE].
* The parent and child ids are incremental.
*/
private int findNextParentId(int associationId, int totalMessageCount) {
int nextParentId = mNextParentId.getOrDefault(associationId, 1);

View File

@@ -26,6 +26,7 @@ import static com.android.server.companion.Utils.prepareForIpc;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -91,6 +92,7 @@ public class SystemDataTransferProcessor {
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mCompanionMessageProcessor = companionMessageProcessor;
mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
}
/**
@@ -201,44 +203,36 @@ public class SystemDataTransferProcessor {
}
/**
* Process message reported by the companion app.
* Process a complete decrypted message reported by the companion app.
*/
public void processMessage(String packageName, int userId, int associationId,
int messageId, byte[] message) {
Slog.i(LOG_TAG, "Start processing message [" + messageId + "] from package ["
+ packageName + "] userId [" + userId + "] associationId [" + associationId + "]");
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
if (association == null) {
throw new DeviceNotAssociatedException("Association "
+ associationId + " is not associated with the app " + packageName
+ " for user " + userId);
public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) {
switch (completeMessage.getType()) {
case CompanionMessage.PERMISSION_SYNC:
processPermissionSyncMessage(completeMessage);
break;
default:
Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType()
+ "]. Unable to process.");
}
}
PermissionsUtils.enforceCallerIsSystemOr(userId, packageName);
private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser());
try {
XmlPullParser parser = Xml.newPullParser();
ByteArrayInputStream stream = new ByteArrayInputStream(
messageInfo.getData());
parser.setInput(stream, UTF_8.name());
CompanionMessageInfo completeMessage = mCompanionMessageProcessor.processMessage(messageId,
associationId, message);
if (completeMessage != null) {
if (completeMessage.getType() == CompanionMessage.PERMISSION_SYNC) {
// Start applying permissions
BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId));
try {
XmlPullParser parser = Xml.newPullParser();
ByteArrayInputStream stream = new ByteArrayInputStream(
completeMessage.getData());
parser.setInput(stream, UTF_8.name());
backupHelper.restoreState(parser);
} catch (IOException e) {
Slog.e(LOG_TAG, "IOException reading message: "
+ new String(completeMessage.getData()));
} catch (XmlPullParserException e) {
Slog.e(LOG_TAG, "Error parsing message: "
+ new String(completeMessage.getData()));
}
}
backupHelper.restoreState(parser);
} catch (IOException e) {
Slog.e(LOG_TAG, "IOException reading message: "
+ new String(messageInfo.getData()));
} catch (XmlPullParserException e) {
Slog.e(LOG_TAG, "Error parsing message: "
+ new String(messageInfo.getData()));
}
}

View File

@@ -17,10 +17,12 @@
package com.android.server.companion.securechannel;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.companion.AssociationInfo;
import android.util.Base64;
import android.util.Log;
import android.util.Slog;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionApplicationController;
@@ -31,9 +33,18 @@ public class CompanionSecureCommunicationsManager {
static final String TAG = "CompanionDevice_SecureComms";
static final boolean DEBUG = false;
/** Listener for incoming decrypted messages. */
public interface Listener {
/** When an incoming message is decrypted. */
void onDecryptedMessageReceived(int messageId, int associationId, byte[] message);
}
private final AssociationStore mAssociationStore;
private final CompanionApplicationController mCompanionAppController;
@Nullable
private Listener mListener;
/** Constructor */
public CompanionSecureCommunicationsManager(AssociationStore associationStore,
CompanionApplicationController companionApplicationController) {
@@ -41,13 +52,18 @@ public class CompanionSecureCommunicationsManager {
mCompanionAppController = companionApplicationController;
}
public void setListener(@NonNull Listener listener) {
mListener = listener;
}
/**
* Send a data to the associated companion device via secure channel (establishing one if
* needed).
* @param associationId associationId of the "recipient" companion device.
* @param messageId id of the message
* @param message data to be sent securely.
*/
public void sendSecureMessage(int associationId, @NonNull byte[] message) {
public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) {
if (DEBUG) {
Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
+ " message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
@@ -62,13 +78,28 @@ public class CompanionSecureCommunicationsManager {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
// Bind to the app if it hasn't been bound.
if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
throw new IllegalStateException("u" + userId + "\\" + packageName + " is NOT bound");
Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName
+ "] is not bound. Binding it now to send a secure message.");
mCompanionAppController.bindCompanionApplication(userId, packageName,
association.isSelfManaged());
// TODO(b/202926196): implement: encrypt and pass on the companion application for
// transporting
mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
message);
Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName
+ "]");
mCompanionAppController.unbindCompanionApplication(userId, packageName);
}
// TODO(b/202926196): implement: encrypt and pass on the companion application for
// transporting
mCompanionAppController.dispatchMessage(userId, packageName, associationId, message);
mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
message);
}
/**
@@ -76,12 +107,15 @@ public class CompanionSecureCommunicationsManager {
* @param associationId associationId of the "sender" companion device.
* @param encryptedMessage data.
*/
public void receiveSecureMessage(int associationId, @NonNull byte[] encryptedMessage) {
public void receiveSecureMessage(int messageId, int associationId,
@NonNull byte[] encryptedMessage) {
if (DEBUG) {
Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
+ " message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\"");
}
// TODO(b/202926196): implement: decrypt and dispatch.
// TODO(b/202926196): implement: decrypt and dispatch
mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage);
}
}