Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit da8f9e4c authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

Introduce CompanionSecureCommunicationsManager

Add com.android.server.companion.securechannel and the outline for the
CompanionSecureCommunicationsManager class.

Bug: 202926196
Bug: 198485833
Test: atest CtsCompanionDeviceManagerCoreTestCases
Ignore-AOSP-First: new OWNERS file (not yet in AOSP)
Change-Id: I7c4d88e65430d31332df69d0d9e6b087c2805df6
parent d40ce7f8
Loading
Loading
Loading
Loading
+27 −2
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ import java.util.Map;
 * @see CompanionDeviceServiceConnector
 */
@SuppressLint("LongLogTag")
class CompanionApplicationController {
public class CompanionApplicationController {
    static final boolean DEBUG = false;
    private static final String TAG = "CompanionDevice_ApplicationController";

@@ -164,7 +164,10 @@ class CompanionApplicationController {
        }
    }

    boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
    /**
     * @return whether the companion application is bound now.
     */
    public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
        synchronized (mBoundCompanionApplications) {
            return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
        }
@@ -234,6 +237,28 @@ class CompanionApplicationController {
        primaryServiceConnector.postOnDeviceDisappeared(association);
    }

    /** Pass an encryped secure message to the companion application for transporting. */
    public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
            int associationId, @NonNull byte[] message) {
        if (DEBUG) {
            Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
                    + " associationId=" + associationId);
        }

        final CompanionDeviceServiceConnector primaryServiceConnector =
                getPrimaryServiceConnector(userId, packageName);
        if (primaryServiceConnector == null) {
            if (DEBUG) {
                Log.e(TAG, "dispatchMessage(): "
                        + "u" + userId + "/" + packageName + " is NOT bound.");
                Log.d(TAG, "Stacktrace", new Throwable());
            }
            return;
        }

        primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, message);
    }

    private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
        if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName);

+17 −8
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.text.BidiFormatter;
import android.util.ArraySet;
import android.util.Base64;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
@@ -102,6 +103,7 @@ import com.android.server.SystemService;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
import com.android.server.pm.UserManagerInternal;

