Support for signing with multiple signature files, key sizes.

If we do a key-rotation in the future, we'll want to sign updates with
two keys. This CL changes the delta generator in a
backwards-compatible way to take multiple key lengths and signature
files: On a command line where one could be given before, now multiple
may be given by colon-delimiting them.

Also, adds two unittests to show that old and new clients can
successfully verify a payload when it's signed by old and new keys.

BUG=chromium-os:19873
TEST=unittests; tested on device

Change-Id: I2063095773a5c71c32704c30b12d6eab2a5f3b80
Reviewed-on: http://gerrit.chromium.org/gerrit/6999
Reviewed-by: Andrew de los Reyes <adlr@chromium.org>
Tested-by: Andrew de los Reyes <adlr@chromium.org>
diff --git a/SConstruct b/SConstruct
index d91b365..5fada88 100644
--- a/SConstruct
+++ b/SConstruct
@@ -227,6 +227,7 @@
                 'dbus-1 dbus-glib-1 gio-2.0 gio-unix-2.0 glib-2.0')
 env.ProtocolBuffer('update_metadata.pb.cc', 'update_metadata.proto')
 env.PublicKey('unittest_key.pub.pem', 'unittest_key.pem')
+env.PublicKey('unittest_key2.pub.pem', 'unittest_key2.pem')
 
 env.DbusBindings('update_engine.dbusclient.h', 'update_engine.xml')
 
diff --git a/delta_diff_generator.cc b/delta_diff_generator.cc
index 5c5b2ef..1c962f1 100644
--- a/delta_diff_generator.cc
+++ b/delta_diff_generator.cc
@@ -1460,7 +1460,7 @@
   if (!private_key_path.empty()) {
     uint64_t signature_blob_length = 0;
     TEST_AND_RETURN_FALSE(
-        PayloadSigner::SignatureBlobLength(private_key_path,
+        PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
                                            &signature_blob_length));
     AddSignatureOp(next_blob_offset, signature_blob_length, &manifest);
   }
@@ -1523,9 +1523,10 @@
   if (!private_key_path.empty()) {
     LOG(INFO) << "Signing the update...";
     vector<char> signature_blob;
-    TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(output_path,
-                                                     private_key_path,
-                                                     &signature_blob));
+    TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
+        output_path,
+        vector<string>(1, private_key_path),
+        &signature_blob));
     TEST_AND_RETURN_FALSE(writer.Write(&signature_blob[0],
                                        signature_blob.size()) ==
                           static_cast<ssize_t>(signature_blob.size()));
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index 6318806..0983cd3 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -36,6 +36,8 @@
 
 extern const char* kUnittestPrivateKeyPath;
 extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
 
 namespace {
 const size_t kBlockSize = 4096;
@@ -101,6 +103,8 @@
   kSignatureGenerated,  // Sign the payload after it's generated.
   kSignatureGeneratedShell,  // Sign the generated payload through shell cmds.
   kSignatureGeneratedShellBadKey,  // Sign with a bad key through shell cmds.
+  kSignatureGeneratedShellRotateCl1,  // Rotate key, test client v1
+  kSignatureGeneratedShellRotateCl2,  // Rotate key, test client v2
 };
 
 size_t GetSignatureSize(const string& private_key_path) {
@@ -117,18 +121,22 @@
 void SignGeneratedPayload(const string& payload_path) {
   int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
   vector<char> hash;
-  ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(payload_path,
-                                                   signature_size,
-                                                   &hash));
+  ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+      payload_path,
+      vector<int>(1, signature_size),
+      &hash));
   vector<char> signature;
   ASSERT_TRUE(PayloadSigner::SignHash(hash,
                                       kUnittestPrivateKeyPath,
                                       &signature));
