blob: 83b014e1211403815c66e8ae65dfd68c1c829b35 [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.
#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/bundled_update_backend.h"
#include "pw_software_update/manifest_accessor.h"
#include "pw_stream/memory_stream.h"
namespace pw::software_update {
class BundledUpdateBackend;
constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
// UpdateBundleAccessor is responsible for parsing, verifying and providing
// target payload access of a software update bundle. It takes the following as
// inputs:
//
// 1. A software update bundle via `BlobStore`.
// 2. A `BundledUpdateBackend`, which implements project-specific update
// operations such as enforcing project update policies and
// verifying/applying target files on device.
//
// The verification is done according to TUF process. Payload can only be
// accessed after successful verification.
//
// Exmple of use:
//
// UpdateBundleAccessor bundle(blob,helper);
// auto status = bundle.OpenAndVerify();
// if (!status.ok()) {
// // handle error
// ...
// }
//
// // Examine and use payload.
// auto exist = bundle.IsTargetPayloadIncluded("audio");
// if (!exist.ok() || !exist.value()) {
// // handle error
// ...
// }
//
// auto payload_reader = bundle.GetTargetPayload("audio");
// // Process payload
// ...
//
// // Get bundle's manifest and write it to the given writer.
// status = bundle.PersistManifest(staged_manifest_writer);
// if (!status.ok()) {
// // handle error
// ...
// }
//
// status = bundle.Close();
// if (!status.ok()) {
// // handle error
// ...
// }
class UpdateBundleAccessor {
public:
// UpdateBundleAccessor
// bundle - The software update bundle data on storage.
// backend - Project-specific BundledUpdateBackend.
// disable_verification - Disable verification.
constexpr UpdateBundleAccessor(blob_store::BlobStore& bundle,
BundledUpdateBackend& backend,
bool disable_verification = false)
: bundle_(bundle),
backend_(backend),
bundle_reader_(bundle_),
disable_verification_(disable_verification) {}
// Opens and verifies the software update bundle.
//
// Specifically, the opening process opens a blob reader to the given bundle
// and initializes the bundle proto parser. No write will be allowed to the
// bundle until Close() is called.
//
// If bundle verification is enabled (see the `option` argument in
// the constructor), the verification process does the following:
//
// 1. Check whether the bundle contains an incoming new root metadata. If it
// does, it verifies the root against the current on-device root. If
// successful, the on-device root will be updated to the new root.
//
// 2. Verify the targets metadata against the current trusted root.
//
// 3. Either verify all target payloads (size and hash) or defer that
// verification till when a target is accessed.
//
// 4. Invoke the backend to do downstream verification of the bundle.
//
// 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 the manifest of the staged bundle to the given writer.
//
// Returns:
// FAILED_PRECONDITION - Bundle is not open and verified.
// TODO(pwbug/456): Add other error codes if necessary.
Status PersistManifest(stream::Writer& staged_manifest_writer);
// Is the target payload present in the bundle (not personalized out).
//
// Returns:
// OK - Whether or not the target_file was included in the UpdateBundle or
// whether it was personalized out.
// FAILED_PRECONDITION - Bundle is not open and verified.
// TODO(pwbug/456): Add other error codes if necessary.
Result<bool> IsTargetPayloadIncluded(std::string_view target_file);
// Returns a reader for the target file by `target_file` in the update
// bundle.
//
// Returns:
// A reader instance for the target file.
// TODO(pwbug/456): Figure out a way to propagate error.
stream::IntervalReader GetTargetPayload(std::string_view target_file);
// Returns a protobuf::Message representation of the update bundle.
//
// Returns:
// An instance of protobuf::Message of the udpate bundle.
// FAILED_PRECONDITION - Bundle is not open and verified.
protobuf::Message GetDecoder();
ManifestAccessor GetManifestAccessor() { return ManifestAccessor(this); };
private:
blob_store::BlobStore& bundle_;
BundledUpdateBackend& backend_;
blob_store::BlobStore::BlobReader bundle_reader_;
protobuf::Message decoder_;
bool disable_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
// disable_verificationroot 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).
//
// TODO(pwbug/456): Should manifest persisting be handled here? The current
// API design of this class exposes a PersistManifest() method, which implies
// that manifest persisting is handled by some higher level logic.
Status VerifyTargetsMetadata();
// A helper to get the on-device trusted root metadata. It returns an
// instance of SignedRootMetadata proto message.
protobuf::Message GetOnDeviceTrustedRoot();
// The method performs verification of the target payloads. Specifically, it
// 1. For target payloads found in the bundle, verify its size and hash.
// 2. For target payloads not found in the bundle, call downstream to verify
// it and report back.
Status VerifyTargetsPayloads();
};
} // namespace pw::software_update