Verify AU payload manifest signature if present.
In order to support downloads over http for a number of reasons, we need
to secure http downloads. The first step in this process is to
verify the signature of the manifest itself before parsing. This can be
done even for https-based downloads in order to provide defense-in-depth
against a SSL attack. This CL adds the required verification logic in
update_engine, if such a manifest signature is present in the Omaha
response.
Until the delta generator is modified in a subsequent check-in to update
the manifest and payload with the required signature, none of this new
code will have any effect.
The delta generator change to populate non-zero values for these new
fields will follow in subsequent CLs.
BUG=chromium-os:33602
TEST=Tested on ZGB to make sure existing functionality works fine.
Added new unit tests.
Change-Id: I2d8b09c23faf87049893b1dee97a34e1f300aded
Reviewed-on: https://gerrit.chromium.org/gerrit/32844
Commit-Ready: Jay Srinivasan <jaysri@chromium.org>
Reviewed-by: Jay Srinivasan <jaysri@chromium.org>
Tested-by: Jay Srinivasan <jaysri@chromium.org>
diff --git a/action_processor.h b/action_processor.h
index 256648d..3c9c262 100644
--- a/action_processor.h
+++ b/action_processor.h
@@ -34,8 +34,8 @@
kActionCodeInstallDeviceOpenError = 7,
kActionCodeKernelDeviceOpenError = 8,
kActionCodeDownloadTransferError = 9,
- kActionCodeDownloadHashMismatchError = 10,
- kActionCodeDownloadSizeMismatchError = 11,
+ kActionCodePayloadHashMismatchError = 10,
+ kActionCodePayloadSizeMismatchError = 11,
kActionCodeDownloadPayloadVerificationError = 12,
kActionCodeDownloadNewPartitionInfoError = 13,
kActionCodeDownloadWriteError = 14,
@@ -44,6 +44,16 @@
kActionCodeSignedDeltaPayloadExpectedError = 17,
kActionCodeDownloadPayloadPubKeyVerificationError = 18,
kActionCodePostinstallBootedFromFirmwareB = 19,
+ kActionCodeDownloadStateInitializationError = 20,
+ kActionCodeDownloadInvalidManifest = 21,
+ kActionCodeDownloadSignatureMissingInManifest = 22,
+ kActionCodeDownloadManifestParseError = 23,
+ kActionCodeDownloadManifestSignatureError = 24,
+ kActionCodeDownloadManifestSignatureVerificationError = 25,
+ kActionCodeDownloadManifestSignatureMismatch = 26,
+ kActionCodeDownloadOperationHashVerificationError = 27,
+ kActionCodeDownloadOperationExecutionError = 28,
+ kActionCodeDownloadOperationHashMismatch = 29,
// use the 2xx range for errors in Omaha response.
kActionCodeOmahaRequestEmptyResponseError = 200,
diff --git a/delta_performer.cc b/delta_performer.cc
index 4d6df80..46739b1 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -199,16 +199,28 @@
DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
const std::vector<char>& payload,
DeltaArchiveManifest* manifest,
- uint64_t* metadata_size) {
- if (payload.size() < strlen(kDeltaMagic) +
- kDeltaVersionLength + kDeltaProtobufLengthLength) {
+ uint64_t* metadata_size,
+ ActionExitCode* error) {
+ *error = kActionCodeSuccess;
+
+ const uint64_t protobuf_offset = strlen(kDeltaMagic) + kDeltaVersionLength +
+ kDeltaProtobufLengthLength;
+ if (payload.size() < protobuf_offset) {
// Don't have enough bytes to know the protobuf length.
return kMetadataParseInsufficientData;
}
+
+ // Validate the magic number.
if (memcmp(payload.data(), kDeltaMagic, strlen(kDeltaMagic)) != 0) {
LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+ *error = kActionCodeDownloadInvalidManifest;
return kMetadataParseError;
}
+
+ // TODO(jaysri): Compare the version number and skip unknown manifest
+ // versions. We don't check the version at all today.
+
+ // Next, parse the proto buf length.
uint64_t protobuf_length;
COMPILE_ASSERT(sizeof(protobuf_length) == kDeltaProtobufLengthLength,
protobuf_length_size_mismatch);
@@ -216,33 +228,83 @@
&payload[strlen(kDeltaMagic) + kDeltaVersionLength],
kDeltaProtobufLengthLength);
protobuf_length = be64toh(protobuf_length); // switch big endian to host
- if (payload.size() < strlen(kDeltaMagic) + kDeltaVersionLength +
- kDeltaProtobufLengthLength + protobuf_length) {
+
+ // Now, check if the metasize we computed matches what was passed in
+ // through Omaha Response.
+ *metadata_size = protobuf_offset + protobuf_length;
+
+ // If the manifest 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 manifest size.
+ if (install_plan_->manifest_size > 0 &&
+ install_plan_->manifest_size != *metadata_size) {
+ LOG(ERROR) << "Invalid manifest size. Expected = "
+ << install_plan_->manifest_size
+ << "Actual = " << *metadata_size;
+ // TODO(jaysri): Add a UMA Stat here to help with the decision to enforce
+ // this check in a future release, as mentioned below.
+
+ // TODO(jaysri): VALIDATION: Initially we don't want to make this a fatal
+ // error. But in the next release, we should uncomment the lines below.
+ // *error = kActionCodeDownloadInvalidManifest;
+ // return kMetadataParseError;
+ }
+
+ // Now that we have validated the metadata size, we should wait for the full
+ // metadata to be read in before we can parse it.
+ if (payload.size() < *metadata_size) {
return kMetadataParseInsufficientData;
}
- // We have the full proto buffer in |payload|. Parse it.
- const int offset = strlen(kDeltaMagic) + kDeltaVersionLength +
- kDeltaProtobufLengthLength;
- if (!manifest->ParseFromArray(&payload[offset], protobuf_length)) {
+
+ // Log whether we validated the size or simply trusting what's in the payload
+ // here. This is logged here (after we received the full manifest data) so
+ // that we just log once (instead of logging n times) if it takes n
+ // DeltaPerformer::Write calls to download the full manifest.
+ if (install_plan_->manifest_size == 0)
+ LOG(WARNING) << "No manifest size specified in Omaha. "
+ << "Trusting manifest size in payload = " << *metadata_size;
+ else
+ LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+
+ // We have the full proto buffer in |payload|. Verify its integrity
+ // and authenticity based on what Omaha says.
+ *error = ValidateManifestSignature(&payload[protobuf_offset],
+ protobuf_length);
+ if (*error != kActionCodeSuccess) {
+ // TODO(jaysri): Add a UMA Stat here to help with the decision to enforce
+ // this check in a future release, as mentioned below.
+
+ // TODO(jaysri): VALIDATION: Initially we don't want to make this a fatal
+ // error. But in the next release, we should remove the line below and
+ // return an error.
+ *error = kActionCodeSuccess;
+ }
+
+ // The proto buffer in |payload| is deemed valid. Parse it.
+ if (!manifest->ParseFromArray(&payload[protobuf_offset], protobuf_length)) {
LOG(ERROR) << "Unable to parse manifest in update file.";
+ *error = kActionCodeDownloadManifestParseError;
return kMetadataParseError;
}
- *metadata_size = strlen(kDeltaMagic) + kDeltaVersionLength +
- kDeltaProtobufLengthLength + protobuf_length;
return kMetadataParseSuccess;
}
// Wrapper around write. Returns true if all requested bytes
-// were written, or false on any error, reguardless of progress.
-bool DeltaPerformer::Write(const void* bytes, size_t count) {
+// were written, or false on any error, reguardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count,
+ ActionExitCode *error) {
+ *error = kActionCodeSuccess;
+
const char* c_bytes = reinterpret_cast<const char*>(bytes);
buffer_.insert(buffer_.end(), c_bytes, c_bytes + count);
-
if (!manifest_valid_) {
MetadataParseResult result = ParsePayloadMetadata(buffer_,
&manifest_,
- &manifest_metadata_size_);
+ &manifest_metadata_size_,
+ error);
if (result == kMetadataParseError) {
return false;
}
@@ -256,8 +318,10 @@
manifest_metadata_size_))
<< "Unable to save the manifest metadata size.";
manifest_valid_ = true;
+
LogPartitionInfo(manifest_);
if (!PrimeUpdateState()) {
+ *error = kActionCodeDownloadStateInitializationError;
LOG(ERROR) << "Unable to prime the update state.";
return false;
}
@@ -270,8 +334,19 @@
manifest_.install_operations(next_operation_num_) :
manifest_.kernel_install_operations(
next_operation_num_ - manifest_.install_operations_size());
- if (!CanPerformInstallOperation(op))
- break;
+ if (!CanPerformInstallOperation(op)) {
+ // This means we don't have enough bytes received yet to carry out the
+ // next operation.
+ return true;
+ }
+
+ *error = ValidateOperationHash(op);
+ if (*error != kActionCodeSuccess) {
+ // Cannot proceed further as operation hash is invalid.
+ // Higher level code will take care of retrying appropriately.
+ return false;
+ }
+
// Makes sure we unblock exit when this operation completes.
ScopedTerminatorExitUnblocker exit_unblocker =
ScopedTerminatorExitUnblocker(); // Avoids a compiler unused var bug.
@@ -288,18 +363,21 @@
if (!PerformReplaceOperation(op, is_kernel_partition)) {
LOG(ERROR) << "Failed to perform replace operation "
<< next_operation_num_;
+ *error = kActionCodeDownloadOperationExecutionError;
return false;
}
} else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
if (!PerformMoveOperation(op, is_kernel_partition)) {
LOG(ERROR) << "Failed to perform move operation "
<< next_operation_num_;
+ *error = kActionCodeDownloadOperationExecutionError;
return false;
}
} else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF) {
if (!PerformBsdiffOperation(op, is_kernel_partition)) {
LOG(ERROR) << "Failed to perform bsdiff operation "
<< next_operation_num_;
+ *error = kActionCodeDownloadOperationExecutionError;
return false;
}
}
@@ -582,6 +660,72 @@
return true;
}
+ActionExitCode DeltaPerformer::ValidateManifestSignature(
+ const char* protobuf, uint64_t protobuf_length) {
+
+ if (install_plan_->manifest_signature.empty()) {
+ // If this is not present, we cannot validate the manifest. This should
+ // never happen in normal circumstances, but this can be used as a
+ // release-knob to turn off the new code path that verify per-operation
+ // hashes. So, for now, we should not treat this as a failure. Once we are
+ // confident this path is bug-free, we should treat this as a failure so
+ // that we remain robust even if the connection to Omaha is subjected to
+ // any SSL attack.
+ LOG(WARNING) << "Cannot validate manifest as the signature is empty";
+ return kActionCodeSuccess;
+ }
+
+ // Convert base64-encoded signature to raw bytes.
+ vector<char> manifest_signature;
+ if (!OmahaHashCalculator::Base64Decode(install_plan_->manifest_signature,
+ &manifest_signature)) {
+ LOG(ERROR) << "Unable to decode base64 manifest signature: "
+ << install_plan_->manifest_signature;
+ return kActionCodeDownloadManifestSignatureError;
+ }
+
+ vector<char> expected_manifest_hash;
+ if (!PayloadSigner::GetRawHashFromSignature(manifest_signature,
+ public_key_path_,
+ &expected_manifest_hash)) {
+ LOG(ERROR) << "Unable to compute expected hash from manifest signature";
+ return kActionCodeDownloadManifestSignatureError;
+ }
+
+ OmahaHashCalculator manifest_hasher;
+ manifest_hasher.Update(protobuf, protobuf_length);
+ if (!manifest_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of manifest";
+ return kActionCodeDownloadManifestSignatureVerificationError;
+ }
+
+ vector<char> calculated_manifest_hash = manifest_hasher.raw_hash();
+ PayloadSigner::PadRSA2048SHA256Hash(&calculated_manifest_hash);
+ if (calculated_manifest_hash.empty()) {
+ LOG(ERROR) << "Computed actual hash of manifest is empty.";
+ return kActionCodeDownloadManifestSignatureVerificationError;
+ }
+
+ if (calculated_manifest_hash != expected_manifest_hash) {
+ LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_manifest_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_manifest_hash);
+ return kActionCodeDownloadManifestSignatureMismatch;
+ }
+
+ LOG(INFO) << "Manifest signature matches expected value in Omaha response";
+ return kActionCodeSuccess;
+}
+
+ActionExitCode DeltaPerformer::ValidateOperationHash(
+ const DeltaArchiveManifest_InstallOperation&
+ operation) {
+
+ // TODO(jaysri): To be implemented.
+ return kActionCodeSuccess;
+}
+
#define TEST_AND_RETURN_VAL(_retval, _condition) \
do { \
if (!(_condition)) { \
@@ -590,31 +734,25 @@
} \
} while (0);
-
ActionExitCode DeltaPerformer::VerifyPayload(
- const string& public_key_path,
const std::string& update_check_response_hash,
const uint64_t update_check_response_size) {
- string key_path = public_key_path;
- if (key_path.empty()) {
- key_path = kUpdatePayloadPublicKeyPath;
- }
- LOG(INFO) << "Verifying delta payload. Public key path: " << key_path;
+ LOG(INFO) << "Verifying delta payload using public key: " << public_key_path_;
// Verifies the download size.
- TEST_AND_RETURN_VAL(kActionCodeDownloadSizeMismatchError,
+ TEST_AND_RETURN_VAL(kActionCodePayloadSizeMismatchError,
update_check_response_size ==
manifest_metadata_size_ + buffer_offset_);
- // Verifies the download hash.
- const string& download_hash_data = hash_calculator_.hash();
+ // Verifies the payload hash.
+ const string& payload_hash_data = hash_calculator_.hash();
TEST_AND_RETURN_VAL(kActionCodeDownloadPayloadVerificationError,
- !download_hash_data.empty());
- TEST_AND_RETURN_VAL(kActionCodeDownloadHashMismatchError,
- download_hash_data == update_check_response_hash);
+ !payload_hash_data.empty());
+ TEST_AND_RETURN_VAL(kActionCodePayloadHashMismatchError,
+ payload_hash_data == update_check_response_hash);
// Verifies the signed payload hash.
- if (!utils::FileExists(key_path.c_str())) {
+ if (!utils::FileExists(public_key_path_.c_str())) {
LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
return kActionCodeSuccess;
}
@@ -624,7 +762,7 @@
TEST_AND_RETURN_VAL(kActionCodeDownloadPayloadPubKeyVerificationError,
PayloadSigner::VerifySignature(
signatures_message_data_,
- key_path,
+ public_key_path_,
&signed_hash_data));
OmahaHashCalculator signed_hasher;
TEST_AND_RETURN_VAL(kActionCodeDownloadPayloadPubKeyVerificationError,
@@ -708,17 +846,19 @@
bool DeltaPerformer::VerifySourcePartitions() {
LOG(INFO) << "Verifying source partitions.";
CHECK(manifest_valid_);
+ CHECK(install_plan_);
if (manifest_.has_old_kernel_info()) {
const PartitionInfo& info = manifest_.old_kernel_info();
- bool valid = !current_kernel_hash_.empty() &&
- current_kernel_hash_.size() == info.hash().size() &&
- memcmp(current_kernel_hash_.data(),
+ bool valid =
+ !install_plan_->kernel_hash.empty() &&
+ install_plan_->kernel_hash.size() == info.hash().size() &&
+ memcmp(install_plan_->kernel_hash.data(),
info.hash().data(),
- current_kernel_hash_.size()) == 0;
+ install_plan_->kernel_hash.size()) == 0;
if (!valid) {
LogVerifyError(true,
- StringForHashBytes(current_kernel_hash_.data(),
- current_kernel_hash_.size()),
+ StringForHashBytes(install_plan_->kernel_hash.data(),
+ install_plan_->kernel_hash.size()),
StringForHashBytes(info.hash().data(),
info.hash().size()));
}
@@ -726,15 +866,16 @@
}
if (manifest_.has_old_rootfs_info()) {
const PartitionInfo& info = manifest_.old_rootfs_info();
- bool valid = !current_rootfs_hash_.empty() &&
- current_rootfs_hash_.size() == info.hash().size() &&
- memcmp(current_rootfs_hash_.data(),
+ bool valid =
+ !install_plan_->rootfs_hash.empty() &&
+ install_plan_->rootfs_hash.size() == info.hash().size() &&
+ memcmp(install_plan_->rootfs_hash.data(),
info.hash().data(),
- current_rootfs_hash_.size()) == 0;
+ install_plan_->rootfs_hash.size()) == 0;
if (!valid) {
LogVerifyError(false,
- StringForHashBytes(current_kernel_hash_.data(),
- current_kernel_hash_.size()),
+ StringForHashBytes(install_plan_->kernel_hash.data(),
+ install_plan_->kernel_hash.size()),
StringForHashBytes(info.hash().data(),
info.hash().size()));
}
diff --git a/delta_performer.h b/delta_performer.h
index dd8f8d8..8ea30c3 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -13,6 +13,7 @@
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "update_engine/file_writer.h"
+#include "update_engine/install_plan.h"
#include "update_engine/omaha_hash_calculator.h"
#include "update_engine/update_metadata.pb.h"
@@ -33,8 +34,9 @@
static const char kUpdatePayloadPublicKeyPath[];
- DeltaPerformer(PrefsInterface* prefs)
+ DeltaPerformer(PrefsInterface* prefs, InstallPlan* install_plan)
: prefs_(prefs),
+ install_plan_(install_plan),
fd_(-1),
kernel_fd_(-1),
manifest_valid_(false),
@@ -42,7 +44,8 @@
next_operation_num_(0),
buffer_offset_(0),
last_updated_buffer_offset_(kuint64max),
- block_size_(0) {}
+ block_size_(0),
+ public_key_path_(kUpdatePayloadPublicKeyPath) {}
// Opens the kernel. Should be called before or after Open(), but before
// Write(). The kernel file will be close()d when Close() is called.
@@ -52,24 +55,29 @@
// Open()ed again.
int Open(const char* path, int flags, mode_t mode);
- // Wrapper around write. Returns true if all requested bytes
- // were written, or false on any error, reguardless of progress.
- bool Write(const void* bytes, size_t count);
+ // FileWriter's Write implementation where caller doesn't care about
+ // error codes.
+ bool Write(const void* bytes, size_t count) {
+ ActionExitCode error;
+ return Write(bytes, count, &error);
+ }
+
+ // FileWriter's Write implementation that returns a more specific |error| code
+ // in case of failures in Write operation.
+ bool Write(const void* bytes, size_t count, ActionExitCode *error);
// Wrapper around close. Returns 0 on success or -errno on error.
// Closes both 'path' given to Open() and the kernel path.
int Close();
// Verifies the downloaded payload against the signed hash included in the
- // payload, against the update check hash and size, and against the public
- // key and returns kActionCodeSuccess on success, an error code on failure.
- // This method should be called after closing the stream. Note this method
- // skips the signed hash check if the public key is unavailable; it returns
- // kActionCodeSignedDeltaPayloadExpectedError if the public key
- // is available but the delta payload doesn't include a signature. If
- // |public_key_path| is an empty string, uses the default public key path.
- ActionExitCode VerifyPayload(const std::string& public_key_path,
- const std::string& update_check_response_hash,
+ // payload, against the update check hash (which is in base64 format) and
+ // size using the public key and returns kActionCodeSuccess on success, an
+ // error code on failure. This method should be called after closing the
+ // stream. Note this method skips the signed hash check if the public key is
+ // unavailable; it returns kActionCodeSignedDeltaPayloadExpectedError if the
+ // public key is available but the delta payload doesn't include a signature.
+ ActionExitCode VerifyPayload(const std::string& update_check_response_hash,
const uint64_t update_check_response_size);
// Reads from the update manifest the expected sizes and hashes of the target
@@ -115,17 +123,14 @@
// 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.
- static MetadataParseResult ParsePayloadMetadata(
+ MetadataParseResult ParsePayloadMetadata(
const std::vector<char>& payload,
DeltaArchiveManifest* manifest,
- uint64_t* metadata_size);
+ uint64_t* metadata_size,
+ ActionExitCode* error);
- void set_current_kernel_hash(const std::vector<char>& hash) {
- current_kernel_hash_ = hash;
- }
-
- void set_current_rootfs_hash(const std::vector<char>& hash) {
- current_rootfs_hash_ = hash;
+ void set_public_key_path(const std::string& public_key_path) {
+ public_key_path_ = public_key_path;
}
private:
@@ -146,6 +151,23 @@
bool CanPerformInstallOperation(
const DeltaArchiveManifest_InstallOperation& operation);
+ // Validates that the hash of the blobs corresponding to the given |operation|
+ // matches what's specified in the manifest in the payload.
+ // Returns kActionCodeSuccess on match or a suitable error code otherwise.
+ ActionExitCode ValidateOperationHash(
+ const DeltaArchiveManifest_InstallOperation& operation);
+
+ // Interprets the given |protobuf| as a DeltaArchiveManifest protocol buffer
+ // of the given protobuf_length and verifies that the signed hash of the
+ // manifest matches what's specified in the install plan from Omaha.
+ // Returns kActionCodeSuccess on match or a suitable error code otherwise.
+ // This method must be called before any part of the |protobuf| 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.
+ ActionExitCode ValidateManifestSignature(const char* protobuf,
+ uint64_t protobuf_length);
+
// Returns true on success.
bool PerformInstallOperation(
const DeltaArchiveManifest_InstallOperation& operation);
@@ -182,6 +204,9 @@
// Update Engine preference store.
PrefsInterface* prefs_;
+ // Install Plan based on Omaha Response.
+ InstallPlan* install_plan_;
+
// File descriptor of open device.
int fd_;
@@ -221,10 +246,9 @@
// Signatures message blob extracted directly from the payload.
std::vector<char> signatures_message_data_;
- // Hashes for the current partitions to be used for source partition
- // verification.
- std::vector<char> current_kernel_hash_;
- std::vector<char> current_rootfs_hash_;
+ // The public key to be used. Provided as a member so that tests can
+ // override with test keys.
+ std::string public_key_path_;
DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
};
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index fbacb36..96b780c 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -465,18 +465,17 @@
}
// Update the A image in place.
- DeltaPerformer performer(&prefs);
+ InstallPlan install_plan;
+ DeltaPerformer performer(&prefs, &install_plan);
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ performer.set_public_key_path(kUnittestPublicKeyPath);
- vector<char> rootfs_hash;
EXPECT_EQ(image_size,
OmahaHashCalculator::RawHashOfFile(a_img,
image_size,
- &rootfs_hash));
- performer.set_current_rootfs_hash(rootfs_hash);
- vector<char> kernel_hash;
+ &install_plan.rootfs_hash));
EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(old_kernel_data,
- &kernel_hash));
- performer.set_current_kernel_hash(kernel_hash);
+ &install_plan.kernel_hash));
EXPECT_EQ(0, performer.Open(a_img.c_str(), 0, 0));
EXPECT_TRUE(performer.OpenKernel(old_kernel.c_str()));
@@ -499,7 +498,6 @@
EXPECT_EQ(0, strncmp(&updated_kernel_partition[0], new_data_string,
strlen(new_data_string)));
- EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
ActionExitCode expect_verify_result = kActionCodeSuccess;
switch (signature_test) {
case kSignatureNone:
@@ -511,11 +509,11 @@
default: break; // appease gcc
}
EXPECT_EQ(expect_verify_result, performer.VerifyPayload(
- kUnittestPublicKeyPath,
OmahaHashCalculator::OmahaHashOfData(delta),
delta.size()));
+
+ performer.set_public_key_path("/public/key/does/not/exists");
EXPECT_EQ(kActionCodeSuccess, performer.VerifyPayload(
- "/public/key/does/not/exists",
OmahaHashCalculator::OmahaHashOfData(delta),
delta.size()));
@@ -540,6 +538,279 @@
&expected_new_rootfs_hash));
EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
}
+
+// TODO(jaysri): Refactor the previous unit test so we can reuse a lot of
+// code between these two methods.
+void DoManifestTest() {
+ bool full_kernel = false;
+ bool full_rootfs = false;
+ string a_img, b_img;
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/a_img.XXXXXX", &a_img, NULL));
+ ScopedPathUnlinker a_img_unlinker(a_img);
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/b_img.XXXXXX", &b_img, NULL));
+ ScopedPathUnlinker b_img_unlinker(b_img);
+
+ CreateExtImageAtPath(a_img, NULL);
+
+ int image_size = static_cast<int>(utils::FileSize(a_img));
+
+ // Extend the "partitions" holding the file system a bit.
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=%d bs=1 count=1",
+ a_img.c_str(),
+ image_size + 1024 * 1024 - 1)));
+ EXPECT_EQ(image_size + 1024 * 1024, utils::FileSize(a_img));
+
+ // Make some changes to the A image.
+ {
+ string a_mnt;
+ ScopedLoopMounter b_mounter(a_img, &a_mnt, 0);
+
+ EXPECT_TRUE(utils::WriteFile(StringPrintf("%s/hardtocompress",
+ a_mnt.c_str()).c_str(),
+ reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString) - 1));
+ // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+ // patch fails to zero out the final block.
+ vector<char> ones(1024 * 1024, 0xff);
+ EXPECT_TRUE(utils::WriteFile(StringPrintf("%s/ones",
+ a_mnt.c_str()).c_str(),
+ &ones[0],
+ ones.size()));
+ }
+
+ {
+ CreateExtImageAtPath(b_img, NULL);
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=%d bs=1 count=1",
+ b_img.c_str(),
+ image_size + 1024 * 1024 - 1)));
+ EXPECT_EQ(image_size + 1024 * 1024, utils::FileSize(b_img));
+
+ // Make some changes to the B image.
+ string b_mnt;
+ ScopedLoopMounter b_mounter(b_img, &b_mnt, 0);
+
+ EXPECT_EQ(0, system(StringPrintf("cp %s/hello %s/hello2", b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("rm %s/hello", b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("mv %s/hello2 %s/hello", b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("echo foo > %s/foo",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("touch %s/emptyfile",
+ b_mnt.c_str()).c_str()));
+ EXPECT_TRUE(WriteSparseFile(StringPrintf("%s/fullsparse", b_mnt.c_str()),
+ 1024 * 1024));
+ EXPECT_EQ(0, system(StringPrintf("dd if=/dev/zero of=%s/partsparese bs=1 "
+ "seek=4096 count=1",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("cp %s/srchardlink0 %s/tmp && "
+ "mv %s/tmp %s/srchardlink1",
+ b_mnt.c_str(), b_mnt.c_str(),
+ b_mnt.c_str(), b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, system(StringPrintf("rm %s/boguslink && "
+ "echo foobar > %s/boguslink",
+ b_mnt.c_str(), b_mnt.c_str()).c_str()));
+ EXPECT_TRUE(utils::WriteFile(StringPrintf("%s/hardtocompress",
+ b_mnt.c_str()).c_str(),
+ reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString)));
+ }
+
+ string old_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/old_kernel.XXXXXX", &old_kernel, NULL));
+ ScopedPathUnlinker old_kernel_unlinker(old_kernel);
+
+ string new_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/new_kernel.XXXXXX", &new_kernel, NULL));
+ ScopedPathUnlinker new_kernel_unlinker(new_kernel);
+
+ vector<char> old_kernel_data(4096); // Something small for a test
+ vector<char> new_kernel_data(old_kernel_data.size());
+ FillWithData(&old_kernel_data);
+ FillWithData(&new_kernel_data);
+
+ // change the new kernel data
+ const char* new_data_string = "This is new data.";
+ strcpy(&new_kernel_data[0], new_data_string);
+
+ // Write kernels to disk
+ EXPECT_TRUE(utils::WriteFile(
+ old_kernel.c_str(), &old_kernel_data[0], old_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(
+ new_kernel.c_str(), &new_kernel_data[0], new_kernel_data.size()));
+
+ string delta_path;
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/delta.XXXXXX", &delta_path, NULL));
+ LOG(INFO) << "delta path: " << delta_path;
+ ScopedPathUnlinker delta_path_unlinker(delta_path);
+ {
+ string a_mnt, b_mnt;
+ ScopedLoopMounter a_mounter(a_img, &a_mnt, MS_RDONLY);
+ ScopedLoopMounter b_mounter(b_img, &b_mnt, MS_RDONLY);
+ const string private_key = kUnittestPrivateKeyPath;
+ EXPECT_TRUE(
+ DeltaDiffGenerator::GenerateDeltaUpdateFile(
+ full_rootfs ? "" : a_mnt,
+ full_rootfs ? "" : a_img,
+ b_mnt,
+ b_img,
+ full_kernel ? "" : old_kernel,
+ new_kernel,
+ delta_path,
+ private_key));
+ }
+
+ // Read delta into memory.
+ vector<char> delta;
+ EXPECT_TRUE(utils::ReadFile(delta_path, &delta));
+
+ uint64_t manifest_metadata_size;
+ const int kManifestSizeOffset = 12;
+ const int kManifestOffset = 20;
+ // Check the metadata.
+ {
+ LOG(INFO) << "delta size: " << delta.size();
+ DeltaArchiveManifest manifest;
+ uint64_t manifest_size = 0;
+ memcpy(&manifest_size, &delta[kManifestSizeOffset], sizeof(manifest_size));
+ manifest_size = be64toh(manifest_size);
+ LOG(INFO) << "manifest size: " << manifest_size;
+ EXPECT_TRUE(manifest.ParseFromArray(&delta[kManifestOffset],
+ manifest_size));
+ manifest_metadata_size = kManifestOffset + manifest_size;
+
+ {
+ EXPECT_TRUE(manifest.has_signatures_offset());
+ EXPECT_TRUE(manifest.has_signatures_size());
+ Signatures sigs_message;
+ EXPECT_TRUE(sigs_message.ParseFromArray(
+ &delta[manifest_metadata_size + manifest.signatures_offset()],
+ manifest.signatures_size()));
+ EXPECT_EQ(1, sigs_message.signatures_size());
+ const Signatures_Signature& signature = sigs_message.signatures(0);
+ EXPECT_EQ(1, signature.version());
+
+ uint64_t expected_sig_data_length = 0;
+ vector<string> key_paths (1, kUnittestPrivateKeyPath);
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ key_paths,
+ &expected_sig_data_length));
+ EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+ EXPECT_FALSE(signature.data().empty());
+ }
+
+ if (full_kernel) {
+ EXPECT_FALSE(manifest.has_old_kernel_info());
+ } else {
+ EXPECT_EQ(old_kernel_data.size(), manifest.old_kernel_info().size());
+ EXPECT_FALSE(manifest.old_kernel_info().hash().empty());
+ }
+
+ if (full_rootfs) {
+ EXPECT_FALSE(manifest.has_old_rootfs_info());
+ } else {
+ EXPECT_EQ(image_size, manifest.old_rootfs_info().size());
+ EXPECT_FALSE(manifest.old_rootfs_info().hash().empty());
+ }
+
+ EXPECT_EQ(new_kernel_data.size(), manifest.new_kernel_info().size());
+ EXPECT_EQ(image_size, manifest.new_rootfs_info().size());
+
+ EXPECT_FALSE(manifest.new_kernel_info().hash().empty());
+ EXPECT_FALSE(manifest.new_rootfs_info().hash().empty());
+
+ }
+
+ PrefsMock prefs;
+ EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+ manifest_metadata_size)).WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+ .WillOnce(Return(true));
+
+ // Update the A image in place.
+ InstallPlan install_plan;
+ install_plan.manifest_size = manifest_metadata_size;
+ LOG(INFO) << "Setting Omaha manifest size = " << manifest_metadata_size;
+ ASSERT_TRUE(PayloadSigner::GetManifestSignature(
+ &delta[kManifestOffset],
+ manifest_metadata_size-kManifestOffset,
+ kUnittestPrivateKeyPath,
+ &install_plan.manifest_signature));
+ EXPECT_FALSE(install_plan.manifest_signature.empty());
+
+ DeltaPerformer performer(&prefs, &install_plan);
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ performer.set_public_key_path(kUnittestPublicKeyPath);
+
+ EXPECT_EQ(image_size,
+ OmahaHashCalculator::RawHashOfFile(a_img,
+ image_size,
+ &install_plan.rootfs_hash));
+ EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(old_kernel_data,
+ &install_plan.kernel_hash));
+
+ EXPECT_EQ(0, performer.Open(a_img.c_str(), 0, 0));
+ EXPECT_TRUE(performer.OpenKernel(old_kernel.c_str()));
+
+ // Write at some number of bytes per operation. Arbitrarily chose 5.
+ const size_t kBytesPerWrite = 5;
+ for (size_t i = 0; i < delta.size(); i += kBytesPerWrite) {
+ size_t count = min(delta.size() - i, kBytesPerWrite);
+ EXPECT_TRUE(performer.Write(&delta[i], count));
+ }
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ EXPECT_EQ(0, performer.Close());
+
+ CompareFilesByBlock(old_kernel, new_kernel);
+ CompareFilesByBlock(a_img, b_img);
+
+ vector<char> updated_kernel_partition;
+ EXPECT_TRUE(utils::ReadFile(old_kernel, &updated_kernel_partition));
+ EXPECT_EQ(0, strncmp(&updated_kernel_partition[0], new_data_string,
+ strlen(new_data_string)));
+
+ ActionExitCode expect_verify_result = kActionCodeSuccess;
+ LOG(INFO) << "Verifying Payload ...";
+ EXPECT_EQ(expect_verify_result, performer.VerifyPayload(
+ OmahaHashCalculator::OmahaHashOfData(delta),
+ delta.size()));
+
+ LOG(INFO) << "Verified Payload";
+
+ uint64_t new_kernel_size;
+ vector<char> new_kernel_hash;
+ uint64_t new_rootfs_size;
+ vector<char> new_rootfs_hash;
+ EXPECT_TRUE(performer.GetNewPartitionInfo(&new_kernel_size,
+ &new_kernel_hash,
+ &new_rootfs_size,
+ &new_rootfs_hash));
+ EXPECT_EQ(4096, new_kernel_size);
+ vector<char> expected_new_kernel_hash;
+ EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(new_kernel_data,
+ &expected_new_kernel_hash));
+ EXPECT_TRUE(expected_new_kernel_hash == new_kernel_hash);
+ EXPECT_EQ(image_size, new_rootfs_size);
+ vector<char> expected_new_rootfs_hash;
+ EXPECT_EQ(image_size,
+ OmahaHashCalculator::RawHashOfFile(b_img,
+ image_size,
+ &expected_new_rootfs_hash));
+ EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
+}
} // namespace {}
TEST(DeltaPerformerTest, RunAsRootSmallImageTest) {
@@ -584,7 +855,8 @@
TEST(DeltaPerformerTest, BadDeltaMagicTest) {
PrefsMock prefs;
- DeltaPerformer performer(&prefs);
+ InstallPlan install_plan;
+ DeltaPerformer performer(&prefs, &install_plan);
EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
EXPECT_TRUE(performer.OpenKernel("/dev/null"));
EXPECT_TRUE(performer.Write("junk", 4));
@@ -609,4 +881,9 @@
EXPECT_FALSE(DeltaPerformer::IsIdempotentOperation(op));
}
+TEST(DeltaPerformerTest, RunAsRootRunValidManifestTest) {
+ DoManifestTest();
+}
+
+
} // namespace chromeos_update_engine
diff --git a/download_action.cc b/download_action.cc
index f84af33..8a18f43 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -44,9 +44,7 @@
if (writer_) {
LOG(INFO) << "Using writer for test.";
} else {
- delta_performer_.reset(new DeltaPerformer(prefs_));
- delta_performer_->set_current_kernel_hash(install_plan_.kernel_hash);
- delta_performer_->set_current_rootfs_hash(install_plan_.rootfs_hash);
+ delta_performer_.reset(new DeltaPerformer(prefs_, &install_plan_));
writer_ = delta_performer_.get();
}
int rc = writer_->Open(install_plan_.install_path.c_str(),
@@ -95,13 +93,13 @@
int length) {
bytes_received_ += length;
if (delegate_)
- delegate_->BytesReceived(bytes_received_, install_plan_.size);
- if (writer_ && !writer_->Write(bytes, length)) {
- LOG(ERROR) << "Write error -- terminating processing.";
+ delegate_->BytesReceived(bytes_received_, install_plan_.payload_size);
+ if (writer_ && !writer_->Write(bytes, length, &code_)) {
+ LOG(ERROR) << "Error " << code_ << " in DeltaPerformer's Write method when "
+ << "processing the received payload -- Terminating processing";
// Don't tell the action processor that the action is complete until we get
// the TransferTerminated callback. Otherwise, this and the HTTP fetcher
// objects may get destroyed before all callbacks are complete.
- code_ = kActionCodeDownloadWriteError;
TerminateProcessing();
return;
}
@@ -118,9 +116,8 @@
ActionExitCode code =
successful ? kActionCodeSuccess : kActionCodeDownloadTransferError;
if (code == kActionCodeSuccess && delta_performer_.get()) {
- code = delta_performer_->VerifyPayload("",
- install_plan_.download_hash,
- install_plan_.size);
+ code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+ install_plan_.payload_size);
if (code != kActionCodeSuccess) {
LOG(ERROR) << "Download of " << install_plan_.download_url
<< " failed due to payload verification error.";
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index 799d8af..fb9acf7 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -134,6 +134,8 @@
"",
size,
hash,
+ 0,
+ "",
output_temp_file.GetPath(),
"");
ObjectFeederAction<InstallPlan> feeder_action;
@@ -250,7 +252,7 @@
// takes ownership of passed in HttpFetcher
ObjectFeederAction<InstallPlan> feeder_action;
- InstallPlan install_plan(false, "", 0, "", temp_file.GetPath(), "");
+ InstallPlan install_plan(false, "", 0, "", 0, "", temp_file.GetPath(), "");
feeder_action.set_obj(install_plan);
PrefsMock prefs;
DownloadAction download_action(&prefs,
@@ -354,6 +356,8 @@
"",
1,
OmahaHashCalculator::OmahaHashOfString("x"),
+ 0,
+ "",
"/dev/null",
"/dev/null");
ObjectFeederAction<InstallPlan> feeder_action;
@@ -389,7 +393,7 @@
DirectFileWriter writer;
// takes ownership of passed in HttpFetcher
- InstallPlan install_plan(false, "", 0, "", path, "");
+ InstallPlan install_plan(false, "", 0, "", 0, "", path, "");
ObjectFeederAction<InstallPlan> feeder_action;
feeder_action.set_obj(install_plan);
PrefsMock prefs;
diff --git a/file_writer.h b/file_writer.h
index 5fd4c5a..b9b45e8 100644
--- a/file_writer.h
+++ b/file_writer.h
@@ -10,6 +10,7 @@
#include <fcntl.h>
#include <unistd.h>
#include "base/logging.h"
+#include "update_engine/action_processor.h"
#include "update_engine/utils.h"
// FileWriter is a class that is used to (synchronously, for now) write to
@@ -31,6 +32,17 @@
// were written, or false on any error, reguardless of progress.
virtual bool Write(const void* bytes, size_t count) = 0;
+ // Same as the Write method above but returns a detailed |error| code
+ // in addition if the returned value is false. By default this method
+ // returns kActionExitDownloadWriteError as the error code, but subclasses
+ // can override if they wish to return more specific error codes.
+ virtual bool Write(const void* bytes,
+ size_t count,
+ ActionExitCode* error) {
+ *error = kActionCodeDownloadWriteError;
+ return Write(bytes, count);
+ }
+
// Wrapper around close. Returns 0 on success or -errno on error.
virtual int Close() = 0;
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 62b39a6..25e9484 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -306,7 +306,7 @@
ObjectFeederAction<InstallPlan> feeder_action;
const char* kUrl = "http://some/url";
- InstallPlan install_plan(true, kUrl, 0, "", "", "");
+ InstallPlan install_plan(true, kUrl, 0, "", 0, "", "", "");
feeder_action.set_obj(install_plan);
FilesystemCopierAction copier_action(false, false);
ObjectCollectorAction<InstallPlan> collector_action;
@@ -335,6 +335,8 @@
"",
0,
"",
+ 0,
+ "",
"/no/such/file",
"/no/such/file");
feeder_action.set_obj(install_plan);
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
index 5a2dda0..f5fe5e7 100644
--- a/generate_delta_main.cc
+++ b/generate_delta_main.cc
@@ -142,6 +142,7 @@
LOG_IF(FATAL, FLAGS_old_image.empty())
<< "Must pass --old_image to apply delta.";
Prefs prefs;
+ InstallPlan install_plan;
LOG(INFO) << "Setting up preferences under: " << FLAGS_prefs_dir;
LOG_IF(ERROR, !prefs.Init(FilePath(FLAGS_prefs_dir)))
<< "Failed to initialize preferences.";
@@ -154,13 +155,11 @@
CHECK(DeltaDiffGenerator::InitializePartitionInfo(false, // is_kernel
FLAGS_old_image,
&root_info));
- vector<char> kern_hash(kern_info.hash().begin(),
- kern_info.hash().end());
- vector<char> root_hash(root_info.hash().begin(),
- root_info.hash().end());
- DeltaPerformer performer(&prefs);
- performer.set_current_kernel_hash(kern_hash);
- performer.set_current_rootfs_hash(root_hash);
+ install_plan.kernel_hash.assign(kern_info.hash().begin(),
+ kern_info.hash().end());
+ install_plan.rootfs_hash.assign(root_info.hash().begin(),
+ root_info.hash().end());
+ DeltaPerformer performer(&prefs, &install_plan);
CHECK_EQ(performer.Open(FLAGS_old_image.c_str(), 0, 0), 0);
CHECK(performer.OpenKernel(FLAGS_old_kernel.c_str()));
vector<char> buf(1024 * 1024);
diff --git a/install_plan.h b/install_plan.h
index 2418aab..67662b4 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -18,26 +18,33 @@
struct InstallPlan {
InstallPlan(bool is_resume,
const std::string& url,
- uint64_t size,
- const std::string& hash,
+ uint64_t payload_size,
+ const std::string& payload_hash,
+ uint64_t manifest_size,
+ const std::string& manifest_signature,
const std::string& install_path,
const std::string& kernel_install_path)
: is_resume(is_resume),
download_url(url),
- size(size),
- download_hash(hash),
+ payload_size(payload_size),
+ payload_hash(payload_hash),
+ manifest_size(manifest_size),
+ manifest_signature(manifest_signature),
install_path(install_path),
kernel_install_path(kernel_install_path),
kernel_size(0),
rootfs_size(0) {}
- InstallPlan() : is_resume(false), size(0) {}
+ InstallPlan() : is_resume(false), payload_size(0), manifest_size(0) {}
bool is_resume;
std::string download_url; // url to download from
- uint64_t size; // size of the download url's data
- std::string download_hash; // hash of the data at the url
- std::string install_path; // path to install device
- std::string kernel_install_path; // path to kernel install device
+
+ uint64_t payload_size; // size of the payload
+ std::string payload_hash ; // SHA256 hash of the payload
+ uint64_t manifest_size; // size of the manifest
+ std::string manifest_signature; // signature of the manifest
+ std::string install_path; // path to install device
+ std::string kernel_install_path; // path to kernel install device
// The fields below are used for kernel and rootfs verification. The flow is:
//
@@ -58,8 +65,10 @@
bool operator==(const InstallPlan& that) const {
return ((is_resume == that.is_resume) &&
(download_url == that.download_url) &&
- (size == that.size) &&
- (download_hash == that.download_hash) &&
+ (payload_size == that.payload_size) &&
+ (payload_hash == that.payload_hash) &&
+ (manifest_size == that.manifest_size) &&
+ (manifest_signature == that.manifest_signature) &&
(install_path == that.install_path) &&
(kernel_install_path == that.kernel_install_path));
}
@@ -70,8 +79,10 @@
LOG(INFO) << "InstallPlan: "
<< (is_resume ? ", resume" : ", new_update")
<< ", url: " << download_url
- << ", size: " << size
- << ", hash: " << download_hash
+ << ", payload size: " << payload_size
+ << ", payload hash: " << payload_hash
+ << ", manifest size: " << manifest_size
+ << ", manifest signature: " << manifest_signature
<< ", install_path: " << install_path
<< ", kernel_install_path: " << kernel_install_path;
}
diff --git a/omaha_hash_calculator.cc b/omaha_hash_calculator.cc
index de70d10..9fe82a2 100644
--- a/omaha_hash_calculator.cc
+++ b/omaha_hash_calculator.cc
@@ -19,6 +19,38 @@
namespace chromeos_update_engine {
+// Helper class to free a BIO structure when a method goes out of scope.
+class ScopedBioHandle {
+ public:
+ explicit ScopedBioHandle(BIO* bio) : bio_(bio) {}
+ ~ScopedBioHandle() {
+ FreeCurrentBio();
+ }
+
+ void set_bio(BIO* bio) {
+ if (bio_ != bio) {
+ // Free the current bio, but only if the caller is not trying to set
+ // the same bio object again, so that the operation can be idempotent.
+ FreeCurrentBio();
+ }
+ bio_ = bio;
+ }
+
+ BIO* bio() {
+ return bio_;
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedBioHandle);
+ BIO* bio_;
+
+ void FreeCurrentBio() {
+ if (bio_) {
+ BIO_free_all(bio_);
+ bio_ = NULL;
+ }
+ }
+};
+
OmahaHashCalculator::OmahaHashCalculator() : valid_(false) {
valid_ = (SHA256_Init(&ctx_) == 1);
LOG_IF(ERROR, !valid_) << "SHA256_Init failed";
@@ -69,10 +101,10 @@
bool success = true;
BIO *b64 = BIO_new(BIO_f_base64());
if (!b64)
- LOG(INFO) << "BIO_new(BIO_f_base64()) failed";
+ LOG(ERROR) << "BIO_new(BIO_f_base64()) failed";
BIO *bmem = BIO_new(BIO_s_mem());
if (!bmem)
- LOG(INFO) << "BIO_new(BIO_s_mem()) failed";
+ LOG(ERROR) << "BIO_new(BIO_s_mem()) failed";
if (b64 && bmem) {
b64 = BIO_push(b64, bmem);
success =
@@ -91,6 +123,33 @@
return success;
}
+bool OmahaHashCalculator::Base64Decode(const string& in,
+ vector<char>* out) {
+ ScopedBioHandle b64(BIO_new(BIO_f_base64()));
+ if (!b64.bio()) {
+ LOG(ERROR) << "Unable to create BIO object to decode base64 hash.";
+ return false;
+ }
+
+ BIO *bmem = BIO_new_mem_buf(const_cast<char*>(in.c_str()), in.size());
+ if (!bmem) {
+ LOG(ERROR) << "Unable to get BIO buffer to decode base64 hash.";
+ return false;
+ }
+
+ b64.set_bio(BIO_push(b64.bio(), bmem));
+ const int kOutBufferSize = 1024;
+ char out_buffer[kOutBufferSize];
+ int num_bytes_read = 1; // any non-zero value is fine to enter the loop.
+ while (num_bytes_read > 0) {
+ num_bytes_read = BIO_read(b64.bio(), &out_buffer, kOutBufferSize);
+ for (int i = 0; i < num_bytes_read; i++)
+ out->push_back(out_buffer[i]);
+ }
+
+ return true;
+}
+
// Call Finalize() when all data has been passed in. This mostly just
// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
bool OmahaHashCalculator::Finalize() {
diff --git a/omaha_hash_calculator.h b/omaha_hash_calculator.h
index 784326a..a9c59d4 100644
--- a/omaha_hash_calculator.h
+++ b/omaha_hash_calculator.h
@@ -74,8 +74,14 @@
static std::string OmahaHashOfString(const std::string& str);
static std::string OmahaHashOfData(const std::vector<char>& data);
+ // Encodes data of given size as a base64 out string
static bool Base64Encode(const void* data, size_t size, std::string* out);
+ // Decodes given base64-encoded in string into the out vector. Since the
+ // output can have null characters, we're returning a byte vector instead of
+ // a string.
+ static bool Base64Decode(const std::string& in, std::vector<char>* out);
+
private:
// If non-empty, the final base64 encoded hash and the raw hash. Will only be
// set to non-empty when Finalize is called.
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 24641cb..4b68c0d 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -494,6 +494,10 @@
output_object.more_info_url = XmlGetProperty(updatecheck_node, "MoreInfo");
output_object.hash = XmlGetProperty(updatecheck_node, "sha256");
output_object.size = ParseInt(XmlGetProperty(updatecheck_node, "size"));
+ output_object.manifest_size =
+ ParseInt(XmlGetProperty(updatecheck_node, "ManifestSize"));
+ output_object.manifest_signature =
+ XmlGetProperty(updatecheck_node, "ManifestSignatureRsa");
output_object.needs_admin =
XmlGetProperty(updatecheck_node, "needsadmin") == "true";
output_object.prompt = XmlGetProperty(updatecheck_node, "Prompt") == "true";
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 2bbbc71..e7dfb08 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -34,6 +34,7 @@
: update_exists(false),
poll_interval(0),
size(0),
+ manifest_size(0),
needs_admin(false),
prompt(false) {}
// True iff there is an update to be downloaded.
@@ -47,8 +48,10 @@
std::string codebase;
std::string more_info_url;
std::string hash;
+ std::string manifest_signature;
std::string deadline;
off_t size;
+ off_t manifest_size;
bool needs_admin;
bool prompt;
};
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index c83b328..a2c3405 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -34,8 +34,10 @@
return;
}
install_plan_.download_url = response.codebase;
- install_plan_.size = response.size;
- install_plan_.download_hash = response.hash;
+ install_plan_.payload_size = response.size;
+ install_plan_.payload_hash = response.hash;
+ install_plan_.manifest_size = response.manifest_size;
+ install_plan_.manifest_signature = response.manifest_signature;
install_plan_.is_resume =
DeltaPerformer::CanResumeUpdate(prefs_, response.hash);
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 9da48a6..bed0f04 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -104,7 +104,7 @@
InstallPlan install_plan;
EXPECT_TRUE(DoTest(in, "/dev/sda3", &install_plan));
EXPECT_EQ(in.codebase, install_plan.download_url);
- EXPECT_EQ(in.hash, install_plan.download_hash);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
EXPECT_EQ("/dev/sda5", install_plan.install_path);
string deadline;
EXPECT_TRUE(utils::ReadFile(
@@ -130,7 +130,7 @@
InstallPlan install_plan;
EXPECT_TRUE(DoTest(in, "/dev/sda5", &install_plan));
EXPECT_EQ(in.codebase, install_plan.download_url);
- EXPECT_EQ(in.hash, install_plan.download_hash);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
EXPECT_EQ("/dev/sda3", install_plan.install_path);
string deadline;
EXPECT_TRUE(utils::ReadFile(
@@ -151,7 +151,7 @@
InstallPlan install_plan;
EXPECT_TRUE(DoTest(in, "/dev/sda3", &install_plan));
EXPECT_EQ(in.codebase, install_plan.download_url);
- EXPECT_EQ(in.hash, install_plan.download_hash);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
EXPECT_EQ("/dev/sda5", install_plan.install_path);
string deadline;
EXPECT_TRUE(utils::ReadFile(
@@ -167,7 +167,7 @@
InstallPlan install_plan;
EXPECT_FALSE(DoTest(in, "/dev/sda1", &install_plan));
EXPECT_EQ("", install_plan.download_url);
- EXPECT_EQ("", install_plan.download_hash);
+ EXPECT_EQ("", install_plan.payload_hash);
EXPECT_EQ("", install_plan.install_path);
}
diff --git a/payload_signer.cc b/payload_signer.cc
index 88188a2..19a9230 100644
--- a/payload_signer.cc
+++ b/payload_signer.cc
@@ -56,7 +56,7 @@
uint32_t version = kSignatureMessageOriginalVersion;
LOG_IF(WARNING, kSignatureMessageCurrentVersion -
kSignatureMessageOriginalVersion + 1 < signatures.size())
- << "You may want to support clients in the rage ["
+ << "You may want to support clients in the range ["
<< kSignatureMessageOriginalVersion << ", "
<< kSignatureMessageCurrentVersion << "] inclusive, but you only "
<< "provided " << signatures.size() << " signatures.";
@@ -86,8 +86,11 @@
// Loads the payload and parses the manifest.
TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
LOG(INFO) << "Payload size: " << payload.size();
- TEST_AND_RETURN_FALSE(DeltaPerformer::ParsePayloadMetadata(
- payload, out_manifest, out_metadata_size) ==
+ ActionExitCode error = kActionCodeSuccess;
+ InstallPlan install_plan;
+ DeltaPerformer delta_performer(NULL, &install_plan);
+ TEST_AND_RETURN_FALSE(delta_performer.ParsePayloadMetadata(
+ payload, out_manifest, out_metadata_size, &error) ==
DeltaPerformer::kMetadataParseSuccess);
LOG(INFO) << "Metadata size: " << *out_metadata_size;
out_payload->swap(payload);
@@ -139,6 +142,7 @@
bool PayloadSigner::SignHash(const vector<char>& hash,
const string& private_key_path,
vector<char>* out_signature) {
+ LOG(INFO) << "Signing hash with private key: " << private_key_path;
string sig_path;
TEST_AND_RETURN_FALSE(
utils::MakeTempFile("/tmp/signature.XXXXXX", &sig_path, NULL));
@@ -217,11 +221,11 @@
bool PayloadSigner::VerifySignature(const std::vector<char>& signature_blob,
const std::string& public_key_path,
std::vector<char>* out_hash_data) {
- return VerifySignatureVersion(signature_blob, public_key_path,
+ return VerifySignatureBlob(signature_blob, public_key_path,
kSignatureMessageCurrentVersion, out_hash_data);
}
-bool PayloadSigner::VerifySignatureVersion(
+bool PayloadSigner::VerifySignatureBlob(
const std::vector<char>& signature_blob,
const std::string& public_key_path,
uint32_t client_version,
@@ -229,6 +233,7 @@
TEST_AND_RETURN_FALSE(!public_key_path.empty());
Signatures signatures;
+ LOG(INFO) << "signature size = " << signature_blob.size();
TEST_AND_RETURN_FALSE(signatures.ParseFromArray(&signature_blob[0],
signature_blob.size()));
@@ -244,7 +249,17 @@
TEST_AND_RETURN_FALSE(sig_index < signatures.signatures_size());
const Signatures_Signature& signature = signatures.signatures(sig_index);
- const string& sig_data = signature.data();
+ vector<char> sig_data(signature.data().begin(), signature.data().end());
+
+ return GetRawHashFromSignature(sig_data, public_key_path, out_hash_data);
+}
+
+
+bool PayloadSigner::GetRawHashFromSignature(
+ const std::vector<char>& sig_data,
+ const std::string& public_key_path,
+ std::vector<char>* out_hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
// The code below executes the equivalent of:
//
@@ -253,7 +268,11 @@
// Loads the public key.
FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
- TEST_AND_RETURN_FALSE(fpubkey != NULL);
+ if (!fpubkey) {
+ LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+ return false;
+ }
+
char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, NULL, NULL, dummy_password);
fclose(fpubkey);
@@ -298,7 +317,7 @@
payload.begin() + metadata_size + manifest.signatures_offset(),
payload.end());
vector<char> signed_hash;
- TEST_AND_RETURN_FALSE(VerifySignatureVersion(
+ TEST_AND_RETURN_FALSE(VerifySignatureBlob(
signature_blob, public_key_path, client_key_check_version, &signed_hash));
TEST_AND_RETURN_FALSE(!signed_hash.empty());
vector<char> hash;
@@ -369,4 +388,27 @@
return true;
}
+bool PayloadSigner::GetManifestSignature(const char* manifest,
+ size_t manifest_size,
+ const string& private_key_path,
+ string* out_signature) {
+ // Calculates the hash on the updated payload. Note that the payload includes
+ // the signature op but doesn't include the signature blob at the end.
+ vector<char> manifest_hash;
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(manifest,
+ manifest_size,
+ &manifest_hash));
+
+ vector<char> signature;
+ TEST_AND_RETURN_FALSE(SignHash(manifest_hash,
+ private_key_path,
+ &signature));
+
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::Base64Encode(&signature[0],
+ signature.size(),
+ out_signature));
+ return true;
+}
+
+
} // namespace chromeos_update_engine
diff --git a/payload_signer.h b/payload_signer.h
index 1e0db35..1c4a3eb 100644
--- a/payload_signer.h
+++ b/payload_signer.h
@@ -64,11 +64,22 @@
const std::string& public_key_path,
std::vector<char>* out_hash_data);
- // Same as previous function, but the client version can be set.
- static bool VerifySignatureVersion(const std::vector<char>& signature_blob,
- const std::string& public_key_path,
- uint32_t client_version,
- std::vector<char>* out_hash_data);
+ // Interprets signature_blob as a protocol buffer containing the Signatures
+ // message and decrypts the signature data using the public_key_path and
+ // stores the resultant raw hash data in out_hash_data. Returns true if
+ // everything is successful. False otherwise. It also takes the client_version
+ // and interprets the signature blob according to that version.
+ static bool VerifySignatureBlob(const std::vector<char>& signature_blob,
+ const std::string& public_key_path,
+ uint32_t client_version,
+ std::vector<char>* out_hash_data);
+
+ // Decrypts sig_data with the given public_key_path and populates
+ // out_hash_data with the decoded raw hash. Returns true if successful,
+ // false otherwise.
+ static bool GetRawHashFromSignature(const std::vector<char>& sig_data,
+ const std::string& public_key_path,
+ std::vector<char>* out_hash_data);
// Returns true if the payload in |payload_path| is signed and its hash can be
// verified using the public key in |public_key_path| with the signature
@@ -83,6 +94,12 @@
// will be modified in place and will result in having a length of
// 2048 bits. Returns true on success, false otherwise.
static bool PadRSA2048SHA256Hash(std::vector<char>* hash);
+
+ static bool GetManifestSignature(const char* manifest,
+ size_t manifest_size,
+ const std::string& private_key_path,
+ std::string* out_signature);
+
private:
// This should never be constructed
DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
diff --git a/update_attempter.cc b/update_attempter.cc
index 6bef755..ca93a9a 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -126,7 +126,7 @@
download_progress_(0.0),
last_checked_time_(0),
new_version_("0.0.0.0"),
- new_size_(0),
+ new_payload_size_(0),
proxy_manual_checks_(0),
obeying_proxies_(true),
chrome_proxy_resolver_(dbus_iface),
@@ -671,7 +671,7 @@
last_checked_time_ = time(NULL);
// TODO(adlr): put version in InstallPlan
new_version_ = "0.0.0.0";
- new_size_ = plan.size;
+ new_payload_size_ = plan.payload_size;
SetupDownload();
SetupPriorityManagement();
SetStatusAndNotify(UPDATE_STATUS_UPDATE_AVAILABLE,
@@ -748,12 +748,12 @@
double* progress,
string* current_operation,
string* new_version,
- int64_t* new_size) {
+ int64_t* new_payload_size) {
*last_checked_time = last_checked_time_;
*progress = download_progress_;
*current_operation = UpdateStatusToString(status_);
*new_version = new_version_;
- *new_size = new_size_;
+ *new_payload_size = new_payload_size_;
return true;
}
@@ -806,7 +806,7 @@
download_progress_,
UpdateStatusToString(status_),
new_version_.c_str(),
- new_size_);
+ new_payload_size_);
}
void UpdateAttempter::SetStatusAndNotify(UpdateStatus status,
@@ -980,7 +980,7 @@
int64_t next_data_offset = 0;
prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
uint64_t resume_offset = manifest_metadata_size + next_data_offset;
- if (resume_offset < response_handler_action_->install_plan().size) {
+ if (resume_offset < response_handler_action_->install_plan().payload_size) {
fetcher->AddRange(resume_offset);
}
} else {
diff --git a/update_attempter.h b/update_attempter.h
index 89e8144..40cf796 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -319,7 +319,7 @@
double download_progress_;
int64_t last_checked_time_;
std::string new_version_;
- int64_t new_size_;
+ int64_t new_payload_size_;
// Device paramaters common to all Omaha requests.
OmahaRequestDeviceParams omaha_request_params_;
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index c28c8ac..55be51d 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -63,7 +63,7 @@
EXPECT_EQ(0.0, attempter_.download_progress_);
EXPECT_EQ(0, attempter_.last_checked_time_);
EXPECT_EQ("0.0.0.0", attempter_.new_version_);
- EXPECT_EQ(0, attempter_.new_size_);
+ EXPECT_EQ(0, attempter_.new_payload_size_);
processor_ = new ActionProcessorMock();
attempter_.processor_.reset(processor_); // Transfers ownership.
attempter_.prefs_ = &prefs_;