/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <endian.h>
#include <stdlib.h>

#include <android-base/file.h>
#include <base/files/file_util.h>
#include <base/strings/string_util.h>
#include <fs_avb/fs_avb.h>
#include <libavb/libavb.h>

#include "fs_avb_test_util.h"

// Target classes or functions to test:
using android::fs_mgr::AvbHandle;
using android::fs_mgr::AvbHandleStatus;
using android::fs_mgr::HashAlgorithm;

namespace fs_avb_host_test {

class PublicFsAvbTest : public BaseFsAvbTest {
  public:
    PublicFsAvbTest(){};

  protected:
    ~PublicFsAvbTest(){};
    // Modifies |flags| field in the vbmeta header in an Avb image.
    // e.g., AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED.
    void ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path, uint32_t flags);
};

void PublicFsAvbTest::ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path,
                                              uint32_t flags) {
    if (!base::PathExists(vbmeta_image_path)) return;

    // Only support modifying the flags in vbmeta*.img.
    std::string image_file_name = vbmeta_image_path.RemoveExtension().BaseName().value();
    ASSERT_TRUE(base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII));

    android::base::unique_fd fd(open(vbmeta_image_path.value().c_str(), O_RDWR | O_CLOEXEC));
    EXPECT_TRUE(fd > 0);

    auto flags_offset = offsetof(AvbVBMetaImageHeader, flags);
    uint32_t flags_data = htobe32(flags);
    EXPECT_EQ(flags_offset, lseek64(fd, flags_offset, SEEK_SET));
    EXPECT_EQ(sizeof flags_data, write(fd, &flags_data, sizeof flags_data));
}

TEST_F(PublicFsAvbTest, LoadAndVerifyVbmeta) {
    // Generates a raw boot.img
    const size_t boot_image_size = 5 * 1024 * 1024;
    const size_t boot_partition_size = 10 * 1024 * 1024;
    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);

    // Adds AVB Hash Footer.
    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates a raw system.img, use a smaller size to speed-up unit test.
    const size_t system_image_size = 10 * 1024 * 1024;
    const size_t system_partition_size = 15 * 1024 * 1024;
    base::FilePath system_path = GenerateImage("system.img", system_image_size);
    // Adds AVB Hashtree Footer.
    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates chain partition descriptors.
    base::FilePath rsa2048_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
    base::FilePath rsa4096_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));

    // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors.
    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
                                           data_dir_.Append("testkey_rsa8192.pem"),
                                           {}, /* include_descriptor_image_paths */
                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
                                            {"system", 2, rsa4096_public_key}},
                                           "--internal_release_string \"unit test\"");

    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
              CalcVBMetaDigest("vbmeta.img", "sha256"));

    // Invokes the public API from fs_avb.h.
    auto vbmeta_image_path = [this](const std::string& partition_name) {
        return test_dir_.Append(partition_name + ".img").value();
    };
    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            false /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());

    // Checks the summary info for all vbmeta images.
    // Checks the digest matches the value calculated by CalcVBMetaDigest().
    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
              avb_handle->vbmeta_info().digest);
    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);

    // Skip loading chained vbmeta.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            false /* allow_verification_error */, false /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());
    EXPECT_EQ("5c31197992b3c72a854ec7dc0eb9609ffebcffab7917ffd381a99ecee328f09c",
              avb_handle->vbmeta_info().digest);
    EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size);
    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
}

TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithModifications) {
    // Generates a raw boot.img
    const size_t boot_image_size = 5 * 1024 * 1024;
    const size_t boot_partition_size = 10 * 1024 * 1024;
    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);

    // Adds AVB Hash Footer.
    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates a raw system.img, use a smaller size to speed-up unit test.
    const size_t system_image_size = 10 * 1024 * 1024;
    const size_t system_partition_size = 15 * 1024 * 1024;
    base::FilePath system_path = GenerateImage("system.img", system_image_size);
    // Adds AVB Hashtree Footer.
    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates chain partition descriptors.
    base::FilePath rsa2048_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
    base::FilePath rsa4096_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));

    // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors.
    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
                                           data_dir_.Append("testkey_rsa8192.pem"),
                                           {}, /* include_descriptor_image_paths */
                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
                                            {"system", 2, rsa4096_public_key}},
                                           "--internal_release_string \"unit test\"");

    // Calculates the digest of all chained partitions, to ensure the chained is formed properly.
    EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f",
              CalcVBMetaDigest("vbmeta.img", "sha256"));

    // Sets AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED in the vbmeta.img.
    ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);

    auto vbmeta_image_path = [this](const std::string& partition_name) {
        return test_dir_.Append(partition_name + ".img").value();
    };
    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            false /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    // Returns a null handler because allow_verification is not True.
    EXPECT_EQ(nullptr, avb_handle);

    // Try again with allow_verification_error set to true.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            true /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kHashtreeDisabled, avb_handle->status());

    // Checks the summary info for all vbmeta images.
    // Checks the digest matches the value calculated by CalcVBMetaDigest().
    EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c",
              CalcVBMetaDigest("vbmeta.img", "sha256"));
    EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c",
              avb_handle->vbmeta_info().digest);
    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);

    // Sets AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED in the vbmeta.img.
    ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
    // Loads the vbmeta with allow_verification_error set to true.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            true /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kVerificationDisabled, avb_handle->status());
    // Only the top-level vbmeta.img is loaded, when VERIFICATION_DISABLED is set.
    // However, CalcVBMetaDigest() reads all vbmeta structs to calculate the digest,
    // including vbmeta.img, boot.img and syste.img. So we don't compare the digest here.
    EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size);

    // Sets a unknown flag in the vbmeta.imgm and expects to get
    // AvbHandleStatus::kVerificationError.
    ModifyVBMetaHeaderFlags(vbmeta_path, 0x10000000);
    // Loads the vbmeta with allow_verification_error set to true.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256,
            true /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status());
    // Checks the digest matches the value calculated by CalcVBMetaDigest().
    EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1",
              CalcVBMetaDigest("vbmeta.img", "sha256"));
    EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1",
              avb_handle->vbmeta_info().digest);
    EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size);
    EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm);
}

TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithPublicKeys) {
    // Generates a raw boot.img
    const size_t boot_image_size = 5 * 1024 * 1024;
    const size_t boot_partition_size = 10 * 1024 * 1024;
    base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);

    // Adds AVB Hash Footer.
    AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10,
                 data_dir_.Append("testkey_rsa2048.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates a raw system.img, use a smaller size to speed-up unit test.
    const size_t system_image_size = 10 * 1024 * 1024;
    const size_t system_partition_size = 15 * 1024 * 1024;
    base::FilePath system_path = GenerateImage("system.img", system_image_size);
    // Adds AVB Hashtree Footer.
    AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20,
                 data_dir_.Append("testkey_rsa4096.pem"), "d00df00d",
                 "--internal_release_string \"unit test\"");

    // Generates chain partition descriptors.
    base::FilePath rsa2048_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem"));
    base::FilePath rsa4096_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem"));
    base::FilePath rsa8192_public_key =
            ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem"));

    // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors.
    auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
                                           data_dir_.Append("testkey_rsa8192.pem"),
                                           {}, /* include_descriptor_image_paths */
                                           {{"boot", 1, rsa2048_public_key}, /* chain_partitions */
                                            {"system", 2, rsa4096_public_key}},
                                           "--internal_release_string \"unit test\"");

    auto vbmeta_image_path = [this](const std::string& partition_name) {
        return test_dir_.Append(partition_name + ".img").value();
    };
    std::vector<VBMetaData> vbmeta_images;
    // Uses the correct expected public key.
    auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            rsa8192_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */,
            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status());

    // Uses a non-existed public key.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            "/path/to/non-existed/key", HashAlgorithm::kSHA256, true /* allow_verification_error */,
            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
    EXPECT_EQ(nullptr, avb_handle);

    // Uses an incorrect public key, with allow_verification_error false.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            rsa4096_public_key.value(), HashAlgorithm::kSHA256,
            false /* allow_verification_error */, true /* load_chained_vbmeta */,
            true /* rollback_protection */, vbmeta_image_path);
    EXPECT_EQ(nullptr, avb_handle);

    // Uses an incorrect public key, with allow_verification_error true.
    avb_handle = AvbHandle::LoadAndVerifyVbmeta(
            "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */,
            rsa4096_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */,
            true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path);
    EXPECT_NE(nullptr, avb_handle);
    EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status());
}

}  // namespace fs_avb_host_test
