Commit 3f738661 authored by Jun Choi's avatar Jun Choi Committed by Commit Bot

Add parsing logic for GetAssertion request

In order to implement virtual CTAP2 security key we need to parse and
validate serialized GetAssertion request that the client sends to the
device. Add corresponding parsing logic.

Bug: 829413
Change-Id: Idd20a7b138ebaeda39b412824519750eccd7c673
Reviewed-on: https://chromium-review.googlesource.com/1110447
Commit-Queue: Jun Choi <hongjunchoi@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#570629}
parent a918047d
......@@ -4,15 +4,47 @@
#include "device/fido/ctap_get_assertion_request.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "base/numerics/safe_conversions.h"
#include "components/cbor/cbor_reader.h"
#include "components/cbor/cbor_writer.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
namespace {
bool AreGetAssertionRequestMapKeysCorrect(
const cbor::CBORValue::MapValue& request_map) {
return std::all_of(request_map.begin(), request_map.end(),
[](const auto& param) {
if (!param.first.is_integer())
return false;
const auto& key = param.first.GetInteger();
return (key <= 7u || key >= 1u);
});
}
bool IsGetAssertionOptionMapFormatCorrect(
const cbor::CBORValue::MapValue& option_map) {
return std::all_of(
option_map.begin(), option_map.end(), [](const auto& param) {
if (!param.first.is_string())
return false;
const auto& key = param.first.GetString();
return (key == kUserPresenceMapKey || key == kUserVerificationMapKey) &&
param.second.is_bool();
});
}
} // namespace
CtapGetAssertionRequest::CtapGetAssertionRequest(
std::string rp_id,
base::span<const uint8_t, kClientDataHashLength> client_data_hash)
......@@ -128,4 +160,90 @@ CtapGetAssertionRequest::SetAlternativeApplicationParameter(
return *this;
}
base::Optional<CtapGetAssertionRequest> ParseCtapGetAssertionRequest(
base::span<const uint8_t> request_bytes) {
const auto& cbor_request = cbor::CBORReader::Read(request_bytes);
if (!cbor_request || !cbor_request->is_map())
return base::nullopt;
const auto& request_map = cbor_request->GetMap();
if (!AreGetAssertionRequestMapKeysCorrect(request_map))
return base::nullopt;
const auto rp_id_it = request_map.find(cbor::CBORValue(1));
if (rp_id_it == request_map.end() || !rp_id_it->second.is_string())
return base::nullopt;
const auto client_data_hash_it = request_map.find(cbor::CBORValue(2));
if (client_data_hash_it == request_map.end() ||
!client_data_hash_it->second.is_bytestring())
return base::nullopt;
const auto client_data_hash =
base::make_span(client_data_hash_it->second.GetBytestring())
.subspan<0, kClientDataHashLength>();
CtapGetAssertionRequest request(rp_id_it->second.GetString(),
client_data_hash);
const auto allow_list_it = request_map.find(cbor::CBORValue(3));
if (allow_list_it != request_map.end()) {
if (!allow_list_it->second.is_array())
return base::nullopt;
const auto& credential_descriptors = allow_list_it->second.GetArray();
std::vector<PublicKeyCredentialDescriptor> allow_list;
for (const auto& credential_descriptor : credential_descriptors) {
auto allowed_credential =
PublicKeyCredentialDescriptor::CreateFromCBORValue(
credential_descriptor);
if (!allowed_credential)
return base::nullopt;
allow_list.push_back(std::move(*allowed_credential));
}
request.SetAllowList(std::move(allow_list));
}
const auto option_it = request_map.find(cbor::CBORValue(5));
if (option_it != request_map.end()) {
if (!option_it->second.is_map())
return base::nullopt;
const auto& option_map = option_it->second.GetMap();
if (!IsGetAssertionOptionMapFormatCorrect(option_map))
return base::nullopt;
const auto user_presence_option =
option_map.find(cbor::CBORValue(kUserPresenceMapKey));
if (user_presence_option != option_map.end())
request.SetUserPresenceRequired(user_presence_option->second.GetBool());
const auto uv_option =
option_map.find(cbor::CBORValue(kUserVerificationMapKey));
if (uv_option != option_map.end())
request.SetUserVerification(
uv_option->second.GetBool()
? UserVerificationRequirement::kRequired
: UserVerificationRequirement::kPreferred);
}
const auto pin_auth_it = request_map.find(cbor::CBORValue(6));
if (pin_auth_it != request_map.end()) {
if (!pin_auth_it->second.is_bytestring())
return base::nullopt;
request.SetPinAuth(pin_auth_it->second.GetBytestring());
}
const auto pin_protocol_it = request_map.find(cbor::CBORValue(7));
if (pin_protocol_it != request_map.end()) {
if (!pin_protocol_it->second.is_unsigned() ||
pin_protocol_it->second.GetUnsigned() >
std::numeric_limits<uint8_t>::max())
return base::nullopt;
request.SetPinProtocol(pin_auth_it->second.GetUnsigned());
}
return request;
}
} // namespace device
......@@ -97,6 +97,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
alternative_application_parameter_;
};
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<CtapGetAssertionRequest> ParseCtapGetAssertionRequest(
base::span<const uint8_t> request_bytes);
} // namespace device
#endif // DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
......@@ -37,69 +37,6 @@ TEST(CTAPRequestTest, TestConstructMakeCredentialRequestParam) {
}
TEST(CTAPRequestTest, TestConstructGetAssertionRequest) {
static constexpr uint8_t kSerializedRequest[] = {
// clang-format off
0x02, // authenticatorGetAssertion command
0xa4, // map(4)
0x01, // rpId
0x68, // text(8)
// "acme.com"
0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
0x02, // unsigned(2) - client data hash
0x58, 0x20, // bytes(32)
0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, 0x42, 0x50,
0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, 0xb8, 0x8c,
0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41,
0x03, // unsigned(3) - allow list
0x82, // array(2)
0xa2, // map(2)
0x62, // text(2)
0x69, 0x64, // "id"
0x58, 0x40,
// credential ID
0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94, 0x2f,
0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b, 0x3d, 0xf8,
0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34, 0x85, 0x8a,
0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9, 0x4f, 0xcb,
0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc, 0xc3, 0x58,
0x52, 0xea, 0x6b, 0x9e,
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6a, // text(10)
// "public-key"
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
0xa2, // map(2)
0x62, // text(2)
0x69, 0x64, // "id"
0x58, 0x32, // text(22)
// credential ID
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03,
0x64, // text(4)
0x74, 0x79, 0x70, 0x65, // "type"
0x6a, // text(10)
// "public-key"
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
0x05, // unsigned(5) - options
0xa2, // map(2)
0x62, // text(2)
0x75, 0x70, // "up"
0xf4, // False(20)
0x62, // text(2)
0x75, 0x76, // "uv"
0xf5 // True(21)
// clang-format on
};
CtapGetAssertionRequest get_assertion_req("acme.com",
test_data::kClientDataHash);
......@@ -125,7 +62,8 @@ TEST(CTAPRequestTest, TestConstructGetAssertionRequest) {
.SetUserVerification(UserVerificationRequirement::kRequired);
auto serialized_data = get_assertion_req.EncodeAsCBOR();
EXPECT_THAT(serialized_data, ::testing::ElementsAreArray(kSerializedRequest));
EXPECT_THAT(serialized_data,
::testing::ElementsAreArray(test_data::kCtapGetAssertionRequest));
}
TEST(CTAPRequestTest, TestConstructCtapAuthenticatorRequestParam) {
......@@ -173,4 +111,36 @@ TEST(CTAPRequestTest, ParseMakeCredentialRequestForVirtualCtapKey) {
EXPECT_TRUE(request->resident_key_supported());
}
TEST(CTAPRequestTest, ParseGetAssertionRequestForVirtualCtapKey) {
constexpr uint8_t kAllowedCredentialOne[] = {
0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94,
0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0,
0x34, 0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98,
0x08, 0xd9, 0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77,
0xaf, 0x0a, 0xdc, 0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e};
constexpr uint8_t kAllowedCredentialTwo[] = {
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03};
const auto request = ParseCtapGetAssertionRequest(
base::make_span(test_data::kCtapGetAssertionRequest).subspan(1));
ASSERT_TRUE(request);
EXPECT_THAT(request->client_data_hash(),
::testing::ElementsAreArray(test_data::kClientDataHash));
EXPECT_EQ(test_data::kRelyingPartyId, request->rp_id());
EXPECT_EQ(UserVerificationRequirement::kRequired,
request->user_verification());
EXPECT_FALSE(request->user_presence_required());
ASSERT_TRUE(request->allow_list());
ASSERT_EQ(2u, request->allow_list()->size());
EXPECT_THAT(request->allow_list()->at(0).id(),
::testing::ElementsAreArray(kAllowedCredentialOne));
EXPECT_THAT(request->allow_list()->at(1).id(),
::testing::ElementsAreArray(kAllowedCredentialTwo));
}
} // namespace device
......@@ -809,6 +809,67 @@ constexpr uint8_t kCtapMakeCredentialRequest[] = {
// True(21)
0xf5};
constexpr uint8_t kCtapGetAssertionRequest[] = {
// authenticatorGetAssertion command
0x02,
// map(4)
0xa4,
// key(01) -rpId
0x01,
// value - "acme.com"
0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
// key(02) - client data hash
0x02,
// value - bytes(32)
0x58, 0x20, 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e,
0x42, 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05,
0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41,
// key(03) - allow list
0x03,
// value - array(2)
0x82,
// map(2)
0xa2,
// key - "id"
0x62, 0x69, 0x64,
// value - credential ID
0x58, 0x40, 0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43,
0x94, 0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0, 0x34,
0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98, 0x08, 0xd9,
0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77, 0xaf, 0x0a, 0xdc,
0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e,
// key - "type"
0x64, 0x74, 0x79, 0x70, 0x65,
// value - "public-key"
0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
// map(2)
0xa2,
// key - "id"
0x62, 0x69, 0x64,
// value - credential ID
0x58, 0x32, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03,
// key - "type"
0x64, 0x74, 0x79, 0x70, 0x65,
// value - "public-key"
0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79,
// unsigned(5) - options
0x05,
// map(2)
0xa2,
// key -"up"
0x62, 0x75, 0x70,
// value - False(20)
0xf4,
// key - "uv"
0x62, 0x75, 0x76,
// value - True(21)
0xf5};
// CTAP responses --------------------------------------------------------------
// A sample well formed response to CTAP AuthenticatorGetInfo request. Supports
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment