blob: 38fdbdccefbd10046347735c97011e6ef298f1e1 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quick_start_decoder.h"
#include <optional>
#include "base/base64.h"
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_tree.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/values.h"
#include "chromeos/ash/components/quick_start/quick_start_message.h"
#include "chromeos/ash/components/quick_start/quick_start_message_type.h"
#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-forward.h"
#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-shared.h"
#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "quick_start_conversions.h"
namespace ash::quick_start {
namespace {
using CBOR = cbor::Value;
using GetAssertionStatus = mojom::GetAssertionResponse::GetAssertionStatus;
constexpr char kCredentialIdKey[] = "id";
constexpr char kEntitiyIdMapKey[] = "id";
constexpr char kDeviceDetailsKey[] = "deviceDetails";
constexpr char kCryptauthDeviceIdKey[] = "cryptauthDeviceId";
constexpr uint8_t kCtapDeviceResponseSuccess = 0x00;
constexpr int kCborDecoderNoError = 0;
constexpr int kCborDecoderUnknownError = 14;
constexpr uint8_t kCtap2ErrInvalidCBOR = 0x12;
constexpr char kFidoMessageKey[] = "fidoMessage";
// Key in Wifi Information response containing information about the wifi
// network as a JSON Dictionary.
constexpr char kWifiNetworkInformationKey[] = "wifi_network";
// Key in wifi_network dictionary containing the SSID of the wifi network.
constexpr char kWifiNetworkSsidKey[] = "wifi_ssid";
// Key in wifi_network dictionary containing the password of the wifi network.
constexpr char kWifiNetworkPasswordKey[] = "wifi_pre_shared_key";
// Key in wifi_network dictionary containing the security type of the wifi
// network.
constexpr char kWifiNetworkSecurityTypeKey[] = "wifi_security_type";
// Key in wifi_network dictionary containing if the wifi network is hidden.
constexpr char kWifiNetworkIsHiddenKey[] = "wifi_hidden_ssid";
// Key in Notify Source of Update response containing bool acknowledging the
// message.
constexpr char kNotifySourceOfUpdateAckKey[] = "forced_update_acknowledged";
// Key in UserVerificationResult containing the result
constexpr char kUserVerificationResultKey[] = "user_verification_result";
// Key in UserVerificationResult indicating if this is the first user
// verification
constexpr char kIsFirstUserVerificationKey[] = "is_first_user_verification";
// Key in UserVerificationRequested indicating if user verification was
// requested
constexpr char kAwaitingUserVerificationKey[] = "await_user_verification";
std::pair<int, absl::optional<cbor::Value>> CborDecodeGetAssertionResponse(
base::span<const uint8_t> response) {
cbor::Reader::DecoderError error;
cbor::Reader::Config config;
config.error_code_out = &error;
absl::optional<cbor::Value> cbor = cbor::Reader::Read(response, config);
if (!cbor) {
int converted_decode_error = static_cast<int>(error);
LOG(ERROR) << "Error CBOR decoding the response bytes: "
<< cbor::Reader::ErrorCodeToString(error);
return std::make_pair(converted_decode_error, absl::nullopt);
}
return std::make_pair(kCborDecoderNoError, std::move(cbor));
}
mojom::GetAssertionResponsePtr ParseGetAssertionResponse(
cbor::Value decoded_response) {
const cbor::Value::MapValue& response_map = decoded_response.GetMap();
// According to FIDO CTAP2 GetAssertionResponse, credential is stored at CBOR
// index 0x01.
auto credential_value_it = response_map.find(CBOR(0x01));
std::string credential_id;
if (credential_value_it != response_map.end() &&
credential_value_it->second.is_map()) {
const cbor::Value::MapValue& credential_value_map =
credential_value_it->second.GetMap();
auto cid = credential_value_map.find(cbor::Value(kCredentialIdKey));
if (cid != credential_value_map.end() && cid->second.is_bytestring()) {
credential_id = std::string(cid->second.GetBytestringAsString());
}
}
// According to FIDO CTAP2 GetAssertionResponse, authData is stored at CBOR
// index 0x02.
auto auth_data_value_it = response_map.find(CBOR(0x02));
std::vector<uint8_t> auth_data;
if (auth_data_value_it != response_map.end() &&
auth_data_value_it->second.is_bytestring()) {
auth_data = auth_data_value_it->second.GetBytestring();
}
// According to FIDO CTAP2 GetAssertionResponse, signature is stored at CBOR
// index 0x03.
auto signature_value_it = response_map.find(CBOR(0x03));
std::vector<uint8_t> signature;
if (signature_value_it != response_map.end() &&
signature_value_it->second.is_bytestring()) {
signature = signature_value_it->second.GetBytestring();
}
// According to FIDO CTAP2 GetAssertionResponse, user is stored at CBOR index
// 0x04.
auto user_value_it = response_map.find(CBOR(0x04));
std::string email;
if (user_value_it != response_map.end() && user_value_it->second.is_map()) {
const cbor::Value::MapValue& user_value_map =
user_value_it->second.GetMap();
auto uid = user_value_map.find(cbor::Value(kEntitiyIdMapKey));
if (uid != user_value_map.end() && uid->second.is_bytestring()) {
email = std::string(uid->second.GetBytestringAsString());
}
}
return mojom::GetAssertionResponse::New(
/*status=*/GetAssertionStatus::kSuccess,
/*ctap_device_response_code=*/kCtapDeviceResponseSuccess,
/*cbor_decoder_error=*/kCborDecoderNoError, email, credential_id,
auth_data, signature);
}
mojom::GetAssertionResponsePtr BuildGetAssertionResponseError(
GetAssertionStatus status,
uint8_t ctap_device_response_code,
int cbor_decoder_error) {
return mojom::GetAssertionResponse::New(status, ctap_device_response_code,
cbor_decoder_error,
/*email=*/"", /*credential_id=*/"",
/*auth_data=*/std::vector<uint8_t>{},
/*signature=*/std::vector<uint8_t>{});
}
} // namespace
QuickStartDecoder::QuickStartDecoder(
mojo::PendingReceiver<mojom::QuickStartDecoder> receiver,
base::OnceClosure on_disconnect)
: receiver_(this, std::move(receiver)) {
receiver_.set_disconnect_handler(std::move(on_disconnect));
}
QuickStartDecoder::~QuickStartDecoder() = default;
mojom::GetAssertionResponsePtr QuickStartDecoder::DoDecodeGetAssertionResponse(
const std::vector<uint8_t>& data) {
absl::optional<std::vector<uint8_t>> parsed_response_bytes =
ExtractFidoDataFromJsonResponse(data);
if (!parsed_response_bytes.has_value()) {
LOG(ERROR) << "Failed to extract Fido data from JSON response.";
return BuildGetAssertionResponseError(
GetAssertionStatus::kMessagePayloadParseError, kCtap2ErrInvalidCBOR,
kCborDecoderUnknownError);
}
std::vector<unsigned char>& response_bytes = parsed_response_bytes.value();
if (response_bytes.size() < 2) {
LOG(ERROR) << "GetAssertionResponse requires a status code byte and "
"response bytes. Data in size: "
<< response_bytes.size();
return BuildGetAssertionResponseError(
GetAssertionStatus::kCtapResponseError, kCtap2ErrInvalidCBOR,
kCborDecoderUnknownError);
}
uint8_t ctap_status = response_bytes[0];
base::span<const uint8_t> cbor_bytes(response_bytes);
cbor_bytes = cbor_bytes.subspan(1);
if (ctap_status != kCtapDeviceResponseSuccess) {
LOG(ERROR) << "Ctap Device Response Status Code is not Success(0x00). Got: "
<< ctap_status;
return BuildGetAssertionResponseError(
GetAssertionStatus::kCtapResponseError, ctap_status,
kCborDecoderUnknownError);
}
std::pair<int, absl::optional<cbor::Value>> decoded_values =
CborDecodeGetAssertionResponse(cbor_bytes);
if (decoded_values.first != kCborDecoderNoError) {
return BuildGetAssertionResponseError(GetAssertionStatus::kCborDecoderError,
ctap_status, decoded_values.first);
}
if (!decoded_values.second || !decoded_values.second->is_map()) {
LOG(ERROR) << "The CBOR decoded response values needs to be a valid CBOR "
"Value Map.";
return BuildGetAssertionResponseError(GetAssertionStatus::kUnknownError,
ctap_status, decoded_values.first);
}
return ParseGetAssertionResponse(std::move(decoded_values.second.value()));
}
void QuickStartDecoder::DoDecodeBootstrapConfigurations(
const std::vector<uint8_t>& data,
DecodeBootstrapConfigurationsCallback callback) {
std::unique_ptr<QuickStartMessage> message = QuickStartMessage::ReadMessage(
data, QuickStartMessageType::kBootstrapConfigurations);
base::Value::Dict* device_details =
message->GetPayload()->FindDict(kDeviceDetailsKey);
if (!device_details) {
LOG(ERROR)
<< "DeviceDetails cannot be found within BootstrapConfigurations.";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::string* cryptauth_device_id_ptr =
device_details->FindString(kCryptauthDeviceIdKey);
if (!cryptauth_device_id_ptr) {
LOG(WARNING)
<< "CryptauthDeviceId for the Android Device could not be found.";
std::move(callback).Run(
mojom::BootstrapConfigurations::New(/*cryptauth_device_id=*/""),
absl::nullopt);
return;
}
std::move(callback).Run(
mojom::BootstrapConfigurations::New(*cryptauth_device_id_ptr),
absl::nullopt);
}
void QuickStartDecoder::DecodeBootstrapConfigurations(
const std::vector<uint8_t>& data,
DecodeBootstrapConfigurationsCallback callback) {
DoDecodeBootstrapConfigurations(data, std::move(callback));
}
void QuickStartDecoder::DecodeWifiCredentialsResponse(
const std::vector<uint8_t>& data,
DecodeWifiCredentialsResponseCallback callback) {
DoDecodeWifiCredentialsResponse(data, std::move(callback));
}
void QuickStartDecoder::DecodeUserVerificationRequested(
const std::vector<uint8_t>& data,
DecodeUserVerificationRequestedCallback callback) {
std::unique_ptr<ash::quick_start::QuickStartMessage> message =
QuickStartMessage::ReadMessage(data,
QuickStartMessageType::kQuickStartPayload);
if (!message) {
LOG(ERROR)
<< "Failed to read UserVerificationRequested as QuickStartMessage";
std::move(callback).Run(nullptr,
mojom::QuickStartDecoderError::kUnableToReadAsJSON);
return;
}
absl::optional<bool> is_awaiting_user_verification =
message->GetPayload()->FindBool(kAwaitingUserVerificationKey);
if (!is_awaiting_user_verification.has_value()) {
LOG(ERROR) << "UserVerificationRequested message does not include "
"await_user_verification";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::move(callback).Run(mojom::UserVerificationRequested::New(
is_awaiting_user_verification.value()),
absl::nullopt);
}
void QuickStartDecoder::DecodeUserVerificationResult(
const std::vector<uint8_t>& data,
DecodeUserVerificationResultCallback callback) {
std::unique_ptr<ash::quick_start::QuickStartMessage> message =
QuickStartMessage::ReadMessage(data,
QuickStartMessageType::kQuickStartPayload);
if (!message) {
LOG(ERROR) << "Failed to read UserVerificationResult as QuickStartMessage";
std::move(callback).Run(nullptr,
mojom::QuickStartDecoderError::kUnableToReadAsJSON);
return;
}
absl::optional<int> user_verification_result_code =
message->GetPayload()->FindInt(kUserVerificationResultKey);
if (!user_verification_result_code.has_value()) {
LOG(ERROR) << "User Verification Result was not include in verification "
"result message";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
mojom::UserVerificationResult user_verification_result =
static_cast<mojom::UserVerificationResult>(
user_verification_result_code.value());
if (!mojom::IsKnownEnumValue(user_verification_result)) {
LOG(ERROR) << "User Verification Result is an unknown status code";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
absl::optional<bool> is_first_user_verification =
message->GetPayload()->FindBool(kIsFirstUserVerificationKey);
if (!is_first_user_verification.has_value()) {
LOG(ERROR) << "Message does not contain key is_first_user_verification";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::move(callback).Run(
mojom::UserVerificationResponse::New(user_verification_result,
is_first_user_verification.value()),
absl::nullopt);
}
void QuickStartDecoder::DoDecodeWifiCredentialsResponse(
const std::vector<uint8_t>& data,
DecodeWifiCredentialsResponseCallback callback) {
std::unique_ptr<ash::quick_start::QuickStartMessage> message =
QuickStartMessage::ReadMessage(data,
QuickStartMessageType::kQuickStartPayload);
if (!message) {
LOG(ERROR) << "Message cannot be parsed as a JSON Dictionary.";
std::move(callback).Run(nullptr,
mojom::QuickStartDecoderError::kUnableToReadAsJSON);
return;
}
base::Value::Dict* wifi_network_information =
message->GetPayload()->FindDict(kWifiNetworkInformationKey);
if (!wifi_network_information) {
LOG(ERROR) << "Wifi Network information not present in payload";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::string* ssid = wifi_network_information->FindString(kWifiNetworkSsidKey);
if (!ssid) {
LOG(ERROR) << "SSID cannot be found within WifiCredentialsResponse.";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
if (ssid->length() == 0) {
LOG(ERROR) << "SSID has a length of 0.";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::string* security_type_string =
wifi_network_information->FindString(kWifiNetworkSecurityTypeKey);
if (!security_type_string) {
LOG(ERROR)
<< "Security Type cannot be found within WifiCredentialsResponse";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
absl::optional<mojom::WifiSecurityType> maybe_security_type =
WifiSecurityTypeFromString(*security_type_string);
if (!maybe_security_type.has_value()) {
{
LOG(ERROR) << "Security type was not a valid value.";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
}
mojom::WifiSecurityType security_type = maybe_security_type.value();
// Password may not be included in payload for passwordless, open networks.
absl::optional<std::string> password = absl::nullopt;
std::string* password_ptr =
wifi_network_information->FindString(kWifiNetworkPasswordKey);
if (password_ptr && security_type == mojom::WifiSecurityType::kOpen) {
LOG(ERROR) << "Password is found but network security type is open.";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
if (!password_ptr && security_type != mojom::WifiSecurityType::kOpen) {
LOG(ERROR) << "Password cannot be found within WifiCredentialsResponse but "
"network is not open. wifi_security_type: "
<< security_type;
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
if (password_ptr) {
password = *password_ptr;
}
absl::optional<bool> is_hidden =
wifi_network_information->FindBool(kWifiNetworkIsHiddenKey);
if (!is_hidden.has_value()) {
LOG(ERROR)
<< "Wifi Hide Status cannot be found within WifiCredentialsResponse";
std::move(callback).Run(
nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
return;
}
std::move(callback).Run(
mojom::WifiCredentials::New(*ssid, security_type, is_hidden.value(),
password),
absl::nullopt);
}
void QuickStartDecoder::DecodeGetAssertionResponse(
const std::vector<uint8_t>& data,
DecodeGetAssertionResponseCallback callback) {
std::move(callback).Run(DoDecodeGetAssertionResponse(data));
}
absl::optional<std::vector<uint8_t>>
QuickStartDecoder::ExtractFidoDataFromJsonResponse(
const std::vector<uint8_t>& data) {
std::unique_ptr<ash::quick_start::QuickStartMessage> parsed_message =
ash::quick_start::QuickStartMessage::ReadMessage(
data, QuickStartMessageType::kSecondDeviceAuthPayload);
if (!parsed_message) {
LOG(ERROR) << "MessagePayload cannot be parsed as a JSON Dictionary.";
return absl::nullopt;
}
base::Value::Dict* second_device_auth_payload = parsed_message->GetPayload();
if (!second_device_auth_payload) {
LOG(ERROR) << "secondDeviceAuthPayload cannot be found within Message.";
return absl::nullopt;
}
std::string* fido_message =
second_device_auth_payload->FindString(kFidoMessageKey);
if (!fido_message) {
LOG(ERROR) << "fidoMessage cannot be found within secondDeviceAuthPayload.";
return absl::nullopt;
}
return base::Base64Decode(*fido_message);
}
void QuickStartDecoder::DecodeNotifySourceOfUpdateResponse(
const std::vector<uint8_t>& data,
DecodeNotifySourceOfUpdateResponseCallback callback) {
std::move(callback).Run(DoDecodeNotifySourceOfUpdateResponse(data));
}
absl::optional<bool> QuickStartDecoder::DoDecodeNotifySourceOfUpdateResponse(
const std::vector<uint8_t>& data) {
std::unique_ptr<ash::quick_start::QuickStartMessage> message =
QuickStartMessage::ReadMessage(data,
QuickStartMessageType::kQuickStartPayload);
if (!message) {
LOG(ERROR) << "Notify Source of Update message cannot be parsed as a JSON "
"Dictionary.";
return absl::nullopt;
}
return message->GetPayload()->FindBool(kNotifySourceOfUpdateAckKey);
}
} // namespace ash::quick_start