Commit 8507af03 authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: plumb things on the mobile side.

This change updates the previous, GATT-based code to use the new
authenticator::Platform class.

BUG=1002262

Binary-Size: This code ends up in a dynamic feature module, and so doesn't impact the main APK size. Also, the previous change (https://chromium-review.googlesource.com/c/chromium/src/+/2430324) deleted 13KiB of code so the delta isn't as large as it seems.
Change-Id: I5ed395492ac440d7fdab6a13edfef7610da505f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2436415
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#812408}
parent 3a4b22b5
......@@ -6,7 +6,7 @@ import("//build/config/android/rules.gni")
android_library("java") {
sources = [
"java/src/org/chromium/chrome/browser/webauth/authenticator/BLEHandler.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/BLEAdvert.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",
......@@ -33,7 +33,11 @@ android_library("java") {
}
generate_jni("jni_headers") {
sources = [ "java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java" ]
sources = [
"java/src/org/chromium/chrome/browser/webauth/authenticator/BLEAdvert.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/CableAuthenticator.java",
"java/src/org/chromium/chrome/browser/webauth/authenticator/USBHandler.java",
]
}
source_set("native") {
......@@ -47,6 +51,8 @@ source_set("native") {
"//content/public/browser",
"//crypto",
"//device/fido",
"//device/fido:cablev2_authenticator",
"//device/fido:cablev2_registration",
"//third_party/boringssl",
]
}
// 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.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.os.ParcelUuid;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.UUID;
class BLEAdvert implements Closeable {
private static final String TAG = "CableBLEAdvert";
// This UUID is allocated to Google.
private static final String CABLE_UUID = "0000fde2-0000-1000-8000-00805f9b34fb";
private AdvertiseCallback mCallback;
BLEAdvert(byte[] payload) {
BluetoothLeAdvertiser advertiser =
BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
mCallback = new AdvertiseCallback() {
@Override
public void onStartFailure(int errorCode) {
Log.i(TAG, "advertising failure " + errorCode);
}
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(TAG, "advertising success");
}
};
AdvertiseSettings settings =
(new AdvertiseSettings.Builder())
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setConnectable(false)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.build();
ParcelUuid fidoUuid = new ParcelUuid(UUID.fromString(CABLE_UUID));
ByteBuffer bb = ByteBuffer.wrap(payload);
long high = bb.getLong();
long low = bb.getLong();
UUID dataUuid = new UUID(high, low);
AdvertiseData data = (new AdvertiseData.Builder())
.addServiceUuid(fidoUuid)
.addServiceUuid(new ParcelUuid(dataUuid))
.setIncludeDeviceName(false)
.setIncludeTxPowerLevel(false)
.build();
advertiser.startAdvertising(settings, data, mCallback);
}
@Override
@CalledByNative
public void close() {
if (mCallback == null) {
return;
}
BluetoothLeAdvertiser advertiser =
BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
Log.i(TAG, "stopping advertising");
advertiser.stopAdvertising(mCallback);
mCallback = null;
}
}
......@@ -10,6 +10,8 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
......@@ -35,6 +37,9 @@ import java.lang.ref.WeakReference;
*/
public class CableAuthenticatorUI extends Fragment
implements OnClickListener, QRScanDialog.Callback, CableAuthenticator.Callback {
/** True if this UI was created because the user connected a desktop via USB. */
private boolean mCreatedByUsbIntent;
private AndroidPermissionDelegate mPermissionDelegate;
private CableAuthenticator mAuthenticator;
......@@ -46,9 +51,27 @@ public class CableAuthenticatorUI extends Fragment
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = getContext();
Bundle arguments = getArguments();
final UsbAccessory accessory =
(UsbAccessory) arguments.getParcelable(UsbManager.EXTRA_ACCESSORY);
mCreatedByUsbIntent = (accessory != null);
final long networkContext = arguments.getLong(
"org.chromium.chrome.modules.cablev2_authenticator.NetworkContext");
final long instanceIdDriver = arguments.getLong(
"org.chromium.chrome.modules.cablev2_authenticator.InstanceIDDriver");
final String settingsActivityClassName = arguments.getString(
"org.chromium.chrome.modules.cablev2_authenticator.SettingsActivityClassName");
final String wrapperClassName = arguments.getString(
"org.chromium.chrome.modules.cablev2_authenticator.WrapperClassName");
final boolean isFcmNotification =
arguments.getBoolean("org.chromium.chrome.modules.cablev2_authenticator.FCM");
mPermissionDelegate = new ActivityAndroidPermissionDelegate(
new WeakReference<Activity>((Activity) context));
mAuthenticator = new CableAuthenticator(getContext(), this);
mAuthenticator =
new CableAuthenticator(getContext(), this, networkContext, instanceIdDriver,
settingsActivityClassName, wrapperClassName, isFcmNotification, accessory);
}
@Override
......@@ -71,12 +94,13 @@ public class CableAuthenticatorUI extends Fragment
mSpinner.setPadding(0, 60, 0, 60);
mStatus = new TextView(context);
mStatus.setText("Looking for known devices nearby");
mStatus.setPadding(0, 60, 0, 60);
mQRButton = new ButtonCompat(context, R.style.TextButtonThemeOverlay);
mQRButton.setText("Connect a new device");
mQRButton.setOnClickListener(this);
if (mCreatedByUsbIntent) {
mStatus.setText("Connected via USB. Awaiting command.");
} else {
mStatus.setText("Looking for known devices nearby");
}
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
......@@ -85,9 +109,16 @@ public class CableAuthenticatorUI extends Fragment
layout.addView(mStatus,
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
layout.addView(mQRButton,
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
if (!mCreatedByUsbIntent) {
mQRButton = new ButtonCompat(context, R.style.TextButtonThemeOverlay);
mQRButton.setText("Connect a new device");
mQRButton.setOnClickListener(this);
layout.addView(mQRButton,
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
}
return layout;
}
......@@ -187,7 +218,6 @@ public class CableAuthenticatorUI extends Fragment
break;
}
Toast.makeText(getActivity(), toast, Toast.LENGTH_SHORT).show();
getActivity().finish();
});
}
......
......@@ -14,6 +14,8 @@ import android.system.OsConstants;
import android.system.StructPollfd;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.SingleThreadTaskRunner;
import org.chromium.base.task.TaskTraits;
......@@ -43,7 +45,7 @@ class USBHandler implements Closeable {
private static final String TAG = "CableUSBHandler";
private final CableAuthenticator mAuthenticator;
private final UsbAccessory mAccessory;
private final Context mContext;
private final SingleThreadTaskRunner mTaskRunner;
private final UsbManager mUsbManager;
......@@ -61,18 +63,23 @@ class USBHandler implements Closeable {
private int mBufferUsed;
private int mBufferOffset;
USBHandler(CableAuthenticator authenticator, Context context, SingleThreadTaskRunner taskRunner,
UsbAccessory accessory) {
mAuthenticator = authenticator;
USBHandler(Context context, SingleThreadTaskRunner taskRunner, UsbAccessory accessory) {
mAccessory = accessory;
mContext = context;
mTaskRunner = taskRunner;
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
mPollFds = new StructPollfd[1];
mPollFds[0] = new StructPollfd();
}
mTaskRunner.postTask(() -> this.openAccessory(accessory));
@CalledByNative
public void startReading() {
assert mTaskRunner.belongsToCurrentThread();
openAccessory(mAccessory);
}
@Override
@CalledByNative
public void close() {
assert mTaskRunner.belongsToCurrentThread();
......@@ -90,6 +97,7 @@ class USBHandler implements Closeable {
* Called by CableAuthenticator to write a deferred reply (e.g. to a makeCredential or
* getAssertion request).
*/
@CalledByNative
public void write(byte[] message) {
assert mTaskRunner.belongsToCurrentThread();
assert mOutput != null;
......@@ -116,6 +124,7 @@ class USBHandler implements Closeable {
Log.i(TAG, "Accessory opened " + accessory);
if (mFd == null) {
Log.i(TAG, "Returned file descriptor is null");
USBHandlerJni.get().onUSBData(null);
return;
}
......@@ -135,7 +144,7 @@ class USBHandler implements Closeable {
mBufferUsed = 0;
mBufferOffset = 0;
PostTask.postTask(TaskTraits.THREAD_POOL_BEST_EFFORT, () -> { this.readLoop(); });
PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, () -> { this.readLoop(); });
}
/**
......@@ -312,16 +321,9 @@ class USBHandler implements Closeable {
if (buffer == null) {
Log.i(TAG, "Error reading from USB");
mAuthenticator.onComplete();
return;
}
byte[] reply = mAuthenticator.onUSBWrite(buffer);
if (reply == null) {
close();
} else if (reply.length > 0) {
PostTask.postTask(TaskTraits.THREAD_POOL_BEST_EFFORT, () -> { this.doWrite(reply); });
}
USBHandlerJni.get().onUSBData(buffer);
}
private void doWrite(byte[] buffer) {
......@@ -340,4 +342,11 @@ class USBHandler implements Closeable {
Log.i(TAG, "USB write failed");
}
}
@NativeMethods
interface Natives {
// onUSBData is called when data is read from the USB data. If data is
// null then an error occurred.
void onUSBData(byte[] data);
}
};
......@@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/browser_process.h"
#include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/gcm_driver/instance_id/instance_id_profile_service.h"
// This "header" actually contains function definitions and thus can only be
// included once across Chromium.
......@@ -15,3 +19,13 @@ static jlong JNI_CableAuthenticatorModuleProvider_GetSystemNetworkContext(
return static_cast<jlong>(reinterpret_cast<uintptr_t>(
SystemNetworkContextManager::GetInstance()->GetContext()));
}
static jlong JNI_CableAuthenticatorModuleProvider_GetInstanceIDDriver(
JNIEnv* env) {
static_assert(sizeof(jlong) >= sizeof(uintptr_t),
"Java longs are too small to contain pointers");
return static_cast<jlong>(reinterpret_cast<uintptr_t>(
instance_id::InstanceIDProfileServiceFactory::GetForProfile(
g_browser_process->profile_manager()->GetPrimaryUserProfile())
->driver()));
}
......@@ -35,6 +35,12 @@ public class CableAuthenticatorModuleProvider extends Fragment {
// Fragment} in the module.
private static final String NETWORK_CONTEXT_KEY =
"org.chromium.chrome.modules.cablev2_authenticator.NetworkContext";
private static final String INSTANCE_ID_DRIVER_KEY =
"org.chromium.chrome.modules.cablev2_authenticator.InstanceIDDriver";
private static final String SETTINGS_ACTIVITY_CLASS_NAME =
"org.chromium.chrome.modules.cablev2_authenticator.SettingsActivityClassName";
private static final String WRAPPER_CLASS_NAME =
"org.chromium.chrome.modules.cablev2_authenticator.WrapperClassName";
private TextView mStatus;
@Override
......@@ -86,6 +92,18 @@ public class CableAuthenticatorModuleProvider extends Fragment {
}
arguments.putLong(NETWORK_CONTEXT_KEY,
CableAuthenticatorModuleProviderJni.get().getSystemNetworkContext());
arguments.putLong(INSTANCE_ID_DRIVER_KEY,
CableAuthenticatorModuleProviderJni.get().getInstanceIDDriver());
// SettingsActivity has to be named as a string here because it cannot
// be depended upon without creating a cycle in the deps graph. It's
// used as the target of a notification and, in time we'll need our own
// top-level Activity in order to handle USB devices. For now, though,
// it serves for testing.
// TODO(agl): replace with custom top-level Activity.
arguments.putString(SETTINGS_ACTIVITY_CLASS_NAME,
"org.chromium.chrome.browser.settings.SettingsActivity");
arguments.putString(
WRAPPER_CLASS_NAME, CableAuthenticatorModuleProvider.class.getCanonicalName());
fragment.setArguments(arguments);
transaction.replace(getId(), fragment);
// This fragment is deliberately not added to the back-stack here so
......@@ -101,5 +119,6 @@ public class CableAuthenticatorModuleProvider extends Fragment {
// static_library, cannot be depended on by another component thus we
// pass this value into the feature module.
long getSystemNetworkContext();
long getInstanceIDDriver();
}
}
......@@ -23,8 +23,6 @@ namespace device {
constexpr size_t kCableEphemeralIdSize = 16;
constexpr size_t kCableSessionPreKeySize = 32;
constexpr size_t kCableNonceSize = 8;
constexpr size_t kCableCompressedPublicKeySize =
/* type byte */ 1 + /* field element */ (256 / 8);
using CableEidArray = std::array<uint8_t, kCableEphemeralIdSize>;
using CableSessionPreKeyArray = std::array<uint8_t, kCableSessionPreKeySize>;
......
......@@ -31,6 +31,12 @@ constexpr size_t kRootSecretSize = 32;
constexpr size_t kQRSecretSize = 16;
constexpr size_t kQRSeedSize = 32;
constexpr size_t kQRKeySize = kQRSeedSize + kQRSecretSize;
// kCompressedPublicKeySize is the size of a compressed X9.62 public key.
constexpr size_t kCompressedPublicKeySize =
/* type byte */ 1 + /* field element */ (256 / 8);
// kQRDataSize is the size of the (unencoded) QR payload. It's a compressed
// public key followed by the QR secret.
constexpr size_t kQRDataSize = kCompressedPublicKeySize + kQRSecretSize;
} // namespace cablev2
} // namespace device
......
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