1 /*
<lambda>null2  * Copyright (C) 2022 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.pandora
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothDevice
21 import android.bluetooth.BluetoothDevice.ACTION_PAIRING_REQUEST
22 import android.bluetooth.BluetoothDevice.BOND_BONDED
23 import android.bluetooth.BluetoothDevice.BOND_NONE
24 import android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC
25 import android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE
26 import android.bluetooth.BluetoothDevice.EXTRA_PAIRING_VARIANT
27 import android.bluetooth.BluetoothDevice.TRANSPORT_BREDR
28 import android.bluetooth.BluetoothDevice.TRANSPORT_LE
29 import android.bluetooth.BluetoothManager
30 import android.content.BroadcastReceiver
31 import android.content.Context
32 import android.content.Intent
33 import android.content.IntentFilter
34 import android.util.Log
35 import com.google.protobuf.ByteString
36 import com.google.protobuf.Empty
37 import io.grpc.stub.StreamObserver
38 import java.io.Closeable
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.Dispatchers
41 import kotlinx.coroutines.cancel
42 import kotlinx.coroutines.flow.Flow
43 import kotlinx.coroutines.flow.SharingStarted
44 import kotlinx.coroutines.flow.filter
45 import kotlinx.coroutines.flow.first
46 import kotlinx.coroutines.flow.launchIn
47 import kotlinx.coroutines.flow.map
48 import kotlinx.coroutines.flow.shareIn
49 import pandora.HostProto.*
50 import pandora.SecurityGrpc.SecurityImplBase
51 import pandora.SecurityProto.*
52 import pandora.SecurityProto.LESecurityLevel.LE_LEVEL1
53 import pandora.SecurityProto.LESecurityLevel.LE_LEVEL2
54 import pandora.SecurityProto.LESecurityLevel.LE_LEVEL3
55 import pandora.SecurityProto.LESecurityLevel.LE_LEVEL4
56 import pandora.SecurityProto.SecurityLevel.LEVEL0
57 import pandora.SecurityProto.SecurityLevel.LEVEL1
58 import pandora.SecurityProto.SecurityLevel.LEVEL2
59 import pandora.SecurityProto.SecurityLevel.LEVEL3
60 
61 private const val TAG = "PandoraSecurity"
62 
63 @kotlinx.coroutines.ExperimentalCoroutinesApi
64 class Security(private val context: Context) : SecurityImplBase(), Closeable {
65 
66     private val globalScope: CoroutineScope =
67         CoroutineScope(Dispatchers.Default.limitedParallelism(1))
68     private val flow: Flow<Intent>
69 
70     private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
71     private val bluetoothAdapter = bluetoothManager.adapter
72 
73     var manuallyConfirm = false
74 
75     private val pairingReceiver: BroadcastReceiver =
76         object : BroadcastReceiver() {
77             override fun onReceive(context: Context, intent: Intent) {
78                 if (!manuallyConfirm && intent.action == BluetoothDevice.ACTION_PAIRING_REQUEST) {
79                     val bluetoothDevice = intent.getBluetoothDeviceExtra()
80                     val pairingVariant =
81                         intent.getIntExtra(
82                             BluetoothDevice.EXTRA_PAIRING_VARIANT,
83                             BluetoothDevice.ERROR
84                         )
85                     val confirmationCases =
86                         intArrayOf(
87                             BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION,
88                             BluetoothDevice.PAIRING_VARIANT_CONSENT,
89                             BluetoothDevice.PAIRING_VARIANT_PIN,
90                         )
91                     if (pairingVariant in confirmationCases) {
92                         bluetoothDevice.setPairingConfirmation(true)
93                     }
94                 }
95             }
96         }
97 
98     init {
99         val intentFilter = IntentFilter()
100         intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
101         intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
102 
103         Log.d(TAG, "registering pairingReceiver")
104         context.registerReceiver(pairingReceiver, intentFilter)
105 
106         flow =
107             intentFlow(context, intentFilter, globalScope)
108                 .shareIn(globalScope, SharingStarted.Eagerly)
109     }
110 
111     override fun close() {
112         globalScope.cancel()
113         context.unregisterReceiver(pairingReceiver)
114     }
115 
116     override fun secure(request: SecureRequest, responseObserver: StreamObserver<SecureResponse>) {
117         grpcUnary(globalScope, responseObserver) {
118             val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter)
119             val transport = request.connection.transport
120             Log.i(TAG, "secure: $bluetoothDevice transport: $transport")
121             var reached =
122                 when (transport) {
123                     TRANSPORT_LE -> {
124                         check(request.getLevelCase() == SecureRequest.LevelCase.LE)
125                         val level = request.le
126                         if (level == LE_LEVEL1) true
127                         else if (level == LE_LEVEL4)
128                             throw RuntimeException("secure: Low-energy level 4 not supported")
129                         else {
130                             bluetoothDevice.createBond(transport)
131                             waitLESecurityLevel(bluetoothDevice, level)
132                         }
133                     }
134                     TRANSPORT_BREDR -> {
135                         check(request.getLevelCase() == SecureRequest.LevelCase.CLASSIC)
136                         val level = request.classic
137                         if (level == LEVEL0) true
138                         else if (level >= LEVEL3)
139                             throw RuntimeException("secure: Classic level up to 3 not supported")
140                         else {
141                             bluetoothDevice.createBond(transport)
142                             waitBREDRSecurityLevel(bluetoothDevice, level)
143                         }
144                     }
145                     else -> throw RuntimeException("secure: Invalid transport")
146                 }
147             val secureResponseBuilder = SecureResponse.newBuilder()
148             if (reached) secureResponseBuilder.setSuccess(Empty.getDefaultInstance())
149             else secureResponseBuilder.setNotReached(Empty.getDefaultInstance())
150             secureResponseBuilder.build()
151         }
152     }
153 
154     suspend fun waitBondIntent(bluetoothDevice: BluetoothDevice): Int {
155         if (bluetoothDevice.getBondState() == BOND_BONDED) {
156             return BOND_BONDED
157         }
158         return flow
159             .filter { it.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED }
160             .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
161             .map { it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) }
162             .filter { it == BOND_BONDED || it == BOND_NONE }
163             .first()
164     }
165 
166     suspend fun waitBREDRSecurityLevel(
167         bluetoothDevice: BluetoothDevice,
168         level: SecurityLevel
169     ): Boolean {
170         Log.i(TAG, "waitBREDRSecurityLevel")
171         return when (level) {
172             LEVEL0 -> true
173             LEVEL3 -> throw RuntimeException("waitSecurity: Classic level 3 not supported")
174             else -> {
175                 val bondState = waitBondIntent(bluetoothDevice)
176                 val isEncrypted = bluetoothDevice.isEncrypted()
177                 when (level) {
178                     LEVEL1 -> !isEncrypted || bondState == BOND_BONDED
179                     LEVEL2 -> isEncrypted && bondState == BOND_BONDED
180                     else -> false
181                 }
182             }
183         }
184     }
185 
186     suspend fun waitLESecurityLevel(
187         bluetoothDevice: BluetoothDevice,
188         level: LESecurityLevel
189     ): Boolean {
190         Log.i(TAG, "waitLESecurityLevel")
191         return when (level) {
192             LE_LEVEL1 -> true
193             LE_LEVEL4 -> throw RuntimeException("waitSecurity: Low-energy level 4 not supported")
194             else -> {
195                 val bondState = waitBondIntent(bluetoothDevice)
196                 val isEncrypted = bluetoothDevice.isEncrypted()
197                 when (level) {
198                     LE_LEVEL2 -> isEncrypted
199                     LE_LEVEL3 -> isEncrypted && bondState == BOND_BONDED
200                     else -> throw RuntimeException("waitSecurity: Low-energy level 4 not supported")
201                 }
202             }
203         }
204     }
205 
206     override fun waitSecurity(
207         request: WaitSecurityRequest,
208         responseObserver: StreamObserver<WaitSecurityResponse>
209     ) {
210         grpcUnary(globalScope, responseObserver) {
211             Log.i(TAG, "waitSecurity")
212             val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter)
213             val transport = if (request.hasClassic()) TRANSPORT_BREDR else TRANSPORT_LE
214             val reached =
215                 when (transport) {
216                     TRANSPORT_LE -> {
217                         check(request.hasLe())
218                         waitLESecurityLevel(bluetoothDevice, request.le)
219                     }
220                     TRANSPORT_BREDR -> {
221                         check(request.hasClassic())
222                         waitBREDRSecurityLevel(bluetoothDevice, request.classic)
223                     }
224                     else -> throw RuntimeException("secure: Invalid transport")
225                 }
226             val waitSecurityBuilder = WaitSecurityResponse.newBuilder()
227             if (reached) waitSecurityBuilder.setSuccess(Empty.getDefaultInstance())
228             else waitSecurityBuilder.setPairingFailure(Empty.getDefaultInstance())
229             waitSecurityBuilder.build()
230         }
231     }
232 
233     override fun onPairing(
234         responseObserver: StreamObserver<PairingEvent>
235     ): StreamObserver<PairingEventAnswer> =
236         grpcBidirectionalStream(globalScope, responseObserver) {
237             Log.i(TAG, "OnPairing: Starting stream")
238             manuallyConfirm = true
239             it.map { answer ->
240                     Log.i(
241                         TAG,
242                         "OnPairing: Handling PairingEventAnswer ${answer.answerCase} for device ${answer.event.address}"
243                     )
244                     val device = answer.event.address.toBluetoothDevice(bluetoothAdapter)
245                     when (answer.answerCase!!) {
246                         PairingEventAnswer.AnswerCase.CONFIRM ->
247                             device.setPairingConfirmation(answer.confirm)
248                         PairingEventAnswer.AnswerCase.PASSKEY ->
249                             device.setPin(answer.passkey.toString().padStart(6, '0'))
250                         PairingEventAnswer.AnswerCase.PIN -> device.setPin(answer.pin.toByteArray())
251                         PairingEventAnswer.AnswerCase.ANSWER_NOT_SET ->
252                             error("unexpected pairing answer type")
253                     }
254                 }
255                 .launchIn(this)
256 
257             flow
258                 .filter { intent -> intent.action == ACTION_PAIRING_REQUEST }
259                 .map { intent ->
260                     val device = intent.getBluetoothDeviceExtra()
261                     val variant = intent.getIntExtra(EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR)
262                     Log.i(
263                         TAG,
264                         "OnPairing: Handling PairingEvent ${variant} for device ${device.address}"
265                     )
266                     val eventBuilder = PairingEvent.newBuilder().setAddress(device.toByteString())
267                     when (variant) {
268                         // SSP / LE Just Works
269                         BluetoothDevice.PAIRING_VARIANT_CONSENT ->
270                             eventBuilder.justWorks = Empty.getDefaultInstance()
271 
272                         // SSP / LE Numeric Comparison
273                         BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ->
274                             eventBuilder.numericComparison =
275                                 intent.getIntExtra(
276                                     BluetoothDevice.EXTRA_PAIRING_KEY,
277                                     BluetoothDevice.ERROR
278                                 )
279                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY -> {
280                             val passkey =
281                                 intent.getIntExtra(
282                                     BluetoothDevice.EXTRA_PAIRING_KEY,
283                                     BluetoothDevice.ERROR
284                                 )
285                             Log.i(TAG, "OnPairing: passkey=${passkey}")
286                             eventBuilder.passkeyEntryNotification = passkey
287                         }
288 
289                         // Out-Of-Band not currently supported
290                         BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT ->
291                             error("Received OOB pairing confirmation (UNSUPPORTED)")
292 
293                         // Legacy PIN entry, or LE legacy passkey entry, depending on transport
294                         BluetoothDevice.PAIRING_VARIANT_PIN ->
295                             when (device.type) {
296                                 DEVICE_TYPE_CLASSIC ->
297                                     eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
298                                 DEVICE_TYPE_LE ->
299                                     eventBuilder.passkeyEntryRequest = Empty.getDefaultInstance()
300                                 else ->
301                                     error(
302                                         "cannot determine pairing variant, since transport is unknown: ${device.type}"
303                                     )
304                             }
305                         BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS ->
306                             eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
307 
308                         // Legacy PIN entry or LE legacy passkey entry, except we just generate the
309                         // PIN in
310                         // the
311                         // stack and display it to the user for convenience
312                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN -> {
313                             val passkey =
314                                 intent.getIntExtra(
315                                     BluetoothDevice.EXTRA_PAIRING_KEY,
316                                     BluetoothDevice.ERROR
317                                 )
318                             when (device.type) {
319                                 DEVICE_TYPE_CLASSIC ->
320                                     eventBuilder.pinCodeNotification =
321                                         ByteString.copyFrom(passkey.toString().toByteArray())
322                                 DEVICE_TYPE_LE -> eventBuilder.passkeyEntryNotification = passkey
323                                 else ->
324                                     error(
325                                         "cannot determine pairing variant, since transport is unknown"
326                                     )
327                             }
328                         }
329                         else -> {
330                             error("Received unknown pairing variant $variant")
331                         }
332                     }
333                     Log.d(TAG, "OnPairing: send event: $eventBuilder")
334                     eventBuilder.build()
335                 }
336         }
337 }
338