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