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