1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wearable; 18 19 import android.annotation.NonNull; 20 import android.companion.AssociationInfo; 21 import android.companion.AssociationRequest; 22 import android.companion.CompanionDeviceManager; 23 import android.os.Binder; 24 import android.os.ParcelFileDescriptor; 25 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 26 import android.os.ParcelFileDescriptor.AutoCloseOutputStream; 27 import android.util.Slog; 28 29 import com.android.internal.annotations.GuardedBy; 30 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.concurrent.Executor; 37 import java.util.concurrent.ExecutorService; 38 import java.util.concurrent.Executors; 39 import java.util.concurrent.RejectedExecutionException; 40 import java.util.concurrent.atomic.AtomicBoolean; 41 import java.util.function.BiConsumer; 42 import java.util.function.Consumer; 43 44 /** 45 * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing. 46 * 47 * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the 48 * connection to the CompanionDeviceManager via {@link 49 * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will 50 * create an encrypted channel using the provided connection as the raw underlying connection. The 51 * wearable device is expected to attach its side of the raw connection to its 52 * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two 53 * devices can perform attestation and set up the encrypted channel. Attestation requirements are 54 * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}. 55 * 56 * <p>When the encrypted channel is available, it will be provided to the caller via the 57 * SecureTransportListener. 58 */ 59 final class WearableSensingSecureChannel { 60 61 /** A listener for secure transport and its error signal. */ 62 interface SecureTransportListener { 63 64 /** Called when the secure transport is available. */ onSecureTransportAvailable(ParcelFileDescriptor secureTransport)65 void onSecureTransportAvailable(ParcelFileDescriptor secureTransport); 66 67 /** 68 * Called when there is a non-recoverable error. The secure channel will be automatically 69 * closed. 70 */ onError()71 void onError(); 72 } 73 74 private static final String TAG = WearableSensingSecureChannel.class.getSimpleName(); 75 private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM"; 76 // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener 77 private static final int READ_BUFFER_SIZE = 8192; 78 79 private final Object mLock = new Object(); 80 // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the 81 // corresponding cleanup methods in CDM have been called (e.g. 82 // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after 83 // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions. 84 private final SoftShutdownExecutor mMessageFromWearableExecutor = 85 new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); 86 private final SoftShutdownExecutor mMessageToWearableExecutor = 87 new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); 88 private final SoftShutdownExecutor mLightWeightExecutor = 89 new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); 90 private final CompanionDeviceManager mCompanionDeviceManager; 91 private final ParcelFileDescriptor mUnderlyingTransport; 92 private final SecureTransportListener mSecureTransportListener; 93 private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false); 94 private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener = 95 this::onTransportsChanged; 96 private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived; 97 private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener 98 // read input received from the ParcelFileDescriptor returned to mSecureTransportListener 99 private final InputStream mLocalIn; 100 // send output to the ParcelFileDescriptor returned to mSecureTransportListener 101 private final OutputStream mLocalOut; 102 103 @GuardedBy("mLock") 104 private boolean mClosed = false; 105 106 private Integer mAssociationId = null; 107 108 /** 109 * Creates a WearableSensingSecureChannel. When the secure transport is ready, 110 * secureTransportListener will be notified. 111 * 112 * @param companionDeviceManager The CompanionDeviceManager system service. 113 * @param underlyingTransport The underlying transport to create the secure channel on. 114 * @param secureTransportListener The listener to receive the secure transport when it is ready. 115 * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair. 116 */ create( @onNull CompanionDeviceManager companionDeviceManager, @NonNull ParcelFileDescriptor underlyingTransport, @NonNull SecureTransportListener secureTransportListener)117 static WearableSensingSecureChannel create( 118 @NonNull CompanionDeviceManager companionDeviceManager, 119 @NonNull ParcelFileDescriptor underlyingTransport, 120 @NonNull SecureTransportListener secureTransportListener) 121 throws IOException { 122 Objects.requireNonNull(companionDeviceManager); 123 Objects.requireNonNull(underlyingTransport); 124 Objects.requireNonNull(secureTransportListener); 125 ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(); 126 WearableSensingSecureChannel channel = 127 new WearableSensingSecureChannel( 128 companionDeviceManager, 129 underlyingTransport, 130 secureTransportListener, 131 pair[0], 132 pair[1]); 133 channel.initialize(); 134 return channel; 135 } 136 WearableSensingSecureChannel( CompanionDeviceManager companionDeviceManager, ParcelFileDescriptor underlyingTransport, SecureTransportListener secureTransportListener, ParcelFileDescriptor remoteFd, ParcelFileDescriptor localFd)137 private WearableSensingSecureChannel( 138 CompanionDeviceManager companionDeviceManager, 139 ParcelFileDescriptor underlyingTransport, 140 SecureTransportListener secureTransportListener, 141 ParcelFileDescriptor remoteFd, 142 ParcelFileDescriptor localFd) { 143 mCompanionDeviceManager = companionDeviceManager; 144 mUnderlyingTransport = underlyingTransport; 145 mSecureTransportListener = secureTransportListener; 146 mRemoteFd = remoteFd; 147 mLocalIn = new AutoCloseInputStream(localFd); 148 mLocalOut = new AutoCloseOutputStream(localFd); 149 } 150 initialize()151 private void initialize() { 152 final long originalCallingIdentity = Binder.clearCallingIdentity(); 153 try { 154 Slog.d(TAG, "Requesting CDM association."); 155 mCompanionDeviceManager.associate( 156 new AssociationRequest.Builder() 157 .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME) 158 .setSelfManaged(true) 159 .build(), 160 mLightWeightExecutor, 161 new CompanionDeviceManager.Callback() { 162 @Override 163 public void onAssociationCreated(AssociationInfo associationInfo) { 164 WearableSensingSecureChannel.this.onAssociationCreated( 165 associationInfo.getId()); 166 } 167 168 @Override 169 public void onFailure(CharSequence error) { 170 Slog.e( 171 TAG, 172 "Failed to create CompanionDeviceManager association: " 173 + error); 174 onError(); 175 } 176 }); 177 } finally { 178 Binder.restoreCallingIdentity(originalCallingIdentity); 179 } 180 } 181 onAssociationCreated(int associationId)182 private void onAssociationCreated(int associationId) { 183 Slog.i(TAG, "CDM association created."); 184 synchronized (mLock) { 185 if (mClosed) { 186 return; 187 } 188 mAssociationId = associationId; 189 mCompanionDeviceManager.addOnMessageReceivedListener( 190 mMessageFromWearableExecutor, 191 CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE, 192 mOnMessageReceivedListener); 193 mCompanionDeviceManager.addOnTransportsChangedListener( 194 mLightWeightExecutor, mOnTransportsChangedListener); 195 mCompanionDeviceManager.attachSystemDataTransport( 196 associationId, 197 new AutoCloseInputStream(mUnderlyingTransport), 198 new AutoCloseOutputStream(mUnderlyingTransport)); 199 } 200 } 201 onTransportsChanged(List<AssociationInfo> associationInfos)202 private void onTransportsChanged(List<AssociationInfo> associationInfos) { 203 synchronized (mLock) { 204 if (mClosed) { 205 return; 206 } 207 if (mAssociationId == null) { 208 Slog.e(TAG, "mAssociationId is null when transport changed"); 209 return; 210 } 211 } 212 // Do not call onTransportAvailable() or onError() when holding the lock because it can 213 // cause a deadlock if the callback holds another lock. 214 boolean transportAvailable = 215 associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId); 216 if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) { 217 onTransportAvailable(); 218 } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) { 219 Slog.i(TAG, "CDM transport is detached. This is not recoverable."); 220 onError(); 221 } 222 } 223 onTransportAvailable()224 private void onTransportAvailable() { 225 // Start sending data received from the remote stream to the wearable. 226 Slog.i(TAG, "Transport available"); 227 mMessageToWearableExecutor.execute( 228 () -> { 229 int[] associationIdsToSendMessageTo = new int[] {mAssociationId}; 230 byte[] buffer = new byte[READ_BUFFER_SIZE]; 231 int readLen; 232 try { 233 while ((readLen = mLocalIn.read(buffer)) != -1) { 234 byte[] data = new byte[readLen]; 235 System.arraycopy(buffer, 0, data, 0, readLen); 236 Slog.v(TAG, "Sending message to wearable"); 237 mCompanionDeviceManager.sendMessage( 238 CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE, 239 data, 240 associationIdsToSendMessageTo); 241 } 242 } catch (IOException e) { 243 Slog.i(TAG, "IOException while reading from remote stream."); 244 onError(); 245 return; 246 } 247 Slog.i( 248 TAG, 249 "Reached EOF when reading from remote stream. Reporting this as an" 250 + " error."); 251 onError(); 252 }); 253 mSecureTransportListener.onSecureTransportAvailable(mRemoteFd); 254 } 255 onMessageReceived(int associationIdForMessage, byte[] data)256 private void onMessageReceived(int associationIdForMessage, byte[] data) { 257 if (associationIdForMessage == mAssociationId) { 258 Slog.v(TAG, "Received message from wearable."); 259 try { 260 mLocalOut.write(data); 261 mLocalOut.flush(); 262 } catch (IOException e) { 263 Slog.i( 264 TAG, 265 "IOException when writing to remote stream. Closing the secure channel."); 266 onError(); 267 } 268 } else { 269 Slog.v( 270 TAG, 271 "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for" 272 + " another association. Ignoring the message."); 273 } 274 } 275 onError()276 private void onError() { 277 synchronized (mLock) { 278 if (mClosed) { 279 return; 280 } 281 } 282 mSecureTransportListener.onError(); 283 close(); 284 } 285 286 /** Closes this secure channel and releases all resources. */ close()287 void close() { 288 synchronized (mLock) { 289 if (mClosed) { 290 return; 291 } 292 Slog.i(TAG, "Closing WearableSensingSecureChannel."); 293 mClosed = true; 294 if (mAssociationId != null) { 295 final long originalCallingIdentity = Binder.clearCallingIdentity(); 296 try { 297 mCompanionDeviceManager.removeOnTransportsChangedListener( 298 mOnTransportsChangedListener); 299 mCompanionDeviceManager.removeOnMessageReceivedListener( 300 CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE, 301 mOnMessageReceivedListener); 302 mCompanionDeviceManager.detachSystemDataTransport(mAssociationId); 303 mCompanionDeviceManager.disassociate(mAssociationId); 304 } finally { 305 Binder.restoreCallingIdentity(originalCallingIdentity); 306 } 307 } 308 try { 309 mLocalIn.close(); 310 } catch (IOException ex) { 311 Slog.e(TAG, "Encountered IOException when closing local input stream.", ex); 312 } 313 try { 314 mLocalOut.close(); 315 } catch (IOException ex) { 316 Slog.e(TAG, "Encountered IOException when closing local output stream.", ex); 317 } 318 mMessageFromWearableExecutor.shutdown(); 319 mMessageToWearableExecutor.shutdown(); 320 mLightWeightExecutor.shutdown(); 321 } 322 } 323 324 /** 325 * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a 326 * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown. 327 */ 328 private static class SoftShutdownExecutor implements Executor { 329 330 private final ExecutorService mExecutorService; 331 SoftShutdownExecutor(ExecutorService executorService)332 SoftShutdownExecutor(ExecutorService executorService) { 333 mExecutorService = executorService; 334 } 335 336 @Override execute(Runnable runnable)337 public void execute(Runnable runnable) { 338 try { 339 mExecutorService.execute(runnable); 340 } catch (RejectedExecutionException ex) { 341 Slog.d(TAG, "Received new runnable after shutdown. Ignoring."); 342 } 343 } 344 345 /** Shutdown the underlying ExecutorService. */ shutdown()346 void shutdown() { 347 mExecutorService.shutdown(); 348 } 349 } 350 } 351