Improve logging when generating delta. am: 84f507bec3
am: 61bff2aeb0

Change-Id: Id97bf3bf4bbeec795ce22976bdfc48ab4edbae6a
diff --git a/Android.mk b/Android.mk
index 59216db..529c1f4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -143,6 +143,7 @@
     payload_consumer/install_plan.cc \
     payload_consumer/mount_history.cc \
     payload_consumer/payload_constants.cc \
+    payload_consumer/payload_metadata.cc \
     payload_consumer/payload_verifier.cc \
     payload_consumer/postinstall_runner_action.cc \
     payload_consumer/xz_extent_writer.cc
diff --git a/binder_bindings/android/os/IUpdateEngine.aidl b/binder_bindings/android/os/IUpdateEngine.aidl
index 7e26752..c0e29f5 100644
--- a/binder_bindings/android/os/IUpdateEngine.aidl
+++ b/binder_bindings/android/os/IUpdateEngine.aidl
@@ -37,4 +37,6 @@
   void cancel();
   /** @hide */
   void resetStatus();
+  /** @hide */
+  boolean verifyPayloadApplicable(in String metadataFilename);
 }
diff --git a/binder_service_android.cc b/binder_service_android.cc
index 0305727..1702ead 100644
--- a/binder_service_android.cc
+++ b/binder_service_android.cc
@@ -139,6 +139,20 @@
   return Status::ok();
 }
 
+Status BinderUpdateEngineAndroidService::verifyPayloadApplicable(
+    const android::String16& metadata_filename, bool* return_value) {
+  const std::string payload_metadata{
+      android::String8{metadata_filename}.string()};
+  LOG(INFO) << "Received a request of verifying payload metadata in "
+            << payload_metadata << ".";
+  brillo::ErrorPtr error;
+  *return_value =
+      service_delegate_->VerifyPayloadApplicable(payload_metadata, &error);
+  if (error != nullptr)
+    return ErrorPtrToStatus(error);
+  return Status::ok();
+}
+
 bool BinderUpdateEngineAndroidService::UnbindCallback(const IBinder* callback) {
   auto it = std::find_if(
       callbacks_.begin(),
diff --git a/binder_service_android.h b/binder_service_android.h
index eb36e4c..694b80a 100644
--- a/binder_service_android.h
+++ b/binder_service_android.h
@@ -65,6 +65,8 @@
   android::binder::Status resume() override;
   android::binder::Status cancel() override;
   android::binder::Status resetStatus() override;
+  android::binder::Status verifyPayloadApplicable(
+      const android::String16& metadata_filename, bool* return_value) override;
 
  private:
   // Remove the passed |callback| from the list of registered callbacks. Called
diff --git a/common/error_code.h b/common/error_code.h
index c7e4967..f7f75a9 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -74,7 +74,7 @@
   kUserCanceled = 48,
   kNonCriticalUpdateInOOBE = 49,
   // kOmahaUpdateIgnoredOverCellular = 50,
-  // kPayloadTimestampError = 51,
+  kPayloadTimestampError = 51,
   kUpdatedButNotActive = 52,
   kNoUpdate = 53,
 
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 5efc120..8b75329 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -144,6 +144,8 @@
       return "ErrorCode::kUserCanceled";
     case ErrorCode::kNonCriticalUpdateInOOBE:
       return "ErrorCode::kNonCriticalUpdateInOOBE";
+    case ErrorCode::kPayloadTimestampError:
+      return "ErrorCode::kPayloadTimestampError";
     case ErrorCode::kUpdatedButNotActive:
       return "ErrorCode::kUpdatedButNotActive";
     case ErrorCode::kNoUpdate:
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 01d23d0..f2b2c9d 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -81,6 +81,8 @@
     return false;
   }
 
+  int64_t GetBuildTimestamp() const override { return build_timestamp_; }
+
   bool GetFirstActiveOmahaPingSent() const override {
     return first_active_omaha_ping_sent_;
   }
@@ -133,6 +135,10 @@
     powerwash_count_ = powerwash_count;
   }
 
+  void SetBuildTimestamp(int64_t build_timestamp) {
+    build_timestamp_ = build_timestamp;
+  }
+
  private:
   bool is_official_build_{true};
   bool is_normal_boot_mode_{true};
@@ -145,6 +151,7 @@
   std::string ec_version_{"Fake EC v1.0a"};
   int powerwash_count_{kPowerwashCountNotSet};
   bool powerwash_scheduled_{false};
+  int64_t build_timestamp_{0};
   bool first_active_omaha_ping_sent_{false};
 
   DISALLOW_COPY_AND_ASSIGN(FakeHardware);
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 541a68a..94442d1 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 #define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 
+#include <stdint.h>
+
 #include <string>
 #include <vector>
 
@@ -90,6 +92,9 @@
   // returns false.
   virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
 
+  // Returns the timestamp of the current OS build.
+  virtual int64_t GetBuildTimestamp() const = 0;
+
   // Returns whether the first active ping was sent to Omaha at some point, and
   // that the value is persisted across recovery (and powerwash) once set with
   // |SetFirstActiveOmahaPingSent()|.
diff --git a/hardware_android.cc b/hardware_android.cc
index c0b87ce..cc052b2 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -34,6 +34,7 @@
 #include "update_engine/utils_android.h"
 
 using android::base::GetBoolProperty;
+using android::base::GetIntProperty;
 using android::base::GetProperty;
 using std::string;
 
@@ -55,6 +56,7 @@
 const char kPropProductManufacturer[] = "ro.product.manufacturer";
 const char kPropBootHardwareSKU[] = "ro.boot.hardware.sku";
 const char kPropBootRevision[] = "ro.boot.revision";
+const char kPropBuildDateUTC[] = "ro.build.date.utc";
 
 // Write a recovery command line |message| to the BCB. The arguments to recovery
 // must be separated by '\n'. An empty string will erase the BCB.
@@ -193,6 +195,10 @@
   return false;
 }
 
+int64_t HardwareAndroid::GetBuildTimestamp() const {
+  return GetIntProperty<int64_t>(kPropBuildDateUTC, 0);
+}
+
 bool HardwareAndroid::GetFirstActiveOmahaPingSent() const {
   LOG(WARNING) << "STUB: Assuming first active omaha was never set.";
   return false;
diff --git a/hardware_android.h b/hardware_android.h
index 37393ce..ca90b62 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -47,6 +47,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
   void SetFirstActiveOmahaPingSent() override;
 
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 8c19aa7..f2bb28a 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -231,6 +231,11 @@
   return true;
 }
 
+int64_t HardwareChromeOS::GetBuildTimestamp() const {
+  // TODO(senj): implement this in Chrome OS.
+  return 0;
+}
+
 void HardwareChromeOS::LoadConfig(const string& root_prefix, bool normal_mode) {
   brillo::KeyValueStore store;
 
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index 3a0bba2..0cf1214 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -51,6 +51,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
   void SetFirstActiveOmahaPingSent() override;
 
diff --git a/metrics_utils.cc b/metrics_utils.cc
index f87828f..40deda8 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -78,6 +78,7 @@
     case ErrorCode::kDownloadPayloadVerificationError:
     case ErrorCode::kSignedDeltaPayloadExpectedError:
     case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kPayloadTimestampError:
       return metrics::AttemptResult::kPayloadVerificationFailed;
 
     case ErrorCode::kNewRootfsVerificationError:
@@ -214,6 +215,7 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
+    case ErrorCode::kPayloadTimestampError:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
       break;
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 133c7e6..a80092b 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>
 
@@ -65,13 +64,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;
 
@@ -422,39 +414,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();
@@ -465,96 +424,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;
       }
     }
   }
@@ -562,7 +460,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
@@ -579,15 +477,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.
@@ -595,19 +503,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)      \
@@ -639,9 +543,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;
@@ -1076,15 +980,10 @@
   return true;
 }
 
