blob: 7d7a79e019841ecdfd8408795fd02e94153536d1 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "u2fd/webauthn_handler.h"
#include <regex> // NOLINT(build/c++11)
#include <string>
#include <utility>
#include <base/strings/string_number_conversions.h>
#include <base/time/time.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libhwsec/mock_dbus_method_response.h"
#include "u2fd/mock_tpm_vendor_cmd.h"
#include "u2fd/mock_user_state.h"
#include "u2fd/util.h"
namespace u2f {
namespace {
using ::testing::_;
using ::testing::MatchesRegex;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
constexpr int kVerificationTimeoutMs = 10000;
constexpr int kVerificationRetryDelayUs = 500 * 1000;
constexpr int kMaxRetries =
kVerificationTimeoutMs * 1000 / kVerificationRetryDelayUs;
constexpr uint32_t kCr50StatusSuccess = 0;
constexpr uint32_t kCr50StatusNotAllowed = 0x507;
constexpr uint32_t kCr50StatusPasswordRequired = 0x50a;
// Dummy User State.
constexpr char kUserSecret[65] = {[0 ... 63] = 'E', '\0'};
// Dummy RP id.
constexpr char kRpId[] = "example.com";
const std::vector<uint8_t> kRpIdHash = util::Sha256(std::string(kRpId));
// Dummy key handle (credential ID).
const std::vector<uint8_t> kKeyHandle(U2F_FIXED_KH_SIZE, 0xab);
// Dummy hash to sign.
const std::vector<uint8_t> kHashToSign(U2F_P256_SIZE, 0xcd);
const std::string ExpectedU2fGenerateRequestRegex() {
// See U2F_GENERATE_REQ in //platform/ec/include/u2f.h
static const std::string request_regex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size()) + // AppId
std::string("(EE){32}") + // User Secret
std::string("03"); // U2F_AUTH_ENFORCE
return request_regex;
}
// Only used to test DoU2fSign, where the hash to sign can be determined.
const std::string ExpectedDeterministicU2fSignRequestRegex() {
// See U2F_SIGN_REQ in //platform/ec/include/u2f.h
static const std::string request_regex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size()) + // AppId
std::string("(EE){32}") + // User Secret
std::string("(AB){64}") + // Key handle
std::string("(CD){32}") + // Hash to sign
std::string("03"); // U2F_AUTH_ENFORCE
return request_regex;
}
const std::string ExpectedU2fSignRequestRegex() {
// See U2F_SIGN_REQ in //platform/ec/include/u2f.h
static const std::string request_regex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size()) + // AppId
std::string("(EE){32}") + // User Secret
std::string("(AB){64}") + // Key handle
// Hash_to_sign depends on signature counter which isn't deterministic
std::string("[A-F0-9]{64}") + // Hash to sign
std::string("03"); // U2F_AUTH_ENFORCE
return request_regex;
}
const std::string ExpectedU2fSignCheckOnlyRequestRegex() {
// See U2F_SIGN_REQ in //platform/ec/include/u2f.h
static const std::string request_regex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size()) + // AppId
std::string("(EE){32}") + // User Secret
std::string("(AB){64}") + // Key handle
std::string("(00){32}") + // Hash to sign (empty)
std::string("07"); // U2F_AUTH_CHECK_ONLY
return request_regex;
}
// Dummy cr50 U2F_GENERATE_RESP.
const struct u2f_generate_resp kU2fGenerateResponse = {
.pubKey = {.pointFormat = 0xAB,
.x = {[0 ... 31] = 0xAB},
.y = {[0 ... 31] = 0xAB}},
.keyHandle = {[0 ... 63] = 0xFD}};
// Dummy cr50 U2F_SIGN_RESP.
const struct u2f_sign_resp kU2fSignResponse = {.sig_r = {[0 ... 31] = 0x12},
.sig_s = {[0 ... 31] = 0x34}};
// AuthenticatorData field sizes, in bytes.
constexpr int kRpIdHashBytes = 32;
constexpr int kAuthenticatorDataFlagBytes = 1;
constexpr int kSignatureCounterBytes = 4;
constexpr int kAaguidBytes = 16;
constexpr int kCredentialIdLengthBytes = 2;
brillo::SecureBlob ArrayToSecureBlob(const char* array) {
brillo::SecureBlob blob;
CHECK(brillo::SecureBlob::HexStringToSecureBlob(array, &blob));
return blob;
}
MATCHER_P(StructMatchesRegex, pattern, "") {
std::string arg_hex = base::HexEncode(&arg, sizeof(arg));
if (std::regex_match(arg_hex, std::regex(pattern))) {
return true;
}
*result_listener << arg_hex << " did not match regex: " << pattern;
return false;
}
} // namespace
class WebAuthnHandlerTest : public ::testing::Test {
public:
void SetUp() override { CreateHandler(); }
void TearDown() override {
EXPECT_EQ(presence_requested_expected_, presence_requested_count_);
}
protected:
void CreateHandler() {
handler_.reset(new WebAuthnHandler());
handler_->Initialize(&mock_tpm_proxy_, &mock_user_state_,
[this]() { presence_requested_count_++; });
}
void ExpectGetUserSecret() { ExpectGetUserSecretForTimes(1); }
void ExpectGetUserSecretForTimes(int times) {
EXPECT_CALL(mock_user_state_, GetUserSecret())
.Times(times)
.WillRepeatedly(Return(ArrayToSecureBlob(kUserSecret)));
}
void ExpectGetUserSecretFails() {
EXPECT_CALL(mock_user_state_, GetUserSecret())
.WillOnce(Return(base::Optional<brillo::SecureBlob>()));
}
void CallAndWaitForPresence(std::function<uint32_t()> fn, uint32_t* status) {
handler_->CallAndWaitForPresence(fn, status);
}
bool PresenceRequested() { return presence_requested_count_ > 0; }
MakeCredentialResponse::MakeCredentialStatus DoU2fGenerate(
PresenceRequirement presence_requirement,
std::vector<uint8_t>* credential_id,
std::vector<uint8_t>* credential_pubkey) {
return handler_->DoU2fGenerate(kRpIdHash, presence_requirement,
credential_id, credential_pubkey);
}
GetAssertionResponse::GetAssertionStatus DoU2fSign(
const std::vector<uint8_t>& hash_to_sign,
const std::vector<uint8_t>& credential_id,
PresenceRequirement presence_requirement,
std::vector<uint8_t>* signature) {
return handler_->DoU2fSign(kRpIdHash, hash_to_sign, credential_id,
presence_requirement, signature);
}
std::vector<uint8_t> MakeAuthenticatorData(
const std::vector<uint8_t>& credential_id,
const std::vector<uint8_t>& credential_public_key,
bool user_verified,
bool include_attested_credential_data) {
return handler_->MakeAuthenticatorData(kRpIdHash, credential_id,
credential_public_key, user_verified,
include_attested_credential_data);
}
StrictMock<MockTpmVendorCommandProxy> mock_tpm_proxy_;
StrictMock<MockUserState> mock_user_state_;
std::unique_ptr<WebAuthnHandler> handler_;
int presence_requested_expected_ = 0;
private:
int presence_requested_count_ = 0;
};
namespace {
TEST_F(WebAuthnHandlerTest, CallAndWaitForPresenceDirectSuccess) {
uint32_t status = kCr50StatusNotAllowed;
// If presence is already available, we won't request it.
CallAndWaitForPresence([]() { return kCr50StatusSuccess; }, &status);
EXPECT_EQ(status, kCr50StatusSuccess);
presence_requested_expected_ = 0;
}
TEST_F(WebAuthnHandlerTest, CallAndWaitForPresenceRequestSuccess) {
uint32_t status = kCr50StatusNotAllowed;
CallAndWaitForPresence(
[this]() {
if (PresenceRequested())
return kCr50StatusSuccess;
return kCr50StatusNotAllowed;
},
&status);
EXPECT_EQ(status, kCr50StatusSuccess);
presence_requested_expected_ = 1;
}
TEST_F(WebAuthnHandlerTest, CallAndWaitForPresenceTimeout) {
uint32_t status = kCr50StatusSuccess;
base::TimeTicks verification_start = base::TimeTicks::Now();
CallAndWaitForPresence([]() { return kCr50StatusNotAllowed; }, &status);
EXPECT_GE(base::TimeTicks::Now() - verification_start,
base::TimeDelta::FromMilliseconds(kVerificationTimeoutMs));
EXPECT_EQ(status, kCr50StatusNotAllowed);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(WebAuthnHandlerTest, DoU2fGenerateNoSecret) {
ExpectGetUserSecretFails();
std::vector<uint8_t> cred_id, cred_pubkey;
EXPECT_EQ(
DoU2fGenerate(PresenceRequirement::kPowerButton, &cred_id, &cred_pubkey),
MakeCredentialResponse::INTERNAL_ERROR);
}
TEST_F(WebAuthnHandlerTest, DoU2fGeneratePresenceNoPresence) {
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fGenerate(StructMatchesRegex(ExpectedU2fGenerateRequestRegex()), _))
.WillRepeatedly(Return(kCr50StatusNotAllowed));
std::vector<uint8_t> cred_id, cred_pubkey;
EXPECT_EQ(
DoU2fGenerate(PresenceRequirement::kPowerButton, &cred_id, &cred_pubkey),
MakeCredentialResponse::VERIFICATION_FAILED);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(WebAuthnHandlerTest, DoU2fGeneratePresenceSuccess) {
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fGenerate(StructMatchesRegex(ExpectedU2fGenerateRequestRegex()), _))
.WillOnce(Return(kCr50StatusNotAllowed))
.WillOnce(DoAll(SetArgPointee<1>(kU2fGenerateResponse),
Return(kCr50StatusSuccess)));
std::vector<uint8_t> cred_id, cred_pubkey;
EXPECT_EQ(
DoU2fGenerate(PresenceRequirement::kPowerButton, &cred_id, &cred_pubkey),
MakeCredentialResponse::SUCCESS);
EXPECT_EQ(cred_id, std::vector<uint8_t>(64, 0xFD));
EXPECT_EQ(cred_pubkey, std::vector<uint8_t>(65, 0xAB));
presence_requested_expected_ = 1;
}
TEST_F(WebAuthnHandlerTest, DoU2fSignNoSecret) {
ExpectGetUserSecretFails();
std::vector<uint8_t> signature;
EXPECT_EQ(DoU2fSign(kHashToSign, kKeyHandle,
PresenceRequirement::kPowerButton, &signature),
GetAssertionResponse::INTERNAL_ERROR);
}
TEST_F(WebAuthnHandlerTest, DoU2fSignPresenceNoPresence) {
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(
StructMatchesRegex(ExpectedDeterministicU2fSignRequestRegex()), _))
.WillRepeatedly(Return(kCr50StatusNotAllowed));
std::vector<uint8_t> signature;
EXPECT_EQ(DoU2fSign(kHashToSign, kKeyHandle,
PresenceRequirement::kPowerButton, &signature),
MakeCredentialResponse::VERIFICATION_FAILED);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(WebAuthnHandlerTest, DoU2fSignPresenceSuccess) {
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(
StructMatchesRegex(ExpectedDeterministicU2fSignRequestRegex()), _))
.WillOnce(Return(kCr50StatusNotAllowed))
.WillOnce(DoAll(SetArgPointee<1>(kU2fSignResponse),
Return(kCr50StatusSuccess)));
std::vector<uint8_t> signature;
EXPECT_EQ(DoU2fSign(kHashToSign, kKeyHandle,
PresenceRequirement::kPowerButton, &signature),
MakeCredentialResponse::SUCCESS);
EXPECT_EQ(signature, util::SignatureToDerBytes(kU2fSignResponse.sig_r,
kU2fSignResponse.sig_s));
presence_requested_expected_ = 1;
}
TEST_F(WebAuthnHandlerTest, MakeCredentialUninitialized) {
// Use an uninitialized WebAuthnHandler object.
handler_.reset(new WebAuthnHandler());
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<MakeCredentialResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const MakeCredentialResponse& resp) {
EXPECT_EQ(resp.status(), MakeCredentialResponse::INTERNAL_ERROR);
*called_ptr = true;
},
&called));
MakeCredentialRequest request;
handler_->MakeCredential(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, MakeCredentialEmptyRpId) {
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<MakeCredentialResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const MakeCredentialResponse& resp) {
EXPECT_EQ(resp.status(), MakeCredentialResponse::INVALID_REQUEST);
*called_ptr = true;
},
&called));
MakeCredentialRequest request;
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
handler_->MakeCredential(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, MakeCredentialNoSecret) {
MakeCredentialRequest request;
request.set_rp_id(kRpId);
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecretFails();
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<MakeCredentialResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const MakeCredentialResponse& resp) {
EXPECT_EQ(resp.status(), MakeCredentialResponse::INTERNAL_ERROR);
*called_ptr = true;
},
&called));
handler_->MakeCredential(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, MakeCredentialPresenceNoPresence) {
MakeCredentialRequest request;
request.set_rp_id(kRpId);
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fGenerate(StructMatchesRegex(ExpectedU2fGenerateRequestRegex()), _))
.WillRepeatedly(Return(kCr50StatusNotAllowed));
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<MakeCredentialResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const MakeCredentialResponse& resp) {
EXPECT_EQ(resp.status(), MakeCredentialResponse::VERIFICATION_FAILED);
*called_ptr = true;
},
&called));
handler_->MakeCredential(std::move(mock_method_response), request);
presence_requested_expected_ = kMaxRetries;
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, MakeCredentialPresenceSuccess) {
MakeCredentialRequest request;
request.set_rp_id(kRpId);
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fGenerate(StructMatchesRegex(ExpectedU2fGenerateRequestRegex()), _))
.WillOnce(Return(kCr50StatusNotAllowed))
.WillOnce(DoAll(SetArgPointee<1>(kU2fGenerateResponse),
Return(kCr50StatusSuccess)));
const std::string expected_authenticator_data_regex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size()) + // RP ID hash
std::string(
"41" // Flag: user present, attested credential data included
"(..){4}" // Signature counter
"(00){16}" // AAGUID
"0040" // Credential ID length
"(FD){64}" // Credential ID, from kU2fGenerateResponse
// CBOR encoded credential public key:
"A5" // Start a CBOR map of 5 elements
"01" // unsigned(1), COSE key type field
"02" // unsigned(2), COSE key type EC2
"03" // unsigned(3), COSE key algorithm field
"26" // negative(6) = -7, COSE key algorithm ES256
"20" // negative(0) = -1, COSE EC key curve field
"01" // unsigned(1), COSE EC key curve
"21" // negative(1) = -2, COSE EC key x coordinate field
"5820" // Start a CBOR array of 32 bytes
"(AB){32}" // x coordinate, from kU2fGenerateResponse
"22" // negative(2) = -3, COSE EC key y coordinate field
"5820" // Start a CBOR array of 32 bytes
"(AB){32}"); // y coordinate, from kU2fGenerateResponse
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<MakeCredentialResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const std::string& expected_authenticator_data,
const MakeCredentialResponse& resp) {
EXPECT_EQ(resp.status(), MakeCredentialResponse::SUCCESS);
EXPECT_THAT(base::HexEncode(resp.authenticator_data().data(),
resp.authenticator_data().size()),
MatchesRegex(expected_authenticator_data));
EXPECT_EQ(resp.attestation_format(), "none");
EXPECT_EQ(resp.attestation_statement(), "\xa0");
*called_ptr = true;
},
&called, expected_authenticator_data_regex));
handler_->MakeCredential(std::move(mock_method_response), request);
presence_requested_expected_ = 1;
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionUninitialized) {
// Use an uninitialized WebAuthnHandler object.
handler_.reset(new WebAuthnHandler());
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::INTERNAL_ERROR);
*called_ptr = true;
},
&called));
GetAssertionRequest request;
handler_->GetAssertion(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionEmptyRpId) {
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::INVALID_REQUEST);
*called_ptr = true;
},
&called));
GetAssertionRequest request;
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH, 0xcd));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
handler_->GetAssertion(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionWrongClientDataHashLength) {
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::INVALID_REQUEST);
*called_ptr = true;
},
&called));
GetAssertionRequest request;
request.set_rp_id(kRpId);
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH - 1, 0xcd));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
handler_->GetAssertion(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionNoSecret) {
GetAssertionRequest request;
request.set_rp_id(kRpId);
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH, 0xcd));
request.add_allowed_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecretFails();
// Since we don't have user secret, we won't even pass DoU2fSignCheckOnly, and
// the TPM won't receive any command.
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::INVALID_REQUEST);
*called_ptr = true;
},
&called));
handler_->GetAssertion(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionInvalidKeyHandle) {
GetAssertionRequest request;
request.set_rp_id(kRpId);
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH, 0xcd));
request.add_allowed_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
// We will only get user secret once because DoU2fSignCheckOnly will fail and
// we won't DoU2fSign.
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignCheckOnlyRequestRegex()),
_))
.WillOnce(Return(kCr50StatusPasswordRequired));
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::INVALID_REQUEST);
*called_ptr = true;
},
&called));
handler_->GetAssertion(std::move(mock_method_response), request);
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionPresenceNoPresence) {
GetAssertionRequest request;
request.set_rp_id(kRpId);
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH, 0xcd));
request.add_allowed_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecretForTimes(2);
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignCheckOnlyRequestRegex()),
_))
.WillOnce(Return(kCr50StatusSuccess));
EXPECT_CALL(mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignRequestRegex()), _))
.WillRepeatedly(Return(kCr50StatusNotAllowed));
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::VERIFICATION_FAILED);
*called_ptr = true;
},
&called));
handler_->GetAssertion(std::move(mock_method_response), request);
presence_requested_expected_ = kMaxRetries;
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, GetAssertionPresenceSuccess) {
GetAssertionRequest request;
request.set_rp_id(kRpId);
request.set_client_data_hash(std::string(SHA256_DIGEST_LENGTH, 0xcd));
request.add_allowed_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
request.set_verification_type(VerificationType::VERIFICATION_USER_PRESENCE);
ExpectGetUserSecretForTimes(2);
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignCheckOnlyRequestRegex()),
_))
.WillOnce(Return(kCr50StatusSuccess));
EXPECT_CALL(mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignRequestRegex()), _))
.WillOnce(Return(kCr50StatusNotAllowed))
.WillOnce(DoAll(SetArgPointee<1>(kU2fSignResponse),
Return(kCr50StatusSuccess)));
auto mock_method_response =
std::make_unique<hwsec::MockDBusMethodResponse<GetAssertionResponse>>();
bool called = false;
mock_method_response->set_return_callback(base::Bind(
[](bool* called_ptr, const GetAssertionResponse& resp) {
EXPECT_EQ(resp.status(), GetAssertionResponse::SUCCESS);
ASSERT_EQ(resp.assertion_size(), 1);
auto assertion = resp.assertion(0);
EXPECT_EQ(assertion.credential_id(),
std::string(U2F_FIXED_KH_SIZE, 0xab));
EXPECT_THAT(
base::HexEncode(assertion.authenticator_data().data(),
assertion.authenticator_data().size()),
MatchesRegex(base::HexEncode(kRpIdHash.data(),
kRpIdHash.size()) + // RP ID hash
std::string("01" // Flag: user present
"(..){4}"))); // Signature counter
EXPECT_EQ(util::ToVector(assertion.signature()),
util::SignatureToDerBytes(kU2fSignResponse.sig_r,
kU2fSignResponse.sig_s));
*called_ptr = true;
},
&called));
handler_->GetAssertion(std::move(mock_method_response), request);
presence_requested_expected_ = 1;
ASSERT_TRUE(called);
}
TEST_F(WebAuthnHandlerTest, HasCredentialsNoMatch) {
HasCredentialsRequest request;
request.set_rp_id(kRpId);
request.add_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignCheckOnlyRequestRegex()),
_))
.WillOnce(Return(kCr50StatusPasswordRequired));
auto resp = handler_->HasCredentials(request);
EXPECT_EQ(resp.credential_id_size(), 0);
}
TEST_F(WebAuthnHandlerTest, HasCredentialsOneMatch) {
HasCredentialsRequest request;
request.set_rp_id(kRpId);
request.add_credential_id(std::string(U2F_FIXED_KH_SIZE, 0xab));
ExpectGetUserSecret();
EXPECT_CALL(
mock_tpm_proxy_,
SendU2fSign(StructMatchesRegex(ExpectedU2fSignCheckOnlyRequestRegex()),
_))
.WillOnce(Return(kCr50StatusSuccess));
auto resp = handler_->HasCredentials(request);
EXPECT_EQ(resp.credential_id_size(), 1);
}
TEST_F(WebAuthnHandlerTest, MakeAuthenticatorDataWithAttestedCredData) {
const std::vector<uint8_t> cred_id(64, 0xAA);
const std::vector<uint8_t> cred_pubkey(65, 0xBB);
std::vector<uint8_t> authenticator_data =
MakeAuthenticatorData(cred_id, cred_pubkey, /* user_verified = */ false,
/* include_attested_credential_data = */ true);
EXPECT_EQ(authenticator_data.size(),
kRpIdHashBytes + kAuthenticatorDataFlagBytes +
kSignatureCounterBytes + kAaguidBytes +
kCredentialIdLengthBytes + cred_id.size() + cred_pubkey.size());
const std::string rp_id_hash_hex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size());
const std::string expected_authenticator_data_regex =
rp_id_hash_hex + // RP ID hash
std::string(
"41" // Flag: user present, attested credential data included
"(..){4}" // Signature counter
"(00){16}" // AAGUID
"0040" // Credential ID length
"(AA){64}" // Credential ID
"(BB){65}"); // Credential public key
EXPECT_THAT(
base::HexEncode(authenticator_data.data(), authenticator_data.size()),
MatchesRegex(expected_authenticator_data_regex));
}
TEST_F(WebAuthnHandlerTest, MakeAuthenticatorDataNoAttestedCredData) {
std::vector<uint8_t> authenticator_data =
MakeAuthenticatorData(std::vector<uint8_t>(), std::vector<uint8_t>(),
/* user_verified = */ false,
/* include_attested_credential_data = */ false);
EXPECT_EQ(
authenticator_data.size(),
kRpIdHashBytes + kAuthenticatorDataFlagBytes + kSignatureCounterBytes);
const std::string rp_id_hash_hex =
base::HexEncode(kRpIdHash.data(), kRpIdHash.size());
const std::string expected_authenticator_data_regex =
rp_id_hash_hex + // RP ID hash
std::string(
"01" // Flag: user present
"(..){4}"); // Signature counter
EXPECT_THAT(
base::HexEncode(authenticator_data.data(), authenticator_data.size()),
MatchesRegex(expected_authenticator_data_regex));
}
} // namespace
} // namespace u2f