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