blob: 958ae1dde793a1fae57e792371ef359ab8b44d1a [file] [log] [blame]
// Copyright 2022 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/u2f_command_processor_generic.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/secure_blob.h>
#include <cryptohome/proto_bindings/UserDataAuth.pb.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <user_data_auth-client-test/user_data_auth/dbus-proxy-mocks.h>
#include <user_data_auth-client/user_data_auth/dbus-proxies.h>
#include "u2fd/mock_user_state.h"
#include "u2fd/sign_manager/mock_sign_manager.h"
#include "u2fd/sign_manager/sign_manager.h"
#include "u2fd/u2f_command_processor.h"
#include "u2fd/util.h"
namespace u2f {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::MatchesRegex;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
constexpr char kCredentialSecretHex[65] = {[0 ... 63] = 'F', '\0'};
constexpr char kUserAccount[5] = "user";
constexpr char kWebAuthnSecretString[33] = {[0 ... 31] = '\x12', '\0'};
// Dummy RP id.
constexpr char kRpId[] = "example.com";
// Wrong RP id is used to test app id extension path.
constexpr char kWrongRpId[] = "wrong.com";
brillo::Blob HexArrayToBlob(const char* array) {
brillo::Blob blob;
CHECK(base::HexStringToBytes(array, &blob));
return blob;
}
std::string ToString(const std::vector<uint8_t>& v) {
return std::string(v.begin(), v.end());
}
brillo::SecureBlob GetWebAuthnSecret() {
return brillo::SecureBlob(kWebAuthnSecretString);
}
std::vector<uint8_t> GetCredentialSecret() {
return HexArrayToBlob(kCredentialSecretHex);
}
std::vector<uint8_t> GetRpIdHash() {
return util::Sha256(std::string(kRpId));
}
std::vector<uint8_t> GetWrongRpIdHash() {
return util::Sha256(std::string(kWrongRpId));
}
std::string GetKeyBlobString() {
return std::string(256, '\x13');
}
std::vector<uint8_t> GetFakeKeyBlob() {
return std::vector<uint8_t>(256, '\x14');
}
std::vector<uint8_t> GetFakeCredentialIdWithoutHash() {
return util::ToVector(std::string(1, '\x01') + std::string(3, '\x00') +
std::string(48, 'C'));
}
std::vector<uint8_t> GetFakeCredentialIdHash() {
return util::Sha256(GetFakeCredentialIdWithoutHash());
}
std::vector<uint8_t> GetFakeCredentialIdValidHash() {
return util::ToVector(ToString(GetFakeCredentialIdWithoutHash()) +
ToString(GetFakeCredentialIdHash()));
}
std::vector<uint8_t> GetFakeCredentialIdInvalidHash() {
return util::ToVector(ToString(GetFakeCredentialIdWithoutHash()) +
std::string(32, 'S'));
}
std::vector<uint8_t> GetPubKey() {
return std::vector<uint8_t>(128, '\x31');
}
std::string GetHashToSign() {
return std::string(32, 'H');
}
std::string GetSignature() {
return std::string(128, 'S');
}
} // namespace
class U2fCommandProcessorGenericTest : public ::testing::Test {
public:
void SetUp() override {
auto mock_sign_manager = std::make_unique<MockSignManager>();
mock_sign_manager_ = mock_sign_manager.get();
auto mock_cryptohome_proxy =
std::make_unique<org::chromium::UserDataAuthInterfaceProxyMock>();
mock_cryptohome_proxy_ = mock_cryptohome_proxy.get();
processor_ = std::unique_ptr<U2fCommandProcessorGeneric>(
new U2fCommandProcessorGeneric(&mock_user_state_,
std::move(mock_cryptohome_proxy),
std::move(mock_sign_manager)));
ExpectNoGetWebAuthnSecret();
ExpectGetUser();
}
protected:
void ExpectNoGetWebAuthnSecret() {
EXPECT_CALL(*mock_cryptohome_proxy_, GetWebAuthnSecret(_, _, _, _))
.Times(0);
}
void ExpectGetWebAuthnSecret() {
user_data_auth::GetWebAuthnSecretReply reply;
reply.set_webauthn_secret(kWebAuthnSecretString);
EXPECT_CALL(*mock_cryptohome_proxy_, GetWebAuthnSecret(_, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(reply), Return(true)));
}
void ExpectGetWebAuthnSecretFail() {
EXPECT_CALL(*mock_cryptohome_proxy_, GetWebAuthnSecret(_, _, _, _))
.WillOnce(Return(false));
}
void ExpectGetUser() {
EXPECT_CALL(mock_user_state_, GetUser())
.WillRepeatedly(Return(kUserAccount));
}
void ExpectSignManagerReady() {
EXPECT_CALL(*mock_sign_manager_, IsReady()).WillRepeatedly(Return(true));
}
MakeCredentialResponse::MakeCredentialStatus U2fGenerate(
std::vector<uint8_t>* credential_id,
CredentialPublicKey* credential_pubkey,
std::vector<uint8_t>* credential_key_blob) {
return processor_->U2fGenerate(
GetRpIdHash(), GetCredentialSecret(), PresenceRequirement::kNone,
/*uv_compatible=*/true, /*auth_time_secret_hash=*/nullptr,
credential_id, credential_pubkey, credential_key_blob);
}
GetAssertionResponse::GetAssertionStatus U2fSign(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& hash_to_sign,
const std::vector<uint8_t>& credential_id,
const std::vector<uint8_t>* credential_key_blob,
std::vector<uint8_t>* signature) {
return processor_->U2fSign(rp_id_hash, hash_to_sign, credential_id,
GetCredentialSecret(), credential_key_blob,
PresenceRequirement::kNone, signature);
}
HasCredentialsResponse::HasCredentialsStatus U2fSignCheckOnly(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& credential_id,
const std::vector<uint8_t>* credential_key_blob) {
return processor_->U2fSignCheckOnly(
rp_id_hash, credential_id, GetCredentialSecret(), credential_key_blob);
}
StrictMock<MockUserState> mock_user_state_;
org::chromium::UserDataAuthInterfaceProxyMock* mock_cryptohome_proxy_;
MockSignManager* mock_sign_manager_;
private:
std::unique_ptr<U2fCommandProcessorGeneric> processor_;
};
namespace {
TEST_F(U2fCommandProcessorGenericTest, U2fGenerateNoWebAuthnSecret) {
std::vector<uint8_t> cred_id, cred_key_blob;
CredentialPublicKey cred_pubkey;
ExpectGetWebAuthnSecretFail();
EXPECT_EQ(U2fGenerate(&cred_id, &cred_pubkey, &cred_key_blob),
MakeCredentialResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorGenericTest, U2fGenerateSignManagerNotReady) {
std::vector<uint8_t> cred_id, cred_key_blob;
CredentialPublicKey cred_pubkey;
ExpectGetWebAuthnSecret();
EXPECT_CALL(*mock_sign_manager_, IsReady()).WillOnce(Return(false));
EXPECT_EQ(U2fGenerate(&cred_id, &cred_pubkey, &cred_key_blob),
MakeCredentialResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorGenericTest, U2fGenerateSignManagerCreateKeyFailed) {
std::vector<uint8_t> cred_id, cred_key_blob;
CredentialPublicKey cred_pubkey;
ExpectGetWebAuthnSecret();
ExpectSignManagerReady();
EXPECT_CALL(*mock_sign_manager_, IsReady()).WillOnce(Return(true));
EXPECT_CALL(*mock_sign_manager_,
CreateKey(KeyType::kRsa, GetWebAuthnSecret(), _, _))
.WillOnce(Return(false));
EXPECT_EQ(U2fGenerate(&cred_id, &cred_pubkey, &cred_key_blob),
MakeCredentialResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignNoCredentialKeyBlob) {
std::vector<uint8_t> signature;
EXPECT_EQ(U2fSign(GetRpIdHash(), util::ToVector(GetHashToSign()),
GetFakeCredentialIdValidHash(),
/*credential_key_blob=*/nullptr, &signature),
GetAssertionResponse::INVALID_REQUEST);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignInvalidHash) {
std::vector<uint8_t> signature;
auto fake_key_blob = GetFakeKeyBlob();
EXPECT_EQ(
U2fSign(GetRpIdHash(), util::ToVector(GetHashToSign()),
GetFakeCredentialIdInvalidHash(), &fake_key_blob, &signature),
GetAssertionResponse::INVALID_REQUEST);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignInvalidHmac) {
std::vector<uint8_t> signature;
ExpectGetWebAuthnSecret();
auto fake_key_blob = GetFakeKeyBlob();
EXPECT_EQ(U2fSign(GetRpIdHash(), util::ToVector(GetHashToSign()),
GetFakeCredentialIdValidHash(), &fake_key_blob, &signature),
GetAssertionResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignWrongRpIdHash) {
std::vector<uint8_t> cred_id, cred_key_blob;
CredentialPublicKey cred_pubkey;
ExpectGetWebAuthnSecret();
ExpectSignManagerReady();
EXPECT_CALL(*mock_sign_manager_,
CreateKey(KeyType::kRsa, GetWebAuthnSecret(), _, _))
.WillOnce(DoAll(SetArgPointee<2>(GetKeyBlobString()),
SetArgPointee<3>(GetPubKey()), Return(true)));
EXPECT_EQ(U2fGenerate(&cred_id, &cred_pubkey, &cred_key_blob),
MakeCredentialResponse::SUCCESS);
std::string expected_cred_pubkey_regex = std::string("(31){128}");
EXPECT_THAT(base::HexEncode(cred_pubkey.cbor),
MatchesRegex(expected_cred_pubkey_regex));
// U2fSign with wrong rp id hash should fail.
std::vector<uint8_t> signature;
ExpectGetWebAuthnSecret();
EXPECT_EQ(U2fSign(GetWrongRpIdHash(), util::ToVector(GetHashToSign()),
cred_id, &cred_key_blob, &signature),
GetAssertionResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignCheckOnlyTooLongCredId) {
std::vector<uint8_t> cred_id(GetFakeCredentialIdValidHash());
cred_id.push_back('C');
auto fake_key_blob = GetFakeKeyBlob();
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), cred_id, &fake_key_blob),
HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID);
}
TEST_F(U2fCommandProcessorGenericTest, U2fSignCheckOnlyInvalidHash) {
auto fake_key_blob = GetFakeKeyBlob();
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), GetFakeCredentialIdInvalidHash(),
&fake_key_blob),
HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID);
}
TEST_F(U2fCommandProcessorGenericTest, U2fGenerateSignSuccess) {
std::vector<uint8_t> cred_id, cred_key_blob;
CredentialPublicKey cred_pubkey;
ExpectGetWebAuthnSecret();
ExpectSignManagerReady();
EXPECT_CALL(*mock_sign_manager_,
CreateKey(KeyType::kRsa, GetWebAuthnSecret(), _, _))
.WillOnce(DoAll(SetArgPointee<2>(GetKeyBlobString()),
SetArgPointee<3>(GetPubKey()), Return(true)));
EXPECT_EQ(U2fGenerate(&cred_id, &cred_pubkey, &cred_key_blob),
MakeCredentialResponse::SUCCESS);
std::string expected_cred_pubkey_regex = std::string("(31){128}");
EXPECT_THAT(base::HexEncode(cred_pubkey.cbor),
MatchesRegex(expected_cred_pubkey_regex));
// U2fSignCheckOnly should succeed.
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), cred_id, &cred_key_blob),
HasCredentialsResponse::SUCCESS);
// U2fSign should succeed.
std::vector<uint8_t> signature;
ExpectGetWebAuthnSecret();
EXPECT_CALL(*mock_sign_manager_,
Sign(GetKeyBlobString(), GetHashToSign(), GetWebAuthnSecret(), _))
.WillOnce(DoAll(SetArgPointee<3>(GetSignature()), Return(true)));
EXPECT_EQ(U2fSign(GetRpIdHash(), util::ToVector(GetHashToSign()), cred_id,
&cred_key_blob, &signature),
GetAssertionResponse::SUCCESS);
EXPECT_EQ(signature, util::ToVector(GetSignature()));
}
} // namespace
} // namespace u2f