blob: 45dcd6bc768151e6ae6d0a47f8ee657c310ff056 [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
// 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.
#pragma once
#include <cstddef>
#include "pw_blob_store/blob_store.h"
#include "pw_protobuf/map_utils.h"
#include "pw_protobuf/message.h"
#include "pw_software_update/blob_store_openable_reader.h"
#include "pw_software_update/bundled_update_backend.h"
#include "pw_software_update/manifest_accessor.h"
#include "pw_software_update/openable_reader.h"
namespace pw::software_update {
class BundledUpdateBackend;
// Name of the top-level Targets metadata.
constexpr std::string_view kTopLevelTargetsName = "targets";
// Name of the "user manifest" target file. The "user manifest" is a product
// specific blob that is opaque to upstream but need to be passed around in
// manifest handling (for now).
constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
// UpdateBundleAccessor is the trusted custodian of a staged incoming update
// bundle.
// It takes exclusive ownership of the blob_store that represents a staged,
// *untrusted* bundle, and presents convenient and *trusted* accessors.
// ALL ACCESS to the staged update bundle MUST GO THROUGH the
// `UpdateBundleAccessor`.
class UpdateBundleAccessor {
// UpdateBundleAccessor
// update_reader - The staged incoming software update bundle.
// backend - Project-specific BundledUpdateBackend.
// self_verification - When set to true, perform a voluntary best effort
// verification against available metadata in the incoming bundle itself.
// Self verification does NOT use any on-device metadata, thus does not
// guard against malicious attacks. Self-verification is primarily meant
// to de-risk 0-day verification turn-on.
constexpr UpdateBundleAccessor(OpenableReader& update_reader,
BundledUpdateBackend& backend,
bool self_verification = false)
: optional_blob_store_reader_unused_(),
self_verification_(self_verification) {}
// Overloaded constructor to maintain backwards compatibility. This should be
// removed once users have migrated.
constexpr UpdateBundleAccessor(blob_store::BlobStore& blob_store,
BundledUpdateBackend& backend,
bool self_verification = false)
: optional_blob_store_openeable_reader_(blob_store),
self_verification_(self_verification) {}
~UpdateBundleAccessor() {
if (&update_reader_ == &optional_blob_store_openeable_reader_) {
// Opens and verifies the software update bundle.
// Verification covers the following:
// 1. If a Root metadata is included with the incoming bundle, the Root
// metadata will be verified and used as the new Root metadata to verify
// other metadata in the bundle.
// 2. The Targets metadata is verified using the Root metadata.
// 3. All target payloads referenced in the Targets metadata are verified.
// Limitations and customizations (compared to standard TUF):
// 1. Does not yet support arbitrary Root key rotations. Which means
// There is only one (reliable) chance to rotate the Root key for all
// devices. Rotation of the Targets key is still unlimited.
// 2. Timestamp and Snapshot metadata are not used or supported.
// 3. Assumes a single top-level Targets metadata and no delegations.
// 4. The top-level Targets metadata doubles as the software update
// "manifest". Anti-rollback IS supported via the Targets metadata version.
// 5. Supports "personalization", where the staged bundle may have been
// stripped of any target payloads that the device already have. For those
// personalized-out targets, verification relies on the cached manifest of
// a previous successful update to verify target length and hash.
// Returns:
// OK - Bundle was successfully opened and verified.
Status OpenAndVerify();
// Closes the bundle by invalidating the verification and closing
// the reader to release the read-only lock
// Returns:
// OK - success.
// DATA_LOSS - Error writing data or fail to verify written data.
Status Close();
// Writes out the manifest of the staged bundle via a backend-supplied writer.
// Returns:
// FAILED_PRECONDITION - Bundle is not open and verified.
Status PersistManifest();
// Returns a reader for the (verified) payload bytes of a specified target
// file.
// Returns:
// A reader instance for the target file.
stream::IntervalReader GetTargetPayload(std::string_view target_name);
stream::IntervalReader GetTargetPayload(protobuf::String target_name);
// Exposes "manifest" information from the incoming update bundle once it has
// passed verification.
ManifestAccessor GetManifest();
// Returns the total number of bytes of all target payloads listed in the
// manifest *AND* exists in the bundle.
Result<uint64_t> GetTotalPayloadSize();
// Union is a temporary measure to allow for migration from the BlobStore
// constructor to the OpenableReader constructor. The BlobStoreOpenableReader
// should never be accessed directly. Access it through the update_reader_.
union {
BlobStoreOpenableReader optional_blob_store_openeable_reader_;
char optional_blob_store_reader_unused_;
OpenableReader& update_reader_;
BundledUpdateBackend& backend_;
protobuf::Message bundle_;
// The current, cached, trusted `SignedRootMetadata{}`.
protobuf::Message trusted_root_;
bool self_verification_;
bool bundle_verified_ = false;
// Opens the bundle for read-only access and readies the parser.
Status DoOpen();
// Performs TUF and downstream custom verification.
Status DoVerify();
// The method checks whether the update bundle contains a root metadata
// different from the on-device one. If it does, it performs the following
// verification and upgrade flow:
// 1. Verify the signatures according to the on-device trusted
// root metadata obtained from the backend.
// 2. Verify 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`.
// 3. Verify the signatures against the new root metadata.
// 4. Check rollback.
// 5. Update on-device root metadata.
Status UpgradeRoot();
// The method verifies the top-level targets metadata against the trusted
// root. The verification includes the following:
// 1. Verify the signatures of the targets metadata.
// 2. Check the content of the targets metadata.
// 3. Check rollback against the version from on-device manifest, if one
// exists (the manifest may be reset in the case of key rotation).
Status VerifyTargetsMetadata();
// A helper to get the on-device trusted root metadata. It returns an
// instance of SignedRootMetadata proto message.
protobuf::Message GetOnDeviceTrustedRoot();
// A helper to get an accessor to the on-device manifest. The on-device
// manifest is a serialized `message Manifest{...}` that represents the
// current running firmware of the device. The on-device manifest storage
// MUST meet the following requirements.
// 1. MUST NOT get wiped by a factory reset, otherwise a FDR can be used
// to circumvent anti-rollback check.
// 2. MUST be kept in-sync with the actual firmware on-device. If any
// mechanism is used to modify the firmware (e.g. via flashing), the
// on-device manifest MUST be updated to reflect the change as well.
// The on-device manifest CAN be erased if updating it is too cumbersome
// BUT ONLY ON DEV DEVICES as erasing the on-device manifest defeats
// anti-rollback.
// 3. MUST be integrity-protected and checked. Corrupted on-device manifest
// cannot be used as it may brick a device as a result of anti-rollback
// check. Integrity check is added and enforced by the backend via
// `BundledUpdateBackend` callbacks.
ManifestAccessor GetOnDeviceManifest();
// Verify all targets referenced in the manifest (Targets metadata) has a
// payload blob either within the bundle or on-device, in both cases
// measuring up to the length and hash recorded in the manifest.
Status VerifyTargetsPayloads();
// Verify a target specified by name measures up to the expected length and
// SHA256 hash. Additionally call the backend to perform any product-specific
// validations.
Status VerifyTargetPayload(ManifestAccessor manifest,
std::string_view name,
protobuf::Uint64 expected_length,
protobuf::Bytes expected_sha256);
// For a target the payload of which is included in the bundle, verify
// it measures up to the expected length and sha256 hash.
Status VerifyInBundleTargetPayload(protobuf::Uint64 expected_length,
protobuf::Bytes expected_sha256,
stream::IntervalReader payload_reader);
// For a target with no corresponding payload in the bundle, verify
// its on-device payload bytes measures up to the expected length and sha256
// hash.
Status VerifyOutOfBundleTargetPayload(std::string_view name,
protobuf::Uint64 expected_length,
protobuf::Bytes expected_sha256);
} // namespace pw::software_update