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

device/fido: add caBLE v2 handshake infrastructure.

This change isn't intended to have any semantic effect but adds
infrastructure for a caBLE v2 handshake based around Noise NNpsk0. The
implementation has been run against the Noise Explorer implementation[1]
to confirm that it matches the spec.

[1] https://noiseexplorer.com/patterns/NNpsk0/

Change-Id: Iff92bcddfb5b4d07280ef4df5331e471d18bdd18
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1752936
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#688339}
parent dae91f52
......@@ -305,6 +305,21 @@ fuzzer_test("fido_cable_handshake_handler_fuzzer") {
libfuzzer_options = [ "max_len=2048" ]
}
fuzzer_test("fido_cable_handshake_handler_v2_fuzzer") {
sources = [
"cable/fido_cable_handshake_handler_v2_fuzzer.cc",
]
deps = [
":fido",
"//base",
"//base/test:test_support",
"//device/bluetooth:mocks",
"//testing/gmock",
"//testing/gtest",
]
libfuzzer_options = [ "max_len=2048" ]
}
is_linux_without_udev = is_linux && !use_udev
source_set("test_support") {
......
......@@ -23,6 +23,7 @@
#include "device/fido/ble/fido_ble_uuids.h"
#include "device/fido/cable/fido_cable_device.h"
#include "device/fido/cable/fido_cable_handshake_handler.h"
#include "device/fido/features.h"
#include "device/fido/fido_parsing_utils.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
......@@ -189,13 +190,39 @@ FidoCableDiscovery::~FidoCableDiscovery() {
advertisement.second->Unregister(base::DoNothing(), base::DoNothing());
}
std::unique_ptr<FidoCableHandshakeHandler>
base::Optional<std::unique_ptr<FidoCableHandshakeHandler>>
FidoCableDiscovery::CreateHandshakeHandler(
FidoCableDevice* device,
base::span<const uint8_t, kCableSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce) {
return std::make_unique<FidoCableHandshakeHandler>(device, nonce,
session_pre_key);
const CableDiscoveryData* discovery_data) {
std::unique_ptr<FidoCableHandshakeHandler> handler;
switch (discovery_data->version) {
case 1: {
// Nonce is embedded as first 8 bytes of client EID.
std::array<uint8_t, 8> nonce;
const bool ok = fido_parsing_utils::ExtractArray(
discovery_data->client_eid, 0, &nonce);
DCHECK(ok);
handler.reset(new FidoCableV1HandshakeHandler(
device, nonce, discovery_data->session_pre_key));
break;
}
case 2:
if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
return base::nullopt;
}
handler.reset(new FidoCableV2HandshakeHandler(
device, discovery_data->session_pre_key));
break;
default:
FIDO_LOG(DEBUG) << "Dropping caBLE handshake request for unknown version "
<< discovery_data->version;
return base::nullopt;
}
return handler;
}
void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter,
......@@ -353,17 +380,19 @@ void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
StopAdvertisements(
base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake,
weak_factory_.GetWeakPtr(), std::move(cable_device),
found_cable_device_data->session_pre_key, nonce));
*found_cable_device_data));
}
void FidoCableDiscovery::ConductEncryptionHandshake(
std::unique_ptr<FidoCableDevice> cable_device,
base::span<const uint8_t, kCableSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce) {
auto handshake_handler =
CreateHandshakeHandler(cable_device.get(), session_pre_key, nonce);
auto* const handshake_handler_ptr = handshake_handler.get();
cable_handshake_handlers_.emplace_back(std::move(handshake_handler));
CableDiscoveryData discovery_data) {
base::Optional<std::unique_ptr<FidoCableHandshakeHandler>> handshake_handler =
CreateHandshakeHandler(cable_device.get(), &discovery_data);
if (!handshake_handler) {
return;
}
auto* const handshake_handler_ptr = handshake_handler->get();
cable_handshake_handlers_.emplace_back(std::move(*handshake_handler));
handshake_handler_ptr->InitiateCableHandshake(
base::BindOnce(&FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage,
......
......@@ -35,10 +35,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
~FidoCableDiscovery() override;
protected:
virtual std::unique_ptr<FidoCableHandshakeHandler> CreateHandshakeHandler(
FidoCableDevice* device,
base::span<const uint8_t, kCableSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce);
virtual base::Optional<std::unique_ptr<FidoCableHandshakeHandler>>
CreateHandshakeHandler(FidoCableDevice* device,
const CableDiscoveryData* discovery_data);
private:
FRIEND_TEST_ALL_PREFIXES(FidoCableDiscoveryTest,
......@@ -76,10 +75,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
// |callback|.
void StopAdvertisements(base::OnceClosure callback);
void CableDeviceFound(BluetoothAdapter* adapter, BluetoothDevice* device);
void ConductEncryptionHandshake(
std::unique_ptr<FidoCableDevice> device,
base::span<const uint8_t, kCableSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce);
void ConductEncryptionHandshake(std::unique_ptr<FidoCableDevice> cable_device,
CableDiscoveryData discovery_data);
void ValidateAuthenticatorHandshakeMessage(
std::unique_ptr<FidoCableDevice> cable_device,
FidoCableHandshakeHandler* handshake_handler,
......
......@@ -266,12 +266,12 @@ class CableMockAdapter : public MockBluetoothAdapter {
~CableMockAdapter() override = default;
};
class FakeHandshakeHandler : public FidoCableHandshakeHandler {
class FakeHandshakeHandler : public FidoCableV1HandshakeHandler {
public:
FakeHandshakeHandler(FidoCableDevice* device,
base::span<const uint8_t, 8> nonce,
base::span<const uint8_t, 32> session_pre_key)
: FidoCableHandshakeHandler(device, nonce, session_pre_key) {}
: FidoCableV1HandshakeHandler(device, nonce, session_pre_key) {}
~FakeHandshakeHandler() override = default;
void InitiateCableHandshake(FidoDevice::DeviceCallback callback) override {
......@@ -295,12 +295,16 @@ class FakeFidoCableDiscovery : public FidoCableDiscovery {
~FakeFidoCableDiscovery() override = default;
private:
std::unique_ptr<FidoCableHandshakeHandler> CreateHandshakeHandler(
FidoCableDevice* device,
base::span<const uint8_t, kCableSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce) override {
return std::make_unique<FakeHandshakeHandler>(device, nonce,
session_pre_key);
base::Optional<std::unique_ptr<FidoCableHandshakeHandler>>
CreateHandshakeHandler(FidoCableDevice* device,
const CableDiscoveryData* discovery_data) override {
// Nonce is embedded as first 8 bytes of client EID.
std::array<uint8_t, 8> nonce;
const bool ok =
fido_parsing_utils::ExtractArray(discovery_data->client_eid, 0, &nonce);
DCHECK(ok);
return std::make_unique<FakeHandshakeHandler>(
device, nonce, discovery_data->session_pre_key);
}
};
......
......@@ -16,27 +16,41 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "device/fido/fido_device.h"
#include "third_party/boringssl/src/include/openssl/base.h"
namespace device {
class FidoCableDevice;
// FidoCableHandshakeHandler abstracts over the different versions of caBLE
// handshakes.
class FidoCableHandshakeHandler {
public:
virtual ~FidoCableHandshakeHandler() = 0;
virtual void InitiateCableHandshake(FidoDevice::DeviceCallback callback) = 0;
virtual bool ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response) = 0;
};
// Handles exchanging handshake messages with external authenticator and
// validating the handshake messages to derive a shared session key to be used
// for message encryption.
// See: fido-client-to-authenticator-protocol.html#cable-encryption-handshake of
// the most up-to-date spec.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableHandshakeHandler {
class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableV1HandshakeHandler
: public FidoCableHandshakeHandler {
public:
FidoCableHandshakeHandler(FidoCableDevice* device,
base::span<const uint8_t, 8> nonce,
base::span<const uint8_t, 32> session_pre_key);
virtual ~FidoCableHandshakeHandler();
FidoCableV1HandshakeHandler(FidoCableDevice* device,
base::span<const uint8_t, 8> nonce,
base::span<const uint8_t, 32> session_pre_key);
~FidoCableV1HandshakeHandler() override;
virtual void InitiateCableHandshake(FidoDevice::DeviceCallback callback);
virtual bool ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response);
// FidoCableHandshakeHandler:
void InitiateCableHandshake(FidoDevice::DeviceCallback callback) override;
bool ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response) override;
private:
FRIEND_TEST_ALL_PREFIXES(FidoCableHandshakeHandlerTest, HandShakeSuccess);
......@@ -52,9 +66,43 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableHandshakeHandler {
std::array<uint8_t, 16> client_session_random_;
std::string handshake_key_;
base::WeakPtrFactory<FidoCableHandshakeHandler> weak_factory_{this};
base::WeakPtrFactory<FidoCableV1HandshakeHandler> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(FidoCableV1HandshakeHandler);
};
// FidoCableV2HandshakeHandler implements an NNpsk0[1] handshake that provides
// forward secrecy.
//
// [1] https://noiseexplorer.com/patterns/NNpsk0/
class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableV2HandshakeHandler
: public FidoCableHandshakeHandler {
public:
FidoCableV2HandshakeHandler(FidoCableDevice* device,
base::span<const uint8_t, 32> session_pre_key);
~FidoCableV2HandshakeHandler() override;
// FidoCableHandshakeHandler:
void InitiateCableHandshake(FidoDevice::DeviceCallback callback) override;
bool ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response) override;
DISALLOW_COPY_AND_ASSIGN(FidoCableHandshakeHandler);
private:
void MixHash(base::span<const uint8_t> in);
void MixKey(base::span<const uint8_t> ikm);
void MixKeyAndHash(base::span<const uint8_t> ikm);
void InitializeKey(base::span<const uint8_t, 32> key);
std::vector<uint8_t> Encrypt(base::span<const uint8_t> plaintext);
base::Optional<std::vector<uint8_t>> Decrypt(
base::span<const uint8_t> ciphertext);
FidoCableDevice* const cable_device_;
std::array<uint8_t, 32> session_pre_key_;
std::array<uint8_t, 32> chaining_key_;
std::array<uint8_t, 32> h_;
std::array<uint8_t, 32> symmetric_key_;
uint32_t symmetric_nonce_;
bssl::UniquePtr<EC_KEY> ephemeral_key_;
};
} // namespace device
......
......@@ -37,8 +37,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) {
auto adapter =
base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>();
device::FidoCableDevice test_cable_device(adapter.get(), kTestDeviceAddress);
device::FidoCableHandshakeHandler handshake_handler(
device::FidoCableV1HandshakeHandler handshake_handler_v1(
&test_cable_device, kTestNonce, kTestSessionPreKey);
handshake_handler.ValidateAuthenticatorHandshakeMessage(data_span);
handshake_handler_v1.ValidateAuthenticatorHandshakeMessage(data_span);
return 0;
}
......@@ -264,11 +264,11 @@ class FidoCableHandshakeHandlerTest : public Test {
connection_->read_callback() = device_->GetReadCallbackForTesting();
}
std::unique_ptr<FidoCableHandshakeHandler> CreateHandshakeHandler(
std::unique_ptr<FidoCableV1HandshakeHandler> CreateHandshakeHandler(
std::array<uint8_t, 8> nonce,
std::array<uint8_t, 32> session_pre_key) {
return std::make_unique<FidoCableHandshakeHandler>(device_.get(), nonce,
session_pre_key);
return std::make_unique<FidoCableV1HandshakeHandler>(device_.get(), nonce,
session_pre_key);
}
void ConnectWithLength(uint16_t length) {
......
// Copyright 2018 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.
#include <stddef.h>
#include <stdint.h>
#include <array>
#include "base/containers/span.h"
#include "base/memory/ref_counted.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/cable/fido_cable_device.h"
#include "device/fido/cable/fido_cable_handshake_handler.h"
#include "device/fido/fido_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr std::array<uint8_t, 32> kTestSessionPreKey = {{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}};
constexpr char kTestDeviceAddress[] = "Fake_Address";
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) {
auto data_span = base::make_span(raw_data, size);
auto adapter =
base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>();
device::FidoCableDevice test_cable_device(adapter.get(), kTestDeviceAddress);
test_cable_device.SetStateForTesting(
device::FidoCableDevice::State::kDeviceError);
device::FidoCableV2HandshakeHandler handshake_handler_v2(&test_cable_device,
kTestSessionPreKey);
handshake_handler_v2.InitiateCableHandshake(base::DoNothing());
handshake_handler_v2.ValidateAuthenticatorHandshakeMessage(data_span);
return 0;
}
......@@ -25,4 +25,7 @@ extern const base::Feature kWebAuthBiometricEnrollment{
extern const base::Feature kWebAuthCredentialManagement{
"WebAuthenticationCredentialManagement", base::FEATURE_ENABLED_BY_DEFAULT};
extern const base::Feature kWebAuthPhoneSupport{
"WebAuthenticationPhoneSupport", base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace device
......@@ -28,6 +28,10 @@ extern const base::Feature kWebAuthBiometricEnrollment;
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthCredentialManagement;
// Enable using a phone as a generic security key.
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthPhoneSupport;
} // namespace device
#endif // DEVICE_FIDO_FEATURES_H_
......@@ -115,6 +115,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice {
}
State state_for_testing() const { return state_; }
void SetStateForTesting(State state) { state_ = state; }
protected:
void OnDeviceInfoReceived(base::OnceClosure done,
......
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