-namespace {
-
-// Compare |calculated_hash| with source hash in |operation|, return false and
-// dump hash and set |error| if don't match.
-// |source_fd| is the file descriptor of the source partition.
-bool ValidateSourceHash(const brillo::Blob& calculated_hash,
-                        const InstallOperation& operation,
-                        const FileDescriptorPtr source_fd,
-                        ErrorCode* error) {
+bool DeltaPerformer::ValidateSourceHash(const brillo::Blob& calculated_hash,
+                                        const InstallOperation& operation,
+                                        const FileDescriptorPtr source_fd,
+                                        ErrorCode* error) {
   brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
                                     operation.src_sha256_hash().end());
   if (calculated_hash != expected_source_hash) {
@@ -1119,8 +1018,6 @@
   return true;
 }
 
-}  // namespace
-
 bool DeltaPerformer::PerformSourceCopyOperation(
     const InstallOperation& operation, ErrorCode* error) {
   if (operation.has_src_length())
@@ -1489,93 +1386,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.
@@ -1638,6 +1448,14 @@
     }
   }
 
+  if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) {
+    LOG(ERROR) << "The current OS build timestamp ("
+               << hardware_->GetBuildTimestamp()
+               << ") is newer than the maximum timestamp in the manifest ("
+               << manifest_.max_timestamp() << ")";
+    return ErrorCode::kPayloadTimestampError;
+  }
+
   // TODO(garnold) we should be adding more and more manifest checks, such as
   // partition boundaries etc (see chromium-os:37661).
 
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index 7076b4f..36c6e34 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;
 
@@ -165,39 +154,26 @@
     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;
 
+  // Compare |calculated_hash| with source hash in |operation|, return false and
+  // dump hash and set |error| if don't match.
+  // |source_fd| is the file descriptor of the source partition.
+  static bool ValidateSourceHash(const brillo::Blob& calculated_hash,
+                                 const InstallOperation& operation,
+                                 const FileDescriptorPtr source_fd,
+                                 ErrorCode* error);
+
  private:
   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
@@ -235,16 +211,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);
 
@@ -325,13 +291,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 5835fe8..99c19f9 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -318,20 +318,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;
 
@@ -346,14 +346,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;
     }
 
@@ -373,7 +373,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_);
   }
 
   void SetSupportedMajorVersion(uint64_t major_version) {
@@ -722,6 +722,20 @@
                         ErrorCode::kUnsupportedMinorPayloadVersion);
 }
 
+TEST_F(DeltaPerformerTest, ValidateManifestDowngrade) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  manifest.set_minor_version(kFullPayloadMinorVersion);
+  manifest.set_max_timestamp(1);
+  fake_hardware_.SetBuildTimestamp(2);
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadTimestampError);
+}
+
 TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
   unsigned int seed = time(nullptr);
   EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
@@ -740,29 +754,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_
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index f6409a2..1f86313 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -335,6 +335,10 @@
   DEFINE_string(properties_file, "",
                 "If passed, dumps the payload properties of the payload passed "
                 "in --in_file and exits.");
+  DEFINE_int64(max_timestamp,
+               0,
+               "The maximum timestamp of the OS allowed to apply this "
+               "payload.");
 
   DEFINE_string(old_channel, "",
                 "The channel for the old image. 'dev-channel', 'npo-channel', "
@@ -576,6 +580,8 @@
     LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
   }
 
+  payload_config.max_timestamp = FLAGS_max_timestamp;
+
   LOG(INFO) << "Generating " << (payload_config.is_delta ? "delta" : "full")
             << " update";
 
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 941b640..f48d2a2 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -73,6 +73,7 @@
     *(manifest_.mutable_new_image_info()) = config.target.image_info;
 
   manifest_.set_block_size(config.block_size);
+  manifest_.set_max_timestamp(config.max_timestamp);
   return true;
 }
 
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index d64bf35..358a76d 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -186,6 +186,9 @@
 
   // The block size used for all the operations in the manifest.
   size_t block_size = 4096;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  int64_t max_timestamp = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
index 824195d..0b47dd4 100644
--- a/payload_generator/payload_signer.cc
+++ b/payload_generator/payload_signer.cc
@@ -18,6 +18,8 @@
 
 #include <endian.h>
 
+#include <utility>
+
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_split.h>
@@ -248,7 +250,7 @@
   TEST_AND_RETURN_FALSE(payload_file);
   brillo::Blob payload_metadata;
 
-  payload_metadata.resize(DeltaPerformer::kMaxPayloadHeaderSize);
+  payload_metadata.resize(kMaxPayloadHeaderSize);
   TEST_AND_RETURN_FALSE(payload_file->ReadAllBlocking(
       payload_metadata.data(), payload_metadata.size(), nullptr));
 