-  ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(payload_path,
-                                                   signature,
-                                                   payload_path));
-  EXPECT_TRUE(PayloadSigner::VerifySignedPayload(payload_path,
-                                                 kUnittestPublicKeyPath));
+  ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+      payload_path,
+      vector<vector<char> >(1, signature),
+      payload_path));
+  EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+      payload_path,
+      kUnittestPublicKeyPath,
+      kSignatureMessageOriginalVersion));
 }
 
 void SignGeneratedShellPayload(SignatureTest signature_test,
@@ -139,7 +147,9 @@
                                     &private_key_path,
                                     NULL));
   } else {
-    ASSERT_EQ(kSignatureGeneratedShell, signature_test);
+    ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+                signature_test == kSignatureGeneratedShellRotateCl1 ||
+                signature_test == kSignatureGeneratedShellRotateCl2);
   }
   ScopedPathUnlinker key_unlinker(private_key_path);
   key_unlinker.set_should_remove(signature_test ==
@@ -156,12 +166,19 @@
   string hash_file;
   ASSERT_TRUE(utils::MakeTempFile("/tmp/hash.XXXXXX", &hash_file, NULL));
   ScopedPathUnlinker hash_unlinker(hash_file);
+  string signature_size_string;
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2)
+    signature_size_string = StringPrintf("%d:%d",
+                                         signature_size, signature_size);
+  else
+    signature_size_string = StringPrintf("%d", signature_size);
   ASSERT_EQ(0,
             System(StringPrintf(
-                "./delta_generator -in_file %s -signature_size %d "
+                "./delta_generator -in_file %s -signature_size %s "
                 "-out_hash_file %s",
                 payload_path.c_str(),
-                signature_size,
+                signature_size_string.c_str(),
                 hash_file.c_str())));
 
   // Pad the hash
@@ -179,6 +196,21 @@
                 private_key_path.c_str(),
                 hash_file.c_str(),
                 sig_file.c_str())));
+  string sig_file2;
+  ASSERT_TRUE(utils::MakeTempFile("/tmp/signature.XXXXXX", &sig_file2, NULL));
+  ScopedPathUnlinker sig2_unlinker(sig_file2);
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2) {
+    ASSERT_EQ(0,
+              System(StringPrintf(
+                  "/usr/bin/openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+                  kUnittestPrivateKey2Path,
+                  hash_file.c_str(),
+                  sig_file2.c_str())));
+    // Append second sig file to first path
+    sig_file += ":" + sig_file2;
+  }
+
   ASSERT_EQ(0,
             System(StringPrintf(
                 "./delta_generator -in_file %s -signature_file %s "
@@ -187,9 +219,12 @@
                 sig_file.c_str(),
                 payload_path.c_str())));
   int verify_result =
-      System(StringPrintf("./delta_generator -in_file %s -public_key %s",
-                          payload_path.c_str(),
-                          kUnittestPublicKeyPath));
+      System(StringPrintf(
+          "./delta_generator -in_file %s -public_key %s -public_key_version %d",
+          payload_path.c_str(),
+          signature_test == kSignatureGeneratedShellRotateCl2 ?
+          kUnittestPublicKey2Path : kUnittestPublicKeyPath,
+          signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
   if (signature_test == kSignatureGeneratedShellBadKey) {
     ASSERT_NE(0, verify_result);
   } else {
@@ -327,7 +362,9 @@
   if (signature_test == kSignatureGenerated) {
     SignGeneratedPayload(delta_path);
   } else if (signature_test == kSignatureGeneratedShell ||
-             signature_test == kSignatureGeneratedShellBadKey) {
+             signature_test == kSignatureGeneratedShellBadKey ||
+             signature_test == kSignatureGeneratedShellRotateCl1 ||
+             signature_test == kSignatureGeneratedShellRotateCl2) {
     SignGeneratedShellPayload(signature_test, delta_path);
   }
 
@@ -361,13 +398,23 @@
       EXPECT_TRUE(sigs_message.ParseFromArray(
           &delta[manifest_metadata_size + manifest.signatures_offset()],
           manifest.signatures_size()));
-      EXPECT_EQ(1, sigs_message.signatures_size());
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2)
+        EXPECT_EQ(2, sigs_message.signatures_size());
+      else
+        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);
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2) {
+        key_paths.push_back(kUnittestPrivateKey2Path);
+      }
       EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
-          kUnittestPrivateKeyPath, &expected_sig_data_length));
+          key_paths,
+          &expected_sig_data_length));
       EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
       EXPECT_FALSE(signature.data().empty());
     }
@@ -492,7 +539,7 @@
                                                &expected_new_rootfs_hash));
   EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
 }
