// Copyright 2021 The Pigweed Authors // // 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 // // https://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. #define PW_LOG_MODULE_NAME "PWSU" #define PW_LOG_LEVEL PW_LOG_LEVEL_WARN #include "pw_software_update/update_bundle_accessor.h" #include #include #include #include "pw_crypto/ecdsa.h" #include "pw_crypto/sha256.h" #include "pw_log/log.h" #include "pw_protobuf/message.h" #include "pw_result/result.h" #include "pw_software_update/config.h" #include "pw_software_update/manifest_accessor.h" #include "pw_software_update/update_bundle.pwpb.h" #include "pw_stream/interval_reader.h" #include "pw_stream/memory_stream.h" #include "pw_string/string_builder.h" namespace pw::software_update { namespace { Result VerifyEcdsaSignature(protobuf::Bytes public_key, ConstByteSpan digest, protobuf::Bytes signature) { // TODO(pwbug/456): Move this logic into an variant of the API in // pw_crypto:ecdsa that takes readers as inputs. std::byte public_key_bytes[65]; std::byte signature_bytes[64]; stream::IntervalReader key_reader = public_key.GetBytesReader(); stream::IntervalReader sig_reader = signature.GetBytesReader(); PW_TRY(key_reader.Read(public_key_bytes)); PW_TRY(sig_reader.Read(signature_bytes)); Status status = crypto::ecdsa::VerifyP256Signature( public_key_bytes, digest, signature_bytes); if (!status.ok()) { return false; } return true; } // Convert an integer from [0, 16) to a hex char char IntToHex(uint8_t val) { PW_ASSERT(val < 16); return val >= 10 ? (val - 10) + 'a' : val + '0'; } void LogKeyId(ConstByteSpan key_id) { char key_id_str[pw::crypto::sha256::kDigestSizeBytes * 2 + 1] = {0}; for (size_t i = 0; i < pw::crypto::sha256::kDigestSizeBytes; i++) { uint8_t value = std::to_integer(key_id[i]); key_id_str[i * 2] = IntToHex((value >> 4) & 0xf); key_id_str[i * 2 + 1] = IntToHex(value & 0xf); } PW_LOG_DEBUG("key_id: %s", key_id_str); } // Verifies signatures of a TUF metadata. Status VerifyMetadataSignatures(protobuf::Bytes message, protobuf::RepeatedMessages signatures, protobuf::Message signature_requirement, protobuf::StringToMessageMap key_mapping) { // Gets the threshold -- at least `threshold` number of signatures must // pass verification in order to trust this metadata. protobuf::Uint32 threshold = signature_requirement.AsUint32( static_cast(SignatureRequirement::Fields::THRESHOLD)); PW_TRY(threshold.status()); // Gets the ids of keys that are allowed for verifying the signatures. protobuf::RepeatedBytes allowed_key_ids = signature_requirement.AsRepeatedBytes( static_cast(SignatureRequirement::Fields::KEY_IDS)); PW_TRY(allowed_key_ids.status()); // Verifies the signatures. Check that at least `threshold` number of // signatures can be verified using the allowed keys. size_t verified_count = 0; size_t total_signatures = 0; for (protobuf::Message signature : signatures) { total_signatures++; protobuf::Bytes key_id = signature.AsBytes(static_cast(Signature::Fields::KEY_ID)); PW_TRY(key_id.status()); // Reads the key id into a buffer, so that we can check whether it is // listed as allowed and look up the key value later. std::byte key_id_buf[pw::crypto::sha256::kDigestSizeBytes]; stream::IntervalReader key_id_reader = key_id.GetBytesReader(); Result key_id_read_res = key_id_reader.Read(key_id_buf); PW_TRY(key_id_read_res.status()); if (key_id_read_res.value().size() != sizeof(key_id_buf)) { return Status::Internal(); } // Verify that the `key_id` is listed in `allowed_key_ids`. // Note that the function assumes that the key id is properly derived // from the key (via sha256). bool key_id_is_allowed = false; for (protobuf::Bytes trusted : allowed_key_ids) { Result key_id_equal = trusted.Equal(key_id_buf); PW_TRY(key_id_equal.status()); if (key_id_equal.value()) { key_id_is_allowed = true; break; } } if (!key_id_is_allowed) { PW_LOG_DEBUG("Skipping a key id not listed in allowed key ids"); LogKeyId(key_id_buf); continue; } // Retrieves the signature bytes. protobuf::Bytes sig = signature.AsBytes(static_cast(Signature::Fields::SIG)); PW_TRY(sig.status()); // Extracts the key type, scheme and value information. std::string_view key_id_str(reinterpret_cast(key_id_buf), sizeof(key_id_buf)); protobuf::Message key_info = key_mapping[key_id_str]; PW_TRY(key_info.status()); protobuf::Bytes key_val = key_info.AsBytes(static_cast(Key::Fields::KEYVAL)); PW_TRY(key_val.status()); // The function assume that all keys are ECDSA keys. This is guaranteed // by the fact that all trusted roots have undergone content check. // computes the sha256 hash std::byte sha256_digest[32]; stream::IntervalReader bytes_reader = message.GetBytesReader(); PW_TRY(crypto::sha256::Hash(bytes_reader, sha256_digest)); Result res = VerifyEcdsaSignature(key_val, sha256_digest, sig); PW_TRY(res.status()); if (res.value()) { verified_count++; if (verified_count == threshold.value()) { return OkStatus(); } } } if (total_signatures == 0) { // For self verification to tell apart unsigned bundles. return Status::NotFound(); } PW_LOG_ERROR("Insufficient signatures. Requires at least %u, verified %u", threshold.value(), verified_count); return Status::Unauthenticated(); } // Verifies the signatures of a signed new root metadata against a given // trusted root. The helper function extracts the corresponding key maping // signature requirement, signatures from the trusted root and passes them // to VerifyMetadataSignatures(). // // Precondition: The trusted root metadata has undergone content validity check. Result VerifyRootMetadataSignatures(protobuf::Message trusted_root, protobuf::Message new_root) { // Retrieves the trusted root metadata content message. protobuf::Message trusted = trusted_root.AsMessage(static_cast( SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA)); PW_TRY(trusted.status()); // Retrieves the serialized new root metadata bytes. protobuf::Bytes serialized = new_root.AsBytes(static_cast( SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA)); PW_TRY(serialized.status()); // Gets the key mapping from the trusted root metadata. protobuf::StringToMessageMap key_mapping = trusted.AsStringToMessageMap( static_cast(RootMetadata::Fields::KEYS)); PW_TRY(key_mapping.status()); // Gets the signatures of the new root. protobuf::RepeatedMessages signatures = new_root.AsRepeatedMessages( static_cast(SignedRootMetadata::Fields::SIGNATURES)); PW_TRY(signatures.status()); // Gets the signature requirement from the trusted root metadata. protobuf::Message signature_requirement = trusted.AsMessage( static_cast(RootMetadata::Fields::ROOT_SIGNATURE_REQUIREMENT)); PW_TRY(signature_requirement.status()); // Verifies the signatures. PW_TRY(VerifyMetadataSignatures( serialized, signatures, signature_requirement, key_mapping)); return true; } Result GetMetadataVersion(protobuf::Message& metadata, uint32_t common_metatdata_field_number) { // message [Root|Targets]Metadata { // ... // CommonMetadata common_metadata = ; // ... // } // // message CommonMetadata { // ... // uint32 version = ; // ... // } protobuf::Message common_metadata = metadata.AsMessage(common_metatdata_field_number); PW_TRY(common_metadata.status()); protobuf::Uint32 res = common_metadata.AsUint32( static_cast(software_update::CommonMetadata::Fields::VERSION)); PW_TRY(res.status()); return res.value(); } // Reads a protobuf::String into a buffer and returns a std::string_view. Result ReadProtoString(protobuf::String str, std::span buffer) { stream::IntervalReader reader = str.GetBytesReader(); if (reader.interval_size() > buffer.size()) { return Status::ResourceExhausted(); } Result res = reader.Read(std::as_writable_bytes(buffer)); PW_TRY(res.status()); return std::string_view(buffer.data(), res.value().size()); } } // namespace Status UpdateBundleAccessor::OpenAndVerify() { if (Status status = DoOpen(); !status.ok()) { PW_LOG_ERROR("Failed to open staged bundle"); return status; } if (Status status = DoVerify(); !status.ok()) { PW_LOG_ERROR("Failed to verified staged bundle"); Close(); return status; } return OkStatus(); } Result UpdateBundleAccessor::GetTotalPayloadSize() { protobuf::RepeatedMessages manifested_targets = GetManifest().GetTargetFiles(); PW_TRY(manifested_targets.status()); protobuf::StringToBytesMap bundled_payloads = bundle_.AsStringToBytesMap( static_cast(UpdateBundle::Fields::TARGET_PAYLOADS)); PW_TRY(bundled_payloads.status()); uint64_t total_bytes; std::array name_buffer = {}; for (protobuf::Message target : manifested_targets) { protobuf::String target_name = target.AsString(static_cast(TargetFile::Fields::FILE_NAME)); stream::IntervalReader name_reader = target_name.GetBytesReader(); PW_TRY(name_reader.status()); if (name_reader.interval_size() > name_buffer.size()) { return Status::OutOfRange(); } Result read_result = name_reader.Read(name_buffer); PW_TRY(read_result.status()); ConstByteSpan name_span = read_result.value(); std::string_view name_view(reinterpret_cast(name_span.data()), name_span.size_bytes()); if (!bundled_payloads[name_view].ok()) { continue; } protobuf::Uint64 target_length = target.AsUint64(static_cast(TargetFile::Fields::LENGTH)); PW_TRY(target_length.status()); total_bytes += target_length.value(); } return total_bytes; } // Get the target element corresponding to `target_file` stream::IntervalReader UpdateBundleAccessor::GetTargetPayload( std::string_view target_name) { protobuf::Message manifest_entry = GetManifest().GetTargetFile(target_name); PW_TRY(manifest_entry.status()); protobuf::StringToBytesMap payloads_map = bundle_.AsStringToBytesMap( static_cast(UpdateBundle::Fields::TARGET_PAYLOADS)); return payloads_map[target_name].GetBytesReader(); } // Get the target element corresponding to `target_file` stream::IntervalReader UpdateBundleAccessor::GetTargetPayload( protobuf::String target_name) { char name_buf[MAX_TARGET_NAME_LENGTH] = {0}; Result name_view = ReadProtoString(target_name, name_buf); PW_TRY(name_view.status()); return GetTargetPayload(name_view.value()); } Status UpdateBundleAccessor::PersistManifest() { ManifestAccessor manifest = GetManifest(); // GetManifest() fails if the bundle is yet to be verified. PW_TRY(manifest.status()); // Notify backend to prepare to receive a new manifest. PW_TRY(backend_.BeforeManifestWrite()); Result writer = backend_.GetManifestWriter(); PW_TRY(writer.status()); PW_CHECK_NOTNULL(writer.value()); PW_TRY(manifest.Export(*writer.value())); // Notify backend we are done writing. Backend should finalize // (seal the box). PW_TRY(backend_.AfterManifestWrite()); return OkStatus(); } Status UpdateBundleAccessor::Close() { bundle_verified_ = false; return blob_store_reader_.IsOpen() ? blob_store_reader_.Close() : OkStatus(); } Status UpdateBundleAccessor::DoOpen() { PW_TRY(blob_store_.Init()); PW_TRY(blob_store_reader_.Open()); bundle_ = protobuf::Message(blob_store_reader_, blob_store_reader_.ConservativeReadLimit()); if (!bundle_.ok()) { blob_store_reader_.Close(); return bundle_.status(); } return OkStatus(); } Status UpdateBundleAccessor::DoVerify() { #if PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION PW_LOG_WARN("Bundle verification is compiled out."); bundle_verified_ = true; return OkStatus(); #else // PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION bundle_verified_ = false; // Verify and upgrade the on-device trust to the incoming root metadata if // one is included. if (Status status = UpgradeRoot(); !status.ok()) { PW_LOG_ERROR("Failed to upgrade to Root in staged bundle"); return status; } // TODO(pwbug/456): Verify the targets metadata against the current trusted // root. if (Status status = VerifyTargetsMetadata(); !status.ok()) { PW_LOG_ERROR("Failed to verify Targets metadata"); return status; } // TODO(pwbug/456): Investigate whether targets payload verification should // be performed here or deferred until a specific target is requested. if (Status status = VerifyTargetsPayloads(); !status.ok()) { PW_LOG_ERROR("Failed to verify all manifested payloads"); return status; } // TODO(pwbug/456): Invoke the backend to do downstream verification of the // bundle (e.g. compatibility and manifest completeness checks). bundle_verified_ = true; return OkStatus(); #endif // PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION } protobuf::Message UpdateBundleAccessor::GetOnDeviceTrustedRoot() { Result res = backend_.GetRootMetadataReader(); if (!(res.ok() && res.value())) { PW_LOG_ERROR("Failed to get on-device Root metadata"); return res.status(); } // Seek to the beginning so that ConservativeReadLimit() returns the correct // value. PW_TRY(res.value()->Seek(0, stream::Stream::Whence::kBeginning)); return protobuf::Message(*res.value(), res.value()->ConservativeReadLimit()); } ManifestAccessor UpdateBundleAccessor::GetOnDeviceManifest() { // Notify backend to check if an on-device manifest exists and is valid and if // yes, prepare a ready-to-go reader. PW_TRY(backend_.BeforeManifestRead()); Result manifest_reader = backend_.GetManifestReader(); PW_TRY(manifest_reader.status()); PW_CHECK_NOTNULL(manifest_reader.value()); // In case `backend_.BeforeManifestRead()` forgot to reset the reader. PW_TRY(manifest_reader.value()->Seek(0, stream::Stream::Whence::kBeginning)); return ManifestAccessor::FromManifest( protobuf::Message(*manifest_reader.value(), manifest_reader.value()->ConservativeReadLimit())); } Status UpdateBundleAccessor::UpgradeRoot() { protobuf::Message new_root = bundle_.AsMessage( static_cast(UpdateBundle::Fields::ROOT_METADATA)); // Try self-verification even if verification is disabled by the caller. This // minimizes surprises when the caller do decide to turn on verification. bool self_verifying = disable_verification_; // Choose and cache the root metadata to trust. trusted_root_ = self_verifying ? new_root : GetOnDeviceTrustedRoot(); if (!new_root.status().ok()) { // Don't bother upgrading if not found or invalid. PW_LOG_WARN("Incoming root metadata not found or invalid"); return OkStatus(); } // A valid trust anchor is required onwards from here. PW_TRY(trusted_root_.status()); // TODO(pwbug/456): Check whether the bundle contains a root metadata that // is different from the on-device trusted root. // Verify the signatures against the trusted root metadata. Result verify_res = VerifyRootMetadataSignatures(trusted_root_, new_root); if (!(verify_res.status().ok() && verify_res.value())) { PW_LOG_ERROR("Failed to verify incoming root against the current root"); return Status::Unauthenticated(); } // TODO(pwbug/456): Verifiy the content of the new root metadata, including: // 1) Check role magic field. // 2) Check signature requirement. Specifically, check that no key is // reused across different roles and keys are unique in the same // requirement. // 3) Check key mapping. Specifically, check that all keys are unique, // ECDSA keys, and the key ids are exactly the SHA256 of `key type + // key scheme + key value`. // Verify the signatures against the new root metadata. verify_res = VerifyRootMetadataSignatures(new_root, new_root); if (!(verify_res.status().ok() && verify_res.value())) { PW_LOG_ERROR("Fail to verify incoming root against itself"); return Status::Unauthenticated(); } // TODO(pwbug/456): Check rollback. // Retrieves the trusted root metadata content message. protobuf::Message trusted_root_content = trusted_root_.AsMessage(static_cast( SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA)); PW_TRY(trusted_root_content.status()); Result trusted_root_version = GetMetadataVersion( trusted_root_content, static_cast(RootMetadata::Fields::COMMON_METADATA)); PW_TRY(trusted_root_version.status()); // Retrieves the serialized new root metadata message. protobuf::Message new_root_content = new_root.AsMessage(static_cast( SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA)); PW_TRY(new_root_content.status()); Result new_root_version = GetMetadataVersion( new_root_content, static_cast(RootMetadata::Fields::COMMON_METADATA)); PW_TRY(new_root_version.status()); if (trusted_root_version.value() > new_root_version.value()) { PW_LOG_ERROR("Root attempts to rollback from %u to %u", trusted_root_version.value(), new_root_version.value()); return Status::Unauthenticated(); } if (!self_verifying) { // Persist the root immediately after it is successfully verified. This is // to make sure the trust anchor is up-to-date in storage as soon as // we are confident. Although targets metadata and product-specific // verification have not been done yet. They should be independent from and // not gate the upgrade of root key. This allows timely revokation of // compromise keys. stream::IntervalReader new_root_reader = new_root.ToBytes().GetBytesReader(); PW_TRY(backend_.SafelyPersistRootMetadata(new_root_reader)); } // TODO(pwbug/456): Implement key change detection to determine whether // rotation has occured or not. Delete the persisted targets metadata version // if any of the targets keys has been rotated. return OkStatus(); } Status UpdateBundleAccessor::VerifyTargetsMetadata() { bool self_verifying = disable_verification_; if (self_verifying && !trusted_root_.status().ok()) { PW_LOG_WARN( "Self-verification won't verify Targets metadata because there is no " "root"); return OkStatus(); } // A valid trust anchor is required from now on. PW_TRY(trusted_root_.status()); // Retrieve the signed targets metadata map. // // message UpdateBundle { // ... // map target_metadata = ; // ... // } protobuf::StringToMessageMap signed_targets_metadata_map = bundle_.AsStringToMessageMap( static_cast(UpdateBundle::Fields::TARGETS_METADATA)); PW_TRY(signed_targets_metadata_map.status()); // The top-level targets metadata is identified by key name "targets" in the // map. protobuf::Message signed_top_level_targets_metadata = signed_targets_metadata_map[kTopLevelTargetsName]; PW_TRY(signed_top_level_targets_metadata.status()); // Retrieve the serialized metadata. // // message SignedTargetsMetadata { // ... // bytes serialized_target_metadata = ; // ... // } protobuf::Message top_level_targets_metadata = signed_top_level_targets_metadata.AsMessage(static_cast( SignedTargetsMetadata::Fields::SERIALIZED_TARGETS_METADATA)); // Get the sigantures from the signed targets metadata. protobuf::RepeatedMessages signatures = signed_top_level_targets_metadata.AsRepeatedMessages( static_cast(SignedTargetsMetadata::Fields::SIGNATURES)); PW_TRY(signatures.status()); // Retrieve the trusted root metadata message. protobuf::Message trusted_root = trusted_root_.AsMessage(static_cast( SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA)); PW_TRY(trusted_root.status()); // Get the key_mapping from the trusted root metadata. protobuf::StringToMessageMap key_mapping = trusted_root.AsStringToMessageMap( static_cast(RootMetadata::Fields::KEYS)); PW_TRY(key_mapping.status()); // Get the targest metadtata siganture requirement from the trusted root. protobuf::Message signature_requirement = trusted_root.AsMessage(static_cast( RootMetadata::Fields::TARGETS_SIGNATURE_REQUIREMENT)); PW_TRY(signature_requirement.status()); // Verify the sigantures Status sig_res = VerifyMetadataSignatures(top_level_targets_metadata.ToBytes(), signatures, signature_requirement, key_mapping); if (self_verifying && sig_res.IsNotFound()) { PW_LOG_WARN("Self-verification ignoring unsigned bundle"); return OkStatus(); } if (!sig_res.ok()) { PW_LOG_ERROR("Targets Metadata failed signature verification"); return Status::Unauthenticated(); } // TODO(pwbug/456): Check targets metadtata content. if (self_verifying) { // Don't bother because it does not matter. PW_LOG_WARN("Self verification does not do Targets metadata anti-rollback"); return OkStatus(); } // Anti-rollback check. ManifestAccessor device_manifest = GetOnDeviceManifest(); if (device_manifest.status().IsNotFound()) { PW_LOG_WARN("Skipping OTA anti-rollback due to absent device manifest"); return OkStatus(); } protobuf::Uint32 current_version = device_manifest.GetVersion(); PW_TRY(current_version.status()); // Retrieves the version from the new metadata Result new_version = GetMetadataVersion( top_level_targets_metadata, static_cast( software_update::TargetsMetadata::Fields::COMMON_METADATA)); PW_TRY(new_version.status()); if (current_version.value() > new_version.value()) { PW_LOG_ERROR("Blocking Targets metadata rollback from %u to %u", current_version.value(), new_version.value()); return Status::Unauthenticated(); } return OkStatus(); } Status UpdateBundleAccessor::VerifyTargetsPayloads() { ManifestAccessor bundle_manifest = ManifestAccessor::FromBundle(bundle_); PW_TRY(bundle_manifest.status()); // Target file descriptors (pathname, length, hash, etc.) listed in the bundle // manifest. protobuf::RepeatedMessages target_files = bundle_manifest.GetTargetFiles(); PW_TRY(target_files.status()); // Verify length and SHA256 hash for each file listed in the manifest. for (protobuf::Message target_file : target_files) { // Extract target file name in the form of a `std::string_view`. protobuf::String name_proto = target_file.AsString( static_cast(TargetFile::Fields::FILE_NAME)); PW_TRY(name_proto.status()); char name_buf[MAX_TARGET_NAME_LENGTH] = {0}; Result target_name = ReadProtoString(name_proto, name_buf); PW_TRY(target_name.status()); // Get target length. protobuf::Uint64 target_length = target_file.AsUint64(static_cast(TargetFile::Fields::LENGTH)); PW_TRY(target_length.status()); if (target_length.value() > PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE) { PW_LOG_ERROR("Target payload too big. Maximum is %llu bytes", PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE); return Status::OutOfRange(); } // Get target SHA256 hash. protobuf::Bytes target_sha256 = Status::NotFound(); protobuf::RepeatedMessages hashes = target_file.AsRepeatedMessages( static_cast(TargetFile::Fields::HASHES)); for (protobuf::Message hash : hashes) { protobuf::Uint32 hash_function = hash.AsUint32(static_cast(Hash::Fields::FUNCTION)); PW_TRY(hash_function.status()); if (hash_function.value() == static_cast(HashFunction::SHA256)) { target_sha256 = hash.AsBytes(static_cast(Hash::Fields::HASH)); break; } } PW_TRY(target_sha256.status()); if (Status status = VerifyTargetPayload( bundle_manifest, target_name.value(), target_length, target_sha256); !status.ok()) { PW_LOG_ERROR("Target: %s failed verification", pw::MakeString(target_name.value()).c_str()); return status; } } // for each target file in manifest. return OkStatus(); } Status UpdateBundleAccessor::VerifyTargetPayload( ManifestAccessor manifest, std::string_view target_name, protobuf::Uint64 expected_length, protobuf::Bytes expected_sha256) { protobuf::StringToBytesMap payloads_map = bundle_.AsStringToBytesMap( static_cast(UpdateBundle::Fields::TARGET_PAYLOADS)); stream::IntervalReader payload_reader = payloads_map[target_name].GetBytesReader(); Status status; if (payload_reader.ok()) { status = VerifyInBundleTargetPayload( expected_length, expected_sha256, payload_reader); } else { status = VerifyOutOfBundleTargetPayload( target_name, expected_length, expected_sha256); } // TODO(alizhang): Notify backend to do additional checks by calling // backend_.VerifyTargetFile(...). return status; } // TODO(alizhang): Add unit tests for all failure conditions. Status UpdateBundleAccessor::VerifyOutOfBundleTargetPayload( std::string_view target_name, protobuf::Uint64 expected_length, protobuf::Bytes expected_sha256) { #if PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION // The target payload is "personalized out". We we can't take a measurement // without backend help. For now we will check against the device manifest // which contains a cached measurement of the last software update. ManifestAccessor device_manifest = GetOnDeviceManifest(); if (!device_manifest.ok()) { PW_LOG_ERROR( "Can't verify personalized-out target because on-device manifest is " "not found"); return Status::Unauthenticated(); } protobuf::Message cached = device_manifest.GetTargetFile(target_name); if (!cached.ok()) { PW_LOG_ERROR( "Can't verify personalized-out target because it is not found from " "on-device manifest"); return Status::Unauthenticated(); } protobuf::Uint64 cached_length = cached.AsUint64(static_cast(TargetFile::Fields::LENGTH)); PW_TRY(cached_length.status()); if (cached_length.value() != expected_length.value()) { PW_LOG_ERROR("Personalized-out target has bad length: %llu, expected: %llu", cached_length.value(), expected_length.value()); return Status::Unauthenticated(); } protobuf::Bytes cached_sha256 = Status::NotFound(); protobuf::RepeatedMessages hashes = cached.AsRepeatedMessages( static_cast(TargetFile::Fields::HASHES)); for (protobuf::Message hash : hashes) { protobuf::Uint32 hash_function = hash.AsUint32(static_cast(Hash::Fields::FUNCTION)); PW_TRY(hash_function.status()); if (hash_function.value() == static_cast(HashFunction::SHA256)) { cached_sha256 = hash.AsBytes(static_cast(Hash::Fields::HASH)); break; } } std::byte sha256[crypto::sha256::kDigestSizeBytes] = {}; PW_TRY(cached_sha256.GetBytesReader().Read(sha256)); Result hash_equal = expected_sha256.Equal(sha256); PW_TRY(hash_equal.status()); if (!hash_equal.value()) { PW_LOG_ERROR("Personalized-out target has a bad hash"); return Status::Unauthenticated(); } return OkStatus(); #else PW_LOG_ERROR("Target file %s not found in bundle", target_name); return Status::Unauthenticated(); #endif // PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION } Status UpdateBundleAccessor::VerifyInBundleTargetPayload( protobuf::Uint64 expected_length, protobuf::Bytes expected_sha256, stream::IntervalReader payload_reader) { // If the target payload is included in the bundle, simply take a // measurement. uint64_t actual_length = payload_reader.interval_size(); if (actual_length != expected_length.value()) { PW_LOG_ERROR("Wrong payload length. Expected: %llu, actual: %llu", expected_length.value(), actual_length); return Status::Unauthenticated(); } std::byte actual_sha256[crypto::sha256::kDigestSizeBytes] = {}; PW_TRY(crypto::sha256::Hash(payload_reader, actual_sha256)); Result hash_equal = expected_sha256.Equal(actual_sha256); PW_TRY(hash_equal.status()); if (!hash_equal.value()) { PW_LOG_ERROR("Wrong payload sha256 hash"); return Status::Unauthenticated(); } return OkStatus(); } ManifestAccessor UpdateBundleAccessor::GetManifest() { if (!bundle_verified_) { PW_LOG_DEBUG("Bundled has not passed verification yet"); return Status::FailedPrecondition(); } return ManifestAccessor::FromBundle(bundle_); } } // namespace pw::software_update