Generate and validate per-operation hashes

As part of securing the HTTP-based updates, we want to add a SHA256 hash
of the data blob for each operation so that they can't be tampered with
by a man in the middle. This CL adds support for generating and
including such hashes for each operation in the payload as well as
validating them in update_engine, if present.

BUG=chromium-os:34298
TEST=Tested on ZGB to make sure existing functionality works fine.
     Existing unit tests cover all the new code paths.
Change-Id: Ie42ed1930a66ceaf183f36ce3af0dea719e44237
Reviewed-on: https://gerrit.chromium.org/gerrit/33389
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Ready: Jay Srinivasan <jaysri@chromium.org>
Tested-by: Jay Srinivasan <jaysri@chromium.org>
diff --git a/delta_diff_generator.cc b/delta_diff_generator.cc
index 06952c6..26f3133 100644
--- a/delta_diff_generator.cc
+++ b/delta_diff_generator.cc
@@ -1178,6 +1178,9 @@
     ssize_t rc = pread(in_fd, &buf[0], buf.size(), op->data_offset());
     TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
 
+    // Add the hash of the data blobs for this operation
+    TEST_AND_RETURN_FALSE(AddOperationHash(op, buf));
+
     op->set_data_offset(out_file_size);
     TEST_AND_RETURN_FALSE(writer.Write(&buf[0], buf.size()));
     out_file_size += buf.size();
@@ -1185,6 +1188,19 @@
   return true;
 }
 
+bool DeltaDiffGenerator::AddOperationHash(
+    DeltaArchiveManifest_InstallOperation* op,
+    const vector<char>& buf) {
+  OmahaHashCalculator hasher;
+
+  TEST_AND_RETURN_FALSE(hasher.Update(&buf[0], buf.size()));
+  TEST_AND_RETURN_FALSE(hasher.Finalize());
+
+  const vector<char>& hash = hasher.raw_hash();
+  op->set_data_sha256_hash(hash.data(), hash.size());
+  return true;
+}
+
 bool DeltaDiffGenerator::ConvertCutToFullOp(Graph* graph,
                                             const CutEdgeVertexes& cut,
                                             const string& new_root,
diff --git a/delta_diff_generator.h b/delta_diff_generator.h
index b646545..f4717e7 100644
--- a/delta_diff_generator.h
+++ b/delta_diff_generator.h
@@ -170,6 +170,15 @@
                                const std::string& data_blobs_path,
                                const std::string& new_data_blobs_path);
 
+  // Computes a SHA256 hash of the given buf and sets the hash value in the
+  // operation so that update_engine could verify. This hash should be set
+  // for all operations that have a non-zero data blob. One exception is the
+  // dummy operation for signature blob because the contents of the signature
+  // blob will not be available at payload creation time. So, update_engine will
+  // gracefully ignore the dummy signature operation.
+  static bool AddOperationHash(DeltaArchiveManifest_InstallOperation* op,
+                               const std::vector<char>& buf);
+
   // Handles allocation of temp blocks to a cut edge by converting the
   // dest node to a full op. This removes the need for temp blocks, but
   // comes at the cost of a worse compression ratio.
diff --git a/delta_performer.cc b/delta_performer.cc
index 46739b1..d0a38e0 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -340,19 +340,37 @@
       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;
+    bool should_log = (next_operation_num_ % 1000 == 0 ||
+                       next_operation_num_ == total_operations - 1);
+
+    // Validate the operation only if the manifest signature is present.
+    // Otherwise, keep the old behavior. This serves as a knob to disable
+    // the validation logic in case we find some regression after rollout.
+    if (!install_plan_->manifest_signature.empty()) {
+      // Note: Validate must be called only if CanPerformInstallOperation is
+      // called. Otherwise, we might be failing operations before even if there
+      // isn't sufficient data to compute the proper hash.
+      *error = ValidateOperationHash(op, should_log);
+      if (*error != kActionCodeSuccess) {
+        // Cannot proceed further as operation hash is invalid.
+        // Higher level code will take care of retrying appropriately.
+        //
+        // TODO(jaysri): Add a UMA stat to indicate that an operation hash
+        // was failed to be validated as expected.
+        //
+        // TODO(jaysri): VALIDATION: For now, we don't treat this as fatal.
+        // But once we're confident that the new code works fine in the field,
+        // we should uncomment the line below.
+        // return false;
+        LOG(INFO) << "Ignoring operation validation errors for now";
+      }
     }
 
     // Makes sure we unblock exit when this operation completes.
     ScopedTerminatorExitUnblocker exit_unblocker =
         ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
     // Log every thousandth operation, and also the first and last ones
