123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- // 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/metrics/field_trial_params.h"
- #include "base/no_destructor.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"
- #include "third_party/abseil-cpp/absl/types/optional.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 {
- absl::optional<std::string> operation;
- std::vector<std::string> rp_id;
- absl::optional<std::string> device;
- absl::optional<std::string> id_type;
- std::vector<std::string> id;
- absl::optional<size_t> id_min_size;
- absl::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->GetListDeprecated();
- 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->GetListDeprecated()) {
- ret.push_back(elem.GetString());
- }
- return ret;
- }
- absl::optional<std::vector<FilterStep>> ParseJSON(base::StringPiece json) {
- absl::optional<base::Value> v =
- base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS);
- if (!v || !v->is_dict()) {
- return absl::nullopt;
- }
- const base::Value* filters = v->FindKey("filters");
- if (!filters || !filters->is_list()) {
- return absl::nullopt;
- }
- std::vector<FilterStep> ret;
- const auto filter_list = filters->GetListDeprecated();
- for (const auto& filter : filter_list) {
- if (!filter.is_dict()) {
- return absl::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 (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 absl::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 absl::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 absl::nullopt;
- }
- if (!rp_id && !device) {
- // Filter is too broad. For safety this is disallowed, although one can
- // still explicitly use a wildcard.
- return absl::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 absl::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 min_size_int = id_min_size->GetInt();
- if (min_size_int < 0) {
- return absl::nullopt;
- }
- step.id_min_size = min_size_int;
- }
- if (id_max_size) {
- const int max_size_int = id_max_size->GetInt();
- if (max_size_int < 0) {
- return absl::nullopt;
- }
- step.id_max_size = max_size_int;
- }
- 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 {
- absl::optional<std::vector<FilterStep>> steps;
- absl::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 = std::string(json);
- 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,
- absl::optional<base::StringPiece> device,
- absl::optional<std::pair<IDType, base::span<const uint8_t>>> id) {
- CurrentFilter* const current_filter = GetCurrentFilter();
- if (!current_filter->steps) {
- return Action::ALLOW;
- }
- absl::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
|