Commit 4c1cd7be authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: enable Bluetooth when needed for caBLEv2.

This change causes the user to be prompted to enable Bluetooth (if
necessary) after scanning a QR code. It also automatically enables
Bluetooth in order to process FCM messages in the background. The user
is advised of this when choosing to link with the device.

BUG=1002262

Change-Id: I918f12177597a49cd6fb2dc0d06bd2c4ba071c0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2538159
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#828472}
parent fb489a09
......@@ -10,6 +10,7 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticatorUI.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/CameraView.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/PendingCloudMessage.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/QRScanDialog.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/USBHandler.java",
]
......
......@@ -9,6 +9,7 @@ import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
......@@ -469,10 +470,10 @@ class CableAuthenticator {
/**
* onCloudMessage is called by {@link CableAuthenticatorUI} when a GCM message is received.
*/
static void onCloudMessage(
long event, long systemNetworkContext, long registration, String activityClassName) {
static void onCloudMessage(long event, long systemNetworkContext, long registration,
String activityClassName, boolean needToDisableBluetooth) {
setup(registration, activityClassName, systemNetworkContext);
CableAuthenticatorJni.get().onCloudMessage(event);
CableAuthenticatorJni.get().onCloudMessage(event, needToDisableBluetooth);
}
/**
......@@ -536,6 +537,12 @@ class CableAuthenticator {
notificationManager.cancel(NOTIFICATION_CHANNEL_ID, ID);
}
@CalledByNative
public static void disableBluetooth() {
Log.i(TAG, "Operation complete. Disabling Bluetooth.");
BluetoothAdapter.getDefaultAdapter().disable();
}
@NativeMethods
interface Natives {
/**
......@@ -581,11 +588,12 @@ class CableAuthenticator {
void stop();
/**
* Called when a GCM message is received. The argument is a pointer to a
* Called when a GCM message is received. The |event| argument is a pointer to a
* |device::cablev2::authenticator::Registration::Event| object that the native code takes
* ownership of.
* ownership of. |needToDisableBluetooth| is true if Bluetooth was enabled for the purposes
* of processing this event and thus |disableBluetooth| should be called once complete.
*/
void onCloudMessage(long event);
void onCloudMessage(long event, boolean needToDisableBluetooth);
/**
* Called to alert native code of a response to a makeCredential request.
......
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.webauth.authenticator;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
......@@ -38,6 +39,10 @@ import java.lang.ref.WeakReference;
*/
public class CableAuthenticatorUI
extends Fragment implements OnClickListener, QRScanDialog.Callback {
// ENABLE_BLUETOOTH_REQUEST_CODE is a random int used to identify responses
// to a request to enable Bluetooth. (Request codes can only be 16-bit.)
private static final int ENABLE_BLUETOOTH_REQUEST_CODE = 64907;
private enum Mode {
QR, // Triggered from Settings; can scan QR code to start handshake.
FCM, // Triggered by user selecting notification; handshake already running.
......@@ -50,6 +55,11 @@ public class CableAuthenticatorUI
private LinearLayout mUnlinkButton;
private ImageView mHeader;
// The following two members store a pending QR-scan result while Bluetooth
// is enabled.
private String mPendingQRCode;
private boolean mPendingShouldLink;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......@@ -185,7 +195,16 @@ public class CableAuthenticatorUI
@SuppressLint("SetTextI18n")
public void onQRCode(String value, boolean link) {
setHeader(R.style.step1);
mAuthenticator.onQRCode(value, link);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled()) {
mAuthenticator.onQRCode(value, link);
} else {
mPendingQRCode = value;
mPendingShouldLink = link;
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE),
ENABLE_BLUETOOTH_REQUEST_CODE);
}
}
void onStatus(int code) {
......@@ -226,6 +245,13 @@ public class CableAuthenticatorUI
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ENABLE_BLUETOOTH_REQUEST_CODE) {
String qrCode = mPendingQRCode;
mPendingQRCode = null;
mAuthenticator.onQRCode(qrCode, mPendingShouldLink);
return;
}
mAuthenticator.onActivityResult(requestCode, resultCode, data);
}
......@@ -266,7 +292,14 @@ public class CableAuthenticatorUI
*/
public static void onCloudMessage(
long event, long systemNetworkContext, long registration, String activityClassName) {
CableAuthenticator.onCloudMessage(
event, systemNetworkContext, registration, activityClassName);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled()) {
CableAuthenticator.onCloudMessage(event, systemNetworkContext, registration,
activityClassName, /*needToDisableBluetooth=*/false);
return;
}
new PendingCloudMessage(
adapter, event, systemNetworkContext, registration, activityClassName);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.webauth.authenticator;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
/**
* PendingCloudMessage contains the information extracted from an FCM message that is pending
* Bluetooth being enabled. It receives Bluetooth status updates and forwards the FCM message once
* Bluetooth is on.
*/
class PendingCloudMessage extends BroadcastReceiver {
private static final String TAG = "PendingCloudMessage";
private final Context mContext;
// The following four members store a pending cloud message while Bluetooth
// is enabled.
private final long mEvent;
private final long mNetworkContext;
private final long mRegistration;
private final String mActivityClassName;
PendingCloudMessage(BluetoothAdapter adapter, long event, long systemNetworkContext,
long registration, String activityClassName) {
super();
mContext = ContextUtils.getApplicationContext();
mEvent = event;
mNetworkContext = systemNetworkContext;
mRegistration = registration;
mActivityClassName = activityClassName;
mContext.registerReceiver(this, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
// Consent to enable Bluetooth in order to respond to requests was
// obtained when scanning the QR code.
if (adapter.enable()) {
Log.i(TAG, "Cloud message is pending Bluetooth enabling");
return;
}
// Bluetooth might have been enabled by another party between checking
// and now.
if (adapter.isEnabled()) {
CableAuthenticator.onCloudMessage(mEvent, mNetworkContext, mRegistration,
mActivityClassName, /*needToDisableBluetooth=*/false);
return;
}
Log.i(TAG, "Bluetooth failed to enable. Dropping cloud message.");
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
int state = extras.getInt(BluetoothAdapter.EXTRA_STATE, -1);
if (state == -1) {
return;
}
switch (state) {
case BluetoothAdapter.STATE_OFF:
Log.i(TAG, "Bluetooth failed to enable. Dropping cloud message.");
break;
case BluetoothAdapter.STATE_ON:
Log.i(TAG, "Bluetooth enabled. Forwarding cloud message.");
CableAuthenticator.onCloudMessage(mEvent, mNetworkContext, mRegistration,
mActivityClassName, /*needToDisableBluetooth=*/true);
break;
default:
// An intermediate state. Wait for the next message.
return;
}
mContext.unregisterReceiver(this);
}
}
......@@ -182,7 +182,9 @@ class AndroidPlatform : public device::cablev2::authenticator::Platform {
InteractionNeededCallback;
AndroidPlatform(JNIEnv* env, const JavaRef<jobject>& cable_authenticator)
: env_(env), cable_authenticator_(cable_authenticator) {
: env_(env),
cable_authenticator_(cable_authenticator),
need_to_disable_bluetooth_(false) {
DCHECK(env_->IsInstanceOf(
cable_authenticator_.obj(),
org_chromium_chrome_browser_webauth_authenticator_CableAuthenticator_clazz(
......@@ -198,14 +200,19 @@ class AndroidPlatform : public device::cablev2::authenticator::Platform {
// when ready, call the passed callback with a reference to it. The pending
// action will then complete as normal.
AndroidPlatform(JNIEnv* env,
InteractionNeededCallback interaction_needed_callback)
InteractionNeededCallback interaction_needed_callback,
bool need_to_disable_bluetooth)
: env_(env),
interaction_needed_callback_(std::move(interaction_needed_callback)) {}
interaction_needed_callback_(std::move(interaction_needed_callback)),
need_to_disable_bluetooth_(need_to_disable_bluetooth) {}
~AndroidPlatform() override {
if (notification_showing_) {
Java_CableAuthenticator_dropNotification(env_);
}
if (need_to_disable_bluetooth_) {
Java_CableAuthenticator_disableBluetooth(env_);
}
}
// Platform:
......@@ -344,6 +351,12 @@ class AndroidPlatform : public device::cablev2::authenticator::Platform {
std::unique_ptr<MakeCredentialParams> pending_make_credential_;
std::unique_ptr<GetAssertionParams> pending_get_assertion_;
InteractionNeededCallback interaction_needed_callback_;
// need_to_disable_bluetooth_ is true if Bluetooth was enabled on the system
// in order to handle this message and thus should be disabled once handling
// is complete.
const bool need_to_disable_bluetooth_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<AndroidPlatform> weak_factory_{this};
};
......@@ -524,8 +537,10 @@ static void JNI_CableAuthenticator_Stop(JNIEnv* env) {
ResetGlobalData();
}
static void JNI_CableAuthenticator_OnCloudMessage(JNIEnv* env,
jlong event_long) {
static void JNI_CableAuthenticator_OnCloudMessage(
JNIEnv* env,
jlong event_long,
jboolean need_to_disable_bluetooth) {
static_assert(sizeof(jlong) >= sizeof(void*), "");
std::unique_ptr<device::cablev2::authenticator::Registration::Event> event(
reinterpret_cast<device::cablev2::authenticator::Registration::Event*>(
......@@ -533,15 +548,14 @@ static void JNI_CableAuthenticator_OnCloudMessage(JNIEnv* env,
GlobalData& global_data = GetGlobalData();
// TODO(agl): should enable Bluetooth here as needed.
// There is deliberately no check for |!global_data.current_transaction|
// because multiple Cloud messages may come in from different paired devices.
// Only the most recent is processed.
global_data.current_transaction =
device::cablev2::authenticator::TransactFromFCM(
std::make_unique<AndroidPlatform>(env,
base::BindOnce(&OnNeedInteractive)),
base::BindOnce(&OnNeedInteractive),
need_to_disable_bluetooth),
global_data.network_context, global_data.root_secret,
event->routing_id, event->tunnel_id, event->pairing_id,
event->client_nonce);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment