chromium/chromeos/ash/components/quick_start/quick_start_requests.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/quick_start/quick_start_requests.h"

#include "base/base64.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/components/quick_start/types.h"
#include "chromeos/constants/devicetype.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "crypto/sha2.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace ash::quick_start::requests {

namespace {

// bootstrapOptions key telling the phone how to handle
// challenge UI in case of fallback.
constexpr char kFlowTypeKey[] = "flowType";
// bootstrapOptions key telling the phone the number of
// accounts are expected to transfer account to the target device.
constexpr char kAccountRequirementKey[] = "accountRequirement";
// bootstrapOptions key telling the deviceName of target device.
constexpr char kDeviceNameKey[] = "deviceName";
// bootstrapOptions key containing object with phone actions after transfer.
constexpr char kPostTransferActionKey[] = "PostTransferAction";
// bootstrapOptions URI key inside the PostTransferAction object.
constexpr char kPostTransferActionURIKey[] = "uri";
// bootstrapOptions PostTransferAction uri value.
constexpr char kPostTransferActionURIValue[] =
    "intent:#Intent;action=com.google.android.gms.quickstart.LANDING_SCREEN;"
    "package=com.google.android.gms;end";

// Device names
constexpr char kChromebook[] = "Chromebook";
constexpr char kChromebase[] = "Chromebase";
constexpr char kChromebox[] = "Chromebox";

// Base64 encoded CBOR bytes containing the Fido command. This will be used
// for GetInfo and GetAssertion.
static constexpr char FIDO_MESSAGE_KEY[] = "fidoMessage";

// Maps to AccountRequirementSingle enum value for Account Requirement field
// meaning that at least one account is required on the phone. The user will
// select the specified account to transfer.
// Enum Source: go/bootstrap-options-account-requirement-single.
constexpr int kAccountRequirementSingle = 2;

// Maps to FlowTypeTargetChallenge enum value for Flow Type field meaning that
// the fallback challenge will happen on the target device.
// Enum Source: go/bootstrap-options-flow-type-target-challenge.
constexpr int kFlowTypeTargetChallenge = 2;

const char kRelyingPartyId[] = "google.com";
const char kOrigin[] = "https://accounts.google.com";

// Maps to CBOR byte labelling FIDO request as GetAssertion.
const uint8_t kAuthenticatorGetAssertionCommand = 0x02;
const char kUserPresenceMapKey[] = "up";
const char kUserVerificationMapKey[] = "uv";

// Maps to CBOR byte labelling FIDO request as GetInfo.
const uint8_t kAuthenticatorGetInfoCommand = 0x04;

// Boolean in WifiCredentialsRequest indicating we should request WiFi
// Credentials
constexpr char kRequestWifiKey[] = "request_wifi";

// Key in WifiCredentialsRequest and NotifySourceOfUpdateMessage including the
// shared secret to resume the connection if a reboot occurs.
constexpr char kSharedSecretKey[] = "shared_secret";

// Key in WifiCredentialsRequest and NotifySourceOfUpdateMessage for the session
// ID
constexpr char kSessionIdKey[] = "SESSION_ID";

// Boolean in NotifySourceOfUpdateMessage indicating target device requires an
// update.
constexpr char kNotifySourceOfUpdateMessageKey[] = "forced_update_required";

// bootstrapOptions key to inform source of the target device type.
constexpr char kDeviceTypeKey[] = "deviceType";

// Device type should map to ChromeOS device type. See:
// http://google3/java/com/google/android/gmscore/integ/client/smartdevice/src/com/google/android/gms/smartdevice/d2d/DeviceType.java;l=57
constexpr int kDeviceTypeChrome = 7;

// BootstrapState maps to values set here:
// http://google3/java/com/google/android/gmscore/integ/modules/smartdevice/src/com/google/android/gms/smartdevice/d2d/data/BootstrapState.java
constexpr int kBootstrapStateCancel = 1;
constexpr int kBootstrapStateComplete = 2;

// Key in BootstrapState message.
constexpr char kBootstrapStateKey[] = "bootstrapState";

std::string GetDeviceName() {
  switch (chromeos::GetDeviceType()) {
    case chromeos::DeviceType::kChromebook:
      return kChromebook;
    case chromeos::DeviceType::kChromebase:
      return kChromebase;
    case chromeos::DeviceType::kChromebox:
      return kChromebox;
    default:
      return kChromebook;
  }
}

}  // namespace

std::unique_ptr<QuickStartMessage> BuildBootstrapOptionsRequest() {
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kBootstrapOptions);
  message->GetPayload()->Set(kAccountRequirementKey, kAccountRequirementSingle);
  message->GetPayload()->Set(kFlowTypeKey, kFlowTypeTargetChallenge);
  message->GetPayload()->Set(kDeviceTypeKey, kDeviceTypeChrome);
  message->GetPayload()->Set(kDeviceNameKey, GetDeviceName());

  // TODO(b/332603236): Remove postTransferAction payload when new device info
  // exchange is implemented.
  base::Value::Dict post_transfer_action;
  post_transfer_action.Set(kPostTransferActionURIKey,
                           kPostTransferActionURIValue);

  message->GetPayload()->Set(kPostTransferActionKey,
                             std::move(post_transfer_action));
  return message;
}

