filter.cc 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "device/fido/filter.h"
  5. #include "base/feature_list.h"
  6. #include "base/json/json_reader.h"
  7. #include "base/metrics/field_trial_params.h"
  8. #include "base/no_destructor.h"
  9. #include "base/strings/pattern.h"
  10. #include "base/strings/string_number_conversions.h"
  11. #include "base/values.h"
  12. #include "components/device_event_log/device_event_log.h"
  13. #include "third_party/abseil-cpp/absl/types/optional.h"
  14. namespace device {
  15. namespace fido_filter {
  16. namespace {
  17. const base::Feature kFilter{"WebAuthenticationFilter",
  18. base::FEATURE_DISABLED_BY_DEFAULT};
  19. const base::FeatureParam<std::string> kFilterJSON{
  20. &kFilter,
  21. "json",
  22. "",
  23. };
  24. struct FilterStep {
  25. absl::optional<std::string> operation;
  26. std::vector<std::string> rp_id;
  27. absl::optional<std::string> device;
  28. absl::optional<std::string> id_type;
  29. std::vector<std::string> id;
  30. absl::optional<size_t> id_min_size;
  31. absl::optional<size_t> id_max_size;
  32. Action action;
  33. };
  34. bool IsString(const base::Value& v) {
  35. return v.is_string();
  36. }
  37. bool IsNonEmptyString(const base::Value& v) {
  38. return v.is_string() && !v.GetString().empty();
  39. }
  40. bool IsListOf(const base::Value* v, bool (*predicate)(const base::Value&)) {
  41. if (!v->is_list()) {
  42. return false;
  43. }
  44. auto contents = v->GetListDeprecated();
  45. return !contents.empty() &&
  46. std::all_of(contents.begin(), contents.end(), predicate);
  47. }
  48. std::vector<std::string> GetStringOrListOfStrings(const base::Value* v) {
  49. if (v->is_string()) {
  50. return {v->GetString()};
  51. }
  52. std::vector<std::string> ret;
  53. for (const auto& elem : v->GetListDeprecated()) {
  54. ret.push_back(elem.GetString());
  55. }
  56. return ret;
  57. }
  58. absl::optional<std::vector<FilterStep>> ParseJSON(base::StringPiece json) {
  59. absl::optional<base::Value> v =
  60. base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS);
  61. if (!v || !v->is_dict()) {
  62. return absl::nullopt;
  63. }
  64. const base::Value* filters = v->FindKey("filters");
  65. if (!filters || !filters->is_list()) {
  66. return absl::nullopt;
  67. }
  68. std::vector<FilterStep> ret;
  69. const auto filter_list = filters->GetListDeprecated();
  70. for (const auto& filter : filter_list) {
  71. if (!filter.is_dict()) {
  72. return absl::nullopt;
  73. }
  74. // These are the keys that are extracted from the JSON:
  75. const base::Value* operation = nullptr;
  76. const base::Value* rp_id = nullptr;
  77. const base::Value* device = nullptr;
  78. const base::Value* id_type = nullptr;
  79. const base::Value* id = nullptr;
  80. const base::Value* id_min_size = nullptr;
  81. const base::Value* id_max_size = nullptr;
  82. const base::Value* action = nullptr;
  83. // DictItems is used so that unknown keys in the dictionary can be rejected.
  84. for (auto pair : filter.DictItems()) {
  85. if (pair.first == "operation") {
  86. operation = &pair.second;
  87. } else if (pair.first == "rp_id") {
  88. rp_id = &pair.second;
  89. } else if (pair.first == "device") {
  90. device = &pair.second;
  91. } else if (pair.first == "id_type") {
  92. id_type = &pair.second;
  93. } else if (pair.first == "id") {
  94. id = &pair.second;
  95. } else if (pair.first == "id_min_size") {
  96. id_min_size = &pair.second;
  97. } else if (pair.first == "id_max_size") {
  98. id_max_size = &pair.second;
  99. } else if (pair.first == "action") {
  100. action = &pair.second;
  101. } else {
  102. // Unknown keys are an error.
  103. return absl::nullopt;
  104. }
  105. }
  106. if (!action || !IsNonEmptyString(*action) ||
  107. (operation && !IsNonEmptyString(*operation)) ||
  108. (rp_id && !IsNonEmptyString(*rp_id) &&
  109. !IsListOf(rp_id, IsNonEmptyString)) ||
  110. (device && !IsNonEmptyString(*device)) ||
  111. (id_type && !IsNonEmptyString(*id_type)) ||
  112. (id && !IsString(*id) && !IsListOf(id, IsString)) ||
  113. (id_min_size && !id_min_size->is_int()) ||
  114. (id_max_size && !id_max_size->is_int())) {
  115. return absl::nullopt;
  116. }
  117. if ((id_min_size || id_max_size || id) && !id_type) {
  118. // If matches on the contents or size of an ID are given then the type
  119. // must also be matched.
  120. return absl::nullopt;
  121. }
  122. if (!rp_id && !device) {
  123. // Filter is too broad. For safety this is disallowed, although one can
  124. // still explicitly use a wildcard.
  125. return absl::nullopt;
  126. }
  127. FilterStep step;
  128. const std::string& action_str = action->GetString();
  129. if (action_str == "allow") {
  130. step.action = Action::ALLOW;
  131. } else if (action_str == "block") {
  132. step.action = Action::BLOCK;
  133. } else if (action_str == "no-attestation") {
  134. step.action = Action::NO_ATTESTATION;
  135. } else {
  136. return absl::nullopt;
  137. }
  138. if (operation) {
  139. step.operation = operation->GetString();
  140. }
  141. if (rp_id) {
  142. step.rp_id = GetStringOrListOfStrings(rp_id);
  143. }
  144. if (device) {
  145. step.device = device->GetString();
  146. }
  147. if (id_type) {
  148. step.id_type = id_type->GetString();
  149. }
  150. if (id) {
  151. step.id = GetStringOrListOfStrings(id);
  152. }
  153. if (id_min_size) {
  154. const int min_size_int = id_min_size->GetInt();
  155. if (min_size_int < 0) {
  156. return absl::nullopt;
  157. }
  158. step.id_min_size = min_size_int;
  159. }
  160. if (id_max_size) {
  161. const int max_size_int = id_max_size->GetInt();
  162. if (max_size_int < 0) {
  163. return absl::nullopt;
  164. }
  165. step.id_max_size = max_size_int;
  166. }
  167. ret.emplace_back(std::move(step));
  168. }
  169. return ret;
  170. }
  171. const char* OperationToString(Operation op) {
  172. switch (op) {
  173. case Operation::MAKE_CREDENTIAL:
  174. return "mc";
  175. case Operation::GET_ASSERTION:
  176. return "ga";
  177. }
  178. }
  179. const char* IDTypeToString(IDType id_type) {
  180. switch (id_type) {
  181. case IDType::CREDENTIAL_ID:
  182. return "cred";
  183. case IDType::USER_ID:
  184. return "user";
  185. }
  186. }
  187. size_t g_testing_depth = 0;
  188. struct CurrentFilter {
  189. absl::optional<std::vector<FilterStep>> steps;
  190. absl::optional<std::string> json;
  191. };
  192. CurrentFilter* GetCurrentFilter() {
  193. static base::NoDestructor<CurrentFilter> current_filter;
  194. return current_filter.get();
  195. }
  196. bool MaybeParseFilter(base::StringPiece json) {
  197. CurrentFilter* const current_filter = GetCurrentFilter();
  198. if (current_filter->json && json == *current_filter->json) {
  199. return true;
  200. }
  201. if (json.size() == 0) {
  202. current_filter->steps.reset();
  203. current_filter->json = "";
  204. return true;
  205. }
  206. current_filter->steps = ParseJSON(json);
  207. if (!current_filter->steps) {
  208. current_filter->json.reset();
  209. return false;
  210. }
  211. current_filter->json = std::string(json);
  212. return true;
  213. }
  214. } // namespace
  215. void MaybeInitialize() {
  216. if (g_testing_depth != 0) {
  217. return;
  218. }
  219. const std::string& json = kFilterJSON.Get();
  220. if (!MaybeParseFilter(json)) {
  221. FIDO_LOG(ERROR) << "Failed to parse filter JSON. Failing open.";
  222. }
  223. }
  224. Action Evaluate(
  225. Operation op,
  226. base::StringPiece rp_id,
  227. absl::optional<base::StringPiece> device,
  228. absl::optional<std::pair<IDType, base::span<const uint8_t>>> id) {
  229. CurrentFilter* const current_filter = GetCurrentFilter();
  230. if (!current_filter->steps) {
  231. return Action::ALLOW;
  232. }
  233. absl::optional<std::string> id_hex;
  234. if (id) {
  235. id_hex = base::HexEncode(id->second);
  236. }
  237. for (const auto& filter : *current_filter->steps) {
  238. if ((!filter.operation ||
  239. base::MatchPattern(OperationToString(op), *filter.operation)) &&
  240. (filter.rp_id.empty() ||
  241. std::any_of(filter.rp_id.begin(), filter.rp_id.end(),
  242. [rp_id](const std::string& pattern) -> bool {
  243. return base::MatchPattern(rp_id, pattern);
  244. })) &&
  245. (!filter.device ||
  246. base::MatchPattern(device.value_or(""), *filter.device)) &&
  247. (!filter.id_type || (id && base::MatchPattern(IDTypeToString(id->first),
  248. *filter.id_type))) &&
  249. (!filter.id_min_size ||
  250. (id && *filter.id_min_size <= id->second.size())) &&
  251. (!filter.id_max_size ||
  252. (id && *filter.id_max_size >= id->second.size())) &&
  253. (filter.id.empty() ||
  254. (id_hex && std::any_of(filter.id.begin(), filter.id.end(),
  255. [&id_hex](const std::string& pattern) -> bool {
  256. return base::MatchPattern(*id_hex, pattern);
  257. })))) {
  258. return filter.action;
  259. }
  260. }
  261. return Action::ALLOW;
  262. }
  263. ScopedFilterForTesting::ScopedFilterForTesting(base::StringPiece json)
  264. : previous_json_(GetCurrentFilter()->json) {
  265. g_testing_depth++;
  266. CHECK(g_testing_depth != 0);
  267. CHECK(MaybeParseFilter(json)) << json;
  268. }
  269. ScopedFilterForTesting::ScopedFilterForTesting(
  270. base::StringPiece json,
  271. ScopedFilterForTesting::PermitInvalidJSON)
  272. : previous_json_(GetCurrentFilter()->json) {
  273. g_testing_depth++;
  274. CHECK(g_testing_depth != 0);
  275. MaybeParseFilter(json);
  276. }
  277. ScopedFilterForTesting::~ScopedFilterForTesting() {
  278. CurrentFilter* const current_filter = GetCurrentFilter();
  279. current_filter->steps.reset();
  280. current_filter->json.reset();
  281. g_testing_depth--;
  282. if (previous_json_) {
  283. CHECK(MaybeParseFilter(*previous_json_));
  284. }
  285. }
  286. bool ParseForTesting(base::StringPiece json) {
  287. CHECK(base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS)) << json;
  288. return MaybeParseFilter(json);
  289. }
  290. } // namespace fido_filter
  291. } // namespace device