cryptohome: Add unittests for WebAuthn secret

Unittests for http://crrev/c/2525241. Also drop the unused
|obfuscated_username| argument which was erroneously added.

BUG=b:144861739
TEST=unittest

Change-Id: I6c07cdb34608f54d7470eb82602aec00b69849f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2528634
Tested-by: Yicheng Li <yichengli@chromium.org>
Reviewed-by: Daniil Lunev <dlunev@chromium.org>
Reviewed-by: Greg Kerr <kerrnel@chromium.org>
Commit-Queue: Yicheng Li <yichengli@chromium.org>
diff --git a/cryptohome/user_session.cc b/cryptohome/user_session.cc
index d4b3561..a01c0e6 100644
--- a/cryptohome/user_session.cc
+++ b/cryptohome/user_session.cc
@@ -83,8 +83,7 @@
   SetCredentials(credentials, vk->GetLegacyIndex());
   UpdateActivityTimestamp(0);
 
-  PrepareWebAuthnSecret(obfuscated_username_, fs_keyset.Key().fek,
-                        fs_keyset.Key().fnek);
+  PrepareWebAuthnSecret(fs_keyset.Key().fek, fs_keyset.Key().fnek);
 
   return code;
 }
@@ -114,8 +113,7 @@
   SetCredentials(auth_session);
   UpdateActivityTimestamp(0);
 
-  PrepareWebAuthnSecret(obfuscated_username_, fs_keyset.Key().fek,
-                        fs_keyset.Key().fnek);
+  PrepareWebAuthnSecret(fs_keyset.Key().fek, fs_keyset.Key().fnek);
 
   return code;
 }
@@ -150,8 +148,7 @@
   return mount_->GetStatus(key_index_);
 }
 
-void UserSession::PrepareWebAuthnSecret(const std::string& obfuscated_username,
-                                        const brillo::SecureBlob& fek,
+void UserSession::PrepareWebAuthnSecret(const brillo::SecureBlob& fek,
                                         const brillo::SecureBlob& fnek) {
   // This WebAuthn secret can be rederived upon in-session user auth success
   // since they will unlock the vault keyset.
diff --git a/cryptohome/user_session.h b/cryptohome/user_session.h
index d7261b9..de89583 100644
--- a/cryptohome/user_session.h
+++ b/cryptohome/user_session.h
@@ -94,8 +94,7 @@
 
  private:
   // Computes a public derivative from |fek| and |fnek| for u2fd to fetch.
-  void PrepareWebAuthnSecret(const std::string& obfuscated_username,
-                             const brillo::SecureBlob& fek,
+  void PrepareWebAuthnSecret(const brillo::SecureBlob& fek,
                              const brillo::SecureBlob& fnek);
 
   // Clears the WebAuthn secret if it's not read yet.
diff --git a/cryptohome/user_session_unittest.cc b/cryptohome/user_session_unittest.cc
index a88700a..635459b 100644
--- a/cryptohome/user_session_unittest.cc
+++ b/cryptohome/user_session_unittest.cc
@@ -37,6 +37,7 @@
 
 constexpr char kUser0[] = "First User";
 constexpr char kUserPassword0[] = "user0_pass";
+constexpr char kWebAuthnSecretHmacMessage[] = "AuthTimeWebAuthnSecret";
 
 }  // namespace
 
@@ -164,6 +165,7 @@
       keyset_management_->LoadVaultKeysetForUser(users_[0].obfuscated, 0);
   const int64_t ts1 = vk0->GetLastActivityTimestamp();
   EXPECT_EQ(ts1, kTs1);
+  EXPECT_NE(session_->GetWebAuthnSecret(), nullptr);
 
   // SETUP
 
@@ -291,6 +293,7 @@
   vk0 = keyset_management_->LoadVaultKeysetForUser(users_[0].obfuscated, 0);
   const int64_t ts3 = vk0->GetLastActivityTimestamp();
   EXPECT_EQ(ts3, ts2);
+  EXPECT_NE(session_->GetWebAuthnSecret(), nullptr);
 }
 
 // Fail to mount because vault doesn't exist and creation is disaalowed.
@@ -310,6 +313,78 @@
   EXPECT_FALSE(platform_.DirectoryExists(users_[0].homedir_path));
   EXPECT_FALSE(session_->VerifyCredentials(users_[0].credentials));
   EXPECT_FALSE(keyset_management_->AreCredentialsValid(users_[0].credentials));
+  EXPECT_EQ(session_->GetWebAuthnSecret(), nullptr);
+}
+
+// WebAuthn secret is cleared after read once.
+TEST_F(UserSessionTest, WebAuthnSecretReadTwice) {
+  // SETUP
+
+  Mount::MountArgs mount_args_create;
+  // Test with ecryptfs since it has a simpler existence check.
+  mount_args_create.create_as_ecryptfs = true;
+  mount_args_create.create_if_missing = true;
+
+  EXPECT_CALL(*mount_,
+              MountCryptohome(users_[0].name, _,
+                              MountArgsEqual(mount_args_create), true, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*mount_, IsNonEphemeralMounted()).WillOnce(Return(true));
+
+  ASSERT_EQ(MOUNT_ERROR_NONE,
+            session_->MountVault(users_[0].credentials, mount_args_create));
+
+  MountError code = MOUNT_ERROR_NONE;
+  std::unique_ptr<VaultKeyset> vk =
+      keyset_management_->LoadUnwrappedKeyset(users_[0].credentials, &code);
+  EXPECT_EQ(code, MOUNT_ERROR_NONE);
+  EXPECT_NE(vk, nullptr);
+  FileSystemKeyset fs_keyset(*vk);
+  const std::string message(kWebAuthnSecretHmacMessage);
+  auto expected_webauthn_secret = std::make_unique<brillo::SecureBlob>(
+      CryptoLib::HmacSha256(brillo::SecureBlob::Combine(fs_keyset.Key().fnek,
+                                                        fs_keyset.Key().fek),
+                            brillo::Blob(message.cbegin(), message.cend())));
+  EXPECT_NE(expected_webauthn_secret, nullptr);
+
+  // TEST
+
+  std::unique_ptr<brillo::SecureBlob> actual_webauthn_secret =
+      session_->GetWebAuthnSecret();
+  EXPECT_NE(actual_webauthn_secret, nullptr);
+  EXPECT_EQ(*actual_webauthn_secret, *expected_webauthn_secret);
+
+  // VERIFY
+  // The second read should get nothing.
+
+  EXPECT_EQ(session_->GetWebAuthnSecret(), nullptr);
+}
+
+// WebAuthn secret is cleared after timeout.
+TEST_F(UserSessionTest, WebAuthnSecretTimeout) {
+  // SETUP
+
+  Mount::MountArgs mount_args_create;
+  // Test with ecryptfs since it has a simpler existence check.
+  mount_args_create.create_as_ecryptfs = true;
+  mount_args_create.create_if_missing = true;
+
+  EXPECT_CALL(*mount_,
+              MountCryptohome(users_[0].name, _,
+                              MountArgsEqual(mount_args_create), true, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*mount_, IsNonEphemeralMounted()).WillOnce(Return(true));
+
+  ASSERT_EQ(MOUNT_ERROR_NONE,
+            session_->MountVault(users_[0].credentials, mount_args_create));
+
+  // TEST
+
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(5));
+
+  // VERIFY
+
+  EXPECT_EQ(session_->GetWebAuthnSecret(), nullptr);
 }
 
 class UserSessionReAuthTest : public ::testing::Test {