| // Copyright 2019 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_msg_handler.h" | |
| #include <iostream> | |
| #include <memory> | |
| #include <optional> | |
| #include <regex> // NOLINT(build/c++11) | |
| #include <string> | |
| #include <utility> | |
| #include <base/check.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include <libhwsec/frontend/u2fd/mock_vendor_frontend.h> | |
| #include <libhwsec/frontend/u2fd/vendor_frontend.h> | |
| #include <libhwsec-foundation/error/testing_helper.h> | |
| #include <metrics/metrics_library_mock.h> | |
| #include "u2fd/mock_allowlisting_util.h" | |
| #include "u2fd/mock_user_state.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::ReturnError; | |
| using hwsec_foundation::error::testing::ReturnOk; | |
| using hwsec_foundation::error::testing::ReturnValue; | |
| using ::testing::_; | |
| using ::testing::DoAll; | |
| using ::testing::Matcher; | |
| using ::testing::MatchesRegex; | |
| using ::testing::NiceMock; | |
| using ::testing::Return; | |
| using ::testing::SaveArg; | |
| using ::testing::SetArgPointee; | |
| using ::testing::StrictMock; | |
| std::string ApduToHexString(const U2fResponseApdu& apdu) { | |
| std::string apdu_str; | |
| apdu.ToString(&apdu_str); | |
| return base::HexEncode(apdu_str.c_str(), apdu_str.size()); | |
| } | |
| brillo::Blob ArrayToBlob(const char* array) { | |
| brillo::Blob blob; | |
| CHECK(base::HexStringToBytes(array, &blob)); | |
| return blob; | |
| } | |
| brillo::SecureBlob ArrayToSecureBlob(const char* array) { | |
| brillo::SecureBlob blob; | |
| CHECK(brillo::SecureBlob::HexStringToSecureBlob(array, &blob)); | |
| return blob; | |
| } | |
| MATCHER_P(MsgEqStr, expected, "") { | |
| std::string match_hex = ApduToHexString(arg); | |
| if (match_hex == expected) { | |
| return true; | |
| } | |
| *result_listener << match_hex | |
| << " did not match expected value: " << expected; | |
| return false; | |
| } | |
| MATCHER_P(StructEqStr, expected, "") { | |
| std::string arg_hex = base::HexEncode(&arg, sizeof(arg)); | |
| if (arg_hex == expected) { | |
| return true; | |
| } | |
| *result_listener << arg_hex << " did not match expected value: " << expected; | |
| return false; | |
| } | |
| 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; | |
| } | |
| // Stub User State. | |
| constexpr char kUserSecret[65] = {[0 ... 63] = 'E', '\0'}; | |
| constexpr uint8_t kCounter = 37; | |
| std::vector<uint8_t> GetPubKey() { | |
| return std::vector<uint8_t>(65, 0xAB); | |
| } | |
| std::vector<uint8_t> GetCredId() { | |
| return std::vector<uint8_t>(64, 0xFD); | |
| } | |
| std::vector<uint8_t> GetSigR() { | |
| return std::vector<uint8_t>(32, 0xFF); | |
| } | |
| std::vector<uint8_t> GetSigS() { | |
| return std::vector<uint8_t>(32, 0x55); | |
| } | |
| 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::make_span(data_.data() + 1, 32u); | |
| } | |
| base::span<const uint8_t> y() const override { | |
| return base::make_span(data_.data() + 33, 32u); | |
| } | |
| const brillo::Blob& raw() const override { return data_; } | |
| private: | |
| brillo::Blob data_; | |
| }; | |
| class U2fMessageHandlerTest : public ::testing::Test { | |
| public: | |
| void SetUp() override { CreateHandler(false); } | |
| void TearDown() override { | |
| EXPECT_EQ(presence_requested_expected_, presence_requested_count_); | |
| } | |
| protected: | |
| void CreateHandler(bool allow_g2f_attestation) { | |
| mock_allowlisting_util_ = new StrictMock<MockAllowlistingUtil>(); | |
| handler_.reset(new U2fMessageHandler( | |
| std::unique_ptr<AllowlistingUtil>(mock_allowlisting_util_), | |
| [this]() { presence_requested_count_++; }, &mock_user_state_, | |
| &mock_u2f_frontend_, /*sm_proxy=*/nullptr, &mock_metrics_, | |
| allow_g2f_attestation, /*u2f_corp_processor=*/nullptr)); | |
| } | |
| 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(std::optional<brillo::SecureBlob>())); | |
| } | |
| void ExpectGetCounter() { | |
| EXPECT_CALL(mock_user_state_, GetCounter()) | |
| .WillOnce(Return(std::optional<std::vector<uint8_t>>({kCounter}))); | |
| } | |
| void ExpectGetCounterFails() { | |
| EXPECT_CALL(mock_user_state_, GetCounter()) | |
| .WillOnce(Return(std::optional<std::vector<uint8_t>>())); | |
| } | |
| void ExpectIncrementCounter() { | |
| EXPECT_CALL(mock_user_state_, IncrementCounter()).WillOnce(Return(true)); | |
| } | |
| void ExpectIncrementCounterFails() { | |
| EXPECT_CALL(mock_user_state_, IncrementCounter()).WillOnce(Return(false)); | |
| } | |
| U2fResponseApdu ProcessMsg(const std::string& hex) { | |
| std::vector<uint8_t> bytes; | |
| CHECK(base::HexStringToBytes(hex, &bytes)); | |
| return handler_->ProcessMsg(std::string(bytes.begin(), bytes.end())); | |
| } | |
| U2fResponseApdu ProcessMsg(const std::string& hex, | |
| const std::string& hex2, | |
| const std::string& hex3, | |
| const std::string& hex4) { | |
| return ProcessMsg(hex + hex2 + hex3 + hex4); | |
| } | |
| void CheckResponseForMsg(const std::string& request, | |
| const std::string& response) { | |
| EXPECT_THAT(ProcessMsg(request), MsgEqStr(response)); | |
| } | |
| void CheckResponseForMsg(const std::string& request, | |
| const std::string& request2, | |
| const std::string& request3, | |
| const std::string& request4, | |
| const std::string& response) { | |
| EXPECT_THAT(ProcessMsg(request, request2, request3, request4), | |
| MsgEqStr(response)); | |
| } | |
| StrictMock<MockAllowlistingUtil>* mock_allowlisting_util_; // Not Owned. | |
| StrictMock<hwsec::MockU2fVendorFrontend> mock_u2f_frontend_; | |
| StrictMock<MockUserState> mock_user_state_; | |
| NiceMock<MetricsLibraryMock> mock_metrics_; | |
| std::unique_ptr<U2fMessageHandler> handler_; | |
| int presence_requested_expected_ = 0; | |
| private: | |
| int presence_requested_count_ = 0; | |
| }; | |
| // U2F Error Codes. | |
| constexpr char kErrorResponseWrongData[] = "6A80"; | |
| constexpr char kErrorResponseConditionsNotSatisfied[] = "6985"; | |
| constexpr char kErrorResponseWrongLength[] = "6700"; | |
| constexpr char kErrorResponseInsNotSupported[] = "6D00"; | |
| constexpr char kErrorResponseClaNotSupported[] = "6E00"; | |
| constexpr char kErrorResponseWtf[] = "6F00"; | |
| // Stub U2F Message Parameters | |
| constexpr char kAppId[65] = {[0 ... 63] = 'A', '\0'}; | |
| constexpr char kChallenge[65] = {[0 ... 63] = 'C', '\0'}; | |
| constexpr char kMaxResponseSize[] = "FF"; | |
| // U2F_REGISTER | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: CLA,INS{U2F_REGISTER},P1{U2F_AUTH_ENFORCE},P2,MSG_SIZE | |
| // All fields one byte. | |
| constexpr char kRequestRegisterPrefix[] = "0001030040"; | |
| constexpr char kRequestRegisterShort[] = "0001030000"; | |
| TEST_F(U2fMessageHandlerTest, RegisterSuccess) { | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| GenerateUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired)) | |
| .WillOnce([](const auto&, const auto&, auto, auto) { | |
| return GenerateResult{ | |
| .public_key = std::make_unique<FakePublicKey>(GetPubKey()), | |
| .key_handle = GetCredId(), | |
| }; | |
| }); | |
| EXPECT_CALL(mock_u2f_frontend_, GetG2fAttestData) | |
| .WillOnce(ReturnValue(brillo::Blob(30))); | |
| std::string apdu_response = ApduToHexString( | |
| ProcessMsg(kRequestRegisterPrefix, kChallenge, kAppId, kMaxResponseSize)); | |
| // See U2F Raw Message Formats Spec | |
| std::string expected_response_regex = | |
| "05" // Reserved Byte | |
| "[0-9A-F]{130}" // Public Key | |
| "40(FD){64}" // Key Handle (matches cr50 response) | |
| ".*" // Attestation Cert + Signature | |
| "9000"; // U2F_SW_NO_ERROR | |
| // Just a basic sanity check, the correctness of message contents is tested by | |
| // integration tests. | |
| EXPECT_THAT(apdu_response, MatchesRegex(expected_response_regex)); | |
| } | |
| // Errors detected during parsing; should not read user state or call cr50. | |
| TEST_F(U2fMessageHandlerTest, RegisterShortMsg) { | |
| CheckResponseForMsg(kRequestRegisterShort, kErrorResponseWrongLength); | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterInvalidFlags) { | |
| std::string prefix = kRequestRegisterPrefix; | |
| prefix[5] = '0'; // Set invalid P1 flag. | |
| CheckResponseForMsg(prefix, kChallenge, kAppId, kMaxResponseSize, | |
| kErrorResponseWtf); | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterChromeStubWinkRequest) { | |
| std::string app_id; | |
| std::string challenge; | |
| // Chrome Sends a bogus request with these parameters, see chromium | |
| // //src/device/fido/fido_constants.cc | |
| for (int i = 0; i < 32; i++) { | |
| app_id.append("41"); | |
| challenge.append("42"); | |
| } | |
| CheckResponseForMsg(kRequestRegisterPrefix, challenge, app_id, | |
| kMaxResponseSize, kErrorResponseConditionsNotSatisfied); | |
| } | |
| // Errors while reading state, should not call cr50. | |
| TEST_F(U2fMessageHandlerTest, RegisterSecretNotAvailable) { | |
| ExpectGetUserSecretFails(); | |
| CheckResponseForMsg(kRequestRegisterPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseWtf); | |
| } | |
| // Errors returned by cr50. | |
| TEST_F(U2fMessageHandlerTest, RegisterNoPresence) { | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, GenerateUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("No presence", TPMRetryAction::kUserPresence)); | |
| CheckResponseForMsg(kRequestRegisterPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseConditionsNotSatisfied); | |
| presence_requested_expected_ = 1; | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterInternalError) { | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, GenerateUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("Internal error", TPMRetryAction::kNoRetry)); | |
| CheckResponseForMsg(kRequestRegisterPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseWtf); | |
| } | |
| // U2F_REGISTER with G2F Attestation | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: | |
| // CLA,INS{U2F_REGISTER},P1{U2F_AUTH_ENFORCE|G2F_ATTEST},P2,MSG_SIZE All fields | |
| // one byte. | |
| constexpr char kRequestRegisterG2fPrefix[] = "0001830040"; | |
| // Example of a cert that would be returned by cr50. | |
| constexpr char kStubG2fCert[] = | |
| "308201363081DDA0030201020210442D32429223D041240350303716EE6B300A06082A8648" | |
| "CE3D040302300F310D300B06035504031304637235303022180F3230303030313031303030" | |
| "3030305A180F32303939313233313233353935395A300F310D300B06035504031304637235" | |
| "303059301306072A8648CE3D020106082A8648CE3D030107034200045165719A9975F6FD30" | |
| "CC2516C22FE841F65F9D2EE7B8B72F76807AEBD8CA3376005C7FA86453E4B10DB7BFAD5D2B" | |
| "D00DB4A7C4845AD06D686ACD0252387618ECA31730153013060B2B0601040182E51C020101" | |
| "040403020308300A06082A8648CE3D0403020348003045022100F09976F373920FEF8205C4" | |
| "B1FB1DA21EB9F3F176B7DF433A1ADE0F3F38B721960220179D9B9051BFCCCC90BA6BB42B86" | |
| "111D7A9C4FB56DFD39FB426081DD027AD609"; | |
| std::vector<uint8_t> GetStubG2fCert() { | |
| std::vector<uint8_t> cert; | |
| base::HexStringToBytes(kStubG2fCert, &cert); | |
| return cert; | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterG2fWhenDisabledSuccess) { | |
| // G2F Attestation is disabled (default flags set in test fixture), so we | |
| // should not see any additional calls to cr50 for G2F attestation. | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| GenerateUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired)) | |
| .WillOnce([](const auto&, const auto&, auto, auto) { | |
| return GenerateResult{ | |
| .public_key = std::make_unique<FakePublicKey>(GetPubKey()), | |
| .key_handle = GetCredId(), | |
| }; | |
| }); | |
| EXPECT_CALL(mock_u2f_frontend_, GetG2fAttestData) | |
| .WillOnce(ReturnValue(brillo::Blob(30))); | |
| std::string apdu_response = ApduToHexString(ProcessMsg( | |
| kRequestRegisterG2fPrefix, kChallenge, kAppId, kMaxResponseSize)); | |
| // See U2F Raw Message Formats Spec | |
| std::string expected_response_regex = | |
| "05" // Reserved Byte | |
| "[0-9A-F]{130}" // Public Key | |
| "40(FD){64}" // Key Handle (matches kCr50GenResp) | |
| ".*" // Attestation Cert + Signature | |
| "9000"; // U2F_SW_NO_ERROR | |
| // Just a basic sanity check, the correctness of message contents is tested by | |
| // integration tests. | |
| EXPECT_THAT(apdu_response, MatchesRegex(expected_response_regex)); | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterG2fSuccess) { | |
| CreateHandler(true /* g2f_attest */); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| GenerateUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired)) | |
| .WillOnce([](const auto&, const auto&, auto, auto) { | |
| return GenerateResult{ | |
| .public_key = std::make_unique<FakePublicKey>(GetPubKey()), | |
| .key_handle = GetCredId(), | |
| }; | |
| }); | |
| EXPECT_CALL(mock_u2f_frontend_, GetG2fCert) | |
| .WillOnce(ReturnValue(GetStubG2fCert())); | |
| EXPECT_CALL(*mock_allowlisting_util_, AppendDataToCert(_)) | |
| .WillOnce(Return(true)); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| G2fAttest(ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), | |
| ArrayToBlob(kChallenge), GetCredId(), GetPubKey())) | |
| .WillOnce(ReturnValue(Signature{ | |
| .r = GetSigR(), | |
| .s = GetSigS(), | |
| })); | |
| std::string apdu_response = ApduToHexString(ProcessMsg( | |
| kRequestRegisterG2fPrefix, kChallenge, kAppId, kMaxResponseSize)); | |
| // See U2F Raw Message Formats Spec | |
| std::string expected_response_regex = | |
| "05" // Reserved Byte | |
| "[0-9A-F]{130}" // Public Key | |
| "40(FD){64}" // Key Handle (matches kCr50GenResp) | |
| ".*" // Attestation Cert + Signature | |
| "9000"; // U2F_SW_NO_ERROR | |
| // Just a basic sanity check, the correctness of message contents is tested by | |
| // integration tests. | |
| EXPECT_THAT(apdu_response, MatchesRegex(expected_response_regex)); | |
| } | |
| // Error from cr50 | |
| TEST_F(U2fMessageHandlerTest, RegisterG2fNoPresence) { | |
| CreateHandler(true /* g2f_attest */); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, GenerateUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("No presence", TPMRetryAction::kUserPresence)); | |
| CheckResponseForMsg(kRequestRegisterG2fPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseConditionsNotSatisfied); | |
| presence_requested_expected_ = 1; | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterG2fAttestSecretNotAvailable) { | |
| CreateHandler(true /* g2f_attest */); | |
| EXPECT_CALL(mock_user_state_, GetUserSecret()) | |
| .WillOnce(Return(std::optional<brillo::SecureBlob>())); | |
| CheckResponseForMsg(kRequestRegisterG2fPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseWtf); | |
| } | |
| TEST_F(U2fMessageHandlerTest, RegisterG2fAttestFails) { | |
| CreateHandler(true /* g2f_attest */); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| GenerateUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired)) | |
| .WillOnce([](const auto&, const auto&, auto, auto) { | |
| return GenerateResult{ | |
| .public_key = std::make_unique<FakePublicKey>(GetPubKey()), | |
| .key_handle = GetCredId(), | |
| }; | |
| }); | |
| EXPECT_CALL(mock_u2f_frontend_, GetG2fCert) | |
| .WillOnce(ReturnValue(GetStubG2fCert())); | |
| EXPECT_CALL(mock_u2f_frontend_, G2fAttest) | |
| .WillOnce( | |
| ReturnError<TPMError>("Internal error", TPMRetryAction::kNoRetry)); | |
| CheckResponseForMsg(kRequestRegisterG2fPrefix, kChallenge, kAppId, | |
| kMaxResponseSize, kErrorResponseWtf); | |
| } | |
| // U2F_AUTHENTICATE | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: CLA,INS{U2F_AUTHENTICATE},P1{U2F_AUTH_ENFORCE},P2,MSG_SIZE | |
| constexpr char kRequestAuthenticatePrefix[] = "0002030042"; | |
| // Message format: KH_LENGTH,KH_BYTE | |
| constexpr char kRequestAuthenticateKeyHandle[] = "0100"; | |
| // All fields one byte. | |
| TEST_F(U2fMessageHandlerTest, AuthenticateSuccess) { | |
| ExpectGetCounter(); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| SignUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), _, | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired, _)) | |
| .WillOnce(ReturnValue(Signature{ | |
| .r = GetSigR(), | |
| .s = GetSigS(), | |
| })); | |
| ExpectIncrementCounter(); | |
| std::string apdu_response = | |
| ApduToHexString(ProcessMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle)); | |
| // Just basic sanity check, validity of the message is tested in integration | |
| // tests. | |
| std::string expected_response_regex = | |
| "01" // User Presence Asserted | |
| ".*" // DER encoding | |
| "(FF){32}" // sig_r | |
| ".*" // DER encoding | |
| "(55){32}" // sig_s | |
| "9000"; // U2F_SW_NO_ERRROR | |
| EXPECT_THAT(apdu_response, MatchesRegex(expected_response_regex)); | |
| } | |
| // Errors reading state, should not call cr50. | |
| TEST_F(U2fMessageHandlerTest, AuthenticateSecretNotAvailable) { | |
| ExpectGetUserSecretFails(); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWtf); | |
| } | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCounterNotAvailable) { | |
| ExpectGetUserSecret(); | |
| ExpectGetCounterFails(); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWtf); | |
| } | |
| // Errors returned by cr50. | |
| TEST_F(U2fMessageHandlerTest, AuthenticateNoPresence) { | |
| ExpectGetCounter(); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, SignUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("No presence", TPMRetryAction::kUserPresence)); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, | |
| kErrorResponseConditionsNotSatisfied); | |
| presence_requested_expected_ = 1; | |
| } | |
| TEST_F(U2fMessageHandlerTest, AuthenticateInvalidKeyHandle) { | |
| ExpectGetCounter(); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, SignUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("Invalid auth", TPMRetryAction::kUserAuth)); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWrongData); | |
| } | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCr50UnknownError) { | |
| ExpectGetCounter(); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, SignUserPresenceOnly) | |
| .WillOnce( | |
| ReturnError<TPMError>("Internal error", TPMRetryAction::kNoRetry)); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWtf); | |
| } | |
| // Error updating state. | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCounterCouldNotUpdate) { | |
| ExpectGetCounter(); | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| SignUserPresenceOnly( | |
| ArrayToBlob(kAppId), ArrayToSecureBlob(kUserSecret), _, | |
| ConsumeMode::kConsume, UserPresenceMode::kRequired, _)) | |
| .WillOnce(ReturnValue(Signature{ | |
| .r = GetSigR(), | |
| .s = GetSigS(), | |
| })); | |
| ExpectIncrementCounterFails(); | |
| CheckResponseForMsg(kRequestAuthenticatePrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWtf); | |
| } | |
| // U2F_AUTHENTICATE "Check Only" | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: CLA,INS{U2F_AUTHENTICATE},P1{U2F_AUTH_CHECK_ONLY},P2,MSG_SIZE | |
| constexpr char kRequestAuthenticateCheckOnlyPrefix[] = "0002070042"; | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCheckOnlySuccess) { | |
| ExpectGetUserSecret(); | |
| std::string expected_cr50_request_regex = | |
| "(AA){32}" // AppId | |
| "(EE){32}" // User Secret | |
| "(00){64}" // Key Handle | |
| "[0-9A-F]{64}" // Hash | |
| "07"; // U2F_AUTH_CHECK_ONLY | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| CheckUserPresenceOnly(ArrayToBlob(kAppId), | |
| ArrayToSecureBlob(kUserSecret), _)) | |
| .WillOnce(ReturnOk<TPMError>()); | |
| // A success response for this kind of message is indicate by a 'presence | |
| // required' error. | |
| CheckResponseForMsg(kRequestAuthenticateCheckOnlyPrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, | |
| kErrorResponseConditionsNotSatisfied); | |
| } | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCheckOnlyInvalidKeyHandle) { | |
| ExpectGetUserSecret(); | |
| EXPECT_CALL(mock_u2f_frontend_, | |
| CheckUserPresenceOnly(ArrayToBlob(kAppId), | |
| ArrayToSecureBlob(kUserSecret), _)) | |
| .WillOnce( | |
| ReturnError<TPMError>("Invalid auth", TPMRetryAction::kUserAuth)); | |
| CheckResponseForMsg(kRequestAuthenticateCheckOnlyPrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWrongData); | |
| } | |
| TEST_F(U2fMessageHandlerTest, AuthenticateCheckOnlySecretNotAvailable) { | |
| ExpectGetUserSecretFails(); | |
| CheckResponseForMsg(kRequestAuthenticateCheckOnlyPrefix, kChallenge, kAppId, | |
| kRequestAuthenticateKeyHandle, kErrorResponseWtf); | |
| } | |
| // U2F_VERSION | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: CLA,INS{U2F_VERSION},P1,P1,MSG_LENGTH,[RESPONSE_LENGTH] | |
| // All fields one byte. | |
| constexpr char kRequestVersion[] = "0003000000"; | |
| constexpr char kRequestVersionInvalidLength[] = "0003000001FF00"; | |
| // "U2F_V2", plus U2F_SW_NO_ERROR response code. | |
| constexpr char kSuccessResponseVersion[] = "5532465F56329000"; | |
| TEST_F(U2fMessageHandlerTest, Version) { | |
| CheckResponseForMsg(kRequestVersion, kSuccessResponseVersion); | |
| } | |
| TEST_F(U2fMessageHandlerTest, VersionInvalidMsg) { | |
| CheckResponseForMsg(kRequestVersionInvalidLength, kErrorResponseWrongLength); | |
| } | |
| // Misc Invalid Messages | |
| // | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Message format: CLA,INS,P1,P2. | |
| // All fields occupy byte. | |
| constexpr char kRequestInvalidIns[] = "00430000"; | |
| constexpr char kRequestInvalidCla[] = "FFFFFFFF"; | |
| constexpr char kRequestInvalidMsg[] = "00"; | |
| TEST_F(U2fMessageHandlerTest, UnknownIns) { | |
| CheckResponseForMsg(kRequestInvalidIns, kErrorResponseInsNotSupported); | |
| } | |
| TEST_F(U2fMessageHandlerTest, InvalidCla) { | |
| CheckResponseForMsg(kRequestInvalidCla, kErrorResponseClaNotSupported); | |
| } | |
| TEST_F(U2fMessageHandlerTest, InvalidMsg) { | |
| CheckResponseForMsg(kRequestInvalidMsg, kErrorResponseWtf); | |
| } | |
| } // namespace | |
| } // namespace u2f |