-}
+}  // namespace {}
 
 TEST(DeltaPerformerTest, RunAsRootSmallImageTest) {
   DoSmallImageTest(false, false, false, kSignatureGenerator);
@@ -526,6 +573,14 @@
   DoSmallImageTest(false, false, false, kSignatureGeneratedShellBadKey);
 }
 
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+  DoSmallImageTest(false, false, false, kSignatureGeneratedShellRotateCl1);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+  DoSmallImageTest(false, false, false, kSignatureGeneratedShellRotateCl2);
+}
+
 TEST(DeltaPerformerTest, BadDeltaMagicTest) {
   PrefsMock prefs;
   DeltaPerformer performer(&prefs);
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
index 1e4d856..5a2dda0 100644
--- a/generate_delta_main.cc
+++ b/generate_delta_main.cc
@@ -14,6 +14,8 @@
 
 #include <base/command_line.h>
 #include <base/logging.h>
+#include <base/string_number_conversions.h>
+#include <base/string_split.h>
 #include <gflags/gflags.h>
 #include <glib.h>
 
@@ -41,10 +43,22 @@
 DEFINE_string(out_hash_file, "", "Path to output hash file");
 DEFINE_string(private_key, "", "Path to private key in .pem format");
 DEFINE_string(public_key, "", "Path to public key in .pem format");
+DEFINE_int32(public_key_version,
+             chromeos_update_engine::kSignatureMessageCurrentVersion,
+             "Key-check version # of client");
 DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
               "Preferences directory, used with apply_delta");
-DEFINE_int32(signature_size, 0, "Raw signature size used for hash calculation");
-DEFINE_string(signature_file, "", "Raw signature file to sign payload with");
+DEFINE_string(signature_size, "",
+              "Raw signature size used for hash calculation. "
+              "You may pass in multiple sizes by colon separating them. E.g. "
+              "2048:2048:4096 will assume 3 signatures, the first two with "
+              "2048 size and the last 4096.");
+DEFINE_string(signature_file, "",
+              "Raw signature file to sign payload with. To pass multiple "
+              "signatures, use a single argument with a colon between paths, "
+              "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
+              "signature will be assigned a client version, starting from "
+              "kSignatureOriginalVersion.");
 
 // This file contains a simple program that takes an old path, a new path,
 // and an output file as arguments and the path to an output file and
@@ -70,11 +84,21 @@
       << "Must pass --in_file to calculate hash for signing.";
   LOG_IF(FATAL, FLAGS_out_hash_file.empty())
       << "Must pass --out_hash_file to calculate hash for signing.";
-  LOG_IF(FATAL, FLAGS_signature_size <= 0)
+  LOG_IF(FATAL, FLAGS_signature_size.empty())
       << "Must pass --signature_size to calculate hash for signing.";
+  vector<int> sizes;
+  vector<string> strsizes;
+  base::SplitString(FLAGS_signature_size, ':', &strsizes);
+  for (vector<string>::iterator it = strsizes.begin(), e = strsizes.end();
+       it != e; ++it) {
+    int size = 0;
+    LOG_IF(FATAL, !base::StringToInt(*it, &size))
+        << "Not an integer: " << *it;
+    sizes.push_back(size);
+  }
   vector<char> hash;
   CHECK(PayloadSigner::HashPayloadForSigning(
-      FLAGS_in_file, FLAGS_signature_size, &hash));
+      FLAGS_in_file, sizes, &hash));
   CHECK(utils::WriteFile(
       FLAGS_out_hash_file.c_str(), hash.data(), hash.size()));
   LOG(INFO) << "Done calculating payload hash for signing.";
@@ -88,10 +112,17 @@
       << "Must pass --out_file to sign payload.";
   LOG_IF(FATAL, FLAGS_signature_file.empty())
       << "Must pass --signature_file to sign payload.";
-  vector<char> signature;
-  CHECK(utils::ReadFile(FLAGS_signature_file, &signature));
+  vector<vector<char> > signatures;
+  vector<string> signature_files;
+  base::SplitString(FLAGS_signature_file, ':', &signature_files);
+  for (vector<string>::iterator it = signature_files.begin(),
+           e = signature_files.end(); it != e; ++it) {
+    vector<char> signature;
+    CHECK(utils::ReadFile(*it, &signature));
+    signatures.push_back(signature);
+  }
   CHECK(PayloadSigner::AddSignatureToPayload(
-      FLAGS_in_file, signature, FLAGS_out_file));
+      FLAGS_in_file, signatures, FLAGS_out_file));
   LOG(INFO) << "Done signing payload.";
 }
 
@@ -101,7 +132,8 @@
       << "Must pass --in_file to verify signed payload.";
   LOG_IF(FATAL, FLAGS_public_key.empty())
       << "Must pass --public_key to verify signed payload.";
-  CHECK(PayloadSigner::VerifySignedPayload(FLAGS_in_file, FLAGS_public_key));
+  CHECK(PayloadSigner::VerifySignedPayload(FLAGS_in_file, FLAGS_public_key,
+                                           FLAGS_public_key_version));
   LOG(INFO) << "Done verifying signed payload.";
 }
 
@@ -158,7 +190,7 @@
                        logging::DONT_LOCK_LOG_FILE,
                        logging::APPEND_TO_OLD_LOG_FILE,
                        logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
-  if (FLAGS_signature_size > 0 || !FLAGS_out_hash_file.empty()) {
+  if (!FLAGS_signature_size.empty() || !FLAGS_out_hash_file.empty()) {
     CalculatePayloadHashForSigning();
     return 0;
   }
diff --git a/payload_signer.cc b/payload_signer.cc
index c27e3bc..d8993d9 100644
--- a/payload_signer.cc
+++ b/payload_signer.cc
@@ -21,7 +21,8 @@
 
 namespace chromeos_update_engine {
 
-const uint32_t kSignatureMessageVersion = 1;
+const uint32_t kSignatureMessageOriginalVersion = 1;
+const uint32_t kSignatureMessageCurrentVersion = 1;
 
 namespace {
 
@@ -46,15 +47,26 @@
   0x00, 0x04, 0x20
 };
 
-// Given a raw |signature|, packs it into a protobuf and serializes it into a
+// Given raw |signatures|, packs them into a protobuf and serializes it into a
 // binary blob. Returns true on success, false otherwise.
-bool ConvertSignatureToProtobufBlob(const vector<char> signature,
+bool ConvertSignatureToProtobufBlob(const vector<vector<char> >& signatures,
                                     vector<char>* out_signature_blob) {
   // Pack it into a protobuf
   Signatures out_message;
-  Signatures_Signature* sig_message = out_message.add_signatures();
-  sig_message->set_version(kSignatureMessageVersion);
-  sig_message->set_data(signature.data(), signature.size());
+  uint32_t version = kSignatureMessageOriginalVersion;
+  LOG_IF(WARNING, kSignatureMessageCurrentVersion -
+         kSignatureMessageOriginalVersion + 1 < signatures.size())
+      << "You may want to support clients in the rage ["
+      << kSignatureMessageOriginalVersion << ", "
+      << kSignatureMessageCurrentVersion << "] inclusive, but you only "
+      << "provided " << signatures.size() << " signatures.";
+  for (vector<vector<char> >::const_iterator it = signatures.begin(),
+           e = signatures.end(); it != e; ++it) {
+    const vector<char>& signature = *it;
+    Signatures_Signature* sig_message = out_message.add_signatures();
+    sig_message->set_version(version++);
+    sig_message->set_data(signature.data(), signature.size());
+  }
 
   // Serialize protobuf
   string serialized;
@@ -165,23 +177,27 @@
 }
 
 bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
-                                const string& private_key_path,
+                                const vector<string>& private_key_paths,
                                 vector<char>* out_signature_blob) {
   vector<char> hash_data;
   TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfFile(
       unsigned_payload_path, -1, &hash_data) ==
                         utils::FileSize(unsigned_payload_path));
 
-  vector<char> signature;
-  TEST_AND_RETURN_FALSE(SignHash(hash_data, private_key_path, &signature));
-  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signature,
+  vector<vector<char> > signatures;
+  for (vector<string>::const_iterator it = private_key_paths.begin(),
+           e = private_key_paths.end(); it != e; ++it) {
+    vector<char> signature;
+    TEST_AND_RETURN_FALSE(SignHash(hash_data, *it, &signature));
+    signatures.push_back(signature);
+  }
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
                                                        out_signature_blob));
   return true;
 }
 
-bool PayloadSigner::SignatureBlobLength(
-    const string& private_key_path,
-    uint64_t* out_length) {
+bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
+                                        uint64_t* out_length) {
   DCHECK(out_length);
 
   string x_path;
@@ -192,7 +208,7 @@
 
   vector<char> sig_blob;
   TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path,
-                                                   private_key_path,
+                                                   private_key_paths,
                                                    &sig_blob));
   *out_length = sig_blob.size();
   return true;
