blob: 57866c7f48d8a193379c95d6354bfc3634a16554 [file]
// Copyright 2021 The ChromiumOS Authors
// 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_vendor.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/containers/span.h>
#include <base/strings/string_number_conversions.h>
#include <base/test/task_environment.h>
#include <base/time/time.h>
#include <brillo/dbus/mock_dbus_method_response.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include <libhwsec/frontend/u2fd/mock_vendor_frontend.h>
#include <libhwsec/frontend/u2fd/vendor_frontend.h>
#include "u2fd/client/util.h"
namespace u2f {
namespace {
using GenerateResult = hwsec::u2f::GenerateResult;
using Signature = hwsec::u2f::Signature;
using ConsumeMode = hwsec::u2f::ConsumeMode;
using UserPresenceMode = hwsec::u2f::UserPresenceMode;
using hwsec::TPMError;
using hwsec::TPMRetryAction;
using hwsec_foundation::status::MakeStatus;
using hwsec_foundation::error::testing::IsOkAndHolds;
using hwsec_foundation::error::testing::NotOk;
using hwsec_foundation::error::testing::ReturnError;
using hwsec_foundation::error::testing::ReturnOk;
using hwsec_foundation::error::testing::ReturnValue;
using ::testing::_;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::Matcher;
using ::testing::MatchesRegex;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
constexpr base::TimeDelta kVerificationTimeout = base::Seconds(10);
constexpr base::TimeDelta kRequestPresenceDelay = base::Milliseconds(500);
constexpr int kMaxRetries = kVerificationTimeout / kRequestPresenceDelay;
constexpr char kCredentialSecret[65] = {[0 ... 63] = 'E', '\0'};
// Stub RP id.
constexpr char kRpId[] = "example.com";
// Wrong RP id is used to test app id extension path.
constexpr char kWrongRpId[] = "wrong.com";
// Example of a cert that would be returned by cr50.
constexpr char kStubG2fCert[] =
"308201363081DDA0030201020210442D32429223D041240350303716EE6B300A06082A8648"
"CE3D040302300F310D300B06035504031304637235303022180F3230303030313031303030"
"3030305A180F32303939313233313233353935395A300F310D300B06035504031304637235"
"303059301306072A8648CE3D020106082A8648CE3D030107034200045165719A9975F6FD30"
"CC2516C22FE841F65F9D2EE7B8B72F76807AEBD8CA3376005C7FA86453E4B10DB7BFAD5D2B"
"D00DB4A7C4845AD06D686ACD0252387618ECA31730153013060B2B0601040182E51C020101"
"040403020308300A06082A8648CE3D0403020348003045022100F09976F373920FEF8205C4"
"B1FB1DA21EB9F3F176B7DF433A1ADE0F3F38B721960220179D9B9051BFCCCC90BA6BB42B86"
"111D7A9C4FB56DFD39FB426081DD027AD609";
constexpr hwsec::u2f::Config kConfig{
.up_only_kh_size = 20,
.kh_size = 30,
};
std::vector<uint8_t> GetRpIdHash() {
return util::Sha256(std::string(kRpId));
}
std::vector<uint8_t> GetWrongRpIdHash() {
return util::Sha256(std::string(kWrongRpId));
}
std::vector<uint8_t> GetHashToSign() {
return std::vector<uint8_t>(32, 0xcd);
}
brillo::SecureBlob GetUserSecret() {
return brillo::SecureBlob(32, 'E');
}
std::vector<uint8_t> GetCredId() {
return std::vector<uint8_t>(kConfig.up_only_kh_size, 0xFD);
}
std::vector<uint8_t> GetVersionedCredId() {
return std::vector<uint8_t>(kConfig.kh_size, 0xFD);
}
std::vector<uint8_t> GetAuthTimeSecretHash() {
return std::vector<uint8_t>(32, 0xFD);
}
std::vector<uint8_t> GetChallenge() {
return std::vector<uint8_t>(32, 0xDD);
}
std::vector<uint8_t> GetSigR() {
return std::vector<uint8_t>(32, 0xEE);
}
std::vector<uint8_t> GetSigS() {
return std::vector<uint8_t>(32, 0xFF);
}
std::vector<uint8_t> GetStubG2fCert() {
std::vector<uint8_t> cert;
base::HexStringToBytes(kStubG2fCert, &cert);
return cert;
}
brillo::SecureBlob ArrayToSecureBlob(const char* array) {
brillo::SecureBlob blob;
CHECK(brillo::SecureBlob::HexStringToSecureBlob(array, &blob));
return blob;
}
} // namespace
class FakePublicKey : public hwsec::u2f::PublicKey {
public:
explicit FakePublicKey(brillo::Blob raw) : data_(std::move(raw)) {
CHECK_EQ(data_.size(), 65);
}
base::span<const uint8_t> x() const override {
return base::span(data_.data() + 1, 32u);
}
base::span<const uint8_t> y() const override {
return base::span(data_.data() + 33, 32u);
}
const brillo::Blob& raw() const override { return data_; }
private:
brillo::Blob data_;
};
class U2fCommandProcessorVendorTest : public ::testing::Test {
public:
void SetUp() override {
auto mock_u2f_frontend = std::make_unique<hwsec::MockU2fVendorFrontend>();
mock_u2f_frontend_ = mock_u2f_frontend.get();
EXPECT_CALL(*mock_u2f_frontend_, GetConfig)
.WillRepeatedly(ReturnValue(kConfig));
processor_ = std::make_unique<U2fCommandProcessorVendor>(
std::move(mock_u2f_frontend), [this]() {
presence_requested_count_++;
task_environment_.FastForwardBy(kRequestPresenceDelay);
});
}
void TearDown() override {
EXPECT_EQ(presence_requested_expected_, presence_requested_count_);
}
protected:
static std::vector<uint8_t> GetCredPubKeyRaw() {
return std::vector<uint8_t>(65, 0xAB);
}
static std::vector<uint8_t> GetCredPubKeyCbor() {
return U2fCommandProcessorVendor::EncodeCredentialPublicKeyInCBOR(
std::vector<uint8_t>(32, 0xAB), std::vector<uint8_t>(32, 0xAB));
}
hwsec::StatusOr<int> CallAndWaitForPresence(
std::function<hwsec::StatusOr<int>()> fn) {
return processor_->CallAndWaitForPresenceForTest(fn);
}
bool PresenceRequested() { return presence_requested_count_ > 0; }
MakeCredentialResponse::MakeCredentialStatus U2fGenerate(
PresenceRequirement presence_requirement,
bool uv_compatible,
const brillo::Blob* auth_time_secret_hash,
std::vector<uint8_t>* credential_id,
CredentialPublicKey* credential_pubkey) {
// U2fGenerate expects some output fields to be non-null, but we
// want to support nullptr output fields in this helper method.
std::vector<uint8_t> cred_id;
CredentialPublicKey pubkey;
if (!credential_id) {
credential_id = &cred_id;
}
if (!credential_pubkey) {
credential_pubkey = &pubkey;
}
return processor_->U2fGenerate(
GetRpIdHash(), ArrayToSecureBlob(kCredentialSecret),
presence_requirement, uv_compatible, auth_time_secret_hash,
credential_id, credential_pubkey, nullptr);
}
GetAssertionResponse::GetAssertionStatus U2fSign(
const std::vector<uint8_t>& hash_to_sign,
const std::vector<uint8_t>& credential_id,
PresenceRequirement presence_requirement,
std::vector<uint8_t>* signature) {
return processor_->U2fSign(GetRpIdHash(), hash_to_sign, credential_id,
ArrayToSecureBlob(kCredentialSecret), nullptr,
presence_requirement, signature);
}
HasCredentialsResponse::HasCredentialsStatus U2fSignCheckOnly(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& credential_id) {
return processor_->U2fSignCheckOnly(rp_id_hash, credential_id,
ArrayToSecureBlob(kCredentialSecret),
nullptr);
}
MakeCredentialResponse::MakeCredentialStatus G2fAttest(
const std::vector<uint8_t>& rp_id_hash,
const brillo::SecureBlob& credential_secret,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& credential_public_key,
const std::vector<uint8_t>& credential_id,
std::vector<uint8_t>* cert_out,
std::vector<uint8_t>* signature_out) {
return processor_->G2fAttest(rp_id_hash, credential_secret, challenge,
credential_public_key, credential_id, cert_out,
signature_out);
}
bool G2fSoftwareAttest(const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& credential_public_key,
const std::vector<uint8_t>& credential_id,
std::vector<uint8_t>* cert_out,
std::vector<uint8_t>* signature_out) {
return processor_->G2fSoftwareAttest(rp_id_hash, challenge,
credential_public_key, credential_id,
cert_out, signature_out);
}
int presence_requested_expected_ = 0;
hwsec::MockU2fVendorFrontend* mock_u2f_frontend_;
private:
int presence_requested_count_ = 0;
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<U2fCommandProcessorVendor> processor_;
};
namespace {
TEST_F(U2fCommandProcessorVendorTest, CallAndWaitForPresenceDirectSuccess) {
// If presence is already available, we won't request it.
EXPECT_THAT(CallAndWaitForPresence([]() { return 0; }), IsOkAndHolds(0));
presence_requested_expected_ = 0;
}
TEST_F(U2fCommandProcessorVendorTest, CallAndWaitForPresenceRequestSuccess) {
EXPECT_THAT(CallAndWaitForPresence([this]() -> hwsec::StatusOr<int> {
if (PresenceRequested()) {
return 0;
}
return MakeStatus<TPMError>("Not allowed",
TPMRetryAction::kUserPresence);
}),
IsOkAndHolds(0));
presence_requested_expected_ = 1;
}
TEST_F(U2fCommandProcessorVendorTest, CallAndWaitForPresenceTimeout) {
base::TimeTicks verification_start = base::TimeTicks::Now();
EXPECT_THAT(CallAndWaitForPresence([]() {
return MakeStatus<TPMError>("Not allowed",
TPMRetryAction::kUserPresence);
}),
NotOk());
EXPECT_GE(base::TimeTicks::Now() - verification_start, kVerificationTimeout);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(U2fCommandProcessorVendorTest,
U2fGenerateVersionedNoAuthTimeSecretHash) {
EXPECT_EQ(U2fGenerate(PresenceRequirement::kPowerButton,
/* uv_compatible = */ true, nullptr, nullptr, nullptr),
MakeCredentialResponse::INTERNAL_ERROR);
}
TEST_F(U2fCommandProcessorVendorTest, U2fGenerateVersionedSuccessUserPresence) {
EXPECT_CALL(*mock_u2f_frontend_, Generate(_, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired, _))
.WillOnce(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence))
.WillOnce([](const auto&, const auto&, auto, auto, const auto&) {
return GenerateResult{
.public_key = std::make_unique<FakePublicKey>(
U2fCommandProcessorVendorTest::GetCredPubKeyRaw()),
.key_handle = GetVersionedCredId(),
};
});
std::vector<uint8_t> cred_id;
CredentialPublicKey cred_pubkey;
auto auth_time_secret_hash = GetAuthTimeSecretHash();
EXPECT_EQ(U2fGenerate(PresenceRequirement::kPowerButton,
/* uv_compatible = */ true, &auth_time_secret_hash,
&cred_id, &cred_pubkey),
MakeCredentialResponse::SUCCESS);
EXPECT_EQ(cred_id, GetVersionedCredId());
EXPECT_EQ(cred_pubkey.cbor,
U2fCommandProcessorVendorTest::GetCredPubKeyCbor());
EXPECT_EQ(cred_pubkey.raw, U2fCommandProcessorVendorTest::GetCredPubKeyRaw());
presence_requested_expected_ = 1;
}
TEST_F(U2fCommandProcessorVendorTest, U2fGenerateVersionedNoUserPresence) {
EXPECT_CALL(*mock_u2f_frontend_, Generate(_, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired, _))
.WillRepeatedly(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence));
auto auth_time_secret_hash = GetAuthTimeSecretHash();
EXPECT_EQ(U2fGenerate(PresenceRequirement::kPowerButton,
/* uv_compatible = */ true, &auth_time_secret_hash,
nullptr, nullptr),
MakeCredentialResponse::VERIFICATION_TIMEOUT);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(U2fCommandProcessorVendorTest, U2fGenerateSuccessUserPresence) {
EXPECT_CALL(*mock_u2f_frontend_,
GenerateUserPresenceOnly(_, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired))
.WillOnce(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence))
.WillOnce([](const auto&, const auto&, auto, auto) {
return GenerateResult{
.public_key = std::make_unique<FakePublicKey>(
U2fCommandProcessorVendorTest::GetCredPubKeyRaw()),
.key_handle = GetCredId(),
};
});
std::vector<uint8_t> cred_id;
CredentialPublicKey cred_pubkey;
EXPECT_EQ(
U2fGenerate(PresenceRequirement::kPowerButton,
/* uv_compatible = */ false, nullptr, &cred_id, &cred_pubkey),
MakeCredentialResponse::SUCCESS);
EXPECT_EQ(cred_id, GetCredId());
EXPECT_EQ(cred_pubkey.cbor,
U2fCommandProcessorVendorTest::GetCredPubKeyCbor());
EXPECT_EQ(cred_pubkey.raw, U2fCommandProcessorVendorTest::GetCredPubKeyRaw());
presence_requested_expected_ = 1;
}
TEST_F(U2fCommandProcessorVendorTest, U2fGenerateNoUserPresence) {
EXPECT_CALL(*mock_u2f_frontend_,
GenerateUserPresenceOnly(_, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired))
.WillRepeatedly(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence));
EXPECT_EQ(U2fGenerate(PresenceRequirement::kPowerButton,
/* uv_compatible = */ false, nullptr, nullptr, nullptr),
MakeCredentialResponse::VERIFICATION_TIMEOUT);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(U2fCommandProcessorVendorTest,
U2fGenerateVersionedSuccessUserVerification) {
EXPECT_CALL(*mock_u2f_frontend_, Generate(_, _, ConsumeMode::kNoConsume,
UserPresenceMode::kNotRequired, _))
.WillOnce([](const auto&, const auto&, auto, auto, const auto&) {
return GenerateResult{
.public_key = std::make_unique<FakePublicKey>(
U2fCommandProcessorVendorTest::GetCredPubKeyRaw()),
.key_handle = GetVersionedCredId(),
};
});
std::vector<uint8_t> cred_id;
CredentialPublicKey cred_pubkey;
auto auth_time_secret_hash = GetAuthTimeSecretHash();
// UI has verified the user so do not require presence.
EXPECT_EQ(U2fGenerate(PresenceRequirement::kNone, /* uv_compatible = */ true,
&auth_time_secret_hash, &cred_id, &cred_pubkey),
MakeCredentialResponse::SUCCESS);
EXPECT_EQ(cred_id, GetVersionedCredId());
EXPECT_EQ(cred_pubkey.cbor,
U2fCommandProcessorVendorTest::GetCredPubKeyCbor());
EXPECT_EQ(cred_pubkey.raw, U2fCommandProcessorVendorTest::GetCredPubKeyRaw());
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignPresenceNoPresence) {
EXPECT_CALL(*mock_u2f_frontend_,
SignUserPresenceOnly(_, _, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired, _))
.WillRepeatedly(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence));
std::vector<uint8_t> signature;
EXPECT_EQ(U2fSign(GetHashToSign(), GetCredId(),
PresenceRequirement::kPowerButton, &signature),
GetAssertionResponse::VERIFICATION_TIMEOUT);
presence_requested_expected_ = kMaxRetries;
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignPresenceSuccess) {
EXPECT_CALL(*mock_u2f_frontend_,
SignUserPresenceOnly(_, _, _, ConsumeMode::kConsume,
UserPresenceMode::kRequired, _))
.WillOnce(
ReturnError<TPMError>("Not allowed", TPMRetryAction::kUserPresence))
.WillOnce(ReturnValue(Signature{
.r = GetSigR(),
.s = GetSigS(),
}));
std::vector<uint8_t> signature;
EXPECT_EQ(U2fSign(GetHashToSign(), GetCredId(),
PresenceRequirement::kPowerButton, &signature),
GetAssertionResponse::SUCCESS);
EXPECT_EQ(signature, util::SignatureToDerBytes(GetSigR(), GetSigS()));
presence_requested_expected_ = 1;
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignVersionedSuccess) {
brillo::Blob credential_id(GetVersionedCredId());
EXPECT_CALL(*mock_u2f_frontend_, Sign(_, _, _, _, ConsumeMode::kNoConsume,
UserPresenceMode::kNotRequired, _))
.WillOnce(ReturnValue(Signature{
.r = GetSigR(),
.s = GetSigS(),
}));
std::vector<uint8_t> signature;
EXPECT_EQ(U2fSign(GetHashToSign(), credential_id, PresenceRequirement::kNone,
&signature),
GetAssertionResponse::SUCCESS);
EXPECT_EQ(signature, util::SignatureToDerBytes(GetSigR(), GetSigS()));
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignCheckOnlyWrongRpIdHash) {
EXPECT_CALL(*mock_u2f_frontend_, CheckUserPresenceOnly)
.WillOnce(ReturnError<TPMError>("Not allowed", TPMRetryAction::kNoRetry));
EXPECT_EQ(U2fSignCheckOnly(GetWrongRpIdHash(), GetCredId()),
HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID);
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignCheckOnlySuccess) {
EXPECT_CALL(*mock_u2f_frontend_, CheckUserPresenceOnly)
.WillOnce(ReturnOk<TPMError>());
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), GetCredId()),
HasCredentialsResponse::SUCCESS);
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignCheckOnlyVersionedSuccess) {
brillo::Blob credential_id(GetVersionedCredId());
EXPECT_CALL(*mock_u2f_frontend_, Check).WillOnce(ReturnOk<TPMError>());
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), credential_id),
HasCredentialsResponse::SUCCESS);
}
TEST_F(U2fCommandProcessorVendorTest, U2fSignCheckOnlyWrongLength) {
std::vector<uint8_t> wrong_length_key_handle(kConfig.up_only_kh_size + 1,
0xab);
EXPECT_EQ(U2fSignCheckOnly(GetRpIdHash(), wrong_length_key_handle),
HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID);
}
TEST_F(U2fCommandProcessorVendorTest, G2fAttestSuccess) {
EXPECT_CALL(*mock_u2f_frontend_, GetG2fCert)
.WillOnce(ReturnValue(GetStubG2fCert()));
EXPECT_CALL(*mock_u2f_frontend_, G2fAttest)
.WillOnce(ReturnValue(Signature{
.r = GetSigR(),
.s = GetSigS(),
}));
auto secret = GetUserSecret();
brillo::Blob cert_out, signature_out;
EXPECT_EQ(G2fAttest(GetRpIdHash(), secret, GetChallenge(), GetCredPubKeyRaw(),
GetCredId(), &cert_out, &signature_out),
MakeCredentialResponse::SUCCESS);
}
TEST_F(U2fCommandProcessorVendorTest, G2fSoftwareAttestSuccess) {
EXPECT_CALL(*mock_u2f_frontend_, GetG2fAttestData)
.WillOnce(ReturnValue(brillo::Blob(30)));
auto secret = GetUserSecret();
brillo::Blob cert_out, signature_out;
EXPECT_TRUE(G2fSoftwareAttest(GetRpIdHash(), GetChallenge(),
GetCredPubKeyRaw(), GetCredId(), &cert_out,
&signature_out));
}
} // namespace
} // namespace u2f