diff options
7 files changed, 177 insertions, 75 deletions
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 943599cd83c7..0deb03923663 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -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) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index fc3a12dd5fb7..ca4f76d8f416 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -626,7 +626,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 diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index ef39b4b13f22..b9b29ffb4e86 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -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)); } /** diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index c071bf640bd8..3d8e87ba6378 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -107,7 +107,8 @@ class CompanionDeviceShellCommand extends ShellCommand { message = sb.toString().getBytes(UTF_8); } - mSecureCommsManager.sendSecureMessage(associationId, message); + mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0, + message); break; default: diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java index 826bafa05441..e83e634ea859 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java @@ -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); diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index cafa78f7e2ab..d39fa2acd978 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -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); - - 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())); - } - } + 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()); + + 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())); } } diff --git a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java index 625360ebd417..16a74ecd2068 100644 --- a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java +++ b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java @@ -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); } } |