@@ -201,6 +217,15 @@
 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,
+                                kSignatureMessageCurrentVersion, out_hash_data);
+}
+
+bool PayloadSigner::VerifySignatureVersion(
+    const std::vector<char>& signature_blob,
+    const std::string& public_key_path,
+    uint32_t client_version,
+    std::vector<char>* out_hash_data) {
   TEST_AND_RETURN_FALSE(!public_key_path.empty());
 
   Signatures signatures;
@@ -212,7 +237,7 @@
   for (; sig_index < signatures.signatures_size(); sig_index++) {
     const Signatures_Signature& signature = signatures.signatures(sig_index);
     if (signature.has_version() &&
-        signature.version() == kSignatureMessageVersion) {
+        signature.version() == client_version) {
       break;
     }
   }
@@ -257,7 +282,8 @@
 }
 
 bool PayloadSigner::VerifySignedPayload(const std::string& payload_path,
-                                        const std::string& public_key_path) {
+                                        const std::string& public_key_path,
+                                        uint32_t client_key_check_version) {
   vector<char> payload;
   DeltaArchiveManifest manifest;
   uint64_t metadata_size;
@@ -272,8 +298,8 @@
       payload.begin() + metadata_size + manifest.signatures_offset(),
       payload.end());
   vector<char> signed_hash;
-  TEST_AND_RETURN_FALSE(VerifySignature(
-      signature_blob, public_key_path, &signed_hash));
+  TEST_AND_RETURN_FALSE(VerifySignatureVersion(
+      signature_blob, public_key_path, client_key_check_version, &signed_hash));
   TEST_AND_RETURN_FALSE(!signed_hash.empty());
   vector<char> hash;
   TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(
@@ -284,14 +310,19 @@
 }
 
 bool PayloadSigner::HashPayloadForSigning(const std::string& payload_path,
-                                          int signature_size,
+                                          const vector<int>& signature_sizes,
                                           vector<char>* out_hash_data) {
   // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
 
   // Loads the payload and adds the signature op to it.
-  vector<char> signature(signature_size, 0);
+  vector<vector<char> > signatures;
+  for (vector<int>::const_iterator it = signature_sizes.begin(),
+           e = signature_sizes.end(); it != e; ++it) {
+    vector<char> signature(*it, 0);
+    signatures.push_back(signature);
+  }
   vector<char> signature_blob;
-  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signature,
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
                                                        &signature_blob));
   vector<char> payload;
   TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
@@ -304,14 +335,15 @@
   return true;
 }
 
-bool PayloadSigner::AddSignatureToPayload(const string& payload_path,
-                                          const vector<char>& signature,
-                                          const string& signed_payload_path) {
+bool PayloadSigner::AddSignatureToPayload(
+    const string& payload_path,
+    const vector<vector<char> >& signatures,
+    const string& signed_payload_path) {
   // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
 
   // Loads the payload and adds the signature op to it.
   vector<char> signature_blob;
-  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signature,
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
                                                        &signature_blob));
   vector<char> payload;
   TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