diff --git a/payload_state.cc b/payload_state.cc
index 48cbb05..5b643b7 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -302,6 +302,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       IncrementUrlIndex();
       break;
 
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index 61ecadf..65c63f5 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -167,6 +167,10 @@
     "Optional: Path to a source image. If specified, this makes a delta update."
   DEFINE_string metadata_size_file "" \
     "Optional: Path to output metadata size."
+  DEFINE_string max_timestamp "" \
+    "Optional: The maximum unix timestamp of the OS allowed to apply this \
+payload, should be set to a number higher than the build timestamp of the \
+system running on the device, 0 if not specified."
 fi
 if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
   DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
@@ -567,6 +571,10 @@
     GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
   fi
 
+  if [[ -n "${FLAGS_max_timestamp}" ]]; then
+    GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" )
+  fi
+
   if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then
     GENERATOR_ARGS+=(
       --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}"
diff --git a/service_delegate_android_interface.h b/service_delegate_android_interface.h
index 7dae40f..5267bb0 100644
--- a/service_delegate_android_interface.h
+++ b/service_delegate_android_interface.h
@@ -70,6 +70,12 @@
   // of error, returns false and sets |error| accordingly.
   virtual bool ResetStatus(brillo::ErrorPtr* error) = 0;
 
+  // Verifies whether a payload (delegated by the payload metadata) can be
+  // applied to the current device. Returns whether the payload is applicable.
+  // In case of error, returns false and sets |error| accordingly.
+  virtual bool VerifyPayloadApplicable(const std::string& metadata_filename,
+                                       brillo::ErrorPtr* error) = 0;
+
  protected:
   ServiceDelegateAndroidInterface() = default;
 };
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 4ec58d0..7a4d34c 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -29,16 +29,23 @@
 #include <brillo/data_encoding.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/strings/string_utils.h>
+#include <log/log_safetynet.h>
 
 #include "update_engine/common/constants.h"
+#include "update_engine/common/error_code_utils.h"
 #include "update_engine/common/file_fetcher.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/daemon_state_interface.h"
 #include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/metrics_utils.h"
 #include "update_engine/network_selector.h"
+#include "update_engine/payload_consumer/delta_performer.h"
 #include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_metadata.h"
 #include "update_engine/payload_consumer/postinstall_runner_action.h"
 #include "update_engine/update_status_utils.h"
 
@@ -326,6 +333,96 @@
   }
 }
 
