Commit a3c0f931 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

device/fido/mac: add fake environment for unit testing Touch ID code

This adds a ScopedTouchIdTestEnvironment class to allow convenient
faking functionality of the Touch ID authenticator that requires
hardware support or user interaction.

TouchIdContext is modified to:
 - allow injection of fakes through TouchIdContext::Create, in order to
disable fingerprint prompts;
 - allow overriding the value returned by
TouchIdContext::TouchIdAvailable, which determines hardware support and
configuration status for Touch ID.

The Keychain wrapper class is modified to allow injection of a fake into
the singleton returned by Keychain::GetInstance. (FakeKeychain
implementation will be done in a follow-up CL).

Change-Id: I1b801b5bbe9b787965a5958206e6772a9a5eb09a
Reviewed-on: https://chromium-review.googlesource.com/1162946
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582278}
parent 8217643e
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_MACOSX)
#include "device/fido/mac/scoped_touch_id_test_environment.h"
#endif
namespace content { namespace content {
using ::testing::_; using ::testing::_;
...@@ -385,11 +389,17 @@ class AuthenticatorImplTest : public content::RenderViewHostTestHarness { ...@@ -385,11 +389,17 @@ class AuthenticatorImplTest : public content::RenderViewHostTestHarness {
return base::ContainsKey(authenticator_impl_->protocols_, protocol); return base::ContainsKey(authenticator_impl_->protocols_, protocol);
} }
void EnableFeature(const base::Feature& feature) {
scoped_feature_list_.emplace();
scoped_feature_list_->InitAndEnableFeature(feature);
}
protected: protected:
std::unique_ptr<AuthenticatorImpl> authenticator_impl_; std::unique_ptr<AuthenticatorImpl> authenticator_impl_;
service_manager::mojom::ConnectorRequest request_; service_manager::mojom::ConnectorRequest request_;
std::unique_ptr<service_manager::Connector> connector_; std::unique_ptr<service_manager::Connector> connector_;
std::unique_ptr<device::FakeHidManager> fake_hid_manager_; std::unique_ptr<device::FakeHidManager> fake_hid_manager_;
base::Optional<base::test::ScopedFeatureList> scoped_feature_list_;
}; };
// Verify behavior for various combinations of origins and RP IDs. // Verify behavior for various combinations of origins and RP IDs.
...@@ -842,10 +852,7 @@ TEST_F(AuthenticatorImplTest, OversizedCredentialId) { ...@@ -842,10 +852,7 @@ TEST_F(AuthenticatorImplTest, OversizedCredentialId) {
TEST_F(AuthenticatorImplTest, TestCableDiscoveryEnabledWithSwitch) { TEST_F(AuthenticatorImplTest, TestCableDiscoveryEnabledWithSwitch) {
TestServiceManagerContext service_manager_context; TestServiceManagerContext service_manager_context;
base::test::ScopedFeatureList scoped_feature_list; EnableFeature(features::kWebAuthCable);
scoped_feature_list.InitWithFeatures(
std::vector<base::Feature>{features::kWebAuthCable},
std::vector<base::Feature>{});
SimulateNavigation(GURL(kTestOrigin1)); SimulateNavigation(GURL(kTestOrigin1));
PublicKeyCredentialRequestOptionsPtr options = PublicKeyCredentialRequestOptionsPtr options =
...@@ -868,8 +875,7 @@ TEST_F(AuthenticatorImplTest, TestCableDiscoveryEnabledWithSwitch) { ...@@ -868,8 +875,7 @@ TEST_F(AuthenticatorImplTest, TestCableDiscoveryEnabledWithSwitch) {
} }
TEST_F(AuthenticatorImplTest, TestCableDiscoveryDisabledForMakeCredential) { TEST_F(AuthenticatorImplTest, TestCableDiscoveryDisabledForMakeCredential) {
base::test::ScopedFeatureList scoped_feature_list; EnableFeature(features::kWebAuthCable);
scoped_feature_list.InitAndEnableFeature(features::kWebAuthCable);
SimulateNavigation(GURL(kTestOrigin1)); SimulateNavigation(GURL(kTestOrigin1));
PublicKeyCredentialCreationOptionsPtr options = PublicKeyCredentialCreationOptionsPtr options =
...@@ -1577,9 +1583,65 @@ TEST_F(AuthenticatorContentBrowserClientTest, ...@@ -1577,9 +1583,65 @@ TEST_F(AuthenticatorContentBrowserClientTest,
} }
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAFalseWithoutTouchId) { TEST_F(AuthenticatorContentBrowserClientTest,
test_client_.supports_touch_id = false; IsUVPAAFalseIfEmbedderDoesNotSupportTouchId) {
if (__builtin_available(macOS 10.12.2, *)) {
// Touch ID is hardware-supported, and flag-enabled, but not enabled by the
// embedder.
EnableFeature(device::kWebAuthTouchId);
device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment;
touch_id_test_environment.SetTouchIdAvailable(true);
test_client_.supports_touch_id = false;
NavigateAndCommit(GURL(kTestOrigin1));
AuthenticatorPtr authenticator = ConnectToAuthenticator();
TestIsUvpaaCallback cb;
authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(cb.callback());
cb.WaitForCallback();
EXPECT_FALSE(cb.value());
}
}
TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAFalseIfFeatureFlagOff) {
if (__builtin_available(macOS 10.12.2, *)) {
// Touch ID is hardware-supported and embedder-enabled, but the flag is off.
device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment;
touch_id_test_environment.SetTouchIdAvailable(true);
test_client_.supports_touch_id = true;
NavigateAndCommit(GURL(kTestOrigin1));
AuthenticatorPtr authenticator = ConnectToAuthenticator();
TestIsUvpaaCallback cb;
authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(cb.callback());
cb.WaitForCallback();
EXPECT_FALSE(cb.value());
}
}
TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAATrueIfTouchIdAvailable) {
if (__builtin_available(macOS 10.12.2, *)) {
// Touch ID is available.
EnableFeature(device::kWebAuthTouchId);
device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment;
touch_id_test_environment.SetTouchIdAvailable(true);
test_client_.supports_touch_id = true;
NavigateAndCommit(GURL(kTestOrigin1));
AuthenticatorPtr authenticator = ConnectToAuthenticator();
TestIsUvpaaCallback cb;
authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(cb.callback());
cb.WaitForCallback();
EXPECT_TRUE(cb.value());
}
}
#endif // defined(OS_MACOSX)
#if !defined(OS_MACOSX)
TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAFalse) {
// No platform authenticator on non-macOS platforms.
NavigateAndCommit(GURL(kTestOrigin1)); NavigateAndCommit(GURL(kTestOrigin1));
AuthenticatorPtr authenticator = ConnectToAuthenticator(); AuthenticatorPtr authenticator = ConnectToAuthenticator();
...@@ -1588,7 +1650,7 @@ TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAFalseWithoutTouchId) { ...@@ -1588,7 +1650,7 @@ TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAFalseWithoutTouchId) {
cb.WaitForCallback(); cb.WaitForCallback();
EXPECT_FALSE(cb.value()); EXPECT_FALSE(cb.value());
} }
#endif #endif // !defined(OS_MACOSX)
class MockAuthenticatorRequestDelegateObserver class MockAuthenticatorRequestDelegateObserver
: public TestAuthenticatorRequestDelegate { : public TestAuthenticatorRequestDelegate {
......
...@@ -280,4 +280,15 @@ source_set("test_support") { ...@@ -280,4 +280,15 @@ source_set("test_support") {
"hid/fake_hid_impl_for_testing.h", "hid/fake_hid_impl_for_testing.h",
] ]
} }
if (is_mac) {
sources += [
"mac/fake_keychain.h",
"mac/fake_keychain.mm",
"mac/fake_touch_id_context.h",
"mac/fake_touch_id_context.mm",
"mac/scoped_touch_id_test_environment.h",
"mac/scoped_touch_id_test_environment.mm",
]
}
} }
...@@ -28,10 +28,7 @@ namespace mac { ...@@ -28,10 +28,7 @@ namespace mac {
bool TouchIdAuthenticator::IsAvailable() { bool TouchIdAuthenticator::IsAvailable() {
if (base::FeatureList::IsEnabled(device::kWebAuthTouchId)) { if (base::FeatureList::IsEnabled(device::kWebAuthTouchId)) {
if (__builtin_available(macOS 10.12.2, *)) { if (__builtin_available(macOS 10.12.2, *)) {
base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); return TouchIdContext::TouchIdAvailable();
return [context
canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:nil];
} }
} }
return false; return false;
......
// 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.
#ifndef DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_
#define DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_
#include <string>
#include <vector>
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "device/fido/mac/keychain.h"
namespace device {
namespace fido {
namespace mac {
class API_AVAILABLE(macos(10.12.2)) FakeKeychain : public Keychain {
public:
struct Item {
Item();
Item(Item&&);
Item& operator=(Item&&);
~Item();
std::string label;
std::string application_label;
std::string application_tag;
base::ScopedCFTypeRef<SecKeyRef> private_key;
private:
DISALLOW_COPY_AND_ASSIGN(Item);
};
FakeKeychain();
~FakeKeychain() override;
protected:
// Keychain:
base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey(
CFDictionaryRef params,
CFErrorRef* error) override;
OSStatus ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) override;
OSStatus ItemDelete(CFDictionaryRef query) override;
private:
std::vector<Item> items_;
DISALLOW_COPY_AND_ASSIGN(FakeKeychain);
};
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_
// 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.
#import <Security/Security.h>
#include "device/fido/mac/fake_keychain.h"
#include "device/fido/mac/keychain.h"
namespace device {
namespace fido {
namespace mac {
FakeKeychain::FakeKeychain() = default;
FakeKeychain::~FakeKeychain() = default;
FakeKeychain::Item::Item() = default;
FakeKeychain::Item::Item(Item&&) = default;
FakeKeychain::Item& FakeKeychain::Item::operator=(Item&&) = default;
FakeKeychain::Item::~Item() = default;
base::ScopedCFTypeRef<SecKeyRef> FakeKeychain::KeyCreateRandomKey(
CFDictionaryRef parameters,
CFErrorRef* error) {
// TODO(martinkr): Implement.
NOTREACHED();
return base::ScopedCFTypeRef<SecKeyRef>();
}
OSStatus FakeKeychain::ItemCopyMatching(CFDictionaryRef query,
CFTypeRef* result) {
// TODO(martinkr): Implement.
NOTREACHED();
return errSecItemNotFound;
}
OSStatus FakeKeychain::ItemDelete(CFDictionaryRef query) {
// TODO(martinkr): Implement.
NOTREACHED();
return errSecItemNotFound;
}
} // namespace mac
} // namespace fido
} // namespace device
// 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.
#ifndef DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_
#define DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_
#include "base/component_export.h"
#include "base/macros.h"
#include "device/fido/mac/touch_id_context.h"
namespace device {
namespace fido {
namespace mac {
class API_AVAILABLE(macosx(10.12.2)) FakeTouchIdContext
: public TouchIdContext {
public:
~FakeTouchIdContext() override;
// TouchIdContext:
void PromptTouchId(std::string reason, Callback callback) override;
void set_callback_result(bool callback_result) {
callback_result_ = callback_result;
}
private:
friend class ScopedTouchIdTestEnvironment;
FakeTouchIdContext();
bool callback_result_ = true;
DISALLOW_COPY_AND_ASSIGN(FakeTouchIdContext);
};
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_
// 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 "device/fido/mac/fake_touch_id_context.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
namespace device {
namespace fido {
namespace mac {
FakeTouchIdContext::FakeTouchIdContext() = default;
FakeTouchIdContext::~FakeTouchIdContext() = default;
void FakeTouchIdContext::PromptTouchId(std::string reason, Callback callback) {
std::move(callback).Run(callback_result_);
}
} // namespace mac
} // namespace fido
} // namespace device
...@@ -25,33 +25,41 @@ namespace mac { ...@@ -25,33 +25,41 @@ namespace mac {
// keychain-access-group entitlements, and therefore requires code signing with // keychain-access-group entitlements, and therefore requires code signing with
// a real Apple developer ID. We therefore group these function here, so they // a real Apple developer ID. We therefore group these function here, so they
// can be mocked out in testing. // can be mocked out in testing.
class COMPONENT_EXPORT(DEVICE_FIDO) API_AVAILABLE(macosx(10.12.2)) Keychain { class COMPONENT_EXPORT(DEVICE_FIDO) API_AVAILABLE(macos(10.12.2)) Keychain {
public: public:
static const Keychain& GetInstance(); static Keychain& GetInstance();
// KeyCreateRandomKey wraps the |SecKeyCreateRandomKey| function. // KeyCreateRandomKey wraps the |SecKeyCreateRandomKey| function.
virtual base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey( virtual base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey(
CFDictionaryRef params, CFDictionaryRef params,
CFErrorRef* error) const; CFErrorRef* error);
// KeyCreateSignature wraps the |SecKeyCreateSignature| function. // KeyCreateSignature wraps the |SecKeyCreateSignature| function.
virtual base::ScopedCFTypeRef<CFDataRef> KeyCreateSignature( virtual base::ScopedCFTypeRef<CFDataRef> KeyCreateSignature(
SecKeyRef key, SecKeyRef key,
SecKeyAlgorithm algorithm, SecKeyAlgorithm algorithm,
CFDataRef data, CFDataRef data,
CFErrorRef* error) const; CFErrorRef* error);
// KeyCopyPublicKey wraps the |SecKeyCopyPublicKey| function. // KeyCopyPublicKey wraps the |SecKeyCopyPublicKey| function.
virtual base::ScopedCFTypeRef<SecKeyRef> KeyCopyPublicKey( virtual base::ScopedCFTypeRef<SecKeyRef> KeyCopyPublicKey(SecKeyRef key);
SecKeyRef key) const;
// ItemCopyMatching wraps the |SecItemCopyMatching| function. // ItemCopyMatching wraps the |SecItemCopyMatching| function.
virtual OSStatus ItemCopyMatching(CFDictionaryRef query, virtual OSStatus ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result);
CFTypeRef* result) const;
// ItemDelete wraps the |SecItemDelete| function. // ItemDelete wraps the |SecItemDelete| function.
virtual OSStatus ItemDelete(CFDictionaryRef query) const; virtual OSStatus ItemDelete(CFDictionaryRef query);
protected:
Keychain();
virtual ~Keychain();
private: private:
friend class base::NoDestructor<Keychain>; friend class base::NoDestructor<Keychain>;
Keychain(); friend class ScopedTouchIdTestEnvironment;
// Set an override to the singleton instance returned by |GetInstance|. The
// caller keeps ownership of the injected keychain and must remove the
// override by calling |ClearInstanceOverride| before deleting it.
static void SetInstanceOverride(Keychain* keychain);
static void ClearInstanceOverride();
DISALLOW_COPY_AND_ASSIGN(Keychain); DISALLOW_COPY_AND_ASSIGN(Keychain);
}; };
......
...@@ -8,17 +8,36 @@ namespace device { ...@@ -8,17 +8,36 @@ namespace device {
namespace fido { namespace fido {
namespace mac { namespace mac {
static API_AVAILABLE(macos(10.12.2)) Keychain* g_keychain_instance_override =
nullptr;
// static // static
const Keychain& Keychain::GetInstance() { Keychain& Keychain::GetInstance() {
static const base::NoDestructor<Keychain> k; if (g_keychain_instance_override) {
return *g_keychain_instance_override;
}
static base::NoDestructor<Keychain> k;
return *k; return *k;
} }
// static
void Keychain::SetInstanceOverride(Keychain* keychain) {
CHECK(!g_keychain_instance_override);
g_keychain_instance_override = keychain;
}
// static
void Keychain::ClearInstanceOverride() {
CHECK(g_keychain_instance_override);
g_keychain_instance_override = nullptr;
}
Keychain::Keychain() = default; Keychain::Keychain() = default;
Keychain::~Keychain() = default;
base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCreateRandomKey( base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCreateRandomKey(
CFDictionaryRef params, CFDictionaryRef params,
CFErrorRef* error) const { CFErrorRef* error) {
return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCreateRandomKey(params, error)); return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCreateRandomKey(params, error));
} }
...@@ -26,22 +45,20 @@ base::ScopedCFTypeRef<CFDataRef> Keychain::KeyCreateSignature( ...@@ -26,22 +45,20 @@ base::ScopedCFTypeRef<CFDataRef> Keychain::KeyCreateSignature(
SecKeyRef key, SecKeyRef key,
SecKeyAlgorithm algorithm, SecKeyAlgorithm algorithm,
CFDataRef data, CFDataRef data,
CFErrorRef* error) const { CFErrorRef* error) {
return base::ScopedCFTypeRef<CFDataRef>( return base::ScopedCFTypeRef<CFDataRef>(
SecKeyCreateSignature(key, algorithm, data, error)); SecKeyCreateSignature(key, algorithm, data, error));
} }
base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCopyPublicKey( base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCopyPublicKey(SecKeyRef key) {
SecKeyRef key) const {
return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCopyPublicKey(key)); return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCopyPublicKey(key));
} }
OSStatus Keychain::ItemCopyMatching(CFDictionaryRef query, OSStatus Keychain::ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) {
CFTypeRef* result) const {
return SecItemCopyMatching(query, result); return SecItemCopyMatching(query, result);
} }
OSStatus Keychain::ItemDelete(CFDictionaryRef query) const { OSStatus Keychain::ItemDelete(CFDictionaryRef query) {
return SecItemDelete(query); return SecItemDelete(query);
} }
......
...@@ -37,7 +37,7 @@ class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation { ...@@ -37,7 +37,7 @@ class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation {
metadata_secret_(std::move(metadata_secret)), metadata_secret_(std::move(metadata_secret)),
keychain_access_group_(std::move(keychain_access_group)), keychain_access_group_(std::move(keychain_access_group)),
callback_(std::move(callback)), callback_(std::move(callback)),
touch_id_context_(std::make_unique<TouchIdContext>()) {} touch_id_context_(TouchIdContext::Create()) {}
~OperationBase() override = default; ~OperationBase() override = default;
......
// 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.
#ifndef DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_
#define DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_
#include <memory>
#include "base/component_export.h"
#include "base/mac/availability.h"
#include "base/macros.h"
namespace device {
namespace fido {
namespace mac {
class FakeKeychain;
class FakeTouchIdContext;
class TouchIdContext;
// ScopedTouchIdTestEnvironment overrides behavior of the Touch ID
// authenticator in testing. While in scope, it
// - installs a fake Keychain to avoid writing to the macOS keychain, which
// requires a valid code signature and keychain-access-group entitlement;
// - allows faking TouchIdContext instances returned by TouchIdContext to stub
// out Touch ID fingerprint prompts.
// Overrides are reset when the instance is destroyed.
class COMPONENT_EXPORT(DEVICE_FIDO)
API_AVAILABLE(macosx(10.12.2)) ScopedTouchIdTestEnvironment {
public:
ScopedTouchIdTestEnvironment();
~ScopedTouchIdTestEnvironment();
// ForgeNextTouchIdContext sets up the FakeTouchIdContext returned by the
// next call to TouchIdContext::Create. The fake will invoke the callback
// passed to TouchIdContext::PromptTouchId with the given result.
//
// It is a fatal error to call TouchIdContext::Create without invoking this
// method first while the test environment is in scope.
void ForgeNextTouchIdContext(bool simulate_prompt_success);
// Sets the value returned by TouchIdContext::TouchIdAvailable. The default on
// instantiation of the test environment is true.
bool SetTouchIdAvailable(bool available);
private:
static std::unique_ptr<TouchIdContext> ForwardCreate();
static bool ForwardTouchIdAvailable();
std::unique_ptr<TouchIdContext> CreateTouchIdContext();
bool TouchIdAvailable();
using CreateFuncPtr = decltype(&ForwardCreate);
CreateFuncPtr touch_id_context_create_ptr_;
using TouchIdAvailableFuncPtr = decltype(&ForwardTouchIdAvailable);
TouchIdAvailableFuncPtr touch_id_context_touch_id_available_ptr_;
std::unique_ptr<FakeTouchIdContext> next_touch_id_context_;
std::unique_ptr<FakeKeychain> keychain_;
bool touch_id_available_ = true;
DISALLOW_COPY_AND_ASSIGN(ScopedTouchIdTestEnvironment);
};
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_
// 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 "device/fido/mac/scoped_touch_id_test_environment.h"
#import <Security/Security.h>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "device/fido/mac/fake_keychain.h"
#include "device/fido/mac/fake_touch_id_context.h"
namespace device {
namespace fido {
namespace mac {
static API_AVAILABLE(macosx(10.12.2))
ScopedTouchIdTestEnvironment* g_current_environment = nullptr;
ScopedTouchIdTestEnvironment::ScopedTouchIdTestEnvironment()
: keychain_(std::make_unique<FakeKeychain>()) {
DCHECK(!g_current_environment);
g_current_environment = this;
// Override TouchIdContext::Create and TouchIdContext::IsAvailable.
touch_id_context_create_ptr_ = TouchIdContext::g_create_;
TouchIdContext::g_create_ = &ForwardCreate;
touch_id_context_touch_id_available_ptr_ =
TouchIdContext::g_touch_id_available_;
TouchIdContext::g_touch_id_available_ = &ForwardTouchIdAvailable;
Keychain::SetInstanceOverride(static_cast<Keychain*>(keychain_.get()));
}
ScopedTouchIdTestEnvironment::~ScopedTouchIdTestEnvironment() {
DCHECK(touch_id_context_create_ptr_);
TouchIdContext::g_create_ = touch_id_context_create_ptr_;
DCHECK(touch_id_context_touch_id_available_ptr_);
TouchIdContext::g_touch_id_available_ =
touch_id_context_touch_id_available_ptr_;
Keychain::ClearInstanceOverride();
}
// static
std::unique_ptr<TouchIdContext> ScopedTouchIdTestEnvironment::ForwardCreate() {
return g_current_environment->CreateTouchIdContext();
}
// static
bool ScopedTouchIdTestEnvironment::ForwardTouchIdAvailable() {
return g_current_environment->TouchIdAvailable();
}
bool ScopedTouchIdTestEnvironment::SetTouchIdAvailable(bool available) {
return touch_id_available_ = available;
}
bool ScopedTouchIdTestEnvironment::TouchIdAvailable() {
return touch_id_available_;
}
void ScopedTouchIdTestEnvironment::ForgeNextTouchIdContext(
bool simulate_prompt_success) {
CHECK(!next_touch_id_context_);
next_touch_id_context_ = base::WrapUnique(new FakeTouchIdContext);
next_touch_id_context_->set_callback_result(simulate_prompt_success);
}
std::unique_ptr<TouchIdContext>
ScopedTouchIdTestEnvironment::CreateTouchIdContext() {
CHECK(next_touch_id_context_) << "Call ForgeNextTouchIdContext() for every "
"context created in the test environment.";
return std::move(next_touch_id_context_);
}
} // namespace mac
} // namespace fido
} // namespace device
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#import <Security/Security.h> #import <Security/Security.h>
#include "base/callback.h" #include "base/callback.h"
#include "base/component_export.h"
#include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -19,21 +20,36 @@ namespace fido { ...@@ -19,21 +20,36 @@ namespace fido {
namespace mac { namespace mac {
// TouchIdContext wraps a macOS Touch ID consent prompt for signing with a // TouchIdContext wraps a macOS Touch ID consent prompt for signing with a
// secure enclave key. // secure enclave key. It is a essentially a simpler facade for the LAContext
class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { // class from the macOS LocalAuthentication framework (c.f.
// https://developer.apple.com/documentation/localauthentication/lacontext?language=objc).
//
// Use |Create| to instantiate a new context. Multiple instances can be created
// at the same time. However, calling |PromptTouchId| on one instance will
// cancel any other pending evaluations with an error. Deleting an instance
// will invalidate any pending evaluation prompts (i.e. the dialog will
// disappear and evaluation will fail with an error).
class COMPONENT_EXPORT(DEVICE_FIDO)
API_AVAILABLE(macosx(10.12.2)) TouchIdContext {
public: public:
// The callback is invoked when the Touch ID prompt completes. It receives a // The callback is invoked when the Touch ID prompt completes. It receives a
// boolean indicating whether obtaining the fingerprint was successful. // boolean indicating whether obtaining the fingerprint was successful.
using Callback = base::OnceCallback<void(bool)>; using Callback = base::OnceCallback<void(bool)>;
TouchIdContext(); // Factory method for instantiating a TouchIdContext.
~TouchIdContext(); static std::unique_ptr<TouchIdContext> Create();
// Returns whether Touch ID is supported on the current hardware and set up to
// be used.
static bool TouchIdAvailable();
virtual ~TouchIdContext();
// PromptTouchId displays a Touch ID consent prompt with the provided reason // PromptTouchId displays a Touch ID consent prompt with the provided reason
// string to the user. On completion or error, the provided callback is // string to the user. On completion or error, the provided callback is
// invoked, unless the TouchIdContext instance has been destroyed in the // invoked, unless the TouchIdContext instance has been destroyed in the
// meantime (in which case nothing happens). // meantime (in which case nothing happens).
void PromptTouchId(std::string reason, Callback callback); virtual void PromptTouchId(std::string reason, Callback callback);
// authentication_context returns the LAContext used for the Touch ID prompt. // authentication_context returns the LAContext used for the Touch ID prompt.
LAContext* authentication_context() const { return context_; } LAContext* authentication_context() const { return context_; }
...@@ -42,7 +58,19 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { ...@@ -42,7 +58,19 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext {
// evaluated/authorized in the Touch ID prompt. // evaluated/authorized in the Touch ID prompt.
SecAccessControlRef access_control() const { return access_control_; } SecAccessControlRef access_control() const { return access_control_; }
protected:
TouchIdContext();
private: private:
using CreateFuncPtr = decltype(&Create);
using TouchIdAvailableFuncPtr = decltype(&TouchIdAvailable);
static CreateFuncPtr g_create_;
static TouchIdAvailableFuncPtr g_touch_id_available_;
static std::unique_ptr<TouchIdContext> CreateImpl();
static bool TouchIdAvailableImpl();
void RunCallback(bool success); void RunCallback(bool success);
base::scoped_nsobject<LAContext> context_; base::scoped_nsobject<LAContext> context_;
...@@ -50,6 +78,7 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { ...@@ -50,6 +78,7 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext {
Callback callback_; Callback callback_;
base::WeakPtrFactory<TouchIdContext> weak_ptr_factory_; base::WeakPtrFactory<TouchIdContext> weak_ptr_factory_;
friend class ScopedTouchIdTestEnvironment;
DISALLOW_COPY_AND_ASSIGN(TouchIdContext); DISALLOW_COPY_AND_ASSIGN(TouchIdContext);
}; };
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/foundation_util.h" #include "base/mac/foundation_util.h"
#include "base/memory/ptr_util.h"
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/sequenced_task_runner_handle.h"
...@@ -20,6 +21,8 @@ namespace mac { ...@@ -20,6 +21,8 @@ namespace mac {
namespace { namespace {
API_AVAILABLE(macosx(10.12.2)) API_AVAILABLE(macosx(10.12.2))
base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() { base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() {
// The default access control policy used for WebAuthn credentials stored by
// the Touch ID platform authenticator.
return base::ScopedCFTypeRef<SecAccessControlRef>( return base::ScopedCFTypeRef<SecAccessControlRef>(
SecAccessControlCreateWithFlags( SecAccessControlCreateWithFlags(
kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
...@@ -28,6 +31,39 @@ base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() { ...@@ -28,6 +31,39 @@ base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() {
} }
} // namespace } // namespace
// static
std::unique_ptr<TouchIdContext> TouchIdContext::CreateImpl() {
return base::WrapUnique(new TouchIdContext());
}
// static
TouchIdContext::CreateFuncPtr TouchIdContext::g_create_ =
&TouchIdContext::CreateImpl;
// static
std::unique_ptr<TouchIdContext> TouchIdContext::Create() {
// Testing seam to allow faking Touch ID in tests.
return (*g_create_)();
}
// static
bool TouchIdContext::TouchIdAvailableImpl() {
base::scoped_nsobject<LAContext> context([[LAContext alloc] init]);
return
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:nil];
}
// static
TouchIdContext::TouchIdAvailableFuncPtr TouchIdContext::g_touch_id_available_ =
&TouchIdContext::TouchIdAvailableImpl;
// static
bool TouchIdContext::TouchIdAvailable() {
// Testing seam to allow faking Touch ID in tests.
return (*g_touch_id_available_)();
}
TouchIdContext::TouchIdContext() TouchIdContext::TouchIdContext()
: context_([[LAContext alloc] init]), : context_([[LAContext alloc] init]),
access_control_(DefaultAccessControl()), access_control_(DefaultAccessControl()),
......
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