diff --git a/payload_signer.h b/payload_signer.h
index ee1bee9..1e0db35 100644
--- a/payload_signer.h
+++ b/payload_signer.h
@@ -15,7 +15,8 @@
 
 namespace chromeos_update_engine {
 
-extern const uint32_t kSignatureMessageVersion;
+extern const uint32_t kSignatureMessageOriginalVersion;
+extern const uint32_t kSignatureMessageCurrentVersion;
 
 class PayloadSigner {
  public:
@@ -25,35 +26,37 @@
                        const std::string& private_key_path,
                        std::vector<char>* out_signature);
 
-  // Given an unsigned payload in |unsigned_payload_path| and a private key in
+  // Given an unsigned payload in |unsigned_payload_path| and private keys in
   // |private_key_path|, calculates the signature blob into
   // |out_signature_blob|. Note that the payload must already have an updated
   // manifest that includes the dummy signature op. Returns true on success,
   // false otherwise.
   static bool SignPayload(const std::string& unsigned_payload_path,
-                          const std::string& private_key_path,
+                          const std::vector<std::string>& private_key_paths,
                           std::vector<char>* out_signature_blob);
 
   // Returns the length of out_signature_blob that will result in a call
-  // to SignPayload with a given private key. Returns true on success.
-  static bool SignatureBlobLength(const std::string& private_key_path,
-                                  uint64_t* out_length);
+  // to SignPayload with the given private keys. Returns true on success.
+  static bool SignatureBlobLength(
+      const std::vector<std::string>& private_key_paths,
+      uint64_t* out_length);
 
   // Given an unsigned payload in |payload_path| (with no dummy signature op)
-  // and the raw |signature_size| calculates the raw hash that needs to be
+  // and the raw |signature_sizes| calculates the raw hash that needs to be
   // signed in |out_hash_data|. Returns true on success, false otherwise.
   static bool HashPayloadForSigning(const std::string& payload_path,
-                                    int signature_size,
+                                    const std::vector<int>& signature_sizes,
                                     std::vector<char>* out_hash_data);
 
   // Given an unsigned payload in |payload_path| (with no dummy signature op)
-  // and the raw |signature| updates the payload to include the signature thus
+  // and the raw |signatures| updates the payload to include the signature thus
   // turning it into a signed payload. The new payload is stored in
   // |signed_payload_path|. |payload_path| and |signed_payload_path| can point
   // to the same file. Returns true on success, false otherwise.
-  static bool AddSignatureToPayload(const std::string& payload_path,
-                                    const std::vector<char>& signature,
-                                    const std::string& signed_payload_path);
+  static bool AddSignatureToPayload(
+      const std::string& payload_path,
+      const std::vector<std::vector<char> >& signatures,
+      const std::string& signed_payload_path);
 
   // Returns false if the payload signature can't be verified. Returns true
   // otherwise and sets |out_hash| to the signed payload hash.
@@ -61,11 +64,18 @@
                               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);
+
   // Returns true if the payload in |payload_path| is signed and its hash can be
-  // verified using the public key in |public_key_path|. Returns false
-  // otherwise.
+  // verified using the public key in |public_key_path| with the signature
+  // of a given version in the signature blob. Returns false otherwise.
   static bool VerifySignedPayload(const std::string& payload_path,
-                                  const std::string& public_key_path);
+                                  const std::string& public_key_path,
+                                  uint32_t client_key_check_version);
 
   // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
   // using the PKCS#1 v1.5 scheme.
diff --git a/payload_signer_unittest.cc b/payload_signer_unittest.cc
index f7c59a6..0a5a883 100644
--- a/payload_signer_unittest.cc
+++ b/payload_signer_unittest.cc
@@ -15,11 +15,14 @@
 
 // Note: the test key was generated with the following command:
 // openssl genrsa -out unittest_key.pem 2048
+// The public-key version is created by the build system.
 
 namespace chromeos_update_engine {
 
 const char* kUnittestPrivateKeyPath = "unittest_key.pem";
 const char* kUnittestPublicKeyPath = "unittest_key.pub.pem";
+const char* kUnittestPrivateKey2Path = "unittest_key2.pem";
+const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem";
 
 // Some data and its corresponding hash and signature:
 const char kDataToSign[] = "This is some data to sign.";
@@ -86,12 +89,14 @@
                                kDataToSign,
                                strlen(kDataToSign)));
   uint64_t length = 0;
-  EXPECT_TRUE(PayloadSigner::SignatureBlobLength(kUnittestPrivateKeyPath,
-                                                 &length));
+  EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+      vector<string> (1, kUnittestPrivateKeyPath),
+      &length));
   EXPECT_GT(length, 0);
-  EXPECT_TRUE(PayloadSigner::SignPayload(data_path,
-                                         kUnittestPrivateKeyPath,
-                                         out_signature_blob));
+  EXPECT_TRUE(PayloadSigner::SignPayload(
+      data_path,
+      vector<string>(1, kUnittestPrivateKeyPath),
+      out_signature_blob));
   EXPECT_EQ(length, out_signature_blob->size());
 }
 }
@@ -106,7 +111,7 @@
                                         signature_blob.size()));
   EXPECT_EQ(1, signatures.signatures_size());
   const Signatures_Signature& signature = signatures.signatures(0);
