blob: 05a138d0bdeac599870886c493ffec08a68d9ec0 [file] [log] [blame]
// 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.
#include "pw_software_update/config.h"
#define PW_LOG_LEVEL PW_SOFTWARE_UPDATE_CONFIG_LOG_LEVEL
#include <cstddef>
#include <string_view>
#include "pw_log/log.h"
#include "pw_protobuf/message.h"
#include "pw_result/result.h"
#include "pw_software_update/update_bundle.pwpb.h"
#include "pw_software_update/update_bundle_accessor.h"
#include "pw_stream/interval_reader.h"
#include "pw_stream/memory_stream.h"
namespace pw::software_update {
namespace {
constexpr std::string_view kTopLevelTargetsName = "targets";
}
Status UpdateBundleAccessor::OpenAndVerify(const ManifestAccessor&) {
PW_TRY(DoOpen());
PW_TRY(DoVerify());
return OkStatus();
}
// Get the target element corresponding to `target_file`
stream::IntervalReader UpdateBundleAccessor::GetTargetPayload(
std::string_view target_file_name) {
protobuf::StringToBytesMap target_payloads =
decoder_.AsStringToBytesMap(static_cast<uint32_t>(
pw::software_update::UpdateBundle::Fields::TARGET_PAYLOADS));
PW_TRY(target_payloads.status());
protobuf::Bytes payload = target_payloads[target_file_name];
PW_TRY(payload.status());
return payload.GetBytesReader();
}
Result<bool> UpdateBundleAccessor::IsTargetPayloadIncluded(
std::string_view target_file_name) {
// TODO(pwbug/456): Perform personalization check first. If the target
// is personalized out. Don't need to proceed.
protobuf::StringToMessageMap signed_targets_metadata_map =
decoder_.AsStringToMessageMap(static_cast<uint32_t>(
pw::software_update::UpdateBundle::Fields::TARGETS_METADATA));
PW_TRY(signed_targets_metadata_map.status());
// There should only be one element in the map, which is the top-level
// targets metadata.
protobuf::Message signed_targets_metadata =
signed_targets_metadata_map[kTopLevelTargetsName];
PW_TRY(signed_targets_metadata.status());
protobuf::Message metadata = signed_targets_metadata.AsMessage(
static_cast<uint32_t>(pw::software_update::SignedTargetsMetadata::Fields::
SERIALIZED_TARGETS_METADATA));
PW_TRY(metadata.status());
protobuf::RepeatedMessages target_files =
metadata.AsRepeatedMessages(static_cast<uint32_t>(
pw::software_update::TargetsMetadata::Fields::TARGET_FILES));
PW_TRY(target_files.status());
for (protobuf::Message target_file : target_files) {
protobuf::String name = target_file.AsString(static_cast<uint32_t>(
pw::software_update::TargetFile::Fields::FILE_NAME));
PW_TRY(name.status());
Result<bool> file_name_matches = name.Equal(target_file_name);
PW_TRY(file_name_matches.status());
if (file_name_matches.value()) {
return true;
}
}
return false;
}
Status UpdateBundleAccessor::WriteManifest(
[[maybe_unused]] stream::Writer& staged_manifest_writer) {
return Status::Unimplemented();
}
Status UpdateBundleAccessor::Close() {
// TODO(pwbug/456): To be implemented.
return bundle_reader_.Close();
}
Status UpdateBundleAccessor::DoOpen() {
PW_TRY(bundle_.Init());
PW_TRY(bundle_reader_.Open());
decoder_ =
protobuf::Message(bundle_reader_, bundle_reader_.ConservativeReadLimit());
(void)backend_;
return OkStatus();
}
Status UpdateBundleAccessor::DoVerify() {
if (disable_verification_) {
PW_LOG_WARN("Update bundle verification is disabled.");
return OkStatus();
}
// Verify and upgrade the on-device trust to the incoming root metadata if
// one is included.
PW_TRY(DoUpgradeRoot());
// TODO(pwbug/456): Verify the targets metadata against the current trusted
// root.
// TODO(pwbug/456): Investigate whether targets payload verification should
// be performed here or deferred until a specific target is requested.
// TODO(pwbug/456): Invoke the backend to do downstream verification of the
// bundle (e.g. compatibility and manifest completeness checks).
return OkStatus();
}
Status UpdateBundleAccessor::DoUpgradeRoot() {
// TODO(pwbug/456): Check whether the bundle contains a root metadata that
// is different from the on-device trusted root.
// TODO(pwbug/456) Verify the signatures against the trusted root metadata.
// 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`.
// TODO(pwbug/456). Verify the signatures against the new root metadata.
// TODO(pwbug/456): Check rollback.
// TODO(pwbug/456): Persist new root.
// 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();
}
} // namespace pw::software_update