payload_signer.cc revision 020600db42428e7e96bb0d2e05da225e68650955
1// Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "update_engine/payload_generator/payload_signer.h" 6 7#include <base/logging.h> 8#include <base/strings/string_split.h> 9#include <base/strings/string_util.h> 10#include <openssl/pem.h> 11 12#include "update_engine/omaha_hash_calculator.h" 13#include "update_engine/payload_generator/delta_diff_generator.h" 14#include "update_engine/payload_verifier.h" 15#include "update_engine/subprocess.h" 16#include "update_engine/update_metadata.pb.h" 17#include "update_engine/utils.h" 18 19using std::string; 20using std::vector; 21 22namespace chromeos_update_engine { 23 24namespace { 25 26// Given raw |signatures|, packs them into a protobuf and serializes it into a 27// binary blob. Returns true on success, false otherwise. 28bool ConvertSignatureToProtobufBlob(const vector<vector<char>>& signatures, 29 vector<char>* out_signature_blob) { 30 // Pack it into a protobuf 31 Signatures out_message; 32 uint32_t version = kSignatureMessageOriginalVersion; 33 LOG_IF(WARNING, kSignatureMessageCurrentVersion - 34 kSignatureMessageOriginalVersion + 1 < signatures.size()) 35 << "You may want to support clients in the range [" 36 << kSignatureMessageOriginalVersion << ", " 37 << kSignatureMessageCurrentVersion << "] inclusive, but you only " 38 << "provided " << signatures.size() << " signatures."; 39 for (const vector<char>& signature : signatures) { 40 Signatures_Signature* sig_message = out_message.add_signatures(); 41 sig_message->set_version(version++); 42 sig_message->set_data(signature.data(), signature.size()); 43 } 44 45 // Serialize protobuf 46 string serialized; 47 TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized)); 48 out_signature_blob->insert(out_signature_blob->end(), 49 serialized.begin(), 50 serialized.end()); 51 LOG(INFO) << "Signature blob size: " << out_signature_blob->size(); 52 return true; 53} 54 55// Given an unsigned payload under |payload_path| and the |signature_blob_size| 56// generates an updated payload that includes a dummy signature op in its 57// manifest. It populates |out_metadata_size| with the size of the final 58// manifest after adding the dummy signature operation, and 59// |out_signatures_offset| with the expected offset for the new blob. Returns 60// true on success, false otherwise. 61bool AddSignatureOpToPayload(const string& payload_path, 62 uint64_t signature_blob_size, 63 vector<char>* out_payload, 64 uint64_t* out_metadata_size, 65 uint64_t* out_signatures_offset) { 66 const int kProtobufOffset = 20; 67 const int kProtobufSizeOffset = 12; 68 69 // Loads the payload. 70 vector<char> payload; 71 DeltaArchiveManifest manifest; 72 uint64_t metadata_size; 73 TEST_AND_RETURN_FALSE(PayloadVerifier::LoadPayload( 74 payload_path, &payload, &manifest, &metadata_size)); 75 76 // Is there already a signature op in place? 77 if (manifest.has_signatures_size()) { 78 // The signature op is tied to the size of the signature blob, but not it's 79 // contents. We don't allow the manifest to change if there is already an op 80 // present, because that might invalidate previously generated 81 // hashes/signatures. 82 if (manifest.signatures_size() != signature_blob_size) { 83 LOG(ERROR) << "Attempt to insert different signature sized blob. " 84 << "(current:" << manifest.signatures_size() 85 << "new:" << signature_blob_size << ")"; 86 return false; 87 } 88 89 LOG(INFO) << "Matching signature sizes already present."; 90 } else { 91 // Updates the manifest to include the signature operation. 92 DeltaDiffGenerator::AddSignatureOp(payload.size() - metadata_size, 93 signature_blob_size, 94 &manifest); 95 96 // Updates the payload to include the new manifest. 97 string serialized_manifest; 98 TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest)); 99 LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size(); 100 payload.erase(payload.begin() + kProtobufOffset, 101 payload.begin() + metadata_size); 102 payload.insert(payload.begin() + kProtobufOffset, 103 serialized_manifest.begin(), 104 serialized_manifest.end()); 105 106 // Updates the protobuf size. 107 uint64_t size_be = htobe64(serialized_manifest.size()); 108 memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be)); 109 metadata_size = serialized_manifest.size() + kProtobufOffset; 110 111 LOG(INFO) << "Updated payload size: " << payload.size(); 112 LOG(INFO) << "Updated metadata size: " << metadata_size; 113 } 114 115 out_payload->swap(payload); 116 *out_metadata_size = metadata_size; 117 *out_signatures_offset = metadata_size + manifest.signatures_offset(); 118 LOG(INFO) << "Signature Blob Offset: " << *out_signatures_offset; 119 return true; 120} 121} // namespace 122 123bool PayloadSigner::SignHash(const vector<char>& hash, 124 const string& private_key_path, 125 vector<char>* out_signature) { 126 LOG(INFO) << "Signing hash with private key: " << private_key_path; 127 string sig_path; 128 TEST_AND_RETURN_FALSE( 129 utils::MakeTempFile("signature.XXXXXX", &sig_path, nullptr)); 130 ScopedPathUnlinker sig_path_unlinker(sig_path); 131 132 string hash_path; 133 TEST_AND_RETURN_FALSE( 134 utils::MakeTempFile("hash.XXXXXX", &hash_path, nullptr)); 135 ScopedPathUnlinker hash_path_unlinker(hash_path); 136 // We expect unpadded SHA256 hash coming in 137 TEST_AND_RETURN_FALSE(hash.size() == 32); 138 vector<char> padded_hash(hash); 139 PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash); 140 TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(), 141 padded_hash.data(), 142 padded_hash.size())); 143 144 // This runs on the server, so it's okay to cop out and call openssl 145 // executable rather than properly use the library 146 vector<string> cmd; 147 base::SplitString("openssl rsautl -raw -sign -inkey x -in x -out x", 148 ' ', 149 &cmd); 150 cmd[cmd.size() - 5] = private_key_path; 151 cmd[cmd.size() - 3] = hash_path; 152 cmd[cmd.size() - 1] = sig_path; 153 154 // When running unittests, we need to use the openssl version from the 155 // SYSROOT instead of the one on the $PATH (host). 156 cmd[0] = utils::GetPathOnBoard("openssl"); 157 158 int return_code = 0; 159 TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code, 160 nullptr)); 161 TEST_AND_RETURN_FALSE(return_code == 0); 162 163 vector<char> signature; 164 TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature)); 165 out_signature->swap(signature); 166 return true; 167} 168 169bool PayloadSigner::SignPayload(const string& unsigned_payload_path, 170 const vector<string>& private_key_paths, 171 vector<char>* out_signature_blob) { 172 vector<char> hash_data; 173 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfFile( 174 unsigned_payload_path, -1, &hash_data) == 175 utils::FileSize(unsigned_payload_path)); 176 177 vector<vector<char>> signatures; 178 for (const string& path : private_key_paths) { 179 vector<char> signature; 180 TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature)); 181 signatures.push_back(signature); 182 } 183 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures, 184 out_signature_blob)); 185 return true; 186} 187 188bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths, 189 uint64_t* out_length) { 190 DCHECK(out_length); 191 192 string x_path; 193 TEST_AND_RETURN_FALSE( 194 utils::MakeTempFile("signed_data.XXXXXX", &x_path, nullptr)); 195 ScopedPathUnlinker x_path_unlinker(x_path); 196 TEST_AND_RETURN_FALSE(utils::WriteFile(x_path.c_str(), "x", 1)); 197 198 vector<char> sig_blob; 199 TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path, 200 private_key_paths, 201 &sig_blob)); 202 *out_length = sig_blob.size(); 203 return true; 204} 205 206bool PayloadSigner::PrepPayloadForHashing( 207 const string& payload_path, 208 const vector<int>& signature_sizes, 209 vector<char>* payload_out, 210 uint64_t* metadata_size_out, 211 uint64_t* signatures_offset_out) { 212 // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory. 213 214 // Loads the payload and adds the signature op to it. 215 vector<vector<char>> signatures; 216 for (int signature_size : signature_sizes) { 217 signatures.emplace_back(signature_size, 0); 218 } 219 vector<char> signature_blob; 220 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures, 221 &signature_blob)); 222 TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path, 223 signature_blob.size(), 224 payload_out, 225 metadata_size_out, 226 signatures_offset_out)); 227 228 return true; 229} 230 231bool PayloadSigner::HashPayloadForSigning(const string& payload_path, 232 const vector<int>& signature_sizes, 233 vector<char>* out_hash_data) { 234 vector<char> payload; 235 uint64_t metadata_size; 236 uint64_t signatures_offset; 237 238 TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path, 239 signature_sizes, 240 &payload, 241 &metadata_size, 242 &signatures_offset)); 243 244 // Calculates the hash on the updated payload. Note that we stop calculating 245 // before we reach the signature information. 246 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(&payload[0], 247 signatures_offset, 248 out_hash_data)); 249 return true; 250} 251 252bool PayloadSigner::HashMetadataForSigning(const string& payload_path, 253 const vector<int>& signature_sizes, 254 vector<char>* out_metadata_hash) { 255 vector<char> payload; 256 uint64_t metadata_size; 257 uint64_t signatures_offset; 258 259 TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path, 260 signature_sizes, 261 &payload, 262 &metadata_size, 263 &signatures_offset)); 264 265 // Calculates the hash on the manifest. 266 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(&payload[0], 267 metadata_size, 268 out_metadata_hash)); 269 return true; 270} 271 272bool PayloadSigner::AddSignatureToPayload( 273 const string& payload_path, 274 const vector<vector<char>>& signatures, 275 const string& signed_payload_path, 276 uint64_t *out_metadata_size) { 277 // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory. 278 279 // Loads the payload and adds the signature op to it. 280 vector<char> signature_blob; 281 TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures, 282 &signature_blob)); 283 vector<char> payload; 284 uint64_t signatures_offset; 285 TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path, 286 signature_blob.size(), 287 &payload, 288 out_metadata_size, 289 &signatures_offset)); 290 // Appends the signature blob to the end of the payload and writes the new 291 // payload. 292 LOG(INFO) << "Payload size before signatures: " << payload.size(); 293 payload.resize(signatures_offset); 294 payload.insert(payload.begin() + signatures_offset, 295 signature_blob.begin(), 296 signature_blob.end()); 297 LOG(INFO) << "Signed payload size: " << payload.size(); 298 TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(), 299 payload.data(), 300 payload.size())); 301 return true; 302} 303 304bool PayloadSigner::GetMetadataSignature(const char* const metadata, 305 size_t metadata_size, 306 const string& private_key_path, 307 string* out_signature) { 308 // Calculates the hash on the updated payload. Note that the payload includes 309 // the signature op but doesn't include the signature blob at the end. 310 vector<char> metadata_hash; 311 TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(metadata, 312 metadata_size, 313 &metadata_hash)); 314 315 vector<char> signature; 316 TEST_AND_RETURN_FALSE(SignHash(metadata_hash, 317 private_key_path, 318 &signature)); 319 320 TEST_AND_RETURN_FALSE(OmahaHashCalculator::Base64Encode(&signature[0], 321 signature.size(), 322 out_signature)); 323 return true; 324} 325 326 327} // namespace chromeos_update_engine 328