#include "device/fido/device_response_converter.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/i18n/streaming_utf8_validator.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "components/cbor/diagnostic_writer.h"
#include "components/cbor/reader.h"
#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/opaque_attestation_statement.h"
namespace device {
namespace {
constexpr size_t kResponseCodeLength = 1;
ProtocolVersion ConvertStringToProtocolVersion(std::string_view version) {
if (version == kCtap2Version || version == kCtap2_1Version)
return ProtocolVersion::kCtap2;
if (version == kU2fVersion)
return ProtocolVersion::kU2f;
return ProtocolVersion::kUnknown;
}
std::optional<Ctap2Version> ConvertStringToCtap2Version(
std::string_view version) {
if (version == kCtap2Version)
return Ctap2Version::kCtap2_0;
if (version == kCtap2_1Version)
return Ctap2Version::kCtap2_1;
return std::nullopt;
}
std::optional<std::vector<uint8_t>> GetPRFOutputs(
const cbor::Value& results_value) {
if (!results_value.is_map()) {
return std::nullopt;
}
const cbor::Value::MapValue& results = results_value.GetMap();
auto first = results.find(cbor::Value(kExtensionPRFFirst));
if (first == results.end() || !first->second.is_bytestring()) {
return std::nullopt;
}
std::vector<uint8_t> output = first->second.GetBytestring();
if (output.size() != kExtensionPRFOutputSize) {
return std::nullopt;
}
auto second = results.find(cbor::Value(kExtensionPRFSecond));
if (second != results.end()) {
if (!second->second.is_bytestring()) {
return std::nullopt;
}
const std::vector<uint8_t>& second_bytes = second->second.GetBytestring();
if (second_bytes.size() != kExtensionPRFOutputSize) {
return std::nullopt;
}
output.insert(output.end(), second_bytes.begin(), second_bytes.end());
}
return output;
}
}
using CBOR = cbor::Value;
CtapDeviceResponseCode GetResponseCode(base::span<const uint8_t> buffer) {
if (buffer.empty())
return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
return kCtapResponseCodeList.contains(buffer[0])
? static_cast<CtapDeviceResponseCode>(buffer[0])
: CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
}
std::optional<AuthenticatorMakeCredentialResponse>
ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used,
const std::optional<cbor::Value>& cbor) {
if (!cbor || !cbor->is_map())
return std::nullopt;
const auto& decoded_map = cbor->GetMap();
auto it = decoded_map.find(CBOR(0x01));
if (it == decoded_map.end() || !it->second.is_string())
return std::nullopt;
auto format = it->second.GetString();
it = decoded_map.find(CBOR(0x02));
if (it == decoded_map.end() || !it->second.is_bytestring())
return std::nullopt;
auto authenticator_data =
AuthenticatorData::DecodeAuthenticatorData(it->second.GetBytestring());
if (!authenticator_data)
return std::nullopt;
it = decoded_map.find(CBOR(0x03));
if (it == decoded_map.end() || !it->second.is_map())
return std::nullopt;
AuthenticatorMakeCredentialResponse response(
transport_used,
AttestationObject(std::move(*authenticator_data),
std::make_unique<OpaqueAttestationStatement>(
format, it->second.Clone())));
it = decoded_map.find(CBOR(0x04));
if (it != decoded_map.end()) {
if (!it->second.is_bool()) {
return std::nullopt;
}
response.enterprise_attestation_returned = it->second.GetBool();
}
it = decoded_map.find(CBOR(0x05));
if (it != decoded_map.end()) {
if (!it->second.is_bytestring() ||
it->second.GetBytestring().size() != kLargeBlobKeyLength) {
return std::nullopt;
}
response.large_blob_type = LargeBlobSupportType::kKey;
}
it = decoded_map.find(CBOR(0x06));
if (it != decoded_map.end()) {
if (!it->second.is_map()) {
return std::nullopt;
}
const auto& unsigned_extension_outputs_map = it->second.GetMap();
for (const auto& map_it : unsigned_extension_outputs_map) {
if (!map_it.first.is_string()) {
return std::nullopt;
}
const std::string& extension_name = map_it.first.GetString();
if (extension_name == kExtensionPRF) {
if (!map_it.second.is_map()) {
return std::nullopt;
}
const cbor::Value::MapValue& prf = map_it.second.GetMap();
const auto enabled_it = prf.find(cbor::Value(kExtensionPRFEnabled));
if (enabled_it != prf.end()) {
if (!enabled_it->second.is_bool()) {
return std::nullopt;
}
response.prf_enabled = enabled_it->second.GetBool();
}
auto results_it = prf.find(cbor::Value(kExtensionPRFResults));
if (results_it != prf.end()) {
response.prf_results = GetPRFOutputs(results_it->second);
if (!response.prf_results) {
return std::nullopt;
}
}
} else if (extension_name == kExtensionLargeBlob) {
if (response.large_blob_type || !map_it.second.is_map()) {
return std::nullopt;
}
const cbor::Value::MapValue& large_blob_ext = map_it.second.GetMap();
const auto supported_it = large_blob_ext.find(
cbor::Value(device::kExtensionLargeBlobSupported));
if (supported_it != large_blob_ext.end()) {
if (!supported_it->second.is_bool()) {
return std::nullopt;
}
if (supported_it->second.GetBool()) {
response.large_blob_type = LargeBlobSupportType::kExtension;
}
}
}
}
}
return response;
}
std::optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
FidoTransportProtocol transport_used,
const std::optional<cbor::Value>& cbor) {
if (!cbor || !cbor->is_map())
return std::nullopt;
auto& response_map = cbor->GetMap();
auto it = response_map.find(CBOR(0x02));
if (it == response_map.end() || !it->second.is_bytestring())
return std::nullopt;
auto auth_data =
AuthenticatorData::DecodeAuthenticatorData(it->second.GetBytestring());
if (!auth_data)
return std::nullopt;
it = response_map.find(CBOR(0x03));
if (it == response_map.end() || !it->second.is_bytestring())
return std::nullopt;
auto signature = it->second.GetBytestring();
AuthenticatorGetAssertionResponse response(
std::move(*auth_data), std::move(signature), transport_used);
it = response_map.find(CBOR(0x01));
if (it != response_map.end()) {
auto credential =
PublicKeyCredentialDescriptor::CreateFromCBORValue(it->second);
if (!credential)
return std::nullopt;
response.credential = std::move(*credential);
}
it = response_map.find(CBOR(0x04));
if (it != response_map.end()) {
auto user = PublicKeyCredentialUserEntity::CreateFromCBORValue(it->second);
if (!user)
return std::nullopt;
response.user_entity = std::move(*user);
}
it = response_map.find(CBOR(0x05));
if (it != response_map.end()) {
if (!it->second.is_unsigned())
return std::nullopt;
response.num_credentials = it->second.GetUnsigned();
}
it = response_map.find(CBOR(0x06));
if (it != response_map.end()) {
if (!it->second.is_bool() || response.num_credentials.has_value()) {
return std::nullopt;
}
response.user_selected = it->second.GetBool();
}
it = response_map.find(CBOR(0x07));
if (it != response_map.end()) {
if (!it->second.is_bytestring()) {
return std::nullopt;
}
const std::vector<uint8_t>& key = it->second.GetBytestring();
response.large_blob_key.emplace();
if (key.size() != response.large_blob_key->size()) {
return std::nullopt;
}
UNSAFE_TODO(memcpy(response.large_blob_key->data(), key.data(),
response.large_blob_key->size()));
}
it = response_map.find(CBOR(0x08));
if (it != response_map.end()) {
if (!it->second.is_map()) {
return std::nullopt;
}
const auto& unsigned_extension_outputs_map = it->second.GetMap();
for (const auto& map_it : unsigned_extension_outputs_map) {
if (!map_it.first.is_string()) {
return std::nullopt;
}
const std::string& extension_name = map_it.first.GetString();
if (extension_name == kExtensionPRF) {
if (!map_it.second.is_map()) {
return std::nullopt;
}
const cbor::Value::MapValue& prf = map_it.second.GetMap();
auto results_it = prf.find(cbor::Value(kExtensionPRFResults));
if (results_it != prf.end()) {
response.hmac_secret = GetPRFOutputs(results_it->second);
if (!response.hmac_secret) {
return std::nullopt;
}
}
} else if (extension_name == kExtensionLargeBlob) {
if (response.large_blob_key || !map_it.second.is_map()) {
return std::nullopt;
}
const cbor::Value::MapValue& large_blob_ext = map_it.second.GetMap();
const auto written_it =
large_blob_ext.find(cbor::Value(kExtensionLargeBlobWritten));
const bool has_written = written_it != large_blob_ext.end();
const auto blob_it =
large_blob_ext.find(cbor::Value(kExtensionLargeBlobBlob));
const bool has_blob = blob_it != large_blob_ext.end();
const auto original_size_it =
large_blob_ext.find(cbor::Value(kExtensionLargeBlobOriginalSize));
const bool has_original_size = original_size_it != large_blob_ext.end();
if ((has_written && !written_it->second.is_bool()) ||
(has_blob && !blob_it->second.is_bytestring()) ||
(has_original_size && !original_size_it->second.is_unsigned())) {
return std::nullopt;
}
if (has_written && !has_blob && !has_original_size) {
response.large_blob_written = written_it->second.GetBool();
} else if (!has_written && has_blob && has_original_size) {
response.large_blob_extension.emplace(
blob_it->second.GetBytestring(),
base::checked_cast<size_t>(
original_size_it->second.GetUnsigned()));
} else {
return std::nullopt;
}
}
}
}
return response;
}
std::optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
base::span<const uint8_t> buffer) {
if (buffer.size() <= kResponseCodeLength) {
FIDO_LOG(ERROR) << "-> (GetInfo response too short: " << buffer.size()
<< " bytes)";
return std::nullopt;
}
if (GetResponseCode(buffer) != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(DEBUG) << "-> (GetInfo CTAP2 error code " << +buffer[0] << ")";
return std::nullopt;
}
cbor::Reader::DecoderError error;
std::optional<CBOR> decoded_response =
cbor::Reader::Read(buffer.subspan<1>(), &error);
if (!decoded_response) {
FIDO_LOG(ERROR) << "-> (CBOR parse error from GetInfo response '"
<< cbor::Reader::ErrorCodeToString(error)
<< "' from raw message " << base::HexEncode(buffer) << ")";
return std::nullopt;
}
if (!decoded_response->is_map())
return std::nullopt;
FIDO_LOG(DEBUG) << "-> " << cbor::DiagnosticWriter::Write(*decoded_response);
const auto& response_map = decoded_response->GetMap();
auto it = response_map.find(CBOR(0x01));
if (it == response_map.end() || !it->second.is_array()) {
return std::nullopt;
}
base::flat_set<ProtocolVersion> protocol_versions;
base::flat_set<Ctap2Version> ctap2_versions;
base::flat_set<std::string_view> advertised_protocols;
for (const auto& version : it->second.GetArray()) {
if (!version.is_string())
return std::nullopt;
const std::string& version_string = version.GetString();
if (!advertised_protocols.insert(version_string).second) {
return std::nullopt;
}
ProtocolVersion protocol = ConvertStringToProtocolVersion(version_string);
if (protocol == ProtocolVersion::kUnknown) {
FIDO_LOG(DEBUG) << "Unexpected protocol version received.";
continue;
}
if (protocol == ProtocolVersion::kCtap2) {
std::optional<Ctap2Version> ctap2_version =
ConvertStringToCtap2Version(version_string);
if (ctap2_version) {
ctap2_versions.insert(*ctap2_version);
}
}
protocol_versions.insert(protocol);
}
if (protocol_versions.empty() ||
(base::Contains(protocol_versions, ProtocolVersion::kCtap2) &&
ctap2_versions.empty())) {
return std::nullopt;
}
it = response_map.find(CBOR(0x03));
if (it == response_map.end() || !it->second.is_bytestring()) {
return std::nullopt;
}
auto aaguid_span =
base::span(it->second.GetBytestring()).to_fixed_extent<kAaguidLength>();
if (!aaguid_span) {
return std::nullopt;
}
AuthenticatorGetInfoResponse response(std::move(protocol_versions),
ctap2_versions, *aaguid_span);
bool cred_blob_extension_seen = false;
bool large_blob_key_extension_seen = false;
AuthenticatorSupportedOptions options;
it = response_map.find(CBOR(0x02));
if (it != response_map.end()) {
if (!it->second.is_array())
return std::nullopt;
std::vector<std::string> extensions;
for (const auto& extension : it->second.GetArray()) {
if (!extension.is_string())
return std::nullopt;
const std::string& extension_str = extension.GetString();
if (extension_str == kExtensionCredProtect) {
options.supports_cred_protect = true;
} else if (extension_str == kExtensionCredBlob) {
cred_blob_extension_seen = true;
} else if (extension_str == kExtensionMinPINLength) {
options.supports_min_pin_length_extension = true;
} else if (extension_str == kExtensionHmacSecret) {
options.supports_hmac_secret = true;
} else if (extension_str == kExtensionPRF) {
options.supports_prf = true;
} else if (extension_str == kExtensionLargeBlob) {
options.large_blob_type = LargeBlobSupportType::kExtension;
} else if (extension_str == kExtensionLargeBlobKey) {
large_blob_key_extension_seen = true;
}
extensions.push_back(extension_str);
}
response.extensions = std::move(extensions);
}
if (cred_blob_extension_seen && !options.supports_cred_protect) {
return std::nullopt;
}
it = response_map.find(CBOR(0x04));
if (it != response_map.end()) {
if (!it->second.is_map())
return std::nullopt;
const auto& option_map = it->second.GetMap();
auto option_map_it = option_map.find(CBOR(kPlatformDeviceMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool())
return std::nullopt;
options.is_platform_device =
option_map_it->second.GetBool()
? AuthenticatorSupportedOptions::PlatformDevice::kYes
: AuthenticatorSupportedOptions::PlatformDevice::kNo;
}
option_map_it = option_map.find(CBOR(kResidentKeyMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool())
return std::nullopt;
options.supports_resident_key = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kUserPresenceMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool())
return std::nullopt;
options.supports_user_presence = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kUserVerificationMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool())
return std::nullopt;
if (option_map_it->second.GetBool()) {
options.user_verification_availability = AuthenticatorSupportedOptions::
UserVerificationAvailability::kSupportedAndConfigured;
} else {
options.user_verification_availability = AuthenticatorSupportedOptions::
UserVerificationAvailability::kSupportedButNotConfigured;
}
}
option_map_it = option_map.find(CBOR(kClientPinMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool())
return std::nullopt;
if (option_map_it->second.GetBool()) {
options.client_pin_availability = AuthenticatorSupportedOptions::
ClientPinAvailability::kSupportedAndPinSet;
} else {
options.client_pin_availability = AuthenticatorSupportedOptions::
ClientPinAvailability::kSupportedButPinNotSet;
}
}
option_map_it = option_map.find(CBOR(kCredentialManagementMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.supports_credential_management = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kCredentialManagementPreviewMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.supports_credential_management_preview =
option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kBioEnrollmentMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
using Availability =
AuthenticatorSupportedOptions::BioEnrollmentAvailability;
options.bio_enrollment_availability =
option_map_it->second.GetBool()
? Availability::kSupportedAndProvisioned
: Availability::kSupportedButUnprovisioned;
}
option_map_it = option_map.find(CBOR(kBioEnrollmentPreviewMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
using Availability =
AuthenticatorSupportedOptions::BioEnrollmentAvailability;
options.bio_enrollment_availability_preview =
option_map_it->second.GetBool()
? Availability::kSupportedAndProvisioned
: Availability::kSupportedButUnprovisioned;
}
option_map_it = option_map.find(CBOR(kPinUvTokenMapKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.supports_pin_uv_auth_token = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kDefaultCredProtectKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_unsigned()) {
return std::nullopt;
}
const int64_t value = option_map_it->second.GetInteger();
if (value != static_cast<uint8_t>(CredProtect::kUVOrCredIDRequired) &&
value != static_cast<uint8_t>(CredProtect::kUVRequired)) {
return std::nullopt;
}
options.default_cred_protect = static_cast<CredProtect>(value);
}
option_map_it = option_map.find(CBOR(kEnterpriseAttestationKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.enterprise_attestation = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kLargeBlobsKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool() || !options.supports_resident_key) {
return std::nullopt;
}
if (option_map_it->second.GetBool() & large_blob_key_extension_seen) {
if (options.large_blob_type) {
return std::nullopt;
}
options.large_blob_type = LargeBlobSupportType::kKey;
}
}
option_map_it = option_map.find(CBOR(kAlwaysUvKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.always_uv = option_map_it->second.GetBool();
}
option_map_it = option_map.find(CBOR(kMakeCredUvNotRqdKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool()) {
return std::nullopt;
}
options.make_cred_uv_not_required = option_map_it->second.GetBool();
}
response.options = std::move(options);
}
it = response_map.find(CBOR(0x05));
if (it != response_map.end()) {
if (!it->second.is_unsigned())
return std::nullopt;
response.max_msg_size =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
it = response_map.find(CBOR(0x06));
if (it != response_map.end()) {
if (!it->second.is_array())
return std::nullopt;
base::flat_set<PINUVAuthProtocol> pin_protocols;
for (const auto& protocol : it->second.GetArray()) {
if (!protocol.is_unsigned()) {
return std::nullopt;
}
std::optional<PINUVAuthProtocol> pin_protocol =
ToPINUVAuthProtocol(protocol.GetUnsigned());
if (!pin_protocol) {
continue;
}
pin_protocols.insert(*pin_protocol);
}
response.pin_protocols = std::move(pin_protocols);
}
if (response.options.supports_pin_uv_auth_token ||
response.options.client_pin_availability !=
AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported) {
if (!response.pin_protocols) {
return std::nullopt;
}
if (response.pin_protocols->empty()) {
FIDO_LOG(ERROR) << "No supported PIN/UV Auth Protocol";
response.options.supports_pin_uv_auth_token = false;
response.options.client_pin_availability =
AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported;
}
}
it = response_map.find(CBOR(0x07));
if (it != response_map.end()) {
if (!it->second.is_unsigned())
return std::nullopt;
response.max_credential_count_in_list =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
it = response_map.find(CBOR(0x08));
if (it != response_map.end()) {
if (!it->second.is_unsigned())
return std::nullopt;
response.max_credential_id_length =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
it = response_map.find(CBOR(0x09));
if (it != response_map.end()) {
if (!it->second.is_array())
return std::nullopt;
response.transports.emplace();
for (const auto& transport_str : it->second.GetArray()) {
if (!transport_str.is_string())
return std::nullopt;
std::optional<FidoTransportProtocol> maybe_transport(
ConvertToFidoTransportProtocol(transport_str.GetString()));
if (maybe_transport.has_value()) {
response.transports->insert(*maybe_transport);
}
}
}
it = response_map.find(CBOR(0x0a));
if (it != response_map.end()) {
if (!it->second.is_array()) {
return std::nullopt;
}
response.algorithms.emplace();
const std::vector<cbor::Value>& algorithms = it->second.GetArray();
for (const auto& algorithm : algorithms) {
if (!algorithm.is_map()) {
return std::nullopt;
}
const auto& map = algorithm.GetMap();
const auto type_it = map.find(CBOR("type"));
if (type_it == map.end() || !type_it->second.is_string()) {
return std::nullopt;
}
if (type_it->second.GetString() != "public-key") {
continue;
}
const auto alg_it = map.find(CBOR("alg"));
if (alg_it == map.end() || !alg_it->second.is_integer()) {
return std::nullopt;
}
const int64_t alg = alg_it->second.GetInteger();
if (alg < std::numeric_limits<int32_t>::min() ||
alg > std::numeric_limits<int32_t>::max()) {
continue;
}
response.algorithms->push_back(alg);
}
}
it = response_map.find(CBOR(0x0b));
if (it != response_map.end()) {
if (!it->second.is_unsigned()) {
return std::nullopt;
}
response.max_serialized_large_blob_array =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
it = response_map.find(CBOR(0x0c));
if (it != response_map.end()) {
if (!it->second.is_bool()) {
return std::nullopt;
}
response.force_pin_change = it->second.GetBool();
}
it = response_map.find(CBOR(0x0d));
if (it != response_map.end()) {
if (!it->second.is_unsigned()) {
return std::nullopt;
}
response.min_pin_length =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
it = response_map.find(CBOR(0x0f));
if ((it != response_map.end()) != cred_blob_extension_seen) {
return std::nullopt;
}
if (cred_blob_extension_seen) {
if (!it->second.is_unsigned()) {
return std::nullopt;
}
const uint16_t max_cred_blob_length =
base::saturated_cast<uint16_t>(it->second.GetUnsigned());
if (max_cred_blob_length < 32) {
return std::nullopt;
}
response.options.max_cred_blob_length = max_cred_blob_length;
}
it = response_map.find(CBOR(0x14));
if (it != response_map.end()) {
if (!it->second.is_unsigned()) {
return std::nullopt;
}
response.remaining_discoverable_credentials =
base::saturated_cast<uint32_t>(it->second.GetUnsigned());
}
return std::optional<AuthenticatorGetInfoResponse>(std::move(response));
}
static std::optional<std::string> FixInvalidUTF8String(
base::span<const uint8_t> utf8_bytes) {
if (utf8_bytes.size() < 64) {
FIDO_LOG(ERROR) << "Not accepting invalid UTF-8 string because it's only "
<< utf8_bytes.size() << " bytes long";
return std::nullopt;
}
base::StreamingUtf8Validator validator;
base::StreamingUtf8Validator::State state;
size_t longest_valid_prefix_len = 0;
for (size_t i = 0; i < utf8_bytes.size(); i++) {
state = validator.AddBytes(utf8_bytes.subspan(i, 1u));
switch (state) {
case base::StreamingUtf8Validator::VALID_ENDPOINT:
longest_valid_prefix_len = i + 1;
break;
case base::StreamingUtf8Validator::INVALID:
return std::nullopt;
case base::StreamingUtf8Validator::VALID_MIDPOINT:
break;
}
}
switch (state) {
case base::StreamingUtf8Validator::VALID_ENDPOINT:
return std::nullopt;
case base::StreamingUtf8Validator::INVALID:
NOTREACHED();
case base::StreamingUtf8Validator::VALID_MIDPOINT: {
const std::string candidate(
reinterpret_cast<const char*>(utf8_bytes.data()),
longest_valid_prefix_len);
if (base::IsStringUTF8(candidate)) {
return candidate;
}
return std::nullopt;
}
}
}
typedef bool (*PathPredicate)(const std::vector<const cbor::Value*>&);
static std::optional<cbor::Value> FixInvalidUTF8Value(
const cbor::Value& v,
std::vector<const cbor::Value*>* path,
PathPredicate predicate) {
switch (v.type()) {
case cbor::Value::Type::INVALID_UTF8: {
if (!predicate(*path)) {
return std::nullopt;
}
std::optional<std::string> maybe_fixed(
FixInvalidUTF8String(v.GetInvalidUTF8()));
if (!maybe_fixed) {
return std::nullopt;
}
return cbor::Value(*maybe_fixed);
}
case cbor::Value::Type::UNSIGNED:
case cbor::Value::Type::NEGATIVE:
case cbor::Value::Type::BYTE_STRING:
case cbor::Value::Type::STRING:
case cbor::Value::Type::TAG:
case cbor::Value::Type::SIMPLE_VALUE:
case cbor::Value::Type::FLOAT_VALUE:
case cbor::Value::Type::NONE:
return v.Clone();
case cbor::Value::Type::ARRAY: {
const cbor::Value::ArrayValue& old_array = v.GetArray();
cbor::Value::ArrayValue new_array;
new_array.reserve(old_array.size());
for (const auto& child : old_array) {
std::optional<cbor::Value> maybe_fixed =
FixInvalidUTF8Value(child, path, predicate);
if (!maybe_fixed) {
return std::nullopt;
}
new_array.emplace_back(std::move(*maybe_fixed));
}
return cbor::Value(new_array);
}
case cbor::Value::Type::MAP: {
const cbor::Value::MapValue& old_map = v.GetMap();
cbor::Value::MapValue new_map;
new_map.reserve(old_map.size());
for (const auto& it : old_map) {
switch (it.first.type()) {
case cbor::Value::Type::INVALID_UTF8:
return std::nullopt;
case cbor::Value::Type::UNSIGNED:
case cbor::Value::Type::NEGATIVE:
case cbor::Value::Type::STRING:
break;
default:
return std::nullopt;
}
path->push_back(&it.first);
std::optional<cbor::Value> maybe_fixed =
FixInvalidUTF8Value(it.second, path, predicate);
path->pop_back();
if (!maybe_fixed) {
return std::nullopt;
}
new_map.emplace(it.first.Clone(), std::move(*maybe_fixed));
}
return cbor::Value(new_map);
}
}
}
static bool ContainsInvalidUTF8(const cbor::Value& v) {
switch (v.type()) {
case cbor::Value::Type::INVALID_UTF8:
return true;
case cbor::Value::Type::UNSIGNED:
case cbor::Value::Type::NEGATIVE:
case cbor::Value::Type::BYTE_STRING:
case cbor::Value::Type::STRING:
case cbor::Value::Type::TAG:
case cbor::Value::Type::SIMPLE_VALUE:
case cbor::Value::Type::FLOAT_VALUE:
case cbor::Value::Type::NONE:
return false;
case cbor::Value::Type::ARRAY: {
const cbor::Value::ArrayValue& array = v.GetArray();
for (const auto& child : array) {
if (ContainsInvalidUTF8(child)) {
return true;
}
}
return false;
}
case cbor::Value::Type::MAP: {
const cbor::Value::MapValue& map = v.GetMap();
for (const auto& it : map) {
if (ContainsInvalidUTF8(it.first) || ContainsInvalidUTF8(it.second)) {
return true;
}
}
return false;
}
}
}
std::optional<cbor::Value> FixInvalidUTF8(cbor::Value in,
PathPredicate predicate) {
if (!ContainsInvalidUTF8(in)) {
return in;
}
std::vector<const cbor::Value*> path;
return FixInvalidUTF8Value(in, &path, predicate);
}
std::optional<PINUVAuthProtocol> ToPINUVAuthProtocol(int64_t in) {
if (in != static_cast<uint8_t>(PINUVAuthProtocol::kV1) &&
in != static_cast<uint8_t>(PINUVAuthProtocol::kV2)) {
return std::nullopt;
}
return static_cast<PINUVAuthProtocol>(in);
}
cbor::Value RedactCtapGetAssertionResponse(const cbor::Value& cbor) {
using fido_parsing_utils::ToCborVector;
constexpr int kSignature = 0x03;
constexpr int kLargeBlobKey = 0x07;
constexpr int kExtension = 0x08;
return fido_parsing_utils::RedactCbor(
cbor, std::array{ToCborVector(kSignature), ToCborVector(kLargeBlobKey),
ToCborVector(kExtension, kExtensionPRF, "results"),
ToCborVector(kExtension, kExtensionLargeBlob)});
}
}