| // |
| // 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 <base/strings/stringprintf.h> |
| #include <brillo/data_encoding.h> |
| |
| #include "update_engine/common/constants.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" |
| |
| using std::string; |
| |
| 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; |
| |
| uint64_t PayloadMetadata::GetMetadataSignatureSizeOffset() const { |
| return kDeltaManifestSizeOffset + kDeltaManifestSizeSize; |
| } |
| |
| uint64_t PayloadMetadata::GetManifestOffset() const { |
| // Actual manifest begins right after the metadata signature size field. |
| return kDeltaManifestSizeOffset + kDeltaManifestSizeSize + |
| kDeltaMetadataSignatureSizeSize; |
| } |
| |
| MetadataParseResult PayloadMetadata::ParsePayloadHeader( |
| const brillo::Blob& payload, ErrorCode* error) { |
| return ParsePayloadHeader(payload.data(), payload.size(), error); |
| } |
| |
| MetadataParseResult PayloadMetadata::ParsePayloadHeader( |
| const unsigned char* payload, size_t size, ErrorCode* error) { |
| // Ensure we have data to cover the major payload version. |
| if (size < kDeltaManifestSizeOffset) |
| return MetadataParseResult::kInsufficientData; |
| |
| // Validate the magic string. |
| if (memcmp(payload, kDeltaMagic, sizeof(kDeltaMagic)) != 0) { |
| LOG(ERROR) << "Bad payload format -- invalid delta magic: " |
| << base::StringPrintf("%02x%02x%02x%02x", |
| payload[0], |
| payload[1], |
| payload[2], |
| payload[3]) |
| << " Expected: " |
| << base::StringPrintf("%02x%02x%02x%02x", |
| kDeltaMagic[0], |
| kDeltaMagic[1], |
| kDeltaMagic[2], |
| kDeltaMagic[3]); |
| *error = ErrorCode::kDownloadInvalidMetadataMagicString; |
| return MetadataParseResult::kError; |
| } |
| |
| uint64_t manifest_offset = GetManifestOffset(); |
| // Check again with the manifest offset. |
| if (size < manifest_offset) |
| return MetadataParseResult::kInsufficientData; |
| |
| // 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_ < kMinSupportedMajorPayloadVersion || |
| major_payload_version_ > kMaxSupportedMajorPayloadVersion) { |
| LOG(ERROR) << "Bad payload format -- unsupported payload version: " |
| << major_payload_version_; |
| *error = ErrorCode::kUnsupportedMajorPayloadVersion; |
| return MetadataParseResult::kError; |
| } |
| |
| // 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 |
| |
| metadata_size_ = manifest_offset + manifest_size_; |
| if (metadata_size_ < manifest_size_) { |
| // Overflow detected. |
| LOG(ERROR) << "Overflow detected on manifest size."; |
| *error = ErrorCode::kDownloadInvalidMetadataSize; |
| return MetadataParseResult::kError; |
| } |
| |
| // Parse the metadata signature size. |
| static_assert( |
| sizeof(metadata_signature_size_) == kDeltaMetadataSignatureSizeSize, |
| "metadata_signature_size size mismatch"); |
| uint64_t metadata_signature_size_offset = GetMetadataSignatureSizeOffset(); |
| memcpy(&metadata_signature_size_, |
| &payload[metadata_signature_size_offset], |
| kDeltaMetadataSignatureSizeSize); |
| metadata_signature_size_ = be32toh(metadata_signature_size_); |
| |
| if (metadata_size_ + metadata_signature_size_ < metadata_size_) { |
| // Overflow detected. |
| LOG(ERROR) << "Overflow detected on metadata and signature size."; |
| *error = ErrorCode::kDownloadInvalidMetadataSize; |
| return MetadataParseResult::kError; |
| } |
| return MetadataParseResult::kSuccess; |
| } |
| |
| bool PayloadMetadata::ParsePayloadHeader(const brillo::Blob& payload) { |
| ErrorCode error; |
| return ParsePayloadHeader(payload, &error) == MetadataParseResult::kSuccess; |
| } |
| |
| bool PayloadMetadata::GetManifest(const brillo::Blob& payload, |
| DeltaArchiveManifest* out_manifest) const { |
| return GetManifest(payload.data(), payload.size(), out_manifest); |
| } |
| |
| bool PayloadMetadata::GetManifest(const unsigned char* payload, |
| size_t size, |
| DeltaArchiveManifest* out_manifest) const { |
| uint64_t manifest_offset = GetManifestOffset(); |
| CHECK_GE(size, manifest_offset + manifest_size_); |
| return out_manifest->ParseFromArray(&payload[manifest_offset], |
| manifest_size_); |
| } |
| |
| ErrorCode PayloadMetadata::ValidateMetadataSignature( |
| const brillo::Blob& payload, |
| const string& metadata_signature, |
| const PayloadVerifier& payload_verifier) const { |
| if (payload.size() < metadata_size_ + metadata_signature_size_) |
| return ErrorCode::kDownloadMetadataSignatureError; |
| |
| // A single signature in raw bytes. |
| brillo::Blob metadata_signature_blob; |
| // The serialized Signatures protobuf message stored in major version >=2 |
| // payload, it may contain multiple signatures. |
| string metadata_signature_protobuf; |
| 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 { |
| metadata_signature_protobuf.assign( |
| payload.begin() + metadata_size_, |
| payload.begin() + metadata_size_ + metadata_signature_size_); |
| } |
| |
| if (metadata_signature_blob.empty() && metadata_signature_protobuf.empty()) { |
| LOG(ERROR) << "Missing mandatory metadata signature in both Omaha " |
| << "response and payload."; |
| return ErrorCode::kDownloadMetadataSignatureMissingError; |
| } |
| |
| brillo::Blob metadata_hash; |
| if (!HashCalculator::RawHashOfBytes( |
| payload.data(), metadata_size_, &metadata_hash)) { |
| LOG(ERROR) << "Unable to compute actual hash of manifest"; |
| return ErrorCode::kDownloadMetadataSignatureVerificationError; |
| } |
| |
| if (metadata_hash.size() != kSHA256Size) { |
| LOG(ERROR) << "Computed actual hash of metadata has incorrect size: " |
| << metadata_hash.size(); |
| return ErrorCode::kDownloadMetadataSignatureVerificationError; |
| } |
| |
| if (!metadata_signature_blob.empty()) { |
| brillo::Blob decrypted_signature; |
| if (!payload_verifier.VerifyRawSignature( |
| metadata_signature_blob, metadata_hash, &decrypted_signature)) { |
| LOG(ERROR) << "Manifest hash verification failed. Decrypted hash = "; |
| utils::HexDumpVector(decrypted_signature); |
| LOG(ERROR) << "Calculated hash before padding = "; |
| utils::HexDumpVector(metadata_hash); |
| return ErrorCode::kDownloadMetadataSignatureMismatch; |
| } |
| } else { |
| if (!payload_verifier.VerifySignature(metadata_signature_protobuf, |
| 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; |
| } |
| |
| bool PayloadMetadata::ParsePayloadFile(const string& payload_path, |
| DeltaArchiveManifest* manifest, |
| Signatures* metadata_signatures) { |
| brillo::Blob payload; |
| TEST_AND_RETURN_FALSE( |
| utils::ReadFileChunk(payload_path, 0, kMaxPayloadHeaderSize, &payload)); |
| TEST_AND_RETURN_FALSE(ParsePayloadHeader(payload)); |
| |
| if (manifest != nullptr) { |
| TEST_AND_RETURN_FALSE( |
| utils::ReadFileChunk(payload_path, |
| kMaxPayloadHeaderSize, |
| GetMetadataSize() - kMaxPayloadHeaderSize, |
| &payload)); |
| TEST_AND_RETURN_FALSE(GetManifest(payload, manifest)); |
| } |
| |
| if (metadata_signatures != nullptr) { |
| payload.clear(); |
| TEST_AND_RETURN_FALSE(utils::ReadFileChunk( |
| payload_path, GetMetadataSize(), GetMetadataSignatureSize(), &payload)); |
| TEST_AND_RETURN_FALSE( |
| metadata_signatures->ParseFromArray(payload.data(), payload.size())); |
| } |
| |
| return true; |
| } |
| |
| } // namespace chromeos_update_engine |