chromium/ash/quick_pair/fast_pair_handshake/fast_pair_encryption_fuzzer.cc

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

#include "ash/quick_pair/fast_pair_handshake/fast_pair_encryption.h"

#include <fuzzer/FuzzedDataProvider.h>
#include <stddef.h>
#include <stdint.h>
#include <array>

#include "ash/quick_pair/common/logging.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_key_pair.h"
#include "base/check.h"
#include "base/no_destructor.h"
#include "chromeos/ash/services/quick_pair/fast_pair_decryption.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/nid.h"

namespace {

constexpr size_t kXSize = 32;
constexpr size_t kYSize = 1;
constexpr size_t kKeySize = /*type byte=*/1 + /*x coord=*/32 + /*y coord=*/32;

struct Environment {
  Environment() {
    // Disable noisy logging for fuzzing.
    logging::SetMinLogLevel(logging::LOGGING_FATAL);
  }

  ash::quick_pair::ScopedDisableLoggingForTesting disable_logging_;
};

}  // namespace

namespace ash {
namespace quick_pair {
namespace fast_pair_encryption {

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  // Enforce a minimum input size so that we can pass in valid parameters
  // to EncryptBytes(), GenerateKeysWithEcdhKeyAgreement(),
  // GenerateHmacSha256(), and EncryptAdditionalData().
  size_t min_size = 2 * ash::quick_pair::fast_pair_decryption::kBlockByteSize +
                    kNonceSizeBytes + kSecretKeySizeBytes;
  if (size < min_size) {
    return 0;
  }

  // Generate data needed for testing.
  static base::NoDestructor<Environment> env;
  FuzzedDataProvider fuzzed_data(data, size);

  std::vector<uint8_t> aes_key_bytes = fuzzed_data.ConsumeBytes<uint8_t>(
      ash::quick_pair::fast_pair_decryption::kBlockByteSize);
  std::array<uint8_t, ash::quick_pair::fast_pair_decryption::kBlockByteSize>
      aes_key_arr{*aes_key_bytes.data()};

  std::vector<uint8_t> data_bytes = fuzzed_data.ConsumeBytes<uint8_t>(
      ash::quick_pair::fast_pair_decryption::kBlockByteSize);
  std::array<uint8_t, ash::quick_pair::fast_pair_decryption::kBlockByteSize>
      data_arr{*data_bytes.data()};

  std::vector<uint8_t> secret_key_bytes =
      fuzzed_data.ConsumeBytes<uint8_t>(kSecretKeySizeBytes);
  std::array<uint8_t, kSecretKeySizeBytes> secret_key_arr{
      *secret_key_bytes.data()};

  std::vector<uint8_t> nonce_bytes =
      fuzzed_data.ConsumeBytes<uint8_t>(kNonceSizeBytes);
  std::array<uint8_t, kNonceSizeBytes> nonce_arr{*nonce_bytes.data()};

  std::string input_data_string = fuzzed_data.ConsumeRandomLengthString();
  std::vector<uint8_t> input_data{input_data_string.begin(),
                                  input_data_string.end()};

  // Test FastPairEncryption functions with generated data.
  EncryptBytes(aes_key_arr, data_arr);
  GenerateHmacSha256(secret_key_arr, nonce_arr, input_data);
  EncryptAdditionalData(secret_key_arr, nonce_arr, input_data);

  // In order to fuzz a valid EC_POINT, the fuzz needs to have at least
  // kXSize + kYSize bytes remaining. For simplicity, exit early if there
  // are not exactly as many bytes as required.
  if (fuzzed_data.remaining_bytes() < kXSize + kYSize) {
    std::string invalid_len_string = fuzzed_data.ConsumeRandomLengthString();

    GenerateKeysWithEcdhKeyAgreement(invalid_len_string);

    return 0;
  }

  // Generates a random point on the curve defined by EC_GROUP.
  bssl::UniquePtr<EC_GROUP> ec_group(
      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(ec_group.get()));

  // Set x value and y bit according to fuzz.
  auto x_value_arr = fuzzed_data.ConsumeBytes<uint8_t>(kXSize);
  DCHECK(x_value_arr.size() == kXSize);
  bssl::UniquePtr<BIGNUM> x(BN_new());
  DCHECK(BN_le2bn(&x_value_arr[0], kXSize, x.get()));

  auto y_value_arr = fuzzed_data.ConsumeBytes<uint8_t>(kYSize);
  DCHECK(y_value_arr.size() == kYSize);
  int y_bit = y_value_arr[0] & 1;

  // Set EC_POINT according to the compressed coordinates x and y_bit. This
  // effectively uses fuzz to generate a point on the EC without us having to
  // explicitly compute the solution y for x on the EC. Fails 50% of the time
  // when generated x value has no solution on the EC.
  if (!EC_POINT_set_compressed_coordinates_GFp(ec_group.get(), point.get(),
                                               x.get(), y_bit, /*ctx=*/nullptr))
    return 0;

  // Convert compressed EC_POINT into the uncompressed string expected by the
  // function.
  std::array<uint8_t, kKeySize> buffer;
  DCHECK(EC_POINT_point2oct(ec_group.get(), point.get(),
                            POINT_CONVERSION_UNCOMPRESSED, buffer.data(),
                            kKeySize, /*ctx=*/nullptr));
  // Function expects a string which is missing the first type byte.
  std::string anti_spoofing_key(buffer.data() + 1, buffer.data() + kKeySize);
  DCHECK(anti_spoofing_key.length() == kKeySize - 1);

  GenerateKeysWithEcdhKeyAgreement(anti_spoofing_key);

  return 0;
}

}  // namespace fast_pair_encryption
}  // namespace quick_pair
}  // namespace ash