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