auth_token_requester_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "device/fido/auth_token_requester.h"
  5. #include <list>
  6. #include <string>
  7. #include "testing/gmock/include/gmock/gmock.h"
  8. #include "testing/gtest/include/gtest/gtest.h"
  9. #include "base/containers/contains.h"
  10. #include "base/containers/span.h"
  11. #include "base/logging.h"
  12. #include "base/memory/scoped_refptr.h"
  13. #include "base/run_loop.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/test/task_environment.h"
  16. #include "device/fido/fido_constants.h"
  17. #include "device/fido/fido_device_authenticator.h"
  18. #include "device/fido/pin.h"
  19. #include "device/fido/virtual_ctap2_device.h"
  20. #include "third_party/abseil-cpp/absl/types/optional.h"
  21. namespace device {
  22. namespace {
  23. using ::testing::ElementsAreArray;
  24. using ClientPinAvailability =
  25. device::AuthenticatorSupportedOptions::ClientPinAvailability;
  26. using UserVerificationAvailability =
  27. device::AuthenticatorSupportedOptions::UserVerificationAvailability;
  28. constexpr char kTestPIN[] = "1234";
  29. constexpr char16_t kTestPIN16[] = u"1234";
  30. constexpr char16_t kNewPIN[] = u"5678";
  31. struct TestExpectation {
  32. pin::PINEntryReason reason;
  33. pin::PINEntryError error = pin::PINEntryError::kNoError;
  34. uint32_t min_pin_length = kMinPinLength;
  35. int attempts = 8;
  36. std::u16string pin = kTestPIN16;
  37. };
  38. struct TestCase {
  39. ClientPinAvailability client_pin;
  40. UserVerificationAvailability user_verification;
  41. bool success;
  42. std::list<TestExpectation> expectations;
  43. };
  44. class TestAuthTokenRequesterDelegate : public AuthTokenRequester::Delegate {
  45. public:
  46. explicit TestAuthTokenRequesterDelegate(
  47. std::list<TestExpectation> expectations)
  48. : expectations_(std::move(expectations)) {}
  49. void WaitForResult() { wait_for_result_loop_.Run(); }
  50. absl::optional<AuthTokenRequester::Result>& result() { return result_; }
  51. absl::optional<pin::TokenResponse>& response() { return response_; }
  52. bool internal_uv_was_retried() { return internal_uv_num_retries_ > 0u; }
  53. size_t internal_uv_num_retries() { return internal_uv_num_retries_; }
  54. std::list<TestExpectation> expectations() { return expectations_; }
  55. void set_selectable(bool selectable) { selectable_ = selectable; }
  56. private:
  57. // AuthTokenRequester::Delegate:
  58. bool AuthenticatorSelectedForPINUVAuthToken(
  59. FidoAuthenticator* authenticator) override {
  60. DCHECK(!authenticator_selected_);
  61. if (selectable_) {
  62. authenticator_selected_ = true;
  63. }
  64. return selectable_;
  65. }
  66. void CollectPIN(pin::PINEntryReason reason,
  67. pin::PINEntryError error,
  68. uint32_t min_pin_length,
  69. int attempts,
  70. ProvidePINCallback provide_pin_cb) override {
  71. DCHECK(authenticator_selected_);
  72. DCHECK_NE(expectations_.size(), 0u);
  73. DCHECK_EQ(reason, expectations_.front().reason);
  74. DCHECK_EQ(error, expectations_.front().error);
  75. DCHECK_EQ(min_pin_length, expectations_.front().min_pin_length);
  76. DCHECK_EQ(attempts, expectations_.front().attempts);
  77. std::u16string pin = expectations_.front().pin;
  78. expectations_.pop_front();
  79. std::move(provide_pin_cb).Run(pin);
  80. }
  81. void PromptForInternalUVRetry(int attempts) override {
  82. DCHECK(authenticator_selected_);
  83. internal_uv_num_retries_++;
  84. }
  85. void HavePINUVAuthTokenResultForAuthenticator(
  86. FidoAuthenticator* authenticator,
  87. AuthTokenRequester::Result result,
  88. absl::optional<pin::TokenResponse> response) override {
  89. if (!base::Contains(
  90. std::vector<AuthTokenRequester::Result>{
  91. AuthTokenRequester::Result::
  92. kPreTouchAuthenticatorResponseInvalid,
  93. AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest},
  94. result)) {
  95. DCHECK(authenticator_selected_);
  96. }
  97. DCHECK(!result_);
  98. result_ = result;
  99. response_ = std::move(response);
  100. wait_for_result_loop_.Quit();
  101. }
  102. std::list<TestExpectation> expectations_;
  103. absl::optional<AuthTokenRequester::Result> result_;
  104. absl::optional<pin::TokenResponse> response_;
  105. bool authenticator_selected_ = false;
  106. size_t internal_uv_num_retries_ = 0u;
  107. bool selectable_ = true;
  108. base::RunLoop wait_for_result_loop_;
  109. };
  110. class AuthTokenRequesterTest : public ::testing::Test {
  111. protected:
  112. void SetUp() override {}
  113. void RunTestCase(VirtualCtap2Device::Config config,
  114. scoped_refptr<VirtualFidoDevice::State> state,
  115. const TestCase& test_case) {
  116. state_ = state;
  117. switch (test_case.client_pin) {
  118. case ClientPinAvailability::kNotSupported:
  119. config.pin_support = false;
  120. break;
  121. case ClientPinAvailability::kSupportedButPinNotSet:
  122. config.pin_support = true;
  123. break;
  124. case ClientPinAvailability::kSupportedAndPinSet:
  125. config.pin_support = true;
  126. state_->pin = kTestPIN;
  127. break;
  128. }
  129. switch (test_case.user_verification) {
  130. case UserVerificationAvailability::kNotSupported:
  131. config.internal_uv_support = false;
  132. break;
  133. case UserVerificationAvailability::kSupportedButNotConfigured:
  134. config.internal_uv_support = true;
  135. break;
  136. case UserVerificationAvailability::kSupportedAndConfigured:
  137. config.internal_uv_support = true;
  138. state_->fingerprints_enrolled = true;
  139. break;
  140. }
  141. auto authenticator = std::make_unique<FidoDeviceAuthenticator>(
  142. std::make_unique<VirtualCtap2Device>(state_, std::move(config)));
  143. base::RunLoop init_loop;
  144. authenticator->InitializeAuthenticator(init_loop.QuitClosure());
  145. init_loop.Run();
  146. delegate_ = std::make_unique<TestAuthTokenRequesterDelegate>(
  147. std::move(test_case.expectations));
  148. AuthTokenRequester::Options options;
  149. options.token_permissions = {pin::Permissions::kMakeCredential};
  150. options.rp_id = "foobar.com";
  151. AuthTokenRequester requester(delegate_.get(), authenticator.get(),
  152. std::move(options));
  153. requester.ObtainPINUVAuthToken();
  154. delegate_->WaitForResult();
  155. }
  156. void TearDown() override {
  157. if (delegate_) {
  158. EXPECT_EQ(delegate_->expectations().size(), 0u);
  159. }
  160. }
  161. base::test::TaskEnvironment task_environment_{
  162. base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  163. scoped_refptr<VirtualFidoDevice::State> state_;
  164. std::unique_ptr<TestAuthTokenRequesterDelegate> delegate_;
  165. };
  166. TEST_F(AuthTokenRequesterTest, AuthenticatorWithoutUVTokenSupport) {
  167. const TestCase kTestCases[]{
  168. {
  169. ClientPinAvailability::kNotSupported,
  170. UserVerificationAvailability::kNotSupported,
  171. false,
  172. },
  173. {
  174. ClientPinAvailability::kNotSupported,
  175. UserVerificationAvailability::kSupportedButNotConfigured,
  176. false,
  177. },
  178. {
  179. ClientPinAvailability::kNotSupported,
  180. UserVerificationAvailability::kSupportedAndConfigured,
  181. false,
  182. },
  183. {
  184. ClientPinAvailability::kSupportedButPinNotSet,
  185. UserVerificationAvailability::kNotSupported,
  186. true,
  187. {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
  188. },
  189. {
  190. ClientPinAvailability::kSupportedButPinNotSet,
  191. UserVerificationAvailability::kSupportedButNotConfigured,
  192. true,
  193. {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
  194. },
  195. {
  196. ClientPinAvailability::kSupportedButPinNotSet,
  197. UserVerificationAvailability::kSupportedAndConfigured,
  198. true,
  199. {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
  200. },
  201. {
  202. ClientPinAvailability::kSupportedAndPinSet,
  203. UserVerificationAvailability::kNotSupported,
  204. true,
  205. {{pin::PINEntryReason::kChallenge}},
  206. },
  207. {
  208. ClientPinAvailability::kSupportedAndPinSet,
  209. UserVerificationAvailability::kSupportedButNotConfigured,
  210. true,
  211. {{pin::PINEntryReason::kChallenge}},
  212. },
  213. {
  214. ClientPinAvailability::kSupportedAndPinSet,
  215. UserVerificationAvailability::kSupportedAndConfigured,
  216. true,
  217. {{pin::PINEntryReason::kChallenge}},
  218. },
  219. };
  220. int i = 0;
  221. for (const TestCase& t : kTestCases) {
  222. SCOPED_TRACE(i++);
  223. VirtualCtap2Device::Config config;
  224. config.pin_uv_auth_token_support = false;
  225. RunTestCase(std::move(config),
  226. base::MakeRefCounted<VirtualFidoDevice::State>(), t);
  227. if (t.success) {
  228. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  229. EXPECT_THAT(delegate_->response()->token_for_testing(),
  230. ElementsAreArray(state_->pin_token));
  231. } else {
  232. EXPECT_EQ(*delegate_->result(),
  233. AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest);
  234. EXPECT_FALSE(delegate_->response());
  235. }
  236. EXPECT_FALSE(delegate_->internal_uv_was_retried());
  237. }
  238. }
  239. TEST_F(AuthTokenRequesterTest, AuthenticatorWithUVTokenSupport) {
  240. const TestCase kTestCases[]{
  241. {
  242. ClientPinAvailability::kNotSupported,
  243. UserVerificationAvailability::kNotSupported,
  244. false,
  245. },
  246. {
  247. ClientPinAvailability::kNotSupported,
  248. UserVerificationAvailability::kSupportedButNotConfigured,
  249. false,
  250. },
  251. {
  252. ClientPinAvailability::kNotSupported,
  253. UserVerificationAvailability::kSupportedAndConfigured,
  254. true,
  255. },
  256. {
  257. ClientPinAvailability::kSupportedButPinNotSet,
  258. UserVerificationAvailability::kNotSupported,
  259. true,
  260. {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
  261. },
  262. {
  263. ClientPinAvailability::kSupportedButPinNotSet,
  264. UserVerificationAvailability::kSupportedButNotConfigured,
  265. true,
  266. {{.reason = pin::PINEntryReason::kSet, .attempts = 0}},
  267. },
  268. {
  269. ClientPinAvailability::kSupportedButPinNotSet,
  270. UserVerificationAvailability::kSupportedAndConfigured,
  271. true,
  272. },
  273. {
  274. ClientPinAvailability::kSupportedAndPinSet,
  275. UserVerificationAvailability::kNotSupported,
  276. true,
  277. {{pin::PINEntryReason::kChallenge}},
  278. },
  279. {
  280. ClientPinAvailability::kSupportedAndPinSet,
  281. UserVerificationAvailability::kSupportedButNotConfigured,
  282. true,
  283. {{pin::PINEntryReason::kChallenge}},
  284. },
  285. {
  286. ClientPinAvailability::kSupportedAndPinSet,
  287. UserVerificationAvailability::kSupportedAndConfigured,
  288. true,
  289. },
  290. };
  291. int i = 0;
  292. for (const TestCase& t : kTestCases) {
  293. SCOPED_TRACE(i++);
  294. VirtualCtap2Device::Config config;
  295. config.pin_uv_auth_token_support = true;
  296. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  297. std::end(kCtap2Versions2_1)};
  298. RunTestCase(std::move(config),
  299. base::MakeRefCounted<VirtualFidoDevice::State>(), t);
  300. if (t.success) {
  301. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  302. EXPECT_EQ(state_->pin_uv_token_rpid, "foobar.com");
  303. EXPECT_EQ(state_->pin_uv_token_permissions,
  304. static_cast<uint8_t>(pin::Permissions::kMakeCredential));
  305. EXPECT_THAT(delegate_->response()->token_for_testing(),
  306. ElementsAreArray(state_->pin_token));
  307. EXPECT_FALSE(delegate_->internal_uv_was_retried());
  308. } else {
  309. EXPECT_EQ(*delegate_->result(),
  310. AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest);
  311. EXPECT_FALSE(delegate_->response());
  312. EXPECT_FALSE(delegate_->internal_uv_was_retried());
  313. }
  314. }
  315. }
  316. TEST_F(AuthTokenRequesterTest, PINSoftLock) {
  317. VirtualCtap2Device::Config config;
  318. config.pin_uv_auth_token_support = true;
  319. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  320. std::end(kCtap2Versions2_1)};
  321. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  322. state->soft_locked = true;
  323. RunTestCase(std::move(config), state,
  324. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  325. UserVerificationAvailability::kNotSupported,
  326. false,
  327. {{pin::PINEntryReason::kChallenge}}});
  328. EXPECT_EQ(*delegate_->result(),
  329. AuthTokenRequester::Result::kPostTouchAuthenticatorPINSoftLock);
  330. EXPECT_FALSE(delegate_->response());
  331. EXPECT_FALSE(delegate_->internal_uv_was_retried());
  332. }
  333. TEST_F(AuthTokenRequesterTest, PINHardLock) {
  334. VirtualCtap2Device::Config config;
  335. config.pin_uv_auth_token_support = true;
  336. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  337. std::end(kCtap2Versions2_1)};
  338. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  339. state->pin_retries = 0;
  340. RunTestCase(std::move(config), state,
  341. TestCase{
  342. ClientPinAvailability::kSupportedAndPinSet,
  343. UserVerificationAvailability::kNotSupported,
  344. false,
  345. });
  346. EXPECT_EQ(*delegate_->result(),
  347. AuthTokenRequester::Result::kPostTouchAuthenticatorPINHardLock);
  348. EXPECT_FALSE(delegate_->response());
  349. EXPECT_FALSE(delegate_->internal_uv_was_retried());
  350. }
  351. TEST_F(AuthTokenRequesterTest, PINInvalid) {
  352. VirtualCtap2Device::Config config;
  353. config.pin_uv_auth_token_support = true;
  354. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  355. std::end(kCtap2Versions2_1)};
  356. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  357. RunTestCase(
  358. std::move(config), state,
  359. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  360. UserVerificationAvailability::kNotSupported,
  361. true,
  362. {{.reason = pin::PINEntryReason::kChallenge,
  363. .pin = std::u16string({0xd800, 0xd800, 0xd800, 0xd800})},
  364. {pin::PINEntryReason::kChallenge,
  365. pin::PINEntryError::kInvalidCharacters}}});
  366. }
  367. TEST_F(AuthTokenRequesterTest, PINTooShort) {
  368. VirtualCtap2Device::Config config;
  369. config.pin_uv_auth_token_support = true;
  370. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  371. std::end(kCtap2Versions2_1)};
  372. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  373. RunTestCase(
  374. std::move(config), state,
  375. TestCase{
  376. ClientPinAvailability::kSupportedAndPinSet,
  377. UserVerificationAvailability::kNotSupported,
  378. true,
  379. {{.reason = pin::PINEntryReason::kChallenge, .pin = u"まどか"},
  380. {pin::PINEntryReason::kChallenge, pin::PINEntryError::kTooShort}}});
  381. }
  382. TEST_F(AuthTokenRequesterTest, UVLockedPINFallback) {
  383. VirtualCtap2Device::Config config;
  384. config.pin_uv_auth_token_support = true;
  385. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  386. std::end(kCtap2Versions2_1)};
  387. config.user_verification_succeeds = false;
  388. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  389. state->uv_retries = 3;
  390. RunTestCase(std::move(config), state,
  391. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  392. UserVerificationAvailability::kSupportedAndConfigured,
  393. true,
  394. {{pin::PINEntryReason::kChallenge,
  395. pin::PINEntryError::kInternalUvLocked}}});
  396. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  397. EXPECT_TRUE(delegate_->response());
  398. EXPECT_EQ(delegate_->internal_uv_num_retries(), 2u);
  399. }
  400. TEST_F(AuthTokenRequesterTest, UVAlreadyLockedPINFallback) {
  401. VirtualCtap2Device::Config config;
  402. config.pin_uv_auth_token_support = true;
  403. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  404. std::end(kCtap2Versions2_1)};
  405. config.user_verification_succeeds = false;
  406. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  407. state->uv_retries = 0;
  408. RunTestCase(std::move(config), state,
  409. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  410. UserVerificationAvailability::kSupportedAndConfigured,
  411. true,
  412. {{pin::PINEntryReason::kChallenge,
  413. pin::PINEntryError::kInternalUvLocked}}});
  414. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  415. EXPECT_TRUE(delegate_->response());
  416. EXPECT_EQ(delegate_->internal_uv_num_retries(), 0u);
  417. }
  418. TEST_F(AuthTokenRequesterTest, ForcePINChange) {
  419. VirtualCtap2Device::Config config;
  420. config.pin_uv_auth_token_support = true;
  421. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  422. std::end(kCtap2Versions2_1)};
  423. config.min_pin_length_support = true;
  424. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  425. state->force_pin_change = true;
  426. RunTestCase(std::move(config), state,
  427. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  428. UserVerificationAvailability::kNotSupported,
  429. true,
  430. {{pin::PINEntryReason::kChallenge},
  431. {
  432. .reason = pin::PINEntryReason::kChange,
  433. .attempts = 0,
  434. .pin = kNewPIN,
  435. }}});
  436. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  437. EXPECT_TRUE(delegate_->response());
  438. }
  439. TEST_F(AuthTokenRequesterTest, ForcePINChangeSameAsCurrent) {
  440. VirtualCtap2Device::Config config;
  441. config.pin_uv_auth_token_support = true;
  442. config.ctap2_versions = {std::begin(kCtap2Versions2_1),
  443. std::end(kCtap2Versions2_1)};
  444. config.min_pin_length_support = true;
  445. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  446. state->force_pin_change = true;
  447. RunTestCase(std::move(config), state,
  448. TestCase{ClientPinAvailability::kSupportedAndPinSet,
  449. UserVerificationAvailability::kNotSupported,
  450. true,
  451. {{pin::PINEntryReason::kChallenge},
  452. {
  453. .reason = pin::PINEntryReason::kChange,
  454. .attempts = 0,
  455. },
  456. {
  457. .reason = pin::PINEntryReason::kChange,
  458. .error = pin::PINEntryError::kSameAsCurrentPIN,
  459. .attempts = 0,
  460. .pin = kNewPIN,
  461. }}});
  462. EXPECT_EQ(*delegate_->result(), AuthTokenRequester::Result::kSuccess);
  463. EXPECT_TRUE(delegate_->response());
  464. }
  465. TEST_F(AuthTokenRequesterTest, NoCallsIfNotSelected) {
  466. // Test that a failure to select an authenticator stops processing.
  467. auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
  468. VirtualCtap2Device::Config config;
  469. config.pin_support = true;
  470. state->pin = kTestPIN;
  471. config.internal_uv_support = true;
  472. state->fingerprints_enrolled = true;
  473. auto authenticator = std::make_unique<FidoDeviceAuthenticator>(
  474. std::make_unique<VirtualCtap2Device>(state, std::move(config)));
  475. base::RunLoop init_loop;
  476. authenticator->InitializeAuthenticator(init_loop.QuitClosure());
  477. init_loop.Run();
  478. auto delegate = std::make_unique<TestAuthTokenRequesterDelegate>(
  479. std::list<TestExpectation>());
  480. delegate->set_selectable(false);
  481. AuthTokenRequester::Options options;
  482. options.token_permissions = {pin::Permissions::kMakeCredential};
  483. options.rp_id = "foobar.com";
  484. AuthTokenRequester requester(delegate.get(), authenticator.get(),
  485. std::move(options));
  486. requester.ObtainPINUVAuthToken();
  487. }
  488. } // namespace
  489. } // namespace device