blob: 7e8cc7ddd979f646d2a548da16a97517b5c450b5 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include <utility>
#include <base/check.h>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include "imageloader/manifest.h"
namespace imageloader {
constexpr char kDlcRedactedId[] = "<REDACTED_ID>";
constexpr char kDlcRedactedSize[] = "<REDACTED_SIZE>";
constexpr char kDlcRedactedHash[] = "<REDACTED_HASH>";
namespace {
// The current version of the manifest file.
constexpr int kCurrentManifestVersion = 1;
// The name of the version field in the manifest.
constexpr char kManifestVersionField[] = "manifest-version";
// The name of the component version field in the manifest.
constexpr char kVersionField[] = "version";
// The name of the field containing the image hash.
constexpr char kImageHashField[] = "image-sha256-hash";
// The name of the bool field indicating whether component is removable.
constexpr char kIsRemovableField[] = "is-removable";
// The name of the metadata field.
constexpr char kMetadataField[] = "metadata";
// The name of the field containing the table hash.
constexpr char kTableHashField[] = "table-sha256-hash";
// Optional manifest fields.
constexpr char kFSType[] = "fs-type";
constexpr char kId[] = "id";
constexpr char kPackage[] = "package";
constexpr char kName[] = "name";
constexpr char kImageType[] = "image-type";
constexpr char kPreallocatedSize[] = "pre-allocated-size";
constexpr char kSize[] = "size";
constexpr char kPreloadAllowed[] = "preload-allowed";
constexpr char kFactoryInstall[] = "factory-install";
constexpr char kMountFileRequired[] = "mount-file-required";
constexpr char kReserved[] = "reserved";
constexpr char kCriticalUpdate[] = "critical-update";
constexpr char kDescription[] = "description";
constexpr char kUseLogicalVolume[] = "use-logical-volume";
constexpr char kScaled[] = "scaled";
constexpr char kPowerwashSafe[] = "powerwash-safe";
constexpr char kUserTied[] = "user-tied";
constexpr char kArtifactsMeta[] = "artifacts-meta";
constexpr char kArtifactsMetaUriKey[] = "uri";
constexpr char kForceOTA[] = "force-ota";
bool GetSHA256FromString(const std::string& hash_str,
std::vector<uint8_t>* bytes) {
if (!base::HexStringToBytes(hash_str, bytes))
return false;
return bytes->size() == 32;
}
// Ensure the metadata entry is a dictionary mapping strings to strings and
// parse it into |out_metadata| and return true if so.
bool ParseMetadata(const base::Value& metadata,
std::map<std::string, std::string>* out_metadata) {
DCHECK(out_metadata);
if (!metadata.is_dict()) {
return false;
}
for (const auto& item : metadata.GetDict()) {
if (!item.second.is_string()) {
LOG(ERROR) << "Key \"" << item.first << "\" did not map to string value";
return false;
}
(*out_metadata)[item.first] = item.second.GetString();
}
return true;
}
bool ParseArtifactsMeta(const base::Value& artifacts_meta,
ArtifactsMeta* artifacts_meta_out) {
static const auto kInvalid = ArtifactsMeta{.valid = false};
auto* dict = artifacts_meta.GetIfDict();
if (!dict) {
*artifacts_meta_out = kInvalid;
return false;
}
for (const auto& item : *dict) {
if (item.first == kArtifactsMetaUriKey) {
auto* opt_uri = item.second.GetIfString();
if (!opt_uri) {
LOG(ERROR) << "Artifacts meta URI value is not a string.";
*artifacts_meta_out = kInvalid;
return false;
}
artifacts_meta_out->uri = *opt_uri;
}
}
artifacts_meta_out->valid = true;
return true;
}
} // namespace
// clang-format off
bool Manifest::operator==(const Manifest& rhs) const {
// NOTE: Update the comparator when a class member is added/removed.
return
// Required manifest fields:
manifest_version() == rhs.manifest_version() &&
image_sha256() == rhs.image_sha256() &&
table_sha256() == rhs.table_sha256() &&
version() == rhs.version() &&
// Optional manifest fields:
fs_type() == rhs.fs_type() &&
id() == rhs.id() &&
package() == rhs.package() &&
name() == rhs.name() &&
image_type() == rhs.image_type() &&
preallocated_size() == rhs.preallocated_size() &&
size() == rhs.size() &&
is_removable() == rhs.is_removable() &&
preload_allowed() == rhs.preload_allowed() &&
factory_install() == rhs.factory_install() &&
mount_file_required() == rhs.mount_file_required() &&
reserved() == rhs.reserved() &&
critical_update() == rhs.critical_update() &&
description() == rhs.description() &&
metadata() == rhs.metadata() &&
use_logical_volume() == rhs.use_logical_volume() &&
scaled() == rhs.scaled() &&
powerwash_safe() == rhs.powerwash_safe() &&
artifacts_meta() == rhs.artifacts_meta() &&
force_ota() == rhs.force_ota() &&
user_tied() == rhs.user_tied();
}
// clang-format on
bool Manifest::ParseManifest(const std::string& manifest_raw) {
// Now deserialize the manifest json and read out the rest of the component.
auto manifest_value = base::JSONReader::ReadAndReturnValueWithError(
manifest_raw, base::JSON_PARSE_RFC);
if (!manifest_value.has_value()) {
LOG(ERROR) << "Could not parse the manifest file as JSON. Error: "
<< manifest_value.error().message;
return false;
}
if (!manifest_value->is_dict()) {
LOG(ERROR) << "Manifest file is not dictionary.";
return false;
}
return ParseManifest(manifest_value->GetDict());
}
bool Manifest::ParseManifest(const base::Value::Dict& manifest_dict) {
// This will have to be changed if the manifest version is bumped.
std::optional<int> manifest_version =
manifest_dict.FindInt(kManifestVersionField);
if (!manifest_version.has_value()) {
LOG(ERROR) << "Could not parse manifest version field from manifest.";
return false;
}
if (manifest_version != kCurrentManifestVersion) {
LOG(ERROR) << "Unsupported version of the manifest.";
return false;
}
manifest_version_ = *manifest_version;
// If `user-tied` field does not exist, by default it is false.
user_tied_ = manifest_dict.FindBool(kUserTied).value_or(false);
const std::string* image_hash_str = manifest_dict.FindString(kImageHashField);
if (!image_hash_str) {
LOG(ERROR) << "Could not parse image hash from manifest.";
return false;
}
if (!GetSHA256FromString(*image_hash_str, &(image_sha256_))) {
LOG(ERROR) << "Could not convert image hash to bytes.";
return false;
}
sanitized_image_sha256_ = user_tied_ ? kDlcRedactedHash : *image_hash_str;
const std::string* table_hash_str = manifest_dict.FindString(kTableHashField);
if (table_hash_str == nullptr) {
LOG(ERROR) << "Could not parse table hash from manifest.";
return false;
}
if (!GetSHA256FromString(*table_hash_str, &(table_sha256_))) {
LOG(ERROR) << "Could not convert table hash to bytes.";
return false;
}
const std::string* version = manifest_dict.FindString(kVersionField);
if (!version) {
LOG(ERROR) << "Could not parse component version from manifest.";
return false;
}
version_ = *version;
// The fs_type field is optional, and squashfs by default.
const std::string* fs_type = manifest_dict.FindString(kFSType);
if (fs_type) {
if (*fs_type == "ext2") {
fs_type_ = FileSystem::kExt2;
} else if (*fs_type == "ext4") {
fs_type_ = FileSystem::kExt4;
} else if (*fs_type == "squashfs") {
fs_type_ = FileSystem::kSquashFS;
} else {
LOG(ERROR) << "Unsupported file system type: " << *fs_type;
return false;
}
} else {
fs_type_ = FileSystem::kSquashFS;
}
std::optional<bool> is_removable = manifest_dict.FindBool(kIsRemovableField);
// If |is-removable| field does not exist, by default it is false.
is_removable_ = is_removable.value_or(false);
std::optional<bool> preload_allowed = manifest_dict.FindBool(kPreloadAllowed);
// If |preaload-allowed| field does not exist, by default it is false.
preload_allowed_ = preload_allowed.value_or(false);
std::optional<bool> factory_install = manifest_dict.FindBool(kFactoryInstall);
// If |factory-install| field does not exist, by default it is false.
factory_install_ = factory_install.value_or(false);
std::optional<bool> mount_file_required =
manifest_dict.FindBool(kMountFileRequired);
// If 'mount-file-required' field does not exist, by default it is false.
mount_file_required_ = mount_file_required.value_or(false);
// If 'reserved' field does not exist, by default it is false.
reserved_ = manifest_dict.FindBool(kReserved).value_or(false);
// If 'critical-update` field does not exist, by default it is false.
critical_update_ = manifest_dict.FindBool(kCriticalUpdate).value_or(false);
// If `use-logical-volume` field does not exist, by default it is false.
use_logical_volume_ =
manifest_dict.FindBool(kUseLogicalVolume).value_or(false);
// If `scaled` field does not exist, by default it is false.
scaled_ = manifest_dict.FindBool(kScaled).value_or(false);
// If `powerwash-safe` field does not exist, by default it is false.
powerwash_safe_ = manifest_dict.FindBool(kPowerwashSafe).value_or(false);
// If `force-ota` field does not exist, by default it is false.
force_ota_ = manifest_dict.FindBool(kForceOTA).value_or(false);
// All of these fields are optional.
const std::string* id = manifest_dict.FindString(kId);
if (id) {
id_ = *id;
sanitized_id_ = user_tied_ ? kDlcRedactedId : *id;
}
const std::string* package = manifest_dict.FindString(kPackage);
if (package)
package_ = *package;
const std::string* name = manifest_dict.FindString(kName);
if (name)
name_ = *name;
const std::string* image_type = manifest_dict.FindString(kImageType);
if (image_type)
image_type_ = *image_type;
const std::string* description = manifest_dict.FindString(kDescription);
if (description)
description_ = *description;
const std::string* preallocated_size_str =
manifest_dict.FindString(kPreallocatedSize);
if (preallocated_size_str) {
if (!base::StringToInt64(*preallocated_size_str, &preallocated_size_)) {
LOG(ERROR) << "Manifest pre-allocated-size was malformed: "
<< *preallocated_size_str;
return false;
}
sanitized_preallocated_size_ =
user_tied_ ? kDlcRedactedSize : *preallocated_size_str;
}
const std::string* size_str = manifest_dict.FindString(kSize);
if (size_str) {
if (!base::StringToInt64(*size_str, &size_)) {
LOG(ERROR) << "Manifest size was malformed: " << *size_str;
return false;
}
sanitized_size_ = user_tied_ ? kDlcRedactedSize : *size_str;
}
// Copy out the metadata, if it's there.
const base::Value* metadata = manifest_dict.Find(kMetadataField);
if (metadata) {
if (!ParseMetadata(*metadata, &metadata_)) {
LOG(ERROR) << "Manifest metadata was malformed";
return false;
}
}
auto* artifacts_meta = manifest_dict.Find(kArtifactsMeta);
if (artifacts_meta) {
if (!ParseArtifactsMeta(*artifacts_meta, &artifacts_meta_)) {
LOG(ERROR) << "Failed to parse artifacts meta.";
return false;
}
}
return true;
}
} // namespace imageloader