// Copyright 2020 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/cbor_extract.h" #include #include "base/callback.h" #include "base/check_op.h" #include "components/cbor/values.h" namespace device { namespace cbor_extract { namespace { using internal::Type; static_assert(sizeof(StepOrByte) == 1, "things should fit into a single byte"); const bool kTrue = true; const bool kFalse = false; constexpr uint8_t CBORTypeToBitfield(const cbor::Value::Type type) { const unsigned type_u = static_cast(type); if (type_u >= 8) { __builtin_unreachable(); } return 1u << type_u; } // ASSERT_TYPE_IS asserts that the type of |a| is |b|. This is used to ensure // that the documented output types for elements of |Type| are correct. #define ASSERT_TYPE_IS(a, b) \ static_assert( \ std::is_same(0))>::value, \ "types need updating"); class Extractor { public: Extractor(base::span outputs, base::span> steps) : outputs_(outputs), steps_(steps) {} bool ValuesFromMap(const cbor::Value::MapValue& map) { for (;;) { const internal::Step step = steps_[step_i_++].step; const Type value_type = static_cast(step.value_type); if (value_type == Type::kStop) { return true; } const cbor::Value::MapValue::const_iterator map_it = map.find(NextKey()); const void** output = nullptr; if (value_type != Type::kMap) { DCHECK_LT(step.output_index, outputs_.size()); output = &outputs_[step.output_index]; } if (map_it == map.end()) { if (step.required) { return false; } if (output) { *output = nullptr; } if (value_type == Type::kMap) { // When skipping an optional map, all the |StepOrByte| for the // elements of the map need to be skipped over. SeekPastNextStop(); } continue; } // kExpectedCBORTypes is an array of bitmaps of acceptable types for each // |Type|. static constexpr uint8_t kExpectedCBORTypes[] = { // kBytestring CBORTypeToBitfield(cbor::Value::Type::BYTE_STRING), // kString CBORTypeToBitfield(cbor::Value::Type::STRING), // kBoolean CBORTypeToBitfield(cbor::Value::Type::SIMPLE_VALUE), // kInt CBORTypeToBitfield(cbor::Value::Type::NEGATIVE) | CBORTypeToBitfield(cbor::Value::Type::UNSIGNED), // kMap CBORTypeToBitfield(cbor::Value::Type::MAP), // kArray CBORTypeToBitfield(cbor::Value::Type::ARRAY), // kValue 0xff, }; const cbor::Value& value = map_it->second; const unsigned cbor_type_u = static_cast(value.type()); const unsigned value_type_u = static_cast(value_type); DCHECK(value_type_u < std::size(kExpectedCBORTypes)); if (cbor_type_u >= 8 || (kExpectedCBORTypes[value_type_u] & (1u << cbor_type_u)) == 0) { return false; } switch (value_type) { case Type::kBytestring: ASSERT_TYPE_IS(value.GetBytestring(), const std::vector); *output = &value.GetBytestring(); break; case Type::kString: ASSERT_TYPE_IS(value.GetString(), const std::string); *output = &value.GetString(); break; case Type::kBoolean: switch (value.GetSimpleValue()) { case cbor::Value::SimpleValue::TRUE_VALUE: *output = &kTrue; break; case cbor::Value::SimpleValue::FALSE_VALUE: *output = &kFalse; break; default: return false; } break; case Type::kInt: ASSERT_TYPE_IS(value.GetInteger(), const int64_t); *output = &value.GetInteger(); break; case Type::kMap: if (!ValuesFromMap(value.GetMap())) { return false; } break; case Type::kArray: ASSERT_TYPE_IS(value.GetArray(), const std::vector); *output = &value.GetArray(); break; case Type::kValue: *output = &value; break; case Type::kStop: return false; } } } private: // SeekPastNextStop increments |step_i_| until just after the next |Stop| // element, taking into account nested maps. void SeekPastNextStop() { for (;;) { const internal::Step step = steps_[step_i_++].step; const Type value_type = static_cast(step.value_type); if (value_type == Type::kStop) { break; } NextKey(); if (value_type == Type::kMap) { SeekPastNextStop(); } else { outputs_[step.output_index] = nullptr; } } } cbor::Value NextKey() { DCHECK_LT(step_i_, steps_.size()); const uint8_t key_or_string_indicator = steps_[step_i_++].u8; if (key_or_string_indicator != StepOrByte::STRING_KEY) { return cbor::Value( static_cast(static_cast(key_or_string_indicator))); } DCHECK_LT(step_i_, steps_.size()); std::string key(&steps_[step_i_].c); step_i_ += key.size() + 1; DCHECK_LE(step_i_, steps_.size()); return cbor::Value(std::move(key)); } base::span outputs_; base::span> steps_; size_t step_i_ = 0; }; } // namespace namespace internal { bool Extract(base::span outputs, base::span> steps, const cbor::Value::MapValue& map) { DCHECK(steps[steps.size() - 1].step.value_type == static_cast(Type::kStop)); Extractor extractor(outputs, steps); return extractor.ValuesFromMap(map); } } // namespace internal bool ForEachPublicKeyEntry( const cbor::Value::ArrayValue& array, const cbor::Value& key, base::RepeatingCallback callback) { const cbor::Value type_key("type"); const std::string public_key("public-key"); for (const cbor::Value& value : array) { if (!value.is_map()) { return false; } const cbor::Value::MapValue& map = value.GetMap(); const auto type_it = map.find(type_key); if (type_it == map.end() || !type_it->second.is_string()) { return false; } if (type_it->second.GetString() != public_key) { continue; } const auto value_it = map.find(key); if (value_it == map.end() || !callback.Run(value_it->second)) { return false; } } return true; } } // namespace cbor_extract } // namespace device