-  EXPECT_EQ(kSignatureMessageVersion, signature.version());
+  EXPECT_EQ(kSignatureMessageOriginalVersion, signature.version());
   const string sig_data = signature.data();
   ASSERT_EQ(arraysize(kDataSignature), sig_data.size());
   for (size_t i = 0; i < arraysize(kDataSignature); i++) {
diff --git a/unittest_key2.pem b/unittest_key2.pem
new file mode 100644
index 0000000..d1f9a78
--- /dev/null
+++ b/unittest_key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqdM7gnd43cWfQzyrt16QDHE+T0yxg18OZO+9sc8jWpjC1zFt
+oMFokESiu9TLUp+KUC274f+DxIgAfSCO6U5DJFRPXUzve1vaXYfqljUBxc+JYcGd
+d6PCUl4Ap9f02DhkrGnf/rkHOCEhFNpm53dovZ9A6Q5KBfcg1HeQ088opVceQGtU
+mL+bONq5tLvNFLUU0Ru0Am4ADJ8Xmw4rpvqvmXJaFmVuf/Gx7+TOnaJvZTeOO9AJ
+K6q6V8E7+ZZiMIc5F4EOs5AX/lZfNO3RVgG2O7Dhzza+nOzJ3ZJGK5R4WwZduhn9
+QRYogYPB0c62878qXpf2Bn3NpUPi8Cf/RLMMmQIDAQABAoIBACyLUWKpP7S770hN
+k6TnUtVQps1aCn2w4y+qipEnCdjrlL+pIV43HNwqhJzL9gDYBAl/1XYz9TYJjkdD
+0Ph1JLtUufR5B5/NufsqeWeow6xFAX34sPr+oyvDqFxeEsTcFdv7cVt44OHiHrE/
+kBpKgdiq+vWmX9gsuBnCuuQzxC+Juo6nupwZXcpa/ow9lC4QsgKqcjaUGrXXy2t9
+Er+9aSl8NdTjK76BXQsDgNkDyJZwNN14xrdS8eFsS4twskaOEYI4hEM0g62NOjgd
+Po8Ap/MnPpGSGcAd3d3Fq8KgT1lpyMKedLFU+k0H+/Y4RBl7grz1XXvSTzGi3Qy6
+38F4eVkCgYEA4mo4iiXSfrov9QffsVM17O0t9hUsOJHnUsEScxWLDm4IzaObyTtv
+tWW33iQSeFu4Wsol0nzjqWo3BaqiRidRUd42yZ07LJvfUDxUX9xPaUPFRs25iwhZ
+6tKAVqGk7/CFrN+R44sIwbsSvbExMAyW6gnj93EWUmMWWYp02hLbN0sCgYEAwAQI
+awVoc56OCtRpfYtlAPD/VOP1mbNzRmVl/UyZ4XYmz6f/hEz63Bk5PhYSZftlmK/r
+nj4qnl7HZ8jrJgZn2e97rPNpk7KDVU1+csCgLWZBTOXl/o9tOTyjh9LoRAjKtBB7
+x6CkWyiyd94xIq5VbnXhvL3a4d4o6OxMWdG5aSsCgYEAo44z1afIzP7WkdzkPIZt
+l/8linR1A1BymBccqsHPN9dIyLP9X3puEc2u6uuH5CXtoLgSZmENXF577L38h0zz
+s34gebf4/RqEUMOj97OAMfxgz+rgs4yO19DEINCYAzPufJjsHEFdTAVFXn5Xl+wg
+QGRwp1ir1Uv64yffjYC9ls0CgYEAjvIxpiKniPNvwUYypmDgt5uyKetvCpaaabzQ
++YpOQJep+wuRYFfCpZotkDf0SHGoR8wnd23GYpIilvPvgyZfp9HuW2n2nhrWROnl
+Cd63IDUwxeOcni7+XA71mwb7HLMC3Jws2geQc8DPZAdIww3P0eT2QYGBcobmI8jO
+akuEYXMCgYAm79Kb/r+3Hew5oAS1Whw70DskVlOutSgNsDPfW9MtDcnETBcGep7A
+1jCL5jjdUYRonimVMFjh1K+UFzV/DQHkgNzjxz9Inbh02y67vL2X836dS9esOcbx
+uZhf+8rL+GnSNqYDqCEuP7qCIloDhguJq9NKyTB4yc59qIkY2zPAzQ==
+-----END RSA PRIVATE KEY-----