1 /* 2 * Copyright (C) 2020 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.car.connecteddevice.ble; 18 19 import static com.android.car.connecteddevice.util.SafeLog.logd; 20 import static com.android.car.connecteddevice.util.SafeLog.loge; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.car.encryptionrunner.EncryptionRunner; 26 import android.car.encryptionrunner.EncryptionRunnerFactory; 27 import android.car.encryptionrunner.HandshakeException; 28 import android.car.encryptionrunner.HandshakeMessage; 29 import android.car.encryptionrunner.HandshakeMessage.HandshakeState; 30 import android.car.encryptionrunner.Key; 31 32 import com.android.car.connecteddevice.BleStreamProtos.BleOperationProto.OperationType; 33 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage; 34 import com.android.car.connecteddevice.util.ByteUtils; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.security.SignatureException; 40 import java.util.UUID; 41 import java.util.concurrent.atomic.AtomicReference; 42 import java.util.function.Consumer; 43 44 /** 45 * Establishes a secure channel with {@link EncryptionRunner} over {@link BleDeviceMessageStream} as 46 * server side, sends and receives messages securely after the secure channel has been established. 47 */ 48 class SecureBleChannel { 49 50 @Retention(RetentionPolicy.SOURCE) 51 @IntDef(prefix = { "CHANNEL_ERROR" }, 52 value = { 53 CHANNEL_ERROR_INVALID_HANDSHAKE, 54 CHANNEL_ERROR_INVALID_MSG, 55 CHANNEL_ERROR_INVALID_DEVICE_ID, 56 CHANNEL_ERROR_INVALID_VERIFICATION, 57 CHANNEL_ERROR_INVALID_STATE, 58 CHANNEL_ERROR_INVALID_ENCRYPTION_KEY, 59 CHANNEL_ERROR_STORAGE_ERROR 60 } 61 ) 62 @interface ChannelError { } 63 64 /** Indicates an error during a Handshake of EncryptionRunner. */ 65 static final int CHANNEL_ERROR_INVALID_HANDSHAKE = 0; 66 /** Received an invalid handshake message or has an invalid handshake message to send. */ 67 static final int CHANNEL_ERROR_INVALID_MSG = 1; 68 /** Unable to retrieve a valid id. */ 69 static final int CHANNEL_ERROR_INVALID_DEVICE_ID = 2; 70 /** Unable to get verification code or there's a error during pin verification. */ 71 static final int CHANNEL_ERROR_INVALID_VERIFICATION = 3; 72 /** Encountered an unexpected handshake state. */ 73 static final int CHANNEL_ERROR_INVALID_STATE = 4; 74 /** Failed to get a valid previous/new encryption key.*/ 75 static final int CHANNEL_ERROR_INVALID_ENCRYPTION_KEY = 5; 76 /** Failed to save the encryption key*/ 77 static final int CHANNEL_ERROR_STORAGE_ERROR = 6; 78 79 @VisibleForTesting 80 static final byte[] CONFIRMATION_SIGNAL = "True".getBytes(); 81 82 private static final String TAG = "SecureBleChannel"; 83 84 private final BleDeviceMessageStream mStream; 85 86 private final ConnectedDeviceStorage mStorage; 87 88 private final boolean mIsReconnect; 89 90 private final EncryptionRunner mEncryptionRunner; 91 92 private final AtomicReference<Key> mEncryptionKey = new AtomicReference<>(); 93 94 private @HandshakeState int mState = HandshakeState.UNKNOWN; 95 96 private String mDeviceId; 97 98 private Callback mCallback; 99 100 private ShowVerificationCodeListener mShowVerificationCodeListener; 101 SecureBleChannel(@onNull BleDeviceMessageStream stream, @NonNull ConnectedDeviceStorage storage)102 SecureBleChannel(@NonNull BleDeviceMessageStream stream, 103 @NonNull ConnectedDeviceStorage storage) { 104 this(stream, storage, /* isReconnect = */ true, EncryptionRunnerFactory.newRunner()); 105 } 106 SecureBleChannel(@onNull BleDeviceMessageStream stream, @NonNull ConnectedDeviceStorage storage, boolean isReconnect, @NonNull EncryptionRunner encryptionRunner)107 SecureBleChannel(@NonNull BleDeviceMessageStream stream, 108 @NonNull ConnectedDeviceStorage storage, boolean isReconnect, 109 @NonNull EncryptionRunner encryptionRunner) { 110 mStream = stream; 111 mStorage = storage; 112 mIsReconnect = isReconnect; 113 mEncryptionRunner = encryptionRunner; 114 mEncryptionRunner.setIsReconnect(isReconnect); 115 mStream.setMessageReceivedListener(mStreamListener); 116 } 117 processHandshake(@onNull byte[] message)118 private void processHandshake(@NonNull byte[] message) throws HandshakeException { 119 switch (mState) { 120 case HandshakeState.UNKNOWN: 121 processHandshakeUnknown(message); 122 break; 123 case HandshakeState.IN_PROGRESS: 124 processHandshakeInProgress(message); 125 break; 126 case HandshakeState.RESUMING_SESSION: 127 processHandshakeResumingSession(message); 128 break; 129 default: 130 loge(TAG, "Encountered unexpected handshake state: " + mState + ". Received " 131 + "message: " + ByteUtils.byteArrayToHexString(message) + "."); 132 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE); 133 } 134 } 135 processHandshakeUnknown(@onNull byte[] message)136 private void processHandshakeUnknown(@NonNull byte[] message) throws HandshakeException { 137 if (mDeviceId != null) { 138 logd(TAG, "Responding to handshake init request."); 139 HandshakeMessage handshakeMessage = mEncryptionRunner.respondToInitRequest(message); 140 mState = handshakeMessage.getHandshakeState(); 141 sendHandshakeMessage(handshakeMessage.getNextMessage()); 142 return; 143 } 144 UUID deviceId = ByteUtils.bytesToUUID(message); 145 if (deviceId == null) { 146 loge(TAG, "Received invalid device id. Ignoring."); 147 return; 148 } 149 mDeviceId = deviceId.toString(); 150 if (mIsReconnect && !hasEncryptionKey(mDeviceId)) { 151 loge(TAG, "Attempted to reconnect device but no key found. Aborting secure channel."); 152 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID); 153 return; 154 } 155 notifyCallback(callback -> callback.onDeviceIdReceived(mDeviceId)); 156 sendUniqueIdToClient(); 157 } 158 processHandshakeInProgress(@onNull byte[] message)159 private void processHandshakeInProgress(@NonNull byte[] message) throws HandshakeException { 160 logd(TAG, "Continuing handshake."); 161 HandshakeMessage handshakeMessage = mEncryptionRunner.continueHandshake(message); 162 mState = handshakeMessage.getHandshakeState(); 163 164 boolean isValidStateForAssociation = !mIsReconnect 165 && mState == HandshakeState.VERIFICATION_NEEDED; 166 boolean isValidStateForReconnect = mIsReconnect 167 && mState == HandshakeState.RESUMING_SESSION; 168 if (!isValidStateForAssociation && !isValidStateForReconnect) { 169 loge(TAG, "processHandshakeInProgress: Encountered unexpected handshake state: " 170 + mState + "."); 171 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE); 172 return; 173 } 174 175 if (!isValidStateForAssociation) { 176 return; 177 } 178 179 String code = handshakeMessage.getVerificationCode(); 180 if (code == null) { 181 loge(TAG, "Unable to get verification code."); 182 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION); 183 return; 184 } 185 186 if (mShowVerificationCodeListener != null) { 187 logd(TAG, "Showing pairing code: " + code); 188 mShowVerificationCodeListener.showVerificationCode(code); 189 } 190 } 191 processHandshakeResumingSession(@onNull byte[] message)192 private void processHandshakeResumingSession(@NonNull byte[] message) 193 throws HandshakeException { 194 logd(TAG, "Start reconnection authentication."); 195 if (mDeviceId == null) { 196 loge(TAG, "processHandshakeResumingSession: Unable to resume session, device id is " 197 + "null."); 198 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID); 199 return; 200 } 201 202 byte[] previousKey = mStorage.getEncryptionKey(mDeviceId); 203 if (previousKey == null) { 204 loge(TAG, "Unable to resume session, previous key is null."); 205 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY); 206 return; 207 } 208 209 HandshakeMessage handshakeMessage = mEncryptionRunner.authenticateReconnection(message, 210 previousKey); 211 mState = handshakeMessage.getHandshakeState(); 212 if (mState != HandshakeState.FINISHED) { 213 loge(TAG, "Unable to resume session, unexpected next handshake state: " + mState + "."); 214 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE); 215 return; 216 } 217 218 Key newKey = handshakeMessage.getKey(); 219 if (newKey == null) { 220 loge(TAG, "Unable to resume session, new key is null."); 221 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY); 222 return; 223 } 224 225 logd(TAG, "Saved new key for reconnection."); 226 mStorage.saveEncryptionKey(mDeviceId, newKey.asBytes()); 227 mEncryptionKey.set(newKey); 228 sendServerAuthToClient(handshakeMessage.getNextMessage()); 229 notifyCallback(callback -> callback.onSecureChannelEstablished()); 230 } 231 sendUniqueIdToClient()232 private void sendUniqueIdToClient() { 233 UUID uniqueId = mStorage.getUniqueId(); 234 DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null, 235 /* isMessageEncrypted = */ false, ByteUtils.uuidToBytes(uniqueId)); 236 logd(TAG, "Sending car's device id of " + uniqueId + " to device."); 237 mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE); 238 } 239 hasEncryptionKey(@onNull String id)240 private boolean hasEncryptionKey(@NonNull String id) { 241 return mStorage.getEncryptionKey(id) != null; 242 } 243 sendHandshakeMessage(@ullable byte[] message)244 private void sendHandshakeMessage(@Nullable byte[] message) { 245 if (message == null) { 246 loge(TAG, "Unable to send next handshake message, message is null."); 247 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_MSG); 248 return; 249 } 250 251 logd(TAG, "Send handshake message: " + ByteUtils.byteArrayToHexString(message) + "."); 252 DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null, 253 /* isMessageEncrypted = */ false, message); 254 mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE); 255 } 256 sendServerAuthToClient(@ullable byte[] message)257 private void sendServerAuthToClient(@Nullable byte[] message) { 258 if (message == null) { 259 loge(TAG, "Unable to send server authentication message to client, message is null."); 260 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_MSG); 261 return; 262 } 263 DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null, 264 /* isMessageEncrypted = */ false, message); 265 mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE); 266 } 267 268 /** 269 * Send an encrypted message. 270 * <p>Note: This should be called only after the secure channel has been established.</p> 271 * 272 * @param deviceMessage The {@link DeviceMessage} to encrypt and send. 273 */ sendEncryptedMessage(@onNull DeviceMessage deviceMessage)274 void sendEncryptedMessage(@NonNull DeviceMessage deviceMessage) throws IllegalStateException { 275 if (!deviceMessage.isMessageEncrypted()) { 276 loge(TAG, "Encryption not required for this message " + deviceMessage + "."); 277 return; 278 } 279 Key key = mEncryptionKey.get(); 280 if (key == null) { 281 throw new IllegalStateException("Secure channel has not been established."); 282 } 283 284 byte[] encryptedMessage = key.encryptData(deviceMessage.getMessage()); 285 deviceMessage.setMessage(encryptedMessage); 286 mStream.writeMessage(deviceMessage, OperationType.CLIENT_MESSAGE); 287 } 288 289 /** 290 * Called by the client to notify that the user has accepted a pairing code or any out-of-band 291 * confirmation, and send confirmation signals to remote bluetooth device. 292 */ notifyOutOfBandAccepted()293 void notifyOutOfBandAccepted() { 294 HandshakeMessage message; 295 try { 296 message = mEncryptionRunner.verifyPin(); 297 } catch (HandshakeException e) { 298 loge(TAG, "Error during PIN verification", e); 299 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_VERIFICATION); 300 return; 301 } 302 if (message.getHandshakeState() != HandshakeState.FINISHED) { 303 loge(TAG, "Handshake not finished after calling verify PIN. Instead got " 304 + "state: " + message.getHandshakeState() + "."); 305 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_STATE); 306 return; 307 } 308 309 Key localKey = message.getKey(); 310 if (localKey == null) { 311 loge(TAG, "Unable to finish association, generated key is null."); 312 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_ENCRYPTION_KEY); 313 return; 314 } 315 316 mState = message.getHandshakeState(); 317 mStorage.saveEncryptionKey(mDeviceId, localKey.asBytes()); 318 mEncryptionKey.set(localKey); 319 if (mDeviceId == null) { 320 loge(TAG, "Unable to finish association, device id is null."); 321 notifySecureChannelFailure(CHANNEL_ERROR_INVALID_DEVICE_ID); 322 return; 323 } 324 logd(TAG, "Pairing code successfully verified and encryption key saved. Sending " 325 + "confirmation to device."); 326 notifyCallback(Callback::onSecureChannelEstablished); 327 DeviceMessage deviceMessage = new DeviceMessage(/* recipient = */ null, 328 /* isMessageEncrypted = */ false, CONFIRMATION_SIGNAL); 329 mStream.writeMessage(deviceMessage, OperationType.ENCRYPTION_HANDSHAKE); 330 } 331 332 /** Get the BLE stream backing this channel. */ 333 @NonNull getStream()334 BleDeviceMessageStream getStream() { 335 return mStream; 336 } 337 338 /**Set the listener that notifies to show verification code. {@code null} to clear.*/ setShowVerificationCodeListener(@ullable ShowVerificationCodeListener listener)339 void setShowVerificationCodeListener(@Nullable ShowVerificationCodeListener listener) { 340 mShowVerificationCodeListener = listener; 341 } 342 343 @VisibleForTesting 344 @Nullable getShowVerificationCodeListener()345 ShowVerificationCodeListener getShowVerificationCodeListener() { 346 return mShowVerificationCodeListener; 347 } 348 349 /** Register a callback that notifies secure channel events. */ registerCallback(Callback callback)350 void registerCallback(Callback callback) { 351 mCallback = callback; 352 } 353 354 /** Unregister a callback. */ unregisterCallback(Callback callback)355 void unregisterCallback(Callback callback) { 356 if (callback == mCallback) { 357 mCallback = null; 358 } 359 } 360 361 @VisibleForTesting 362 @Nullable getCallback()363 Callback getCallback() { 364 return mCallback; 365 } 366 notifyCallback(Consumer<Callback> notification)367 private void notifyCallback(Consumer<Callback> notification) { 368 if (mCallback != null) { 369 notification.accept(mCallback); 370 } 371 } 372 notifySecureChannelFailure(@hannelError int error)373 private void notifySecureChannelFailure(@ChannelError int error) { 374 loge(TAG, "Secure channel error: " + error); 375 notifyCallback(callback -> callback.onEstablishSecureChannelFailure(error)); 376 } 377 378 private final BleDeviceMessageStream.MessageReceivedListener mStreamListener = 379 new BleDeviceMessageStream.MessageReceivedListener() { 380 @Override 381 public void onMessageReceived(DeviceMessage deviceMessage, 382 OperationType operationType) { 383 byte[] message = deviceMessage.getMessage(); 384 switch(operationType) { 385 case ENCRYPTION_HANDSHAKE: 386 logd(TAG, "Message received and handed off to handshake."); 387 try { 388 processHandshake(message); 389 } catch (HandshakeException e) { 390 loge(TAG, "Handshake failed.", e); 391 notifyCallback(callback -> callback.onEstablishSecureChannelFailure( 392 CHANNEL_ERROR_INVALID_HANDSHAKE)); 393 } 394 break; 395 case CLIENT_MESSAGE: 396 logd(TAG, "Received client message."); 397 if (!deviceMessage.isMessageEncrypted()) { 398 notifyCallback(callback -> callback.onMessageReceived( 399 deviceMessage)); 400 return; 401 } 402 Key key = mEncryptionKey.get(); 403 if (key == null) { 404 loge(TAG, "Received encrypted message before secure channel has " 405 + "been established."); 406 notifyCallback(callback -> callback.onMessageReceivedError(null)); 407 return; 408 } 409 try { 410 byte[] decryptedPayload = 411 key.decryptData(deviceMessage.getMessage()); 412 deviceMessage.setMessage(decryptedPayload); 413 notifyCallback( 414 callback -> callback.onMessageReceived(deviceMessage)); 415 } catch (SignatureException e) { 416 loge(TAG, "Could not decrypt client credentials.", e); 417 notifyCallback(callback -> callback.onMessageReceivedError(e)); 418 } 419 break; 420 default: 421 loge(TAG, "Received unexpected operation type: " + operationType + "."); 422 } 423 } 424 }; 425 426 /** 427 * Callbacks that will be invoked during establishing secure channel, sending and receiving 428 * messages securely. 429 */ 430 interface Callback { 431 /** 432 * Invoked when secure channel has been established successfully. 433 */ onSecureChannelEstablished()434 void onSecureChannelEstablished(); 435 436 /** 437 * Invoked when a {@link ChannelError} has been encountered in attempting to establish 438 * a secure channel. 439 * 440 * @param error The failure indication. 441 */ onEstablishSecureChannelFailure(@ecureBleChannel.ChannelError int error)442 void onEstablishSecureChannelFailure(@SecureBleChannel.ChannelError int error); 443 444 /** 445 * Invoked when a complete message is received securely from the client and decrypted. 446 * 447 * @param deviceMessage The {@link DeviceMessage} with decrypted message. 448 */ onMessageReceived(@onNull DeviceMessage deviceMessage)449 void onMessageReceived(@NonNull DeviceMessage deviceMessage); 450 451 /** 452 * Invoked when there was an error during a processing or decrypting of a client message. 453 * 454 * @param exception The error. 455 */ onMessageReceivedError(@ullable Exception exception)456 void onMessageReceivedError(@Nullable Exception exception); 457 458 /** 459 * Invoked when the device id was received from the client. 460 * 461 * @param deviceId The unique device id of client. 462 */ onDeviceIdReceived(@onNull String deviceId)463 void onDeviceIdReceived(@NonNull String deviceId); 464 } 465 466 /** 467 * Listener that will be invoked to display verification code. 468 */ 469 interface ShowVerificationCodeListener { 470 /** 471 * Invoke when a verification need to be displayed during device association. 472 * 473 * @param code The verification code to show. 474 */ showVerificationCode(@onNull String code)475 void showVerificationCode(@NonNull String code); 476 } 477 } 478