123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- // 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.
- #include "device/fido/auth_token_requester.h"
- #include <list>
- #include <string>
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "base/containers/contains.h"
- #include "base/containers/span.h"
- #include "base/logging.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/run_loop.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/test/task_environment.h"
- #include "device/fido/fido_constants.h"
- #include "device/fido/fido_device_authenticator.h"
- #include "device/fido/pin.h"
- #include "device/fido/virtual_ctap2_device.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace device {
- namespace {
- using ::testing::ElementsAreArray;
- using ClientPinAvailability =
- device::AuthenticatorSupportedOptions::ClientPinAvailability;
- using UserVerificationAvailability =
- device::AuthenticatorSupportedOptions::UserVerificationAvailability;
- constexpr char kTestPIN[] = "1234";
- constexpr char16_t kTestPIN16[] = u"1234";
- constexpr char16_t kNewPIN[] = u"5678";
- struct TestExpectation {
- pin::PINEntryReason reason;
- pin::PINEntryError error = pin::PINEntryError::kNoError;
- uint32_t min_pin_length = kMinPinLength;
- int attempts = 8;
- std::u16string pin = kTestPIN16;
- };
- struct TestCase {
- ClientPinAvailability client_pin;
- UserVerificationAvailability user_verification;
- bool success;
- std::list<TestExpectation> expectations;
- };
- class TestAuthTokenRequesterDelegate : public AuthTokenRequester::Delegate {
- public:
- explicit TestAuthTokenRequesterDelegate(
- std::list<TestExpectation> expectations)
- : expectations_(std::move(expectations)) {}
- void WaitForResult() { wait_for_result_loop_.Run(); }
- absl::optional<AuthTokenRequester::Result>& result() { return result_; }
- absl::optional<pin::TokenResponse>& response() { return response_; }
- bool internal_uv_was_retried() { return internal_uv_num_retries_ > 0u; }
- size_t internal_uv_num_retries() { return internal_uv_num_retries_; }
- std::list<TestExpectation> expectations() { return expectations_; }
- void set_selectable(bool selectable) { selectable_ = selectable; }
- private:
- // AuthTokenRequester::Delegate:
- bool AuthenticatorSelectedForPINUVAuthToken(
- FidoAuthenticator* authenticator) override {
- DCHECK(!authenticator_selected_);
- if (selectable_) {
- authenticator_selected_ = true;
- }
- return selectable_;
- }
- void CollectPIN(pin::PINEntryReason reason,
- pin::PINEntryError error,
- uint32_t min_pin_length,
- int attempts,
- ProvidePINCallback provide_pin_cb) override {
- DCHECK(authenticator_selected_);
- DCHECK_NE(expectations_.size(), 0u);
- DCHECK_EQ(reason, expectations_.front().reason);
- DCHECK_EQ(error, expectations_.front().error);
- DCHECK_EQ(min_pin_length, expectations_.front().min_pin_length);
- DCHECK_EQ(attempts, expectations_.front().attempts);
- std::u16string pin = expectations_.front().pin;
- expectations_.pop_front();
- std::move(provide_pin_cb).Run(pin);
- }
- void PromptForInternalUVRetry(int attempts) override {
- DCHECK(authenticator_selected_);
- internal_uv_num_retries_++;
- }
- void HavePINUVAuthTokenResultForAuthenticator(
- FidoAuthenticator* authenticator,
- AuthTokenRequester::Result result,
- absl::optional<pin::TokenResponse> response) override {
- if (!base::Contains(
- std::vector<AuthTokenRequester::Result>{
- AuthTokenRequester::Result::
- kPreTouchAuthenticatorResponseInvalid,
- AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest},
- result)) {
- DCHECK(authenticator_selected_);
- }
- DCHECK(!result_);
- result_ = result;
- response_ = std::move(response);
- wait_for_result_loop_.Quit();
- }
- std::list<TestExpectation> expectations_;
- absl::optional<AuthTokenRequester::Result> result_;
- absl::optional<pin::TokenResponse> response_;
- bool authenticator_selected_ = false;
- size_t internal_uv_num_retries_ = 0u;
- bool selectable_ = true;
- base::RunLoop wait_for_result_loop_;
- };
- class AuthTokenRequesterTest : public ::testing::Test {
- protected:
- void SetUp() override {}
- void RunTestCase(VirtualCtap2Device::Config config,
- scoped_refptr<VirtualFidoDevice::State> state,
- const TestCase& test_case) {
- state_ = state;
- switch (test_case.client_pin) {
- case ClientPinAvailability::kNotSupported:
- config.pin_support = false;
- break;
- case ClientPinAvailability::kSupportedButPinNotSet:
- config.pin_support = true;
- break;
- case ClientPinAvailability::kSupportedAndPinSet:
- config.pin_support = true;
- state_->pin = kTestPIN;
- break;
- }
- switch (test_case.user_verification) {
- case UserVerificationAvailability::kNotSupported:
- config.internal_uv_support = false;
- break;
- case UserVerificationAvailability::kSupportedButNotConfigured:
- config.internal_uv_support = true;
- break;
- case UserVerificationAvailability::kSupportedAndConfigured:
- config.internal_uv_support = true;
- state_->fingerprints_enrolled = true;
- break;
- }
- auto authenticator = std::make_unique<FidoDeviceAuthenticator>(
- std::make_unique<VirtualCtap2Device>(state_, std::move(config)));
- base::RunLoop init_loop;
- authenticator->InitializeAuthenticator(init_loop.QuitClosure());
- init_loop.Run();
- delegate_ = std::make_unique<TestAuthTokenRequesterDelegate>(
- std::move(test_case.expectations));
- AuthTokenRequester::Options options;
- options.token_permissions = {pin::Permissions::kMakeCredential};
- options.rp_id = "foobar.com";
- AuthTokenRequester requester(delegate_.get(), authenticator.get(),
- std::move(options));
- requester.ObtainPINUVAuthToken();
- delegate_->WaitForResult();
- }
- void TearDown() override {
- if (delegate_) {
- EXPECT_EQ(delegate_->expectations().size(), 0u);
- }
- }
- base::test::TaskEnvironment task_environment_{
- base::test::TaskEnvironment::TimeSource::MOCK_TIME};
- scoped_refptr<VirtualFidoDevice::State> state_;
- std::unique_ptr<TestAuthTokenRequesterDelegate> delegate_;
- };
- TEST_F(AuthTokenRequesterTest, AuthenticatorWithoutUVTokenSupport) {
- const TestCase kTestCases[]{
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kNotSupported,
- false,
- },
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kSupportedButNotConfigured,
- false,
- },
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kSupportedAndConfigured,
- false,
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kSupportedButNotConfigured,
- true,
- {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{pin::PINEntryReason::kChallenge}},
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedButNotConfigured,
- true,
- {{pin::PINEntryReason::kChallenge}},
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- {{pin::PINEntryReason::kChallenge}},
- },
- };
- int i = 0;
- for (const TestCase& t : kTestCases) {
- SCOPED_TRACE(i++);
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = false;
- RunTestCase(std::move(config),
- base::MakeRefCounted<VirtualFidoDevice::State>(), t);
- if (t.success) {
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_THAT(delegate_->response()->token_for_testing(),
- ElementsAreArray(state_->pin_token));
- } else {
- EXPECT_EQ(*delegate_->result(),
- AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest);
- EXPECT_FALSE(delegate_->response());
- }
- EXPECT_FALSE(delegate_->internal_uv_was_retried());
- }
- }
- TEST_F(AuthTokenRequesterTest, AuthenticatorWithUVTokenSupport) {
- const TestCase kTestCases[]{
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kNotSupported,
- false,
- },
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kSupportedButNotConfigured,
- false,
- },
- {
- ClientPinAvailability::kNotSupported,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kSupportedButNotConfigured,
- true,
- {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
- },
- {
- ClientPinAvailability::kSupportedButPinNotSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{pin::PINEntryReason::kChallenge}},
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedButNotConfigured,
- true,
- {{pin::PINEntryReason::kChallenge}},
- },
- {
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- },
- };
- int i = 0;
- for (const TestCase& t : kTestCases) {
- SCOPED_TRACE(i++);
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- RunTestCase(std::move(config),
- base::MakeRefCounted<VirtualFidoDevice::State>(), t);
- if (t.success) {
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_EQ(state_->pin_uv_token_rpid, "foobar.com");
- EXPECT_EQ(state_->pin_uv_token_permissions,
- static_cast<uint8_t>(pin::Permissions::kMakeCredential));
- EXPECT_THAT(delegate_->response()->token_for_testing(),
- ElementsAreArray(state_->pin_token));
- EXPECT_FALSE(delegate_->internal_uv_was_retried());
- } else {
- EXPECT_EQ(*delegate_->result(),
- AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest);
- EXPECT_FALSE(delegate_->response());
- EXPECT_FALSE(delegate_->internal_uv_was_retried());
- }
- }
- }
- TEST_F(AuthTokenRequesterTest, PINSoftLock) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->soft_locked = true;
- RunTestCase(std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- false,
- {{pin::PINEntryReason::kChallenge}}});
- EXPECT_EQ(*delegate_->result(),
- AuthTokenRequester::Result::kPostTouchAuthenticatorPINSoftLock);
- EXPECT_FALSE(delegate_->response());
- EXPECT_FALSE(delegate_->internal_uv_was_retried());
- }
- TEST_F(AuthTokenRequesterTest, PINHardLock) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->pin_retries = 0;
- RunTestCase(std::move(config), state,
- TestCase{
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- false,
- });
- EXPECT_EQ(*delegate_->result(),
- AuthTokenRequester::Result::kPostTouchAuthenticatorPINHardLock);
- EXPECT_FALSE(delegate_->response());
- EXPECT_FALSE(delegate_->internal_uv_was_retried());
- }
- TEST_F(AuthTokenRequesterTest, PINInvalid) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- RunTestCase(
- std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{.reason = pin::PINEntryReason::kChallenge,
- .pin = std::u16string({0xd800, 0xd800, 0xd800, 0xd800})},
- {pin::PINEntryReason::kChallenge,
- pin::PINEntryError::kInvalidCharacters}}});
- }
- TEST_F(AuthTokenRequesterTest, PINTooShort) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- RunTestCase(
- std::move(config), state,
- TestCase{
- ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{.reason = pin::PINEntryReason::kChallenge, .pin = u"まどか"},
- {pin::PINEntryReason::kChallenge, pin::PINEntryError::kTooShort}}});
- }
- TEST_F(AuthTokenRequesterTest, UVLockedPINFallback) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- config.user_verification_succeeds = false;
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->uv_retries = 3;
- RunTestCase(std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- {{pin::PINEntryReason::kChallenge,
- pin::PINEntryError::kInternalUvLocked}}});
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_TRUE(delegate_->response());
- EXPECT_EQ(delegate_->internal_uv_num_retries(), 2u);
- }
- TEST_F(AuthTokenRequesterTest, UVAlreadyLockedPINFallback) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- config.user_verification_succeeds = false;
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->uv_retries = 0;
- RunTestCase(std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kSupportedAndConfigured,
- true,
- {{pin::PINEntryReason::kChallenge,
- pin::PINEntryError::kInternalUvLocked}}});
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_TRUE(delegate_->response());
- EXPECT_EQ(delegate_->internal_uv_num_retries(), 0u);
- }
- TEST_F(AuthTokenRequesterTest, ForcePINChange) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- config.min_pin_length_support = true;
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->force_pin_change = true;
- RunTestCase(std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{pin::PINEntryReason::kChallenge},
- {
- .reason = pin::PINEntryReason::kChange,
- .attempts = 0,
- .pin = kNewPIN,
- }}});
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_TRUE(delegate_->response());
- }
- TEST_F(AuthTokenRequesterTest, ForcePINChangeSameAsCurrent) {
- VirtualCtap2Device::Config config;
- config.pin_uv_auth_token_support = true;
- config.ctap2_versions = {std::begin(kCtap2Versions2_1),
- std::end(kCtap2Versions2_1)};
- config.min_pin_length_support = true;
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- state->force_pin_change = true;
- RunTestCase(std::move(config), state,
- TestCase{ClientPinAvailability::kSupportedAndPinSet,
- UserVerificationAvailability::kNotSupported,
- true,
- {{pin::PINEntryReason::kChallenge},
- {
- .reason = pin::PINEntryReason::kChange,
- .attempts = 0,
- },
- {
- .reason = pin::PINEntryReason::kChange,
- .error = pin::PINEntryError::kSameAsCurrentPIN,
- .attempts = 0,
- .pin = kNewPIN,
- }}});
- EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
- EXPECT_TRUE(delegate_->response());
- }
- TEST_F(AuthTokenRequesterTest, NoCallsIfNotSelected) {
- // Test that a failure to select an authenticator stops processing.
- auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
- VirtualCtap2Device::Config config;
- config.pin_support = true;
- state->pin = kTestPIN;
- config.internal_uv_support = true;
- state->fingerprints_enrolled = true;
- auto authenticator = std::make_unique<FidoDeviceAuthenticator>(
- std::make_unique<VirtualCtap2Device>(state, std::move(config)));
- base::RunLoop init_loop;
- authenticator->InitializeAuthenticator(init_loop.QuitClosure());
- init_loop.Run();
- auto delegate = std::make_unique<TestAuthTokenRequesterDelegate>(
- std::list<TestExpectation>());
- delegate->set_selectable(false);
- AuthTokenRequester::Options options;
- options.token_permissions = {pin::Permissions::kMakeCredential};
- options.rp_id = "foobar.com";
- AuthTokenRequester requester(delegate.get(), authenticator.get(),
- std::move(options));
- requester.ObtainPINUVAuthToken();
- }
- } // namespace
- } // namespace device
|