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