std::unique_ptr<QuickStartMessage> BuildAssertionRequestMessage(
    std::array<uint8_t, crypto::kSHA256Length> client_data_hash) {
  cbor::Value request = GenerateGetAssertionRequest(client_data_hash);
  std::vector<uint8_t> ctap_request_command =
      CBOREncodeGetAssertionRequest(std::move(request));

  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kSecondDeviceAuthPayload);

  message->GetPayload()->Set(FIDO_MESSAGE_KEY,
                             base::Base64Encode(ctap_request_command));
  return message;
}

std::unique_ptr<QuickStartMessage> BuildGetInfoRequestMessage() {
  std::vector<uint8_t> ctap_request_command({kAuthenticatorGetInfoCommand});
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kSecondDeviceAuthPayload);
  message->GetPayload()->Set(FIDO_MESSAGE_KEY,
                             base::Base64Encode(ctap_request_command));
  return message;
}

std::unique_ptr<QuickStartMessage> BuildRequestWifiCredentialsMessage(
    uint64_t session_id,
    std::string& shared_secret) {
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kQuickStartPayload);
  message->GetPayload()->Set(kRequestWifiKey, true);
  std::string shared_secret_str(shared_secret.begin(), shared_secret.end());
  std::string shared_secret_base64 = base::Base64Encode(shared_secret_str);
  message->GetPayload()->Set(kSharedSecretKey, shared_secret_base64);
  message->GetPayload()->Set(kSessionIdKey, base::NumberToString(session_id));
  return message;
}

cbor::Value GenerateGetAssertionRequest(
    std::array<uint8_t, crypto::kSHA256Length> client_data_hash) {
  url::Origin origin = url::Origin::Create(GURL(kOrigin));
  cbor::Value::MapValue cbor_map;
  cbor_map.insert_or_assign(cbor::Value(0x01), cbor::Value(kRelyingPartyId));
  cbor_map.insert_or_assign(cbor::Value(0x02), cbor::Value(client_data_hash));
  cbor::Value::MapValue option_map;
  option_map.insert_or_assign(cbor::Value(kUserPresenceMapKey),
                              cbor::Value(true));
  option_map.insert_or_assign(cbor::Value(kUserVerificationMapKey),
                              cbor::Value(true));
  cbor_map.insert_or_assign(cbor::Value(0x05),
                            cbor::Value(std::move(option_map)));
  return cbor::Value(std::move(cbor_map));
}

std::vector<uint8_t> CBOREncodeGetAssertionRequest(const cbor::Value& request) {
  // Encode the CtapGetAssertionRequest into cbor bytes vector.
  std::optional<std::vector<uint8_t>> cbor_bytes = cbor::Writer::Write(request);
  CHECK(cbor_bytes);
  std::vector<uint8_t> request_bytes = std::move(*cbor_bytes);
  // Add the command byte to the beginning of this now fully encoded cbor bytes
  // vector.
  request_bytes.insert(request_bytes.begin(),
                       kAuthenticatorGetAssertionCommand);
  return request_bytes;
}

std::unique_ptr<QuickStartMessage> BuildNotifySourceOfUpdateMessage(
    uint64_t session_id,
    const base::span<uint8_t, 32> shared_secret) {
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kQuickStartPayload);
  message->GetPayload()->Set(kNotifySourceOfUpdateMessageKey, true);

  std::string shared_secret_str(shared_secret.begin(), shared_secret.end());
  std::string shared_secret_base64 = base::Base64Encode(shared_secret_str);
  message->GetPayload()->Set(kSharedSecretKey, shared_secret_base64);
  message->GetPayload()->Set(kSessionIdKey, base::NumberToString(session_id));

  return message;
}

std::unique_ptr<QuickStartMessage> BuildBootstrapStateCancelMessage() {
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kBootstrapState);
  message->GetPayload()->Set(kBootstrapStateKey, kBootstrapStateCancel);
  return message;
}

std::unique_ptr<QuickStartMessage> BuildBootstrapStateCompleteMessage() {
  std::unique_ptr<QuickStartMessage> message =
      std::make_unique<QuickStartMessage>(
          QuickStartMessageType::kBootstrapState);
  message->GetPayload()->Set(kBootstrapStateKey, kBootstrapStateComplete);

  // TODO(b/332603236): Remove postTransferAction payload when new device info
  // exchange is implemented.
  base::Value::Dict post_transfer_action;
  post_transfer_action.Set(kPostTransferActionURIKey,
                           kPostTransferActionURIValue);
  message->GetPayload()->Set(kPostTransferActionKey,
                             std::move(post_transfer_action));
  return message;
}

}  // namespace ash::quick_start::requests