//
// Copyright (C) 2018 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 "update_engine/payload_consumer/payload_metadata.h"
#include <endian.h>
#include <brillo/data_encoding.h>
#include "update_engine/common/hash_calculator.h"
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/payload_constants.h"
#include "update_engine/payload_consumer/payload_verifier.h"
namespace chromeos_update_engine {
const uint64_t PayloadMetadata::kDeltaVersionOffset = sizeof(kDeltaMagic);
const uint64_t PayloadMetadata::kDeltaVersionSize = 8;
const uint64_t PayloadMetadata::kDeltaManifestSizeOffset =
kDeltaVersionOffset + kDeltaVersionSize;
const uint64_t PayloadMetadata::kDeltaManifestSizeSize = 8;
const uint64_t PayloadMetadata::kDeltaMetadataSignatureSizeSize = 4;
bool PayloadMetadata::GetMetadataSignatureSizeOffset(
uint64_t* out_offset) const {
if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
*out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
return true;
}
return false;
}
bool PayloadMetadata::GetManifestOffset(uint64_t* out_offset) const {
// Actual manifest begins right after the manifest size field or
// metadata signature size field if major version >= 2.
if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
*out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
return true;
}
if (major_payload_version_ == kBrilloMajorPayloadVersion) {
*out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize +
kDeltaMetadataSignatureSizeSize;
return true;
}
LOG(ERROR) << "Unknown major payload version: " << major_payload_version_;
return false;
}
MetadataParseResult PayloadMetadata::ParsePayloadHeader(
const brillo::Blob& payload,
uint64_t supported_major_version,
ErrorCode* error) {
uint64_t manifest_offset;
// Ensure we have data to cover the major payload version.
if (payload.size() < kDeltaManifestSizeOffset)
return MetadataParseResult::kInsufficientData;
// Validate the magic string.
if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {
LOG(ERROR) << "Bad payload format -- invalid delta magic.";
*error = ErrorCode::kDownloadInvalidMetadataMagicString;
return MetadataParseResult::kError;
}
// Extract the payload version from the metadata.
static_assert(sizeof(major_payload_version_) == kDeltaVersionSize,
"Major payload version size mismatch");
memcpy(&major_payload_version_,
&payload[kDeltaVersionOffset],
kDeltaVersionSize);
// Switch big endian to host.
major_payload_version_ = be64toh(major_payload_version_);
if (major_payload_version_ != supported_major_version &&
major_payload_version_ != kChromeOSMajorPayloadVersion) {
LOG(ERROR) << "Bad payload format -- unsupported payload version: "
<< major_payload_version_;
*error = ErrorCode::kUnsupportedMajorPayloadVersion;
return MetadataParseResult::kError;
}
// Get the manifest offset now that we have payload version.
if (!GetManifestOffset(&manifest_offset)) {
*error = ErrorCode::kUnsupportedMajorPayloadVersion;
return MetadataParseResult::kError;
}
// Check again with the manifest offset.
if (payload.size() < manifest_offset)
return MetadataParseResult::kInsufficientData;
// Next, parse the manifest size.
static_assert(sizeof(manifest_size_) == kDeltaManifestSizeSize,
"manifest_size size mismatch");
memcpy(&manifest_size_,
&payload[kDeltaManifestSizeOffset],
kDeltaManifestSizeSize);
manifest_size_ = be64toh(manifest_size_); // switch big endian to host
if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
// Parse the metadata signature size.
static_assert(
sizeof(metadata_signature_size_) == kDeltaMetadataSignatureSizeSize,
"metadata_signature_size size mismatch");
uint64_t metadata_signature_size_offset;
if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) {
*error = ErrorCode::kError;
return MetadataParseResult::kError;
}
memcpy(&metadata_signature_size_,
&payload[metadata_signature_size_offset],
kDeltaMetadataSignatureSizeSize);
metadata_signature_size_ = be32toh(metadata_signature_size_);
}
metadata_size_ = manifest_offset + manifest_size_;
return MetadataParseResult::kSuccess;
}
bool PayloadMetadata::GetManifest(const brillo::Blob& payload,
DeltaArchiveManifest* out_manifest) const {
uint64_t manifest_offset;
if (!GetManifestOffset(&manifest_offset))
return false;
CHECK_GE(payload.size(), manifest_offset + manifest_size_);
return out_manifest->ParseFromArray(&payload[manifest_offset],
manifest_size_);
}
ErrorCode PayloadMetadata::ValidateMetadataSignature(
const brillo::Blob& payload,
std::string metadata_signature,
base::FilePath path_to_public_key) const {
if (payload.size() < metadata_size_ + metadata_signature_size_)
return ErrorCode::kDownloadMetadataSignatureError;
brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
if (!metadata_signature.empty()) {
// Convert base64-encoded signature to raw bytes.
if (!brillo::data_encoding::Base64Decode(metadata_signature,
&metadata_signature_blob)) {
LOG(ERROR) << "Unable to decode base64 metadata signature: "
<< metadata_signature;
return ErrorCode::kDownloadMetadataSignatureError;
}
} else if (major_payload_version_ == kBrilloMajorPayloadVersion) {
metadata_signature_protobuf_blob.assign(
payload.begin() + metadata_size_,
payload.begin() + metadata_size_ + metadata_signature_size_);
}
if (metadata_signature_blob.empty() &&
metadata_signature_protobuf_blob.empty()) {
LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
<< "response and payload.";
return ErrorCode::kDownloadMetadataSignatureMissingError;
}
LOG(INFO) << "Verifying metadata hash signature using public key: "
<< path_to_public_key.value();
brillo::Blob calculated_metadata_hash;
if (!HashCalculator::RawHashOfBytes(
payload.data(), metadata_size_, &calculated_metadata_hash)) {
LOG(ERROR) << "Unable to compute actual hash of manifest";
return ErrorCode::kDownloadMetadataSignatureVerificationError;
}
PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
if (calculated_metadata_hash.empty()) {
LOG(ERROR) << "Computed actual hash of metadata is empty.";
return ErrorCode::kDownloadMetadataSignatureVerificationError;
}
if (!metadata_signature_blob.empty()) {
brillo::Blob expected_metadata_hash;
if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature_blob,
path_to_public_key.value(),
&expected_metadata_hash)) {
LOG(ERROR) << "Unable to compute expected hash from metadata signature";
return ErrorCode::kDownloadMetadataSignatureError;
}
if (calculated_metadata_hash != expected_metadata_hash) {
LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
utils::HexDumpVector(expected_metadata_hash);
LOG(ERROR) << "Calculated hash = ";
utils::HexDumpVector(calculated_metadata_hash);
return ErrorCode::kDownloadMetadataSignatureMismatch;
}
} else {
if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob,
path_to_public_key.value(),
calculated_metadata_hash)) {
LOG(ERROR) << "Manifest hash verification failed.";
return ErrorCode::kDownloadMetadataSignatureMismatch;
}
}
// The autoupdate_CatchBadSignatures test checks for this string in
// log-files. Keep in sync.
LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
return ErrorCode::kSuccess;
}
} // namespace chromeos_update_engine