chromium/components/error_page/common/alt_game_images/alt_game_images.cc.template

// 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.

// This file was automatically generated from {{ template_file }}.
// See {{ readme_file }} for details about how to make changes.

#include "components/error_page/common/alt_game_images.h"

#include <memory>
#include <string>

#include "base/base64url.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"

namespace error_page {
namespace {
{% for image in images_1x %}
  const uint8_t kAltGameImages1x{{ image.name }}[] = {
    {{ image.literals }}
  };
{% endfor %}

const uint8_t* const kAltGameImages1x[] = {
  {% for image in images_1x %}
    kAltGameImages1x{{ image.name }},
  {% endfor %}
};
constexpr int kAltGameImagesCount = std::size(kAltGameImages1x);
const size_t kAltGameImages1xLength[] = {
  {% for image in images_1x %}
    std::size(kAltGameImages1x{{ image.name }}),
  {% endfor %}
};
static_assert(std::size(kAltGameImages1xLength) == kAltGameImagesCount, "");

{% for image in images_2x %}
  const uint8_t kAltGameImages2x{{ image.name }}[] = {
    {{ image.literals }}
  };
{% endfor %}

const uint8_t* const kAltGameImages2x[] = {
  {% for image in images_2x %}
    kAltGameImages2x{{ image.name }},
  {% endfor %}
};

const size_t kAltGameImages2xLength[] = {
  {% for image in images_2x %}
    std::size(kAltGameImages2x{{ image.name }}),
  {% endfor %}
};

static_assert(std::size(kAltGameImages2x) == kAltGameImagesCount, "");
static_assert(std::size(kAltGameImages2xLength) == kAltGameImagesCount, "");

std::string DecryptImage(const std::string& image_ciphertext,
                         crypto::SymmetricKey* key) {
  constexpr int kBlockSize = 16;
  crypto::Encryptor encryptor;
  // *Never* use this pattern for encrypting anything that matters! There are
  // many problems with this, but it's just obfuscation so it doesn't matter.
  if (!encryptor.Init(key, crypto::Encryptor::Mode::CBC,
                      /*iv=*/key->key().substr(0, kBlockSize))) {
    DLOG(ERROR) << "Failed to initialize encryptor.";
    return std::string();
  }

  std::string image_plaintext;
  if (!encryptor.Decrypt(image_ciphertext, &image_plaintext)) {
    DLOG(ERROR) << "Failed to decrypt image.";
    return std::string();
  }
  return image_plaintext;
}

bool LookupImage(int id, int scale, std::string* image) {
  if (id < 0 || id >= kAltGameImagesCount)
    return false;

  if (scale == 1) {
    *image = std::string(reinterpret_cast<const char*>(kAltGameImages1x[id]),
                         kAltGameImages1xLength[id]);
    return true;
  }
  if (scale == 2) {
    *image = std::string(reinterpret_cast<const char*>(kAltGameImages2x[id]),
                         kAltGameImages2xLength[id]);
    return true;
  }
  return false;
}

std::unique_ptr<crypto::SymmetricKey> GetKey() {
  std::string b64_key = kNetErrorAltGameModeKey.Get();
  if (b64_key.empty()) {
    DLOG(ERROR) << "No image encryption key present.";
    return nullptr;
  }

  // Note: key is base64url-encoded because the default base64 format includes
  // the '/' character which interferes with setting the param from the command
  // line for testing.
  std::string raw_key;
  if (!base::Base64UrlDecode(
          b64_key, base::Base64UrlDecodePolicy::IGNORE_PADDING, &raw_key)) {
    DLOG(ERROR) << "Failed to base64-decode image encryption key.";
    return nullptr;
  }

  std::unique_ptr<crypto::SymmetricKey> key = crypto::SymmetricKey::Import(
      crypto::SymmetricKey::Algorithm::AES, raw_key);
  if (!key) {
    DLOG(ERROR) << "Invalid image encryption key: wrong length?";
    return nullptr;
  }
  return key;
}

}  // namespace

BASE_FEATURE(kNetErrorAltGameMode, "NetErrorAltGameMode",
             base::FEATURE_DISABLED_BY_DEFAULT);
const base::FeatureParam<std::string> kNetErrorAltGameModeKey{
    &kNetErrorAltGameMode, "Key", ""};

bool EnableAltGameMode() {
  return base::FeatureList::IsEnabled(kNetErrorAltGameMode);
}

std::string GetAltGameImage(int image_id, int scale) {
  std::string encrypted_image;
  if (!LookupImage(image_id, scale, &encrypted_image)) {
    DLOG(ERROR) << "Couldn't find alt game image with ID=" << image_id
                << " and scale=" << scale << "x.";
    return std::string();
  }

  std::unique_ptr<crypto::SymmetricKey> key = GetKey();
  if (!key) {
    DLOG(ERROR) << "Failed to load alt game image encryption key.";
    return std::string();
  }

  std::string image = DecryptImage(encrypted_image, key.get());
  if (image.empty()) {
    DLOG(ERROR) << "Failed to decrypt alt game image " << image_id << " at "
                << scale << "x scale.";
    return std::string();
  }

  if (!base::StartsWith(image, "data:image/png;base64,")) {
    DLOG(ERROR) << "Decrypted data doesn't look like an image data URL.";
    return std::string();
  }

  return image;
}

int ChooseAltGame() {
  // Image 0 should be "common".
  static_assert(1 <= kAltGameImagesCount - 1, "");
  return base::RandInt(1, kAltGameImagesCount - 1);
}

}  // namespace error_page