update_engine: Split payload signing and verification.
Payloads are only signed on server-side code (delta_generator) and
verified on both sides and unittest. This removes the dependency of
payload_generator/ code from delta_performer.cc by spliting the
payload signing and verification in two files.
Currently, both files are still included on all the built files.
This patch also includes some minor linter fixes.
BUG=chromium:394184
TEST=FEATURES="test" emerge-link update_engine; sudo emerge update_engine
Change-Id: Ia4268257f4260902bc37612f429f44ba7e8f65fd
Reviewed-on: https://chromium-review.googlesource.com/208540
Tested-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
index 587892c..1367b17 100644
--- a/payload_generator/delta_diff_generator.cc
+++ b/payload_generator/delta_diff_generator.cc
@@ -39,8 +39,9 @@
#include "update_engine/payload_generator/graph_types.h"
#include "update_engine/payload_generator/graph_utils.h"
#include "update_engine/payload_generator/metadata.h"
+#include "update_engine/payload_generator/payload_signer.h"
#include "update_engine/payload_generator/topological_sort.h"
-#include "update_engine/payload_signer.h"
+#include "update_engine/payload_verifier.h"
#include "update_engine/subprocess.h"
#include "update_engine/update_metadata.pb.h"
#include "update_engine/utils.h"
@@ -60,7 +61,7 @@
const uint64_t kFullUpdateChunkSize = 1024 * 1024; // bytes
const size_t kBlockSize = 4096; // bytes
-const string kNonexistentPath = "";
+const char kEmptyPath[] = "";
static const char* kInstallOperationTypes[] = {
"REPLACE",
@@ -81,7 +82,7 @@
const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024;
// Needed for testing purposes, in case we can't use actual filesystem objects.
-// TODO(garnold)(chromium:331965) Replace this hack with a properly injected
+// TODO(garnold) (chromium:331965) Replace this hack with a properly injected
// parameter in form of a mockable abstract class.
bool (*get_extents_with_chunk_func)(const std::string&, off_t, off_t,
std::vector<Extent>*) =
@@ -122,7 +123,7 @@
vector<char> data;
DeltaArchiveManifest_InstallOperation operation;
- string old_path = (old_root == kNonexistentPath) ? kNonexistentPath :
+ string old_path = (old_root == kEmptyPath) ? kEmptyPath :
old_root + path;
// If bsdiff breaks again, blacklist the problem file by using:
@@ -243,7 +244,7 @@
blocks,
(should_diff_from_source ?
old_root :
- kNonexistentPath),
+ kEmptyPath),
new_root,
fs_iter.GetPartialPath(),
offset,
@@ -470,7 +471,7 @@
new_kernel_part,
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
false));
@@ -1038,19 +1039,20 @@
class SortCutsByTopoOrderLess {
public:
- SortCutsByTopoOrderLess(vector<vector<Vertex::Index>::size_type>& table)
+ explicit SortCutsByTopoOrderLess(
+ const vector<vector<Vertex::Index>::size_type>& table)
: table_(table) {}
bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) {
return table_[a.old_dst] < table_[b.old_dst];
}
private:
- vector<vector<Vertex::Index>::size_type>& table_;
+ const vector<vector<Vertex::Index>::size_type>& table_;
};
} // namespace
void DeltaDiffGenerator::GenerateReverseTopoOrderMap(
- vector<Vertex::Index>& op_indexes,
+ const vector<Vertex::Index>& op_indexes,
vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) {
vector<vector<Vertex::Index>::size_type> table(op_indexes.size());
for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size();
@@ -1064,8 +1066,9 @@
reverse_op_indexes->swap(table);
}
-void DeltaDiffGenerator::SortCutsByTopoOrder(vector<Vertex::Index>& op_indexes,
- vector<CutEdgeVertexes>* cuts) {
+void DeltaDiffGenerator::SortCutsByTopoOrder(
+ const vector<Vertex::Index>& op_indexes,
+ vector<CutEdgeVertexes>* cuts) {
// first, make a reverse lookup table.
vector<vector<Vertex::Index>::size_type> table;
GenerateReverseTopoOrderMap(op_indexes, &table);
@@ -1438,7 +1441,7 @@
TEST_AND_RETURN_FALSE(DeltaReadFile(graph,
cut.old_dst,
NULL,
- kNonexistentPath,
+ kEmptyPath,
new_root,
(*graph)[cut.old_dst].file_name,
(*graph)[cut.old_dst].chunk_offset,
@@ -1563,7 +1566,7 @@
<< "Old and new images have different block counts.";
// If new_image_info is present, old_image_info must be present.
- TEST_AND_RETURN_FALSE((bool)old_image_info == (bool)new_image_info);
+ TEST_AND_RETURN_FALSE(!old_image_info == !new_image_info);
} else {
// old_image_info must not be present for a full update.
TEST_AND_RETURN_FALSE(!old_image_info);
diff --git a/payload_generator/delta_diff_generator.h b/payload_generator/delta_diff_generator.h
index 596d9c0..3c4ad69 100644
--- a/payload_generator/delta_diff_generator.h
+++ b/payload_generator/delta_diff_generator.h
@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
-#define CHROMEOS_PLATFORM_UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#include <set>
#include <string>
#include <vector>
@@ -167,8 +168,9 @@
// Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is
// determined by the order of elements in op_indexes.
- static void SortCutsByTopoOrder(std::vector<Vertex::Index>& op_indexes,
- std::vector<CutEdgeVertexes>* cuts);
+ static void SortCutsByTopoOrder(
+ const std::vector<Vertex::Index>& op_indexes,
+ std::vector<CutEdgeVertexes>* cuts);
// Returns true iff there are no extents in the graph that refer to temp
// blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole).
@@ -211,7 +213,7 @@
// which the op is performed -> graph vertex index, and produces the
// reverse: a mapping from graph vertex index -> op_indexes index.
static void GenerateReverseTopoOrderMap(
- std::vector<Vertex::Index>& op_indexes,
+ const std::vector<Vertex::Index>& op_indexes,
std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes);
// Takes a |graph|, which has edges that must be cut, as listed in
@@ -264,7 +266,7 @@
DeltaArchiveManifest* manifest);
private:
- // This should never be constructed
+ // This should never be constructed.
DISALLOW_IMPLICIT_CONSTRUCTORS(DeltaDiffGenerator);
};
@@ -273,4 +275,4 @@
}; // namespace chromeos_update_engine
-#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
diff --git a/payload_generator/delta_diff_generator_unittest.cc b/payload_generator/delta_diff_generator_unittest.cc
index c9ac8cf..977b34d 100644
--- a/payload_generator/delta_diff_generator_unittest.cc
+++ b/payload_generator/delta_diff_generator_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "update_engine/payload_generator/delta_diff_generator.h"
+
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -22,7 +24,6 @@
#include "update_engine/extent_ranges.h"
#include "update_engine/payload_constants.h"
#include "update_engine/payload_generator/cycle_breaker.h"
-#include "update_engine/payload_generator/delta_diff_generator.h"
#include "update_engine/payload_generator/extent_mapper.h"
#include "update_engine/payload_generator/graph_types.h"
#include "update_engine/payload_generator/graph_utils.h"
@@ -91,7 +92,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
true));
@@ -181,7 +182,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
true));
@@ -245,7 +246,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
true));
@@ -278,7 +279,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- false, // bsdiff_allowed
+ false, // bsdiff_allowed
&data,
&op,
true));
@@ -304,7 +305,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- false, // bsdiff_allowed
+ false, // bsdiff_allowed
&data,
&op,
true));
@@ -331,7 +332,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
true));
@@ -365,7 +366,7 @@
new_path(),
0, // chunk_offset
-1, // chunk_size
- true, // bsdiff_allowed
+ true, // bsdiff_allowed
&data,
&op,
false));
@@ -398,7 +399,7 @@
extent->set_start_block(start);
extent->set_num_blocks(length);
}
-}
+} // namespace
TEST_F(DeltaDiffGeneratorTest, SubstituteBlocksTest) {
vector<Extent> remove_blocks;
@@ -498,8 +499,8 @@
cycle_breaker.BreakCycles(graph, &cut_edges);
EXPECT_EQ(1, cut_edges.size());
- EXPECT_TRUE(cut_edges.end() != cut_edges.find(make_pair<Vertex::Index>(1,
- 0)));
+ EXPECT_TRUE(cut_edges.end() != cut_edges.find(
+ std::pair<Vertex::Index, Vertex::Index>(1, 0)));
vector<CutEdgeVertexes> cuts;
EXPECT_TRUE(DeltaDiffGenerator::CutEdges(&graph, cut_edges, &cuts));
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 89bb2b9..a2b6b3c 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -21,7 +21,8 @@
#include "update_engine/delta_performer.h"
#include "update_engine/payload_generator/delta_diff_generator.h"
-#include "update_engine/payload_signer.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_verifier.h"
#include "update_engine/prefs.h"
#include "update_engine/subprocess.h"
#include "update_engine/terminator.h"
@@ -142,7 +143,6 @@
const string& build_channel,
const string& build_version,
ImageInfo* image_info) {
-
// All of these arguments should be present or missing.
bool empty = channel.empty();
@@ -237,8 +237,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,
- FLAGS_public_key_version));
+ CHECK(PayloadVerifier::VerifySignedPayload(FLAGS_in_file, FLAGS_public_key,
+ FLAGS_public_key_version));
LOG(INFO) << "Done verifying signed payload.";
}
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
new file mode 100644
index 0000000..4bbb155
--- /dev/null
+++ b/payload_generator/payload_signer.cc
@@ -0,0 +1,331 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_signer.h"
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <openssl/pem.h>
+
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// 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<vector<char> >& signatures,
+ vector<char>* out_signature_blob) {
+ // Pack it into a protobuf
+ Signatures out_message;
+ uint32_t version = kSignatureMessageOriginalVersion;
+ LOG_IF(WARNING, kSignatureMessageCurrentVersion -
+ kSignatureMessageOriginalVersion + 1 < signatures.size())
+ << "You may want to support clients in the range ["
+ << 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;
+ TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
+ out_signature_blob->insert(out_signature_blob->end(),
+ serialized.begin(),
+ serialized.end());
+ LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
+ return true;
+}
+
+// Given an unsigned payload under |payload_path| and the |signature_blob_size|
+// generates an updated payload that includes a dummy signature op in its
+// manifest. It populates |out_metadata_size| with the size of the final
+// manifest after adding the dummy signature operation, and
+// |out_signatures_offset| with the expected offset for the new blob. Returns
+// true on success, false otherwise.
+bool AddSignatureOpToPayload(const string& payload_path,
+ uint64_t signature_blob_size,
+ vector<char>* out_payload,
+ uint64_t* out_metadata_size,
+ uint64_t* out_signatures_offset) {
+ const int kProtobufOffset = 20;
+ const int kProtobufSizeOffset = 12;
+
+ // Loads the payload.
+ vector<char> payload;
+ DeltaArchiveManifest manifest;
+ uint64_t metadata_size;
+ TEST_AND_RETURN_FALSE(PayloadVerifier::LoadPayload(
+ payload_path, &payload, &manifest, &metadata_size));
+
+ // Is there already a signature op in place?
+ if (manifest.has_signatures_size()) {
+ // The signature op is tied to the size of the signature blob, but not it's
+ // contents. We don't allow the manifest to change if there is already an op
+ // present, because that might invalidate previously generated
+ // hashes/signatures.
+ if (manifest.signatures_size() != signature_blob_size) {
+ LOG(ERROR) << "Attempt to insert different signature sized blob. "
+ << "(current:" << manifest.signatures_size()
+ << "new:" << signature_blob_size << ")";
+ return false;
+ }
+
+ LOG(INFO) << "Matching signature sizes already present.";
+ } else {
+ // Updates the manifest to include the signature operation.
+ DeltaDiffGenerator::AddSignatureOp(payload.size() - metadata_size,
+ signature_blob_size,
+ &manifest);
+
+ // Updates the payload to include the new manifest.
+ string serialized_manifest;
+ TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
+ LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
+ payload.erase(payload.begin() + kProtobufOffset,
+ payload.begin() + metadata_size);
+ payload.insert(payload.begin() + kProtobufOffset,
+ serialized_manifest.begin(),
+ serialized_manifest.end());
+
+ // Updates the protobuf size.
+ uint64_t size_be = htobe64(serialized_manifest.size());
+ memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
+ metadata_size = serialized_manifest.size() + kProtobufOffset;
+
+ LOG(INFO) << "Updated payload size: " << payload.size();
+ LOG(INFO) << "Updated metadata size: " << metadata_size;
+ }
+
+ out_payload->swap(payload);
+ *out_metadata_size = metadata_size;
+ *out_signatures_offset = metadata_size + manifest.signatures_offset();
+ LOG(INFO) << "Signature Blob Offset: " << *out_signatures_offset;
+ return true;
+}
+} // namespace
+
+bool PayloadSigner::SignHash(const vector<char>& hash,
+ const string& private_key_path,
+ vector<char>* out_signature) {
+ LOG(INFO) << "Signing hash with private key: " << private_key_path;
+ string sig_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("signature.XXXXXX", &sig_path, NULL));
+ ScopedPathUnlinker sig_path_unlinker(sig_path);
+
+ string hash_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("hash.XXXXXX", &hash_path, NULL));
+ ScopedPathUnlinker hash_path_unlinker(hash_path);
+ // We expect unpadded SHA256 hash coming in
+ TEST_AND_RETURN_FALSE(hash.size() == 32);
+ vector<char> padded_hash(hash);
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash);
+ TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(),
+ padded_hash.data(),
+ padded_hash.size()));
+
+ // This runs on the server, so it's okay to cop out and call openssl
+ // executable rather than properly use the library
+ vector<string> cmd;
+ base::SplitString("openssl rsautl -raw -sign -inkey x -in x -out x",
+ ' ',
+ &cmd);
+ cmd[cmd.size() - 5] = private_key_path;
+ cmd[cmd.size() - 3] = hash_path;
+ cmd[cmd.size() - 1] = sig_path;
+
+ // When running unittests, we need to use the openssl version from the
+ // SYSROOT instead of the one on the $PATH (host).
+ cmd[0] = utils::GetPathOnBoard("openssl");
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code, NULL));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ vector<char> signature;
+ TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature));
+ out_signature->swap(signature);
+ return true;
+}
+
+bool PayloadSigner::SignPayload(const string& unsigned_payload_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<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 vector<string>& private_key_paths,
+ uint64_t* out_length) {
+ DCHECK(out_length);
+
+ string x_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("signed_data.XXXXXX", &x_path, NULL));
+ ScopedPathUnlinker x_path_unlinker(x_path);
+ TEST_AND_RETURN_FALSE(utils::WriteFile(x_path.c_str(), "x", 1));
+
+ vector<char> sig_blob;
+ TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path,
+ private_key_paths,
+ &sig_blob));
+ *out_length = sig_blob.size();
+ return true;
+}
+
+bool PayloadSigner::PrepPayloadForHashing(
+ const string& payload_path,
+ const vector<int>& signature_sizes,
+ vector<char>* payload_out,
+ uint64_t* metadata_size_out,
+ uint64_t* signatures_offset_out) {
+ // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+ // Loads the payload and adds the signature op to it.
+ 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(signatures,
+ &signature_blob));
+ TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+ signature_blob.size(),
+ payload_out,
+ metadata_size_out,
+ signatures_offset_out));
+
+ return true;
+}
+
+bool PayloadSigner::HashPayloadForSigning(const string& payload_path,
+ const vector<int>& signature_sizes,
+ vector<char>* out_hash_data) {
+ vector<char> payload;
+ uint64_t metadata_size;
+ uint64_t signatures_offset;
+
+ TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+ signature_sizes,
+ &payload,
+ &metadata_size,
+ &signatures_offset));
+
+ // Calculates the hash on the updated payload. Note that we stop calculating
+ // before we reach the signature information.
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(&payload[0],
+ signatures_offset,
+ out_hash_data));
+ return true;
+}
+
+bool PayloadSigner::HashMetadataForSigning(const string& payload_path,
+ const vector<int>& signature_sizes,
+ vector<char>* out_metadata_hash) {
+ vector<char> payload;
+ uint64_t metadata_size;
+ uint64_t signatures_offset;
+
+ TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+ signature_sizes,
+ &payload,
+ &metadata_size,
+ &signatures_offset));
+
+ // Calculates the hash on the manifest.
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(&payload[0],
+ metadata_size,
+ out_metadata_hash));
+ return true;
+}
+
+bool PayloadSigner::AddSignatureToPayload(
+ const string& payload_path,
+ const vector<vector<char> >& signatures,
+ const string& signed_payload_path,
+ uint64_t *out_metadata_size) {
+ // 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(signatures,
+ &signature_blob));
+ vector<char> payload;
+ uint64_t signatures_offset;
+ TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+ signature_blob.size(),
+ &payload,
+ out_metadata_size,
+ &signatures_offset));
+ // Appends the signature blob to the end of the payload and writes the new
+ // payload.
+ LOG(INFO) << "Payload size before signatures: " << payload.size();
+ payload.resize(signatures_offset);
+ payload.insert(payload.begin() + signatures_offset,
+ signature_blob.begin(),
+ signature_blob.end());
+ LOG(INFO) << "Signed payload size: " << payload.size();
+ TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
+ payload.data(),
+ payload.size()));
+ return true;
+}
+
+bool PayloadSigner::GetMetadataSignature(const char* const metadata,
+ size_t metadata_size,
+ const string& private_key_path,
+ string* out_signature) {
+ // Calculates the hash on the updated payload. Note that the payload includes
+ // the signature op but doesn't include the signature blob at the end.
+ vector<char> metadata_hash;
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(metadata,
+ metadata_size,
+ &metadata_hash));
+
+ vector<char> signature;
+ TEST_AND_RETURN_FALSE(SignHash(metadata_hash,
+ private_key_path,
+ &signature));
+
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::Base64Encode(&signature[0],
+ signature.size(),
+ out_signature));
+ return true;
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h
new file mode 100644
index 0000000..0c540e6
--- /dev/null
+++ b/payload_generator/payload_signer.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signing.
+// See update_metadata.proto for more info.
+
+namespace chromeos_update_engine {
+
+class PayloadSigner {
+ public:
+ // Given a raw |hash| and a private key in |private_key_path| calculates the
+ // raw signature in |out_signature|. Returns true on success, false otherwise.
+ static bool SignHash(const std::vector<char>& hash,
+ const std::string& private_key_path,
+ std::vector<char>* out_signature);
+
+ // 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::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 the given private keys. Returns true on success.
+ static bool SignatureBlobLength(
+ const std::vector<std::string>& private_key_paths,
+ uint64_t* out_length);
+
+ // This is a helper method for HashPayloadforSigning and
+ // HashMetadataForSigning. It loads the payload into memory, and inserts
+ // signature placeholders if Signatures aren't already present.
+ static bool PrepPayloadForHashing(
+ const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ std::vector<char>* payload_out,
+ uint64_t* metadata_size_out,
+ uint64_t* signatures_offset_out);
+
+ // Given an unsigned payload in |payload_path|,
+ // this method does two things:
+ // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+ // to make the manifest match what the final signed payload will look
+ // like based on |signatures_sizes|, if needed.
+ // 2. It calculates the raw SHA256 hash of the payload in |payload_path|
+ // (except signatures) and returns the result in |out_hash_data|.
+ //
+ // The dummy signatures are not preserved or written to disk.
+ static bool HashPayloadForSigning(const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ std::vector<char>* out_hash_data);
+
+ // Given an unsigned payload in |payload_path|,
+ // this method does two things:
+ // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+ // to make the manifest match what the final signed payload will look
+ // like based on |signatures_sizes|, if needed.
+ // 2. It calculates the raw SHA256 hash of the metadata from the payload in
+ // |payload_path| (except signatures) and returns the result in
+ // |out_metadata_hash|.
+ //
+ // The dummy signatures are not preserved or written to disk.
+ static bool HashMetadataForSigning(const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ std::vector<char>* out_metadata_hash);
+
+ // Given an unsigned payload in |payload_path| (with no dummy signature op)
+ // 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. Populates |out_metadata_size| with the size of the
+ // metadata after adding the signature operation in the manifest.Returns true
+ // on success, false otherwise.
+ static bool AddSignatureToPayload(
+ const std::string& payload_path,
+ const std::vector<std::vector<char> >& signatures,
+ const std::string& signed_payload_path,
+ uint64_t* out_metadata_size);
+
+ // Computes the SHA256 hash of the first metadata_size bytes of |metadata|
+ // and signs the hash with the given private_key_path and writes the signed
+ // hash in |out_signature|. Returns true if successful or false if there was
+ // any error in the computations.
+ static bool GetMetadataSignature(const char* const metadata,
+ size_t metadata_size,
+ const std::string& private_key_path,
+ std::string* out_signature);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc
new file mode 100644
index 0000000..00df88c
--- /dev/null
+++ b/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_signer.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_verifier.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+// 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.";
+
+// Generated by:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const unsigned char kDataHash[] = {
+ 0x7a, 0x07, 0xa6, 0x44, 0x08, 0x86, 0x20, 0xa6,
+ 0xc1, 0xf8, 0xd9, 0x02, 0x05, 0x63, 0x0d, 0xb7,
+ 0xfc, 0x2b, 0xa0, 0xa9, 0x7c, 0x9d, 0x1d, 0x8c,
+ 0x01, 0xf5, 0x78, 0x6d, 0xc5, 0x11, 0xb4, 0x06
+};
+
+// Generated with openssl 1.0, which at the time of this writing, you need
+// to download and install yourself. Here's my command:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// ~/local/bin/openssl pkeyutl -sign -inkey unittest_key.pem -pkeyopt
+// digest:sha256 | hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const unsigned char kDataSignature[] = {
+ 0x9f, 0x86, 0x25, 0x8b, 0xf3, 0xcc, 0xe3, 0x95,
+ 0x5f, 0x45, 0x83, 0xb2, 0x66, 0xf0, 0x2a, 0xcf,
+ 0xb7, 0xaa, 0x52, 0x25, 0x7a, 0xdd, 0x9d, 0x65,
+ 0xe5, 0xd6, 0x02, 0x4b, 0x37, 0x99, 0x53, 0x06,
+ 0xc2, 0xc9, 0x37, 0x36, 0x25, 0x62, 0x09, 0x4f,
+ 0x6b, 0x22, 0xf8, 0xb3, 0x89, 0x14, 0x98, 0x1a,
+ 0xbc, 0x30, 0x90, 0x4a, 0x43, 0xf5, 0xea, 0x2e,
+ 0xf0, 0xa4, 0xba, 0xc3, 0xa7, 0xa3, 0x44, 0x70,
+ 0xd6, 0xc4, 0x89, 0xd8, 0x45, 0x71, 0xbb, 0xee,
+ 0x59, 0x87, 0x3d, 0xd5, 0xe5, 0x40, 0x22, 0x3d,
+ 0x73, 0x7e, 0x2a, 0x58, 0x93, 0x8e, 0xcb, 0x9c,
+ 0xf2, 0xbb, 0x4a, 0xc9, 0xd2, 0x2c, 0x52, 0x42,
+ 0xb0, 0xd1, 0x13, 0x22, 0xa4, 0x78, 0xc7, 0xc6,
+ 0x3e, 0xf1, 0xdc, 0x4c, 0x7b, 0x2d, 0x40, 0xda,
+ 0x58, 0xac, 0x4a, 0x11, 0x96, 0x3d, 0xa0, 0x01,
+ 0xf6, 0x96, 0x74, 0xf6, 0x6c, 0x0c, 0x49, 0x69,
+ 0x4e, 0xc1, 0x7e, 0x9f, 0x2a, 0x42, 0xdd, 0x15,
+ 0x6b, 0x37, 0x2e, 0x3a, 0xa7, 0xa7, 0x6d, 0x91,
+ 0x13, 0xe8, 0x59, 0xde, 0xfe, 0x99, 0x07, 0xd9,
+ 0x34, 0x0f, 0x17, 0xb3, 0x05, 0x4c, 0xd2, 0xc6,
+ 0x82, 0xb7, 0x38, 0x36, 0x63, 0x1d, 0x9e, 0x21,
+ 0xa6, 0x32, 0xef, 0xf1, 0x65, 0xe6, 0xed, 0x95,
+ 0x25, 0x9b, 0x61, 0xe0, 0xba, 0x86, 0xa1, 0x7f,
+ 0xf8, 0xa5, 0x4a, 0x32, 0x1f, 0x15, 0x20, 0x8a,
+ 0x41, 0xc5, 0xb0, 0xd9, 0x4a, 0xda, 0x85, 0xf3,
+ 0xdc, 0xa0, 0x98, 0x5d, 0x1d, 0x18, 0x9d, 0x2e,
+ 0x42, 0xea, 0x69, 0x13, 0x74, 0x3c, 0x74, 0xf7,
+ 0x6d, 0x43, 0xb0, 0x63, 0x90, 0xdb, 0x04, 0xd5,
+ 0x05, 0xc9, 0x73, 0x1f, 0x6c, 0xd6, 0xfa, 0x46,
+ 0x4e, 0x0f, 0x33, 0x58, 0x5b, 0x0d, 0x1b, 0x55,
+ 0x39, 0xb9, 0x0f, 0x43, 0x37, 0xc0, 0x06, 0x0c,
+ 0x29, 0x93, 0x43, 0xc7, 0x43, 0xb9, 0xab, 0x7d
+};
+
+namespace {
+void SignSampleData(vector<char>* out_signature_blob) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, NULL));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(),
+ kDataToSign,
+ strlen(kDataToSign)));
+ uint64_t length = 0;
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ vector<string> (1, kUnittestPrivateKeyPath),
+ &length));
+ EXPECT_GT(length, 0);
+ EXPECT_TRUE(PayloadSigner::SignPayload(
+ data_path,
+ vector<string>(1, kUnittestPrivateKeyPath),
+ out_signature_blob));
+ EXPECT_EQ(length, out_signature_blob->size());
+}
+} // namespace
+
+TEST(PayloadSignerTest, SimpleTest) {
+ vector<char> signature_blob;
+ SignSampleData(&signature_blob);
+
+ // Check the signature itself
+ Signatures signatures;
+ EXPECT_TRUE(signatures.ParseFromArray(&signature_blob[0],
+ signature_blob.size()));
+ EXPECT_EQ(1, signatures.signatures_size());
+ const Signatures_Signature& signature = signatures.signatures(0);
+ 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++) {
+ EXPECT_EQ(static_cast<char>(kDataSignature[i]), sig_data[i]);
+ }
+}
+
+TEST(PayloadSignerTest, VerifySignatureTest) {
+ vector<char> signature_blob;
+ SignSampleData(&signature_blob);
+
+ vector<char> hash_data;
+ EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKeyPath,
+ &hash_data));
+ vector<char> padded_hash_data(reinterpret_cast<const char *>(kDataHash),
+ reinterpret_cast<const char *>(kDataHash +
+ sizeof(kDataHash)));
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data);
+ ASSERT_EQ(padded_hash_data.size(), hash_data.size());
+ for (size_t i = 0; i < padded_hash_data.size(); i++) {
+ EXPECT_EQ(padded_hash_data[i], hash_data[i]);
+ }
+}
+
+} // namespace chromeos_update_engine