Commit 0e3a642a authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: add caBLEv2 unittests

It's now possible to mock out the network service and run the
authenticator code against the desktop code in a test environment.

BUG=1002262

Change-Id: Ie2bf611d529baddfb1d04be1b0c60d6c4026d419
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2419337
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#810740}
parent 31d8b847
......@@ -47,6 +47,11 @@
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/attested_credential_data.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/cable/v2_authenticator.h"
#include "device/fido/cable/v2_constants.h"
#include "device/fido/cable/v2_discovery.h"
#include "device/fido/cable/v2_handshake.h"
#include "device/fido/cable/v2_test_util.h"
#include "device/fido/fake_fido_discovery.h"
#include "device/fido/features.h"
#include "device/fido/fido_authenticator.h"
......@@ -62,7 +67,9 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/obj.h"
#include "url/url_util.h"
#if defined(OS_MAC)
......@@ -5693,4 +5700,169 @@ TEST_F(TouchIdAuthenticatorImplTest, IsUVPAA) {
}
#endif // defined(OS_MAC)
class CableV2AuthenticatorImplTest : public AuthenticatorImplTest {
public:
CableV2AuthenticatorImplTest()
: network_context_(device::cablev2::NewMockTunnelServer(
base::BindRepeating(&CableV2AuthenticatorImplTest::OnContact,
base::Unretained(this)))) {}
void SetUp() override {
AuthenticatorImplTest::SetUp();
EnableFeature(features::kWebAuthCable);
EnableFeature(device::kWebAuthPhoneSupport);
NavigateAndCommit(GURL(kTestOrigin1));
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_KEY> peer_identity(EC_KEY_derive_from_secret(
p256.get(), zero_seed_.data(), zero_seed_.size()));
CHECK_EQ(sizeof(peer_identity_x962_),
EC_POINT_point2oct(
p256.get(), EC_KEY_get0_public_key(peer_identity.get()),
POINT_CONVERSION_UNCOMPRESSED, peer_identity_x962_,
sizeof(peer_identity_x962_), /*ctx=*/nullptr));
}
base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)>
GetPairingCallback() {
return base::BindRepeating(&CableV2AuthenticatorImplTest::OnPairing,
base::Unretained(this));
}
protected:
class DiscoveryFactory : public device::FidoDiscoveryFactory {
public:
explicit DiscoveryFactory(
std::unique_ptr<device::cablev2::Discovery> discovery)
: discovery_(std::move(discovery)) {}
std::vector<std::unique_ptr<device::FidoDiscoveryBase>> Create(
device::FidoTransportProtocol transport) override {
if (transport !=
device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy ||
!discovery_) {
return {};
}
return SingleDiscovery(std::move(discovery_));
}
private:
std::unique_ptr<device::cablev2::Discovery> discovery_;
};
void OnContact(
base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
base::span<const uint8_t> pairing_id,
base::span<const uint8_t, device::cablev2::kClientNonceSize>
client_nonce) {
std::move(contact_callback_).Run(tunnel_id, pairing_id, client_nonce);
}
void OnPairing(std::unique_ptr<device::cablev2::Pairing> pairing) {
pairings_.emplace_back(std::move(pairing));
}
const std::array<uint8_t, device::cablev2::kRootSecretSize> root_secret_ = {
0};
const device::QRGeneratorKey qr_generator_key_ = {0};
const std::array<uint8_t, 16> zero_qr_secret_ = {0};
const device::CableIdentityKeySeed zero_seed_ = {0};
std::unique_ptr<network::mojom::NetworkContext> network_context_;
uint8_t peer_identity_x962_[device::kP256X962Length] = {0};
device::VirtualCtap2Device virtual_device_;
std::vector<std::unique_ptr<device::cablev2::Pairing>> pairings_;
base::OnceCallback<void(
base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
base::span<const uint8_t> pairing_id,
base::span<const uint8_t, device::cablev2::kClientNonceSize>
client_nonce)>
contact_callback_;
};
TEST_F(CableV2AuthenticatorImplTest, QRBasedWithNoPairing) {
auto discovery = std::make_unique<device::cablev2::Discovery>(
network_context_.get(), qr_generator_key_,
/*pairings=*/std::vector<std::unique_ptr<device::cablev2::Pairing>>(),
GetPairingCallback());
auto* const discovery_ptr = discovery.get();
AuthenticatorEnvironmentImpl::GetInstance()
->ReplaceDefaultDiscoveryFactoryForTesting(
std::make_unique<DiscoveryFactory>(std::move(discovery)));
std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
device::cablev2::authenticator::TransactFromQRCode(
device::cablev2::authenticator::NewMockPlatform(discovery_ptr,
&virtual_device_),
network_context_.get(), root_secret_, "Test Authenticator",
zero_qr_secret_, peer_identity_x962_,
/*contact_id=*/base::nullopt, base::DoNothing());
EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
EXPECT_EQ(pairings_.size(), 0u);
}
TEST_F(CableV2AuthenticatorImplTest, PairingBased) {
// First do unpaired exchange to get pairing data.
auto discovery = std::make_unique<device::cablev2::Discovery>(
network_context_.get(), qr_generator_key_,
/*pairings=*/std::vector<std::unique_ptr<device::cablev2::Pairing>>(),
GetPairingCallback());
auto* discovery_ptr = discovery.get();
AuthenticatorEnvironmentImpl::GetInstance()
->ReplaceDefaultDiscoveryFactoryForTesting(
std::make_unique<DiscoveryFactory>(std::move(discovery)));
std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
device::cablev2::authenticator::TransactFromQRCode(
device::cablev2::authenticator::NewMockPlatform(discovery_ptr,
&virtual_device_),
network_context_.get(), root_secret_, "Test Authenticator",
zero_qr_secret_, peer_identity_x962_,
/*contact_id=*/std::vector<uint8_t>({1, 2, 3}), base::DoNothing());
EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
EXPECT_EQ(pairings_.size(), 1u);
// Now do a pairing-based exchange.
discovery = std::make_unique<device::cablev2::Discovery>(
network_context_.get(), qr_generator_key_, std::move(pairings_),
GetPairingCallback());
discovery_ptr = discovery.get();
const std::array<uint8_t, device::cablev2::kRoutingIdSize> routing_id = {0};
bool contact_callback_was_called = false;
// When the |cablev2::Discovery| starts it'll make a connection to the tunnel
// service with the contact ID from the pairing data. This will be handled by
// the |TestNetworkContext| and turned into a call to |contact_callback_|.
// This simulates the tunnel server sending a cloud message to a phone. Given
// the information from the connection, a transaction can be created.
contact_callback_ = base::BindLambdaForTesting(
[this, &transaction, discovery_ptr, routing_id,
&contact_callback_was_called](
base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
base::span<const uint8_t> pairing_id,
base::span<const uint8_t, device::cablev2::kClientNonceSize>
client_nonce) -> void {
contact_callback_was_called = true;
transaction = device::cablev2::authenticator::TransactFromFCM(
device::cablev2::authenticator::NewMockPlatform(discovery_ptr,
&virtual_device_),
network_context_.get(), root_secret_, routing_id, tunnel_id,
pairing_id, client_nonce, base::DoNothing());
});
AuthenticatorEnvironmentImpl::GetInstance()
->ReplaceDefaultDiscoveryFactoryForTesting(
std::make_unique<DiscoveryFactory>(std::move(discovery)));
EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
EXPECT_TRUE(contact_callback_was_called);
}
} // namespace content
......@@ -2400,6 +2400,8 @@ test("content_unittests") {
"//components/autofill/content/browser/webauthn",
"//device/base",
"//device/fido",
"//device/fido:cablev2_authenticator",
"//device/fido:cablev2_test_util",
"//device/fido:mocks",
"//device/fido:test_support",
"//services/device/public/cpp/hid:test_support",
......
......@@ -294,6 +294,22 @@ static_library("cablev2_authenticator") {
]
}
static_library("cablev2_test_util") {
testonly = true
sources = [
"cable/v2_test_util.cc",
"cable/v2_test_util.h",
]
deps = [
":cablev2_authenticator",
":fido",
"//components/cbor",
"//crypto",
"//services/network:test_support",
"//services/network/public/mojom",
]
}
if (is_chromeos) {
proto_library("u2f_proto") {
sources = [ "//third_party/cros_system_api/dbus/u2f/u2f_interface.proto" ]
......@@ -383,7 +399,6 @@ source_set("test_support") {
testonly = true
sources = [ "test_callback_receiver.h" ]
deps = [
":cablev2_authenticator",
"//base",
"//components/apdu",
"//device/fido",
......
This diff is collapsed.
// 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.
#ifndef DEVICE_FIDO_CABLE_V2_TEST_UTIL_H_
#define DEVICE_FIDO_CABLE_V2_TEST_UTIL_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/containers/span.h"
#include "device/fido/cable/v2_constants.h"
#include "services/network/public/mojom/network_context.mojom-forward.h"
namespace device {
class VirtualCtap2Device;
namespace cablev2 {
class Discovery;
// ContactCallback is called when a mock tunnel server (see
// |NewMockTunnelServer|) is asked to contact a phone. This simulates a tunnel
// server using a cloud messaging solution to wake a device.
using ContactCallback = base::RepeatingCallback<void(
base::span<const uint8_t, kTunnelIdSize> tunnel_id,
base::span<const uint8_t> pairing_id,
base::span<const uint8_t, kClientNonceSize> client_nonce)>;
// NewMockTunnelServer returns a |NetworkContext| that implements WebSocket
// requests and simulates a tunnel server.
std::unique_ptr<network::mojom::NetworkContext> NewMockTunnelServer(
ContactCallback contact_callback);
namespace authenticator {
class Platform;
// NewMockPlatform returns a |Platform| that implements the makeCredential
// operation by forwarding it to |ctap2_device|. Transmitted BLE adverts are
// forwarded to |discovery|.
std::unique_ptr<Platform> NewMockPlatform(
Discovery* discovery,
device::VirtualCtap2Device* ctap2_device);
} // namespace authenticator
} // namespace cablev2
} // namespace device
#endif // DEVICE_FIDO_CABLE_V2_TEST_UTIL_H_
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