import java.io.File;
@@ -137,6 +139,7 @@ public class CompanionDeviceManagerService extends SystemService {
    private SystemDataTransferProcessor mSystemDataTransferProcessor;
    private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
    private CompanionApplicationController mCompanionAppController;
    private CompanionSecureCommunicationsManager mSecureCommsManager;

    private final ActivityManagerInternal mAmInternal;
    private final IAppOpsService mAppOpsManager;
@@ -173,6 +176,8 @@ public class CompanionDeviceManagerService extends SystemService {

    @Override
    public void onStart() {
        final Context context = getContext();

        mPersistentStore = new PersistentDataStore();

        loadAssociationsFromDisk();
@@ -185,10 +190,10 @@ public class CompanionDeviceManagerService extends SystemService {
                /* cdmService */this, mAssociationStore);
        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
                mSystemDataTransferRequestStore);

        final Context context = getContext();
        mCompanionAppController = new CompanionApplicationController(
                context, mApplicationControllerCallback);
        mSecureCommsManager = new CompanionSecureCommunicationsManager(
                mAssociationStore, mCompanionAppController);

        // Publish "binder" service.
        final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -257,7 +262,7 @@ public class CompanionDeviceManagerService extends SystemService {
        if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);

        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
        if (DEBUG) Log.d(TAG, "  association=" + associationId);
        if (DEBUG) Log.d(TAG, "  association=" + association);

        if (!association.shouldBindWhenPresent()) return;

@@ -279,7 +284,7 @@ public class CompanionDeviceManagerService extends SystemService {
        if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);

        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
        if (DEBUG) Log.d(TAG, "  association=" + associationId);
        if (DEBUG) Log.d(TAG, "  association=" + association);

        final int userId = association.getUserId();
        final String packageName = association.getPackageName();
@@ -603,9 +608,13 @@ public class CompanionDeviceManagerService extends SystemService {
        }

        @Override
        public void dispatchMessage(int messageId, int associationId, byte[] message)
                throws RemoteException {
            // TODO(b/199427116): implement.
        public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
            if (DEBUG) {
                Log.i(TAG, "dispatchMessage() associationId=" + associationId + "\n"
                        + " message(Base64)=" + Base64.encodeToString(message, 0));
            }

            mSecureCommsManager.receiveSecureMessage(associationId, message);
        }

        @Override
@@ -740,7 +749,7 @@ public class CompanionDeviceManagerService extends SystemService {
                throws RemoteException {
            enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
            new CompanionDeviceShellCommand(
                    CompanionDeviceManagerService.this, mAssociationStore)
                    CompanionDeviceManagerService.this, mAssociationStore, mSecureCommsManager)
                    .exec(this, in, out, err, args, callback, resultReceiver);
        }

+11 −0
Original line number Diff line number Diff line
@@ -98,6 +98,17 @@ 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.
        post(companionService ->
                companionService.onMessageDispatchedFromSystem(
                        /* messageId*/ 0, associationId, message));
    }

    /**
     * Post "unbind" job, which will run *after* all previously posted jobs complete.
     *
+48 −14
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package com.android.server.companion;

import static java.nio.charset.StandardCharsets.UTF_8;

import android.companion.AssociationInfo;
import android.os.ShellCommand;
import android.util.Log;
import android.util.Slog;
import android.util.Base64;

import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;

import java.io.PrintWriter;
import java.util.List;
@@ -29,11 +32,14 @@ class CompanionDeviceShellCommand extends ShellCommand {

    private final CompanionDeviceManagerService mService;
    private final AssociationStore mAssociationStore;
    private final CompanionSecureCommunicationsManager mSecureCommsManager;

    CompanionDeviceShellCommand(CompanionDeviceManagerService service,
            AssociationStore associationStore) {
            AssociationStore associationStore,
            CompanionSecureCommunicationsManager secureCommsManager) {
        mService = service;
        mAssociationStore = associationStore;
        mSecureCommsManager = secureCommsManager;
    }

    @Override
@@ -42,7 +48,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
        try {
            switch (cmd) {
                case "list": {
                    final int userId = getNextArgInt();
                    final int userId = getNextIntArgRequired();
                    final List<AssociationInfo> associationsForUser =
                            mAssociationStore.getAssociationsForUser(userId);
                    for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
                break;

                case "associate": {
                    int userId = getNextArgInt();
                    int userId = getNextIntArgRequired();
                    String packageName = getNextArgRequired();
                    String address = getNextArgRequired();
                    mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
                break;

                case "disassociate": {
                    final int userId = getNextArgInt();
                    final int userId = getNextIntArgRequired();
                    final String packageName = getNextArgRequired();
                    final String address = getNextArgRequired();
                    final AssociationInfo association =
@@ -74,24 +80,50 @@ class CompanionDeviceShellCommand extends ShellCommand {
                }
                break;

                case "clear-association-memory-cache": {
                case "clear-association-memory-cache":
                    mService.persistState();
                    mService.loadAssociationsFromDisk();
                    break;

                case "send-secure-message":
                    final int associationId = getNextIntArgRequired();
                    final byte[] message;

                    // The message should be either a UTF-8 String or Base64-encoded data.
                    final boolean isBase64 = "--base64".equals(getNextOption());
                    if (isBase64) {
                        final String base64encodedMessage = getNextArgRequired();
                        message = Base64.decode(base64encodedMessage, 0);
                    } else {
                        // We treat the rest of the command as the message, which should contain at
                        // least one word (hence getNextArg_Required() below), but there may be
                        // more.
                        final StringBuilder sb = new StringBuilder(getNextArgRequired());
                        // Pick up the rest.
                        for (String word : peekRemainingArgs()) {
                            sb.append(" ").append(word);
                        }
                        // And now convert to byte[]...
                        message = sb.toString().getBytes(UTF_8);
                    }

                    mSecureCommsManager.sendSecureMessage(associationId, message);
                    break;

                default:
                    return handleDefaultCommands(cmd);
            }
            return 0;
        } catch (Throwable t) {
            Slog.e(TAG, "Error running a command: $ " + cmd, t);
            getErrPrintWriter().println(Log.getStackTraceString(t));
        } catch (Throwable e) {
            final PrintWriter errOut = getErrPrintWriter();
            errOut.println();
            errOut.println("Exception occurred while executing '" + cmd + "':");
            e.printStackTrace(errOut);
            return 1;
        }
        return 0;
    }

    private int getNextArgInt() {
    private int getNextIntArgRequired() {
        return Integer.parseInt(getNextArgRequired());
    }

@@ -107,6 +139,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
        pw.println("      Create a new Association.");
        pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
        pw.println("      Remove an existing Association.");
        pw.println("  send-secure-message ASSOCIATION_ID [--base64] MESSAGE");
        pw.println("      Send a secure message to an associated companion device.");
        pw.println("  clear-association-memory-cache");
        pw.println("      Clear the in-memory association cache and reload all association "
                + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+87 −0
Original line number Diff line number Diff line
/*
 * 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.companion.securechannel;

import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.companion.AssociationInfo;
import android.util.Base64;
import android.util.Log;

import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionApplicationController;

/** Secure Comms Manager */
@SuppressLint("LongLogTag")
public class CompanionSecureCommunicationsManager {
    static final String TAG = "CompanionDevice_SecureComms";
    static final boolean DEBUG = false;

    private final AssociationStore mAssociationStore;
    private final CompanionApplicationController mCompanionAppController;

    /** Constructor */
    public CompanionSecureCommunicationsManager(AssociationStore associationStore,
            CompanionApplicationController companionApplicationController) {
        mAssociationStore = associationStore;
        mCompanionAppController = companionApplicationController;
    }

    /**
     * Send a data to the associated companion device via secure channel (establishing one if
     * needed).
     * @param associationId associationId of the "recipient" companion device.
     * @param message data to be sent securely.
     */
    public void sendSecureMessage(int associationId, @NonNull byte[] message) {
        if (DEBUG) {
            Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
                    + "   message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
        }

        final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
        if (association == null) {
            throw new IllegalArgumentException(
                    "Association with ID " + associationId + " does not exist");
        }
        if (DEBUG) Log.d(TAG, "  association=" + association);

        final int userId = association.getUserId();
        final String packageName = association.getPackageName();
        if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
            throw new IllegalStateException("u" + userId + "\\" + packageName + " is NOT bound");
        }

        // TODO(b/202926196): implement: encrypt and pass on the companion application for
        //  transporting
        mCompanionAppController.dispatchMessage(userId, packageName, associationId, message);
    }

    /**
     * Decrypt and dispatch message received from an associated companion device.
     * @param associationId associationId of the "sender" companion device.
     * @param encryptedMessage data.
     */
    public void receiveSecureMessage(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.
    }
}
Loading