Commit 051b9793 authored by Adam Langley's avatar Adam Langley Committed by Chromium LUCI CQ

device/fido: add filter ability.

At several points in the past we have wanted a Finch-controllable
filter. This change attempts to add a fairly generic one that would have
met all the previous needs.

Change-Id: Iec25677b9bca532c45844933cf7d5d0d95e00764
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612051Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842117}
parent 6fa9ec49
...@@ -23,13 +23,14 @@ ...@@ -23,13 +23,14 @@
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "crypto/sha2.h" #include "crypto/sha2.h"
#include "device/fido/features.h" #include "device/fido/filter.h"
#include "extensions/browser/extension_api_frame_id_map.h" #include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/common/error_utils.h" #include "extensions/common/error_utils.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/origin.h" #include "url/origin.h"
#if defined(OS_WIN) #if defined(OS_WIN)
#include "device/fido/features.h"
#include "device/fido/win/webauthn_api.h" #include "device/fido/win/webauthn_api.h"
#endif // defined(OS_WIN) #endif // defined(OS_WIN)
...@@ -200,8 +201,10 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { ...@@ -200,8 +201,10 @@ CryptotokenPrivateCanAppIdGetAttestationFunction::Run() {
} }
// If the origin is blocked, reject attestation. // If the origin is blocked, reject attestation.
if (device::DoesMatchWebAuthAttestationBlockedDomains( if (device::fido_filter::Evaluate(
url::Origin::Create(origin_url))) { device::fido_filter::Operation::MAKE_CREDENTIAL, origin.Serialize(),
/*device=*/base::nullopt, /*id=*/base::nullopt) ==
device::fido_filter::Action::NO_ATTESTATION) {
return RespondNow(OneArgument(base::Value(false))); return RespondNow(OneArgument(base::Value(false)));
} }
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h" #include "device/fido/fido_transport_protocol.h"
#include "device/fido/filter.h"
#include "device/fido/get_assertion_request_handler.h" #include "device/fido/get_assertion_request_handler.h"
#include "device/fido/make_credential_request_handler.h" #include "device/fido/make_credential_request_handler.h"
#include "device/fido/public_key.h" #include "device/fido/public_key.h"
...@@ -828,6 +829,24 @@ void AuthenticatorCommon::MakeCredential( ...@@ -828,6 +829,24 @@ void AuthenticatorCommon::MakeCredential(
options->relying_party.id = std::move(*rp_id); options->relying_party.id = std::move(*rp_id);
request_delegate_->SetRelyingPartyId(relying_party_id_); request_delegate_->SetRelyingPartyId(relying_party_id_);
device::fido_filter::MaybeInitialize();
switch (device::fido_filter::Evaluate(
device::fido_filter::Operation::MAKE_CREDENTIAL, relying_party_id_,
/*device=*/base::nullopt,
/*id=*/base::nullopt)) {
case device::fido_filter::Action::ALLOW:
break;
case device::fido_filter::Action::NO_ATTESTATION:
// This will be handled by the request handler.
break;
case device::fido_filter::Action::BLOCK:
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
Focus::kDontCheck);
return;
}
base::Optional<std::string> appid_exclude; base::Optional<std::string> appid_exclude;
if (options->appid_exclude) { if (options->appid_exclude) {
appid_exclude = appid_exclude =
...@@ -1096,6 +1115,17 @@ void AuthenticatorCommon::GetAssertion( ...@@ -1096,6 +1115,17 @@ void AuthenticatorCommon::GetAssertion(
client_data::kGetType, caller_origin_.Serialize(), client_data::kGetType, caller_origin_.Serialize(),
options->challenge, is_cross_origin); options->challenge, is_cross_origin);
device::fido_filter::MaybeInitialize();
if (device::fido_filter::Evaluate(
device::fido_filter::Operation::GET_ASSERTION, relying_party_id_,
/*device=*/base::nullopt,
/*id=*/base::nullopt) == device::fido_filter::Action::BLOCK) {
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
// Cryptotoken requests should be proxied without UI. // Cryptotoken requests should be proxied without UI.
if (origin_is_crypto_token_extension || disable_ui_) if (origin_is_crypto_token_extension || disable_ui_)
request_delegate_->DisableUI(); request_delegate_->DisableUI();
...@@ -1395,7 +1425,7 @@ void AuthenticatorCommon::OnRegisterResponse( ...@@ -1395,7 +1425,7 @@ void AuthenticatorCommon::OnRegisterResponse(
// cryptotoken checks the attestation blocklist itself. // cryptotoken checks the attestation blocklist itself.
if (!origin_is_crypto_token_extension && if (!origin_is_crypto_token_extension &&
device::DoesMatchWebAuthAttestationBlockedDomains(caller_origin_) && response_data->attestation_should_be_filtered &&
!request_delegate_->ShouldPermitIndividualAttestation( !request_delegate_->ShouldPermitIndividualAttestation(
relying_party_id_)) { relying_party_id_)) {
attestation_erasure = attestation_erasure =
......
...@@ -168,6 +168,7 @@ test("device_unittests") { ...@@ -168,6 +168,7 @@ test("device_unittests") {
"fido/fido_device_discovery_unittest.cc", "fido/fido_device_discovery_unittest.cc",
"fido/fido_parsing_utils_unittest.cc", "fido/fido_parsing_utils_unittest.cc",
"fido/fido_request_handler_unittest.cc", "fido/fido_request_handler_unittest.cc",
"fido/filter_unittest.cc",
"fido/get_assertion_handler_unittest.cc", "fido/get_assertion_handler_unittest.cc",
"fido/get_assertion_task_unittest.cc", "fido/get_assertion_task_unittest.cc",
"fido/hid/fido_hid_message_unittest.cc", "fido/hid/fido_hid_message_unittest.cc",
......
...@@ -155,6 +155,8 @@ component("fido") { ...@@ -155,6 +155,8 @@ component("fido") {
"fido_task.cc", "fido_task.cc",
"fido_task.h", "fido_task.h",
"fido_types.h", "fido_types.h",
"filter.cc",
"filter.h",
"get_assertion_request_handler.cc", "get_assertion_request_handler.cc",
"get_assertion_request_handler.h", "get_assertion_request_handler.h",
"get_assertion_task.cc", "get_assertion_task.cc",
......
...@@ -96,6 +96,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse ...@@ -96,6 +96,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse
// but the authenticator may have created one anyway. // but the authenticator may have created one anyway.
base::Optional<bool> is_resident_key; base::Optional<bool> is_resident_key;
// attestation_should_be_filtered is true iff a filter indicated that the
// attestation should not be returned. This is acted upon by
// |AuthenticatorCommon| based on enterprise policy.
bool attestation_should_be_filtered = false;
private: private:
AttestationObject attestation_object_; AttestationObject attestation_object_;
......
...@@ -44,38 +44,4 @@ const base::Feature kWebAuthCrosPlatformAuthenticator{ ...@@ -44,38 +44,4 @@ const base::Feature kWebAuthCrosPlatformAuthenticator{
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
#endif // BUILDFLAG(IS_CHROMEOS_ASH) #endif // BUILDFLAG(IS_CHROMEOS_ASH)
extern const base::Feature kWebAuthAttestationBlockList{
"WebAuthentiationAttestationBlockList", base::FEATURE_DISABLED_BY_DEFAULT};
extern const base::FeatureParam<std::string> kWebAuthAttestationBlockedDomains{
&kWebAuthAttestationBlockList,
"domains",
"",
};
bool DoesMatchWebAuthAttestationBlockedDomains(const url::Origin& origin) {
const std::string& blocked_domains = kWebAuthAttestationBlockedDomains.Get();
if (blocked_domains.empty()) {
return false;
}
const std::vector<std::string> domains = base::SplitString(
blocked_domains, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const std::string& domain : domains) {
static constexpr char kWildcardPrefix[] = "(*.)";
if (!domain.empty() && domain[0] == '(' &&
domain.find(kWildcardPrefix) == 0) {
base::StringPiece domain_part(domain);
domain_part.remove_prefix(sizeof(kWildcardPrefix) - 1);
if (origin.DomainIs(domain_part)) {
return true;
}
} else if (!origin.opaque() && origin.host() == domain) {
return true;
}
}
return false;
}
} // namespace device } // namespace device
...@@ -54,17 +54,6 @@ COMPONENT_EXPORT(DEVICE_FIDO) ...@@ -54,17 +54,6 @@ COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthCrosPlatformAuthenticator; extern const base::Feature kWebAuthCrosPlatformAuthenticator;
#endif // BUILDFLAG(IS_CHROMEOS_ASH) #endif // BUILDFLAG(IS_CHROMEOS_ASH)
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthAttestationBlockList;
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::FeatureParam<std::string> kWebAuthAttestationBlockedDomains;
// DoesMatchWebAuthAttestationBlockedDomains returns true if the
// |kWebAuthAttestationBlocked| feature is enabled and |origin| is listed
// in |kWebAuthAttestationBlockedDomains|.
COMPONENT_EXPORT(DEVICE_FIDO)
bool DoesMatchWebAuthAttestationBlockedDomains(const url::Origin& origin);
} // namespace device } // namespace device
#endif // DEVICE_FIDO_FEATURES_H_ #endif // DEVICE_FIDO_FEATURES_H_
// Copyright 2021 The Chromium 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 "device/fido/filter.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "components/device_event_log/device_event_log.h"
namespace device {
namespace fido_filter {
namespace {
const base::Feature kFilter{"WebAuthenticationFilter",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<std::string> kFilterJSON{
&kFilter,
"json",
"",
};
struct FilterStep {
base::Optional<std::string> operation;
std::vector<std::string> rp_id;
base::Optional<std::string> device;
base::Optional<std::string> id_type;
std::vector<std::string> id;
base::Optional<size_t> id_min_size;
base::Optional<size_t> id_max_size;
Action action;
};
bool IsString(const base::Value& v) {
return v.is_string();
}
bool IsNonEmptyString(const base::Value& v) {
return v.is_string() && !v.GetString().empty();
}
bool IsListOf(const base::Value* v, bool (*predicate)(const base::Value&)) {
if (!v->is_list()) {
return false;
}
auto contents = v->GetList();
return !contents.empty() &&
std::all_of(contents.begin(), contents.end(), predicate);
}
std::vector<std::string> GetStringOrListOfStrings(const base::Value* v) {
if (v->is_string()) {
return {v->GetString()};
}
std::vector<std::string> ret;
for (const auto& elem : v->GetList()) {
ret.push_back(elem.GetString());
}
return ret;
}
base::Optional<std::vector<FilterStep>> ParseJSON(base::StringPiece json) {
base::Optional<base::Value> v =
base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS);
if (!v || !v->is_dict()) {
return base::nullopt;
}
const base::Value* filters = v->FindKey("filters");
if (!filters || !filters->is_list()) {
return base::nullopt;
}
std::vector<FilterStep> ret;
const auto filter_list = filters->GetList();
for (const auto& filter : filter_list) {
if (!filter.is_dict()) {
return base::nullopt;
}
// These are the keys that are extracted from the JSON:
const base::Value* operation = nullptr;
const base::Value* rp_id = nullptr;
const base::Value* device = nullptr;
const base::Value* id_type = nullptr;
const base::Value* id = nullptr;
const base::Value* id_min_size = nullptr;
const base::Value* id_max_size = nullptr;
const base::Value* action = nullptr;
// DictItems is used so that unknown keys in the dictionary can be rejected.
for (const auto& pair : filter.DictItems()) {
if (pair.first == "operation") {
operation = &pair.second;
} else if (pair.first == "rp_id") {
rp_id = &pair.second;
} else if (pair.first == "device") {
device = &pair.second;
} else if (pair.first == "id_type") {
id_type = &pair.second;
} else if (pair.first == "id") {
id = &pair.second;
} else if (pair.first == "id_min_size") {
id_min_size = &pair.second;
} else if (pair.first == "id_max_size") {
id_max_size = &pair.second;
} else if (pair.first == "action") {
action = &pair.second;
} else {
// Unknown keys are an error.
return base::nullopt;
}
}
if (!action || !IsNonEmptyString(*action) ||
(operation && !IsNonEmptyString(*operation)) ||
(rp_id && !IsNonEmptyString(*rp_id) &&
!IsListOf(rp_id, IsNonEmptyString)) ||
(device && !IsNonEmptyString(*device)) ||
(id_type && !IsNonEmptyString(*id_type)) ||
(id && !IsString(*id) && !IsListOf(id, IsString)) ||
(id_min_size && !id_min_size->is_int()) ||
(id_max_size && !id_max_size->is_int())) {
return base::nullopt;
}
if ((id_min_size || id_max_size || id) && !id_type) {
// If matches on the contents or size of an ID are given then the type
// must also be matched.
return base::nullopt;
}
if (!rp_id && !device) {
// Filter is too broad. For safety this is disallowed, although one can
// still explicitly use a wildcard.
return base::nullopt;
}
FilterStep step;
const std::string& action_str = action->GetString();
if (action_str == "allow") {
step.action = Action::ALLOW;
} else if (action_str == "block") {
step.action = Action::BLOCK;
} else if (action_str == "no-attestation") {
step.action = Action::NO_ATTESTATION;
} else {
return base::nullopt;
}
if (operation) {
step.operation = operation->GetString();
}
if (rp_id) {
step.rp_id = GetStringOrListOfStrings(rp_id);
}
if (device) {
step.device = device->GetString();
}
if (id_type) {
step.id_type = id_type->GetString();
}
if (id) {
step.id = GetStringOrListOfStrings(id);
}
if (id_min_size) {
const int v = id_min_size->GetInt();
if (v < 0) {
return base::nullopt;
}
step.id_min_size = v;
}
if (id_max_size) {
const int v = id_max_size->GetInt();
if (v < 0) {
return base::nullopt;
}
step.id_max_size = v;
}
ret.emplace_back(std::move(step));
}
return ret;
}
const char* OperationToString(Operation op) {
switch (op) {
case Operation::MAKE_CREDENTIAL:
return "mc";
case Operation::GET_ASSERTION:
return "ga";
}
}
const char* IDTypeToString(IDType id_type) {
switch (id_type) {
case IDType::CREDENTIAL_ID:
return "cred";
case IDType::USER_ID:
return "user";
}
}
size_t g_testing_depth = 0;
struct CurrentFilter {
base::Optional<std::vector<FilterStep>> steps;
base::Optional<std::string> json;
};
CurrentFilter* GetCurrentFilter() {
static base::NoDestructor<CurrentFilter> current_filter;
return current_filter.get();
}
bool MaybeParseFilter(base::StringPiece json) {
CurrentFilter* const current_filter = GetCurrentFilter();
if (current_filter->json && json == *current_filter->json) {
return true;
}
if (json.size() == 0) {
current_filter->steps.reset();
current_filter->json = "";
return true;
}
current_filter->steps = ParseJSON(json);
if (!current_filter->steps) {
current_filter->json.reset();
return false;
}
current_filter->json = json.as_string();
return true;
}
} // namespace
void MaybeInitialize() {
if (g_testing_depth != 0) {
return;
}
const std::string& json = kFilterJSON.Get();
if (!MaybeParseFilter(json)) {
FIDO_LOG(ERROR) << "Failed to parse filter JSON. Failing open.";
}
}
Action Evaluate(
Operation op,
base::StringPiece rp_id,
base::Optional<base::StringPiece> device,
base::Optional<std::pair<IDType, base::span<const uint8_t>>> id) {
CurrentFilter* const current_filter = GetCurrentFilter();
if (!current_filter->steps) {
return Action::ALLOW;
}
base::Optional<std::string> id_hex;
if (id) {
id_hex = base::HexEncode(id->second);
}
for (const auto& filter : *current_filter->steps) {
if ((!filter.operation ||
base::MatchPattern(OperationToString(op), *filter.operation)) &&
(filter.rp_id.empty() ||
std::any_of(filter.rp_id.begin(), filter.rp_id.end(),
[rp_id](const std::string& pattern) -> bool {
return base::MatchPattern(rp_id, pattern);
})) &&
(!filter.device ||
base::MatchPattern(device.value_or(""), *filter.device)) &&
(!filter.id_type || (id && base::MatchPattern(IDTypeToString(id->first),
*filter.id_type))) &&
(!filter.id_min_size ||
(id && *filter.id_min_size <= id->second.size())) &&
(!filter.id_max_size ||
(id && *filter.id_max_size >= id->second.size())) &&
(filter.id.empty() ||
(id_hex && std::any_of(filter.id.begin(), filter.id.end(),
[&id_hex](const std::string& pattern) -> bool {
return base::MatchPattern(*id_hex, pattern);
})))) {
return filter.action;
}
}
return Action::ALLOW;
}
ScopedFilterForTesting::ScopedFilterForTesting(base::StringPiece json)
: previous_json_(GetCurrentFilter()->json) {
g_testing_depth++;
CHECK(g_testing_depth != 0);
CHECK(MaybeParseFilter(json)) << json;
}
ScopedFilterForTesting::ScopedFilterForTesting(
base::StringPiece json,
ScopedFilterForTesting::PermitInvalidJSON)
: previous_json_(GetCurrentFilter()->json) {
g_testing_depth++;
CHECK(g_testing_depth != 0);
MaybeParseFilter(json);
}
ScopedFilterForTesting::~ScopedFilterForTesting() {
CurrentFilter* const current_filter = GetCurrentFilter();
current_filter->steps.reset();
current_filter->json.reset();
g_testing_depth--;
if (previous_json_) {
CHECK(MaybeParseFilter(*previous_json_));
}
}
bool ParseForTesting(base::StringPiece json) {
CHECK(base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS)) << json;
return MaybeParseFilter(json);
}
} // namespace fido_filter
} // namespace device
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_FIDO_FILTER_H_
#define DEVICE_FIDO_FILTER_H_
#include <string>
#include <utility>
#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/strings/string_piece.h"
namespace device {
namespace fido_filter {
// This code is intended to allow Finch control of several cases that have
// cropped up over time:
// * Disabling a device on the USB bus that immediately answers all requests,
// thus stopping anything else from working.
// * Filtering attestation from devices that are found to be sending
// excessively identifying information.
// * Filtering attestation from websites that are performing tight
// allowlisting in a public context.
// * Filtering IDs from devices that are using them to tunnel other protocols.
//
// Thus Finch can set the "json" parameter of "WebAuthenticationFilter" to a
// JSON string with the following structure:
//
// {
// "filters": [{
// "operation": matched against "ga" (getAssertion) or "mc" (makeCredential)
// "rp_id": matched against the RP ID (or AppID via the U2F API). Can be a
// list of such strings which matches if any element matches.
// "device": matched against the GetDisplayName value of the authenticator
// "id_type": matched against "cred" (credential IDs) or "user" (user IDs).
// "cred" matches against the allowCredentials of
// PublicKeyCredentialRequestOptions and the excludeCredentials
// of PublicKeyCredentialCreationOptions. "user" matches against
// the user.id of PublicKeyCredentialCreationOptions.
// "id": matched against the uppercase hex of the given ID. Can be
// a list of such strings which matches if any element matches.
// "id_min_size": matches if <= to the ID length, in bytes.
// "id_max_size": matches if >= to the ID length, in bytes.
// "action": "allow", "block", or "no-attestation".
// }, { ... }
// ]}
//
// The JSON is allowed to have trailing commas, unlike standard JSON.
//
// When strings are matched, it is using base/strings/pattern.h. Note the
// comment in that file well because it's more like a file glob than a regexp.
//
// The only required field is "action", but:
// * "id_type" must be given if "id_min_size", "id_max_size", or "id" are.
// * At least one of "device" or "rp_id" must be given.
//
// Any structural errors, or unknown keys, in the JSON cause a parse error and
// the filter fails open.
//
// A result of "block" rejects the action. If an action is blocked for all
// devices and doesn't specify an ID then it'll result in an immediate rejection
// of the WebAuthn Promise. Otherwise, a block causes an authenticator to be
// ignored, potentially hanging the request if all authenticators are ignored. A
// result of "allow" permits the action. (This can be useful to permit a narrow
// range of cases before blocking a wider range.) Lastly, a result of
// "no-attestation" causes attestation to be suppressed for makeCredential
// operations, unless the RP ID is listed in enterprise policy.
//
// At various points in the code, |Evaluate| can be called in order to process
// any configured filter. Before |Evaluate| can be called, |MaybeInitialize|
// must be called to check whether the filter has been updated.
//
// Processing stops at the first matching filter. If none match, |ALLOW| is
// returned.
// MaybeInitialize parses any update to the Finch-controlled filter.
COMPONENT_EXPORT(DEVICE_FIDO)
void MaybeInitialize();
// Operation enumerates the possible operations for calling |Evaluate|.
enum class Operation {
MAKE_CREDENTIAL,
GET_ASSERTION,
};
// Operation enumerates the possible types of IDs for calling |Evaluate|.
enum class IDType {
CREDENTIAL_ID,
USER_ID,
};
// Action enumerates the result of evaluating a set of filterse.
enum class Action {
ALLOW = 1,
NO_ATTESTATION = 2,
BLOCK = 3,
};
// Evaluate consults any configured filters and returns the result of evaluating
// them. See above about the format of filters.
COMPONENT_EXPORT(DEVICE_FIDO)
Action Evaluate(
Operation op,
base::StringPiece rp_id,
base::Optional<base::StringPiece> device,
base::Optional<std::pair<IDType, base::span<const uint8_t>>> id);
// ScopedFilterForTesting sets the current filter JSON for the duration of its
// lifetime. It is a fatal error if |json| is ill-formed.
class COMPONENT_EXPORT(DEVICE_FIDO) ScopedFilterForTesting {
public:
enum class PermitInvalidJSON {
kYes,
};
explicit ScopedFilterForTesting(base::StringPiece json);
ScopedFilterForTesting(base::StringPiece json, PermitInvalidJSON);
~ScopedFilterForTesting();
private:
const base::Optional<std::string> previous_json_;
};
// ParseForTesting returns true iff |json| is a well-formed filter.
COMPONENT_EXPORT(DEVICE_FIDO)
bool ParseForTesting(base::StringPiece json);
} // namespace fido_filter
} // namespace device
#endif // DEVICE_FIDO_FILTER_H_
This diff is collapsed.
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "device/fido/fido_authenticator.h" #include "device/fido/fido_authenticator.h"
#include "device/fido/fido_discovery_factory.h" #include "device/fido/fido_discovery_factory.h"
#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_parsing_utils.h"
#include "device/fido/filter.h"
#include "device/fido/get_assertion_task.h" #include "device/fido/get_assertion_task.h"
#include "device/fido/large_blob.h" #include "device/fido/large_blob.h"
#include "device/fido/pin.h" #include "device/fido/pin.h"
...@@ -341,6 +342,28 @@ void GetAssertionRequestHandler::DispatchRequest( ...@@ -341,6 +342,28 @@ void GetAssertionRequestHandler::DispatchRequest(
return; return;
} }
const std::string authenticator_name = authenticator->GetDisplayName();
if (fido_filter::Evaluate(fido_filter::Operation::GET_ASSERTION,
request_.rp_id, authenticator_name,
base::nullopt) == fido_filter::Action::BLOCK) {
FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name;
return;
}
for (const auto& cred : request_.allow_list) {
if (fido_filter::Evaluate(
fido_filter::Operation::GET_ASSERTION, request_.rp_id,
authenticator_name,
std::pair<fido_filter::IDType, base::span<const uint8_t>>(
fido_filter::IDType::CREDENTIAL_ID, cred.id())) ==
fido_filter::Action::BLOCK) {
FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name
<< " for credential ID " << base::HexEncode(cred.id());
return;
}
}
CtapGetAssertionRequest request = CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator); SpecializeRequestForAuthenticator(request_, *authenticator);
PINUVDisposition uv_disposition = PINUVDisposition uv_disposition =
......
...@@ -120,6 +120,8 @@ device::mojom::HidDeviceInfoPtr TestHidDevice() { ...@@ -120,6 +120,8 @@ device::mojom::HidDeviceInfoPtr TestHidDevice() {
c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0); c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
auto hid_device = device::mojom::HidDeviceInfo::New(); auto hid_device = device::mojom::HidDeviceInfo::New();
hid_device->guid = "A"; hid_device->guid = "A";
hid_device->vendor_id = 0x1234;
hid_device->product_id = 0x5678;
hid_device->product_name = "Test Fido device"; hid_device->product_name = "Test Fido device";
hid_device->serial_number = "123FIDO"; hid_device->serial_number = "123FIDO";
hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB; hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
...@@ -227,26 +229,35 @@ class FidoHidDeviceTest : public ::testing::Test { ...@@ -227,26 +229,35 @@ class FidoHidDeviceTest : public ::testing::Test {
} }
protected: protected:
std::unique_ptr<FidoHidDevice> GetMockDevice() {
FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get());
auto hid_device = TestHidDevice();
fake_hid_manager_->AddDevice(std::move(hid_device));
hid_manager_->GetDevices(receiver.callback());
receiver.WaitForCallback();
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
CHECK_EQ(static_cast<size_t>(1), u2f_devices.size());
return std::move(u2f_devices.front());
}
base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME}; base::test::TaskEnvironment::TimeSource::MOCK_TIME};
mojo::Remote<device::mojom::HidManager> hid_manager_; mojo::Remote<device::mojom::HidManager> hid_manager_;
std::unique_ptr<FakeFidoHidManager> fake_hid_manager_; std::unique_ptr<FakeFidoHidManager> fake_hid_manager_;
}; };
TEST_F(FidoHidDeviceTest, TestDeviceError) { TEST_F(FidoHidDeviceTest, DisplayName) {
// Setup and enumerate mock device. // If this format changes then beware of any configured filters (see filter.h)
FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); // that might be matching against these names.
CHECK_EQ(GetMockDevice()->GetDisplayName(), "usb-1234:5678");
auto hid_device = TestHidDevice(); }
fake_hid_manager_->AddDevice(std::move(hid_device));
hid_manager_->GetDevices(receiver.callback());
receiver.WaitForCallback();
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
ASSERT_EQ(static_cast<size_t>(1), u2f_devices.size()); TEST_F(FidoHidDeviceTest, TestDeviceError) {
auto& device = u2f_devices.front(); std::unique_ptr<FidoHidDevice> device = GetMockDevice();
// Mock connection where writes always fail. // Mock connection where writes always fail.
FakeFidoHidConnection::mock_connection_error_ = true; FakeFidoHidConnection::mock_connection_error_ = true;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "device/fido/fido_authenticator.h" #include "device/fido/fido_authenticator.h"
#include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h" #include "device/fido/fido_transport_protocol.h"
#include "device/fido/filter.h"
#include "device/fido/make_credential_task.h" #include "device/fido/make_credential_task.h"
#if defined(OS_WIN) #if defined(OS_WIN)
...@@ -391,6 +392,35 @@ void MakeCredentialRequestHandler::DispatchRequest( ...@@ -391,6 +392,35 @@ void MakeCredentialRequestHandler::DispatchRequest(
return; return;
} }
const std::string authenticator_name = authenticator->GetDisplayName();
switch (fido_filter::Evaluate(
fido_filter::Operation::MAKE_CREDENTIAL, request_.rp.id,
authenticator_name,
std::pair<fido_filter::IDType, base::span<const uint8_t>>(
fido_filter::IDType::USER_ID, request_.user.id))) {
case fido_filter::Action::ALLOW:
break;
case fido_filter::Action::NO_ATTESTATION:
suppress_attestation_ = true;
break;
case fido_filter::Action::BLOCK:
FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name;
return;
}
for (const auto& cred : request_.exclude_list) {
if (fido_filter::Evaluate(
fido_filter::Operation::MAKE_CREDENTIAL, request_.rp.id,
authenticator_name,
std::pair<fido_filter::IDType, base::span<const uint8_t>>(
fido_filter::IDType::CREDENTIAL_ID, cred.id())) ==
fido_filter::Action::BLOCK) {
FIDO_LOG(DEBUG) << "Filtered request to device " << authenticator_name
<< " for credential ID " << base::HexEncode(cred.id());
return;
}
}
std::unique_ptr<CtapMakeCredentialRequest> request( std::unique_ptr<CtapMakeCredentialRequest> request(
new CtapMakeCredentialRequest(request_)); new CtapMakeCredentialRequest(request_));
SpecializeRequestForAuthenticator(request.get(), authenticator); SpecializeRequestForAuthenticator(request.get(), authenticator);
...@@ -624,6 +654,7 @@ void MakeCredentialRequestHandler::HandleResponse( ...@@ -624,6 +654,7 @@ void MakeCredentialRequestHandler::HandleResponse(
return; return;
} }
CancelActiveAuthenticators(authenticator->GetId()); CancelActiveAuthenticators(authenticator->GetId());
response->attestation_should_be_filtered = suppress_attestation_;
std::move(completion_callback_) std::move(completion_callback_)
.Run(WinCtapDeviceResponseCodeToMakeCredentialStatus(status), .Run(WinCtapDeviceResponseCodeToMakeCredentialStatus(status),
std::move(*response), authenticator); std::move(*response), authenticator);
...@@ -712,6 +743,7 @@ void MakeCredentialRequestHandler::HandleResponse( ...@@ -712,6 +743,7 @@ void MakeCredentialRequestHandler::HandleResponse(
*authenticator->AuthenticatorTransport()); *authenticator->AuthenticatorTransport());
} }
response->attestation_should_be_filtered = suppress_attestation_;
std::move(completion_callback_) std::move(completion_callback_)
.Run(MakeCredentialStatus::kSuccess, std::move(*response), authenticator); .Run(MakeCredentialStatus::kSuccess, std::move(*response), authenticator);
} }
......
...@@ -194,6 +194,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler ...@@ -194,6 +194,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
CompletionCallback completion_callback_; CompletionCallback completion_callback_;
State state_ = State::kWaitingForTouch; State state_ = State::kWaitingForTouch;
bool suppress_attestation_ = false;
CtapMakeCredentialRequest request_; CtapMakeCredentialRequest request_;
base::Optional<base::RepeatingClosure> bio_enrollment_complete_barrier_; base::Optional<base::RepeatingClosure> bio_enrollment_complete_barrier_;
const Options options_; const Options options_;
......
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