+bool UpdateAttempterAndroid::VerifyPayloadApplicable(
+    const std::string& metadata_filename, brillo::ErrorPtr* error) {
+  FileDescriptorPtr fd(new EintrSafeFileDescriptor);
+  if (!fd->Open(metadata_filename.c_str(), O_RDONLY)) {
+    return LogAndSetError(
+        error, FROM_HERE, "Failed to open " + metadata_filename);
+  }
+  brillo::Blob metadata(kMaxPayloadHeaderSize);
+  if (!fd->Read(metadata.data(), metadata.size())) {
+    return LogAndSetError(
+        error,
+        FROM_HERE,
+        "Failed to read payload header from " + metadata_filename);
+  }
+  ErrorCode errorcode;
+  PayloadMetadata payload_metadata;
+  if (payload_metadata.ParsePayloadHeader(
+          metadata, kBrilloMajorPayloadVersion, &errorcode) !=
+      MetadataParseResult::kSuccess) {
+    return LogAndSetError(error,
+                          FROM_HERE,
+                          "Failed to parse payload header: " +
+                              utils::ErrorCodeToString(errorcode));
+  }
+  metadata.resize(payload_metadata.GetMetadataSize() +
+                  payload_metadata.GetMetadataSignatureSize());
+  if (metadata.size() < kMaxPayloadHeaderSize) {
+    return LogAndSetError(
+        error,
+        FROM_HERE,
+        "Metadata size too small: " + std::to_string(metadata.size()));
+  }
+  if (!fd->Read(metadata.data() + kMaxPayloadHeaderSize,
+                metadata.size() - kMaxPayloadHeaderSize)) {
+    return LogAndSetError(
+        error,
+        FROM_HERE,
+        "Failed to read metadata and signature from " + metadata_filename);
+  }
+  fd->Close();
+  errorcode = payload_metadata.ValidateMetadataSignature(
+      metadata, "", base::FilePath(constants::kUpdatePayloadPublicKeyPath));
+  if (errorcode != ErrorCode::kSuccess) {
+    return LogAndSetError(error,
+                          FROM_HERE,
+                          "Failed to validate metadata signature: " +
+                              utils::ErrorCodeToString(errorcode));
+  }
+  DeltaArchiveManifest manifest;
+  if (!payload_metadata.GetManifest(metadata, &manifest)) {
+    return LogAndSetError(error, FROM_HERE, "Failed to parse manifest.");
+  }
+
+  BootControlInterface::Slot current_slot = boot_control_->GetCurrentSlot();
+  for (const PartitionUpdate& partition : manifest.partitions()) {
+    if (!partition.has_old_partition_info())
+      continue;
+    string partition_path;
+    if (!boot_control_->GetPartitionDevice(
+            partition.partition_name(), current_slot, &partition_path)) {
+      return LogAndSetError(
+          error,
+          FROM_HERE,
+          "Failed to get partition device for " + partition.partition_name());
+    }
+    if (!fd->Open(partition_path.c_str(), O_RDONLY)) {
+      return LogAndSetError(
+          error, FROM_HERE, "Failed to open " + partition_path);
+    }
+    for (const InstallOperation& operation : partition.operations()) {
+      if (!operation.has_src_sha256_hash())
+        continue;
+      brillo::Blob source_hash;
+      if (!fd_utils::ReadAndHashExtents(fd,
+                                        operation.src_extents(),
+                                        manifest.block_size(),
+                                        &source_hash)) {
+        return LogAndSetError(
+            error, FROM_HERE, "Failed to hash " + partition_path);
+      }
+      if (!DeltaPerformer::ValidateSourceHash(
+              source_hash, operation, fd, &errorcode)) {
+        return false;
+      }
+    }
+    fd->Close();
+  }
+  return true;
+}
+
 void UpdateAttempterAndroid::ProcessingDone(const ActionProcessor* processor,
                                             ErrorCode code) {
   LOG(INFO) << "Processing Done.";
@@ -350,6 +447,11 @@
       LOG(INFO) << "Resetting update progress.";
       break;
 
+    case ErrorCode::kPayloadTimestampError:
+      // SafetyNet logging, b/36232423
+      android_errorWriteLog(0x534e4554, "36232423");
+      break;
+
     default:
       // Ignore all other error codes.
       break;
diff --git a/update_attempter_android.h b/update_attempter_android.h
index 28bf90a..f00692e 100644
--- a/update_attempter_android.h
+++ b/update_attempter_android.h
@@ -69,6 +69,8 @@
   bool ResumeUpdate(brillo::ErrorPtr* error) override;
   bool CancelUpdate(brillo::ErrorPtr* error) override;
   bool ResetStatus(brillo::ErrorPtr* error) override;
+  bool VerifyPayloadApplicable(const std::string& metadata_filename,
+                               brillo::ErrorPtr* error) override;
 
   // ActionProcessorDelegate methods:
   void ProcessingDone(const ActionProcessor* processor,
diff --git a/update_engine.gyp b/update_engine.gyp
index dc47da4..ba46266 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -183,6 +183,7 @@
         'payload_consumer/install_plan.cc',
         'payload_consumer/mount_history.cc',
         'payload_consumer/payload_constants.cc',
+        'payload_consumer/payload_metadata.cc',
         'payload_consumer/payload_verifier.cc',
         'payload_consumer/postinstall_runner_action.cc',
         'payload_consumer/xz_extent_writer.cc',
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index ae2ec9d..0947603 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -85,6 +85,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       LOG(INFO) << "Advancing download URL due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
diff --git a/update_metadata.proto b/update_metadata.proto
index fe81efb..a0f278b 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -290,4 +290,8 @@
   // array can have more than two partitions if needed, and they are identified
   // by the partition name.
   repeated PartitionUpdate partitions = 13;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  // Can be used to prevent downgrading the OS.
+  optional int64 max_timestamp = 14;
 }