Split payload metadata logic from DeltaPerformer into its own class.
DeltaPerformer have code for both parsing payload and performing
operations. This change moves parsing payload header and validating
metadata signature to a new class PayloadMetadata so that
DeltaPerformer can focus on performing.
We will also have new code in another class that will use the new
PayloadMetadata class to parse payload metadata.
Bug: 65283633
Test: update_engine_unittests
Change-Id: Ie20b84713a0c66867a1de9d3d0cc29d0189b3c97
(cherry picked from commit 9c89e8499abeac3894718120d12b41301ffa3fc1)
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 14b6fa8..5303e03 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -16,7 +16,6 @@
#include "update_engine/payload_consumer/delta_performer.h"
-#include <endian.h>
#include <errno.h>
#include <linux/fs.h>
@@ -68,13 +67,6 @@
namespace chromeos_update_engine {
-const uint64_t DeltaPerformer::kDeltaVersionOffset = sizeof(kDeltaMagic);
-const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
-const uint64_t DeltaPerformer::kDeltaManifestSizeOffset =
- kDeltaVersionOffset + kDeltaVersionSize;
-const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
-const uint64_t DeltaPerformer::kDeltaMetadataSignatureSizeSize = 4;
-const uint64_t DeltaPerformer::kMaxPayloadHeaderSize = 24;
const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 2;
const uint32_t DeltaPerformer::kSupportedMinorPayloadVersion = 5;
@@ -473,39 +465,6 @@
} // namespace
-bool DeltaPerformer::GetMetadataSignatureSizeOffset(
- uint64_t* out_offset) const {
- if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
- *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
- return true;
- }
- return false;
-}
-
-bool DeltaPerformer::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;
-}
-
-uint64_t DeltaPerformer::GetMetadataSize() const {
- return metadata_size_;
-}
-
-uint64_t DeltaPerformer::GetMajorVersion() const {
- return major_payload_version_;
-}
-
uint32_t DeltaPerformer::GetMinorVersion() const {
if (manifest_.has_minor_version()) {
return manifest_.minor_version();
@@ -516,96 +475,35 @@
}
}
-bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
- if (!manifest_parsed_)
- return false;
- *out_manifest_p = manifest_;
- return true;
-}
-
bool DeltaPerformer::IsHeaderParsed() const {
return metadata_size_ != 0;
}
-DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
const brillo::Blob& payload, ErrorCode* error) {
*error = ErrorCode::kSuccess;
- uint64_t manifest_offset;
if (!IsHeaderParsed()) {
- // Ensure we have data to cover the major payload version.
- if (payload.size() < kDeltaManifestSizeOffset)
- return kMetadataParseInsufficientData;
+ MetadataParseResult result = payload_metadata_.ParsePayloadHeader(
+ payload, supported_major_version_, error);
+ if (result != MetadataParseResult::kSuccess)
+ return result;
- // Validate the magic string.
- if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {
- LOG(ERROR) << "Bad payload format -- invalid delta magic.";
- *error = ErrorCode::kDownloadInvalidMetadataMagicString;
- return kMetadataParseError;
- }
-
- // 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 kMetadataParseError;
- }
-
- // Get the manifest offset now that we have payload version.
- if (!GetManifestOffset(&manifest_offset)) {
- *error = ErrorCode::kUnsupportedMajorPayloadVersion;
- return kMetadataParseError;
- }
- // Check again with the manifest offset.
- if (payload.size() < manifest_offset)
- return kMetadataParseInsufficientData;
-
- // 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 kMetadataParseError;
- }
- memcpy(&metadata_signature_size_,
- &payload[metadata_signature_size_offset],
- kDeltaMetadataSignatureSizeSize);
- metadata_signature_size_ = be32toh(metadata_signature_size_);
- }
+ metadata_size_ = payload_metadata_.GetMetadataSize();
+ metadata_signature_size_ = payload_metadata_.GetMetadataSignatureSize();
+ major_payload_version_ = payload_metadata_.GetMajorVersion();
// If the metadata size is present in install plan, check for it immediately
// even before waiting for that many number of bytes to be downloaded in the
// payload. This will prevent any attack which relies on us downloading data
// beyond the expected metadata size.
- metadata_size_ = manifest_offset + manifest_size_;
if (install_plan_->hash_checks_mandatory) {
if (payload_->metadata_size != metadata_size_) {
LOG(ERROR) << "Mandatory metadata size in Omaha response ("
<< payload_->metadata_size
<< ") is missing/incorrect, actual = " << metadata_size_;
*error = ErrorCode::kDownloadInvalidMetadataSize;
- return kMetadataParseError;
+ return MetadataParseResult::kError;
}
}
}
@@ -613,7 +511,7 @@
// Now that we have validated the metadata size, we should wait for the full
// metadata and its signature (if exist) to be read in before we can parse it.
if (payload.size() < metadata_size_ + metadata_signature_size_)
- return kMetadataParseInsufficientData;
+ return MetadataParseResult::kInsufficientData;
// Log whether we validated the size or simply trusting what's in the payload
// here. This is logged here (after we received the full metadata data) so
@@ -630,15 +528,25 @@
<< "Trusting metadata size in payload = " << metadata_size_;
}
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
// We have the full metadata in |payload|. Verify its integrity
// and authenticity based on the information we have in Omaha response.
- *error = ValidateMetadataSignature(payload);
+ *error = payload_metadata_.ValidateMetadataSignature(
+ payload, payload_->metadata_signature, path_to_public_key);
if (*error != ErrorCode::kSuccess) {
if (install_plan_->hash_checks_mandatory) {
// The autoupdate_CatchBadSignatures test checks for this string
// in log-files. Keep in sync.
LOG(ERROR) << "Mandatory metadata signature validation failed";
- return kMetadataParseError;
+ return MetadataParseResult::kError;
}
// For non-mandatory cases, just send a UMA stat.
@@ -646,19 +554,15 @@
*error = ErrorCode::kSuccess;
}
- if (!GetManifestOffset(&manifest_offset)) {
- *error = ErrorCode::kUnsupportedMajorPayloadVersion;
- return kMetadataParseError;
- }
// The payload metadata is deemed valid, it's safe to parse the protobuf.
- if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_)) {
+ if (!payload_metadata_.GetManifest(payload, &manifest_)) {
LOG(ERROR) << "Unable to parse manifest in update file.";
*error = ErrorCode::kDownloadManifestParseError;
- return kMetadataParseError;
+ return MetadataParseResult::kError;
}
manifest_parsed_ = true;
- return kMetadataParseSuccess;
+ return MetadataParseResult::kSuccess;
}
#define OP_DURATION_HISTOGRAM(_op_name, _start_time) \
@@ -690,9 +594,9 @@
metadata_size_ + metadata_signature_size_));
MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
- if (result == kMetadataParseError)
+ if (result == MetadataParseResult::kError)
return false;
- if (result == kMetadataParseInsufficientData) {
+ if (result == MetadataParseResult::kInsufficientData) {
// If we just processed the header, make an attempt on the manifest.
if (do_read_header && IsHeaderParsed())
continue;
@@ -1591,93 +1495,6 @@
return true;
}
-ErrorCode DeltaPerformer::ValidateMetadataSignature(
- const brillo::Blob& payload) {
- if (payload.size() < metadata_size_ + metadata_signature_size_)
- return ErrorCode::kDownloadMetadataSignatureError;
-
- brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
- if (!payload_->metadata_signature.empty()) {
- // Convert base64-encoded signature to raw bytes.
- if (!brillo::data_encoding::Base64Decode(payload_->metadata_signature,
- &metadata_signature_blob)) {
- LOG(ERROR) << "Unable to decode base64 metadata signature: "
- << payload_->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()) {
- if (install_plan_->hash_checks_mandatory) {
- LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
- << "response and payload.";
- return ErrorCode::kDownloadMetadataSignatureMissingError;
- }
-
- LOG(WARNING) << "Cannot validate metadata as the signature is empty";
- return ErrorCode::kSuccess;
- }
-
- // See if we should use the public RSA key in the Omaha response.
- base::FilePath path_to_public_key(public_key_path_);
- base::FilePath tmp_key;
- if (GetPublicKeyFromResponse(&tmp_key))
- path_to_public_key = tmp_key;
- ScopedPathUnlinker tmp_key_remover(tmp_key.value());
- if (tmp_key.empty())
- tmp_key_remover.set_should_remove(false);
-
- 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;
-}
-
ErrorCode DeltaPerformer::ValidateManifest() {
// Perform assorted checks to sanity check the manifest, make sure it
// matches data from other sources, and that it is a supported version.
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index 48b6aa5..55a19d8 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -33,6 +33,7 @@
#include "update_engine/payload_consumer/file_descriptor.h"
#include "update_engine/payload_consumer/file_writer.h"
#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_consumer/payload_metadata.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
@@ -47,18 +48,6 @@
class DeltaPerformer : public FileWriter {
public:
- enum MetadataParseResult {
- kMetadataParseSuccess,
- kMetadataParseError,
- kMetadataParseInsufficientData,
- };
-
- static const uint64_t kDeltaVersionOffset;
- static const uint64_t kDeltaVersionSize;
- static const uint64_t kDeltaManifestSizeOffset;
- static const uint64_t kDeltaManifestSizeSize;
- static const uint64_t kDeltaMetadataSignatureSizeSize;
- static const uint64_t kMaxPayloadHeaderSize;
static const uint64_t kSupportedMajorPayloadVersion;
static const uint32_t kSupportedMinorPayloadVersion;
@@ -169,30 +158,9 @@
public_key_path_ = public_key_path;
}
- // Set |*out_offset| to the byte offset where the size of the metadata
- // signature is stored in a payload. Return true on success, if this field is
- // not present in the payload, return false.
- bool GetMetadataSignatureSizeOffset(uint64_t* out_offset) const;
-
- // Set |*out_offset| to the byte offset at which the manifest protobuf begins
- // in a payload. Return true on success, false if the offset is unknown.
- bool GetManifestOffset(uint64_t* out_offset) const;
-
- // Returns the size of the payload metadata, which includes the payload header
- // and the manifest. If the header was not yet parsed, returns zero.
- uint64_t GetMetadataSize() const;
-
- // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
- // Returns true on success.
- bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
-
// Return true if header parsing is finished and no errors occurred.
bool IsHeaderParsed() const;
- // Returns the major payload version. If the version was not yet parsed,
- // returns zero.
- uint64_t GetMajorVersion() const;
-
// Returns the delta minor version. If this value is defined in the manifest,
// it returns that value, otherwise it returns the default value.
uint32_t GetMinorVersion() const;
@@ -201,7 +169,7 @@
friend class DeltaPerformerTest;
friend class DeltaPerformerIntegrationTest;
FRIEND_TEST(DeltaPerformerTest, BrilloMetadataSignatureSizeTest);
- FRIEND_TEST(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest);
+ FRIEND_TEST(DeltaPerformerTest, BrilloParsePayloadMetadataTest);
FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
// Parse and move the update instructions of all partitions into our local
@@ -239,16 +207,6 @@
// Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
ErrorCode ValidateOperationHash(const InstallOperation& operation);
- // Given the |payload|, verifies that the signed hash of its metadata matches
- // what's specified in the install plan from Omaha (if present) or the
- // metadata signature in payload itself (if present). Returns
- // ErrorCode::kSuccess on match or a suitable error code otherwise. This
- // method must be called before any part of the metadata is parsed so that a
- // man-in-the-middle attack on the SSL connection to the payload server
- // doesn't exploit any vulnerability in the code that parses the protocol
- // buffer.
- ErrorCode ValidateMetadataSignature(const brillo::Blob& payload);
-
// Returns true on success.
bool PerformInstallOperation(const InstallOperation& operation);
@@ -345,13 +303,14 @@
std::string source_path_;
std::string target_path_;
+ PayloadMetadata payload_metadata_;
+
// Parsed manifest. Set after enough bytes to parse the manifest were
// downloaded.
DeltaArchiveManifest manifest_;
bool manifest_parsed_{false};
bool manifest_valid_{false};
uint64_t metadata_size_{0};
- uint64_t manifest_size_{0};
uint32_t metadata_signature_size_{0};
uint64_t major_payload_version_{0};
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index 5e1d10f..0f19041 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -337,20 +337,20 @@
install_plan_.hash_checks_mandatory = hash_checks_mandatory;
- DeltaPerformer::MetadataParseResult expected_result, actual_result;
+ MetadataParseResult expected_result, actual_result;
ErrorCode expected_error, actual_error;
// Fill up the metadata signature in install plan according to the test.
switch (metadata_signature_test) {
case kEmptyMetadataSignature:
payload_.metadata_signature.clear();
- expected_result = DeltaPerformer::kMetadataParseError;
+ expected_result = MetadataParseResult::kError;
expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
break;
case kInvalidMetadataSignature:
payload_.metadata_signature = kBogusMetadataSignature1;
- expected_result = DeltaPerformer::kMetadataParseError;
+ expected_result = MetadataParseResult::kError;
expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
break;
@@ -365,14 +365,14 @@
GetBuildArtifactsPath(kUnittestPrivateKeyPath),
&payload_.metadata_signature));
EXPECT_FALSE(payload_.metadata_signature.empty());
- expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_result = MetadataParseResult::kSuccess;
expected_error = ErrorCode::kSuccess;
break;
}
// Ignore the expected result/error if hash checks are not mandatory.
if (!hash_checks_mandatory) {
- expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_result = MetadataParseResult::kSuccess;
expected_error = ErrorCode::kSuccess;
}
@@ -392,7 +392,7 @@
// Check that the parsed metadata size is what's expected. This test
// implicitly confirms that the metadata signature is valid, if required.
- EXPECT_EQ(payload_.metadata_size, performer_.GetMetadataSize());
+ EXPECT_EQ(payload_.metadata_size, performer_.metadata_size_);
}
// Helper function to pretend that the ECC file descriptor was already opened.
@@ -831,29 +831,21 @@
EXPECT_LT(performer_.Close(), 0);
EXPECT_TRUE(performer_.IsHeaderParsed());
- EXPECT_EQ(kBrilloMajorPayloadVersion, performer_.GetMajorVersion());
- uint64_t manifest_offset;
- EXPECT_TRUE(performer_.GetManifestOffset(&manifest_offset));
- EXPECT_EQ(24U, manifest_offset); // 4 + 8 + 8 + 4
- EXPECT_EQ(manifest_offset + manifest_size, performer_.GetMetadataSize());
+ EXPECT_EQ(kBrilloMajorPayloadVersion, performer_.major_payload_version_);
+ EXPECT_EQ(24 + manifest_size, performer_.metadata_size_); // 4 + 8 + 8 + 4
EXPECT_EQ(metadata_signature_size, performer_.metadata_signature_size_);
}
-TEST_F(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest) {
+TEST_F(DeltaPerformerTest, BrilloParsePayloadMetadataTest) {
brillo::Blob payload_data = GeneratePayload({}, {}, true,
kBrilloMajorPayloadVersion,
kSourceMinorPayloadVersion);
install_plan_.hash_checks_mandatory = true;
- // Just set these value so that we can use ValidateMetadataSignature directly.
- performer_.major_payload_version_ = kBrilloMajorPayloadVersion;
- performer_.metadata_size_ = payload_.metadata_size;
- uint64_t signature_length;
- EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
- {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}, &signature_length));
- performer_.metadata_signature_size_ = signature_length;
performer_.set_public_key_path(GetBuildArtifactsPath(kUnittestPublicKeyPath));
- EXPECT_EQ(ErrorCode::kSuccess,
- performer_.ValidateMetadataSignature(payload_data));
+ ErrorCode error;
+ EXPECT_EQ(MetadataParseResult::kSuccess,
+ performer_.ParsePayloadMetadata(payload_data, &error));
+ EXPECT_EQ(ErrorCode::kSuccess, error);
}
TEST_F(DeltaPerformerTest, BadDeltaMagicTest) {
diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc
index 29daddc..e679316 100644
--- a/payload_consumer/payload_constants.cc
+++ b/payload_consumer/payload_constants.cc
@@ -28,6 +28,8 @@
const uint32_t kBrotliBsdiffMinorPayloadVersion = 4;
const uint32_t kPuffdiffMinorPayloadVersion = 5;
+const uint64_t kMaxPayloadHeaderSize = 24;
+
const char kLegacyPartitionNameKernel[] = "boot";
const char kLegacyPartitionNameRoot[] = "system";
diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h
index 9d29afd..ac3e882 100644
--- a/payload_consumer/payload_constants.h
+++ b/payload_consumer/payload_constants.h
@@ -49,6 +49,9 @@
// The minor version that allows PUFFDIFF operation.
extern const uint32_t kPuffdiffMinorPayloadVersion;
+// The maximum size of the payload header (anything before the protobuf).
+extern const uint64_t kMaxPayloadHeaderSize;
+
// The kernel and rootfs partition names used by the BootControlInterface when
// handling update payloads with a major version 1. The names of the updated
// partitions are include in the payload itself for major version 2.
diff --git a/payload_consumer/payload_metadata.cc b/payload_consumer/payload_metadata.cc
new file mode 100644
index 0000000..fe2df0a
--- /dev/null
+++ b/payload_consumer/payload_metadata.cc
@@ -0,0 +1,216 @@
+//
+// 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
diff --git a/payload_consumer/payload_metadata.h b/payload_consumer/payload_metadata.h
new file mode 100644
index 0000000..e00b5c1
--- /dev/null
+++ b/payload_consumer/payload_metadata.h
@@ -0,0 +1,108 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_METADATA_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_METADATA_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+enum class MetadataParseResult {
+ kSuccess,
+ kError,
+ kInsufficientData,
+};
+
+// This class parses payload metadata and validate its signature.
+class PayloadMetadata {
+ public:
+ static const uint64_t kDeltaVersionOffset;
+ static const uint64_t kDeltaVersionSize;
+ static const uint64_t kDeltaManifestSizeOffset;
+ static const uint64_t kDeltaManifestSizeSize;
+ static const uint64_t kDeltaMetadataSignatureSizeSize;
+
+ PayloadMetadata() = default;
+
+ // Attempts to parse the update payload header starting from the beginning of
+ // |payload|. On success, returns kMetadataParseSuccess. Returns
+ // kMetadataParseInsufficientData if more data is needed to parse the complete
+ // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+ // the payload.
+ MetadataParseResult ParsePayloadHeader(const brillo::Blob& payload,
+ uint64_t supported_major_version,
+ ErrorCode* error);
+
+ // Given the |payload|, verifies that the signed hash of its metadata matches
+ // |metadata_signature| (if present) or the metadata signature in payload
+ // itself (if present). Returns ErrorCode::kSuccess on match or a suitable
+ // error code otherwise. This method must be called before any part of the
+ // metadata is parsed so that a man-in-the-middle attack on the SSL connection
+ // to the payload server doesn't exploit any vulnerability in the code that
+ // parses the protocol buffer.
+ ErrorCode ValidateMetadataSignature(const brillo::Blob& payload,
+ std::string metadata_signature,
+ base::FilePath path_to_public_key) const;
+
+ // Returns the major payload version. If the version was not yet parsed,
+ // returns zero.
+ uint64_t GetMajorVersion() const { return major_payload_version_; }
+
+ // Returns the size of the payload metadata, which includes the payload header
+ // and the manifest. If the header was not yet parsed, returns zero.
+ uint64_t GetMetadataSize() const { return metadata_size_; }
+
+ // Returns the size of the payload metadata signature. If the header was not
+ // yet parsed, returns zero.
+ uint32_t GetMetadataSignatureSize() const { return metadata_signature_size_; }
+
+ // Set |*out_manifest| to the manifest in |payload|.
+ // Returns true on success.
+ bool GetManifest(const brillo::Blob& payload,
+ DeltaArchiveManifest* out_manifest) const;
+
+ private:
+ // Set |*out_offset| to the byte offset at which the manifest protobuf begins
+ // in a payload. Return true on success, false if the offset is unknown.
+ bool GetManifestOffset(uint64_t* out_offset) const;
+
+ // Set |*out_offset| to the byte offset where the size of the metadata
+ // signature is stored in a payload. Return true on success, if this field is
+ // not present in the payload, return false.
+ bool GetMetadataSignatureSizeOffset(uint64_t* out_offset) const;
+
+ uint64_t metadata_size_{0};
+ uint64_t manifest_size_{0};
+ uint32_t metadata_signature_size_{0};
+ uint64_t major_payload_version_{0};
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadMetadata);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_METADATA_H_