-    if ((next_operation_num_ % 1000 == 0) ||
-        (next_operation_num_ + 1 == total_operations)) {
+    if (should_log) {
       LOG(INFO) << "Performing operation " << (next_operation_num_ + 1) << "/"
                 << total_operations;
     }
@@ -719,10 +737,70 @@
 }
 
 ActionExitCode DeltaPerformer::ValidateOperationHash(
-    const DeltaArchiveManifest_InstallOperation&
-    operation) {
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool should_log) {
 
-  // TODO(jaysri): To be implemented.
+  if (!operation.data_sha256_hash().size()) {
+    if (!operation.data_length()) {
+      // Operations that do not have any data blob won't have any operation hash
+      // either. So, these operations are always considered validated since the
+      // manifest that contains all the non-data-blob portions of the operation
+      // has already been validated.
+      return kActionCodeSuccess;
+    }
+
+    // TODO(jaysri): Add a UMA stat here so we're aware of any
+    // man-in-the-middle attempts to bypass these checks.
+    //
+    // TODO(jaysri): VALIDATION: no hash is present for the operation. This
+    // shouldn't happen normally for any client that has this code, because the
+    // corresponding update should have been produced with the operation
+    // hashes. But if it happens it's likely that we've turned this feature off
+    // in Omaha rule for some reason. Once we make these hashes mandatory, we
+    // should return an error here.
+    // One caveat though: The last operation is a dummy signature operation
+    // that doesn't have a hash at the time the manifest is created. So we
+    // should not complaint about that operation. This operation can be
+    // recognized by the fact that it's offset is mentioned in the manifest.
+    if (manifest_.signatures_offset() &&
+        manifest_.signatures_offset() == operation.data_offset()) {
+      LOG(INFO) << "Skipping hash verification for signature operation "
+                << next_operation_num_ + 1;
+    } else {
+      // TODO(jaysri): Uncomment this logging after fixing dev server
+      // LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+      //             << " as no expected hash present";
+    }
+    return kActionCodeSuccess;
+  }
+
+  vector<char> expected_op_hash;
+  expected_op_hash.assign(operation.data_sha256_hash().data(),
+                          (operation.data_sha256_hash().data() +
+                           operation.data_sha256_hash().size()));
+
+  OmahaHashCalculator operation_hasher;
+  operation_hasher.Update(&buffer_[0], operation.data_length());
+  if (!operation_hasher.Finalize()) {
+    LOG(ERROR) << "Unable to compute actual hash of operation "
+               << next_operation_num_;
+    return kActionCodeDownloadOperationHashVerificationError;
+  }
+
+  vector<char> calculated_op_hash = operation_hasher.raw_hash();
+  if (calculated_op_hash != expected_op_hash) {
+    LOG(ERROR) << "Hash verification failed for operation "
+               << next_operation_num_ << ". Expected hash = ";
+    utils::HexDumpVector(expected_op_hash);
+    LOG(ERROR) << "Calculated hash over " << operation.data_length()
+               << " bytes at offset: " << operation.data_offset() << " = ";
+    utils::HexDumpVector(calculated_op_hash);
+    return kActionCodeDownloadOperationHashMismatch;
+  }
+
+  if (should_log)
+    LOG(INFO) << "Validated operation " << next_operation_num_ + 1;
+
   return kActionCodeSuccess;
 }
 
diff --git a/delta_performer.h b/delta_performer.h
index 8ea30c3..a9d70e7 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -155,7 +155,7 @@
   // 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);
+      const DeltaArchiveManifest_InstallOperation& operation, bool should_log);
 
   // Interprets the given |protobuf| as a DeltaArchiveManifest protocol buffer
   // of the given protobuf_length and verifies that the signed hash of the