mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-09-20 05:41:06 +00:00
pw_software_update: Add root metadata to a bundle
Adds support for including a root metadata when creating a bundle. No-Docs-Update-Reason: module in early development. Change-Id: Ibdced04fd355e8f520d0d3ba6e5cf25276929d72 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/63640 Commit-Queue: Ali Zhang <alizhang@google.com> Reviewed-by: Joe Ethier <jethier@google.com>
This commit is contained in:
parent
8990f7fad0
commit
e059107b73
|
@ -21,7 +21,7 @@ import shutil
|
|||
from typing import Dict, Iterable, Optional, Tuple
|
||||
|
||||
from pw_software_update import metadata
|
||||
from pw_software_update.tuf_pb2 import SignedTargetsMetadata
|
||||
from pw_software_update.tuf_pb2 import SignedRootMetadata, SignedTargetsMetadata
|
||||
from pw_software_update.update_bundle_pb2 import UpdateBundle
|
||||
|
||||
_LOG = logging.getLogger(__package__)
|
||||
|
@ -78,14 +78,15 @@ def targets_from_directory(
|
|||
def gen_unsigned_update_bundle(
|
||||
targets: Dict[Path, str],
|
||||
persist: Optional[Path] = None,
|
||||
targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION
|
||||
) -> UpdateBundle:
|
||||
targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION,
|
||||
root_metadata: SignedRootMetadata = None) -> UpdateBundle:
|
||||
"""Given a set of targets, generates an unsigned UpdateBundle.
|
||||
|
||||
Args:
|
||||
targets: A dict mapping payload Paths to their target names.
|
||||
persist: If not None, persist the raw TUF repository to this directory.
|
||||
targets_metadata_version: version number for the targets metadata.
|
||||
root_metadata: Optional signed Root metadata.
|
||||
|
||||
The input targets will be treated as an ephemeral TUF repository for the
|
||||
purposes of building an UpdateBundle instance. This approach differs
|
||||
|
@ -97,6 +98,10 @@ def gen_unsigned_update_bundle(
|
|||
NOTE: If path separator characters (like '/') are used in target names, then
|
||||
persisting the repository to disk via the 'persist' argument will create the
|
||||
corresponding directory structure.
|
||||
|
||||
NOTE: If a root metadata is included, the client is expected to first
|
||||
upgrade its on-device trusted root metadata before verifying the rest of
|
||||
the bundle.
|
||||
"""
|
||||
if persist:
|
||||
if persist.exists() and not persist.is_dir():
|
||||
|
@ -119,7 +124,9 @@ def gen_unsigned_update_bundle(
|
|||
target_payloads, version=targets_metadata_version)
|
||||
unsigned_targets_metadata = SignedTargetsMetadata(
|
||||
serialized_targets_metadata=targets_metadata.SerializeToString())
|
||||
|
||||
return UpdateBundle(
|
||||
root_metadata=root_metadata,
|
||||
targets_metadata=dict(targets=unsigned_targets_metadata),
|
||||
target_payloads=target_payloads)
|
||||
|
||||
|
@ -164,23 +171,33 @@ def parse_args() -> argparse.Namespace:
|
|||
type=int,
|
||||
default=metadata.DEFAULT_METADATA_VERSION,
|
||||
help='Version number for the targets metadata')
|
||||
parser.add_argument('--signed-root-metadata',
|
||||
type=Path,
|
||||
default=None,
|
||||
help='Path to the signed Root metadata')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main(
|
||||
targets: Iterable[str],
|
||||
def main(targets: Iterable[str],
|
||||
out: Path,
|
||||
persist: Path = None,
|
||||
targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION
|
||||
) -> None:
|
||||
targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION,
|
||||
signed_root_metadata: Path = None) -> None:
|
||||
"""Generates an UpdateBundle and serializes it to disk."""
|
||||
target_dict = {}
|
||||
for target_arg in targets:
|
||||
path, target_name = parse_target_arg(target_arg)
|
||||
target_dict[path] = target_name
|
||||
|
||||
root_metadata = None
|
||||
if signed_root_metadata:
|
||||
root_metadata = SignedRootMetadata.FromString(
|
||||
signed_root_metadata.read_bytes())
|
||||
|
||||
bundle = gen_unsigned_update_bundle(target_dict, persist,
|
||||
targets_metadata_version)
|
||||
targets_metadata_version,
|
||||
root_metadata)
|
||||
|
||||
out.write_bytes(bundle.SerializeToString())
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import tempfile
|
|||
import unittest
|
||||
|
||||
from pw_software_update import update_bundle
|
||||
from pw_software_update.tuf_pb2 import TargetsMetadata
|
||||
from pw_software_update.tuf_pb2 import SignedRootMetadata, TargetsMetadata
|
||||
|
||||
|
||||
class TargetsFromDirectoryTest(unittest.TestCase):
|
||||
|
@ -128,18 +128,23 @@ class GenUnsignedUpdateBundleTest(unittest.TestCase):
|
|||
baz_path: 'baz',
|
||||
qux_path: 'qux',
|
||||
}
|
||||
serialized_root_metadata_bytes = b'\x12\x34\x56\x78'
|
||||
|
||||
bundle = update_bundle.gen_unsigned_update_bundle(
|
||||
targets, targets_metadata_version=42)
|
||||
targets,
|
||||
targets_metadata_version=42,
|
||||
root_metadata=SignedRootMetadata(
|
||||
serialized_root_metadata=serialized_root_metadata_bytes))
|
||||
|
||||
self.assertEqual(foo_bytes, bundle.target_payloads['foo'])
|
||||
self.assertEqual(bar_bytes, bundle.target_payloads['bar'])
|
||||
self.assertEqual(baz_bytes, bundle.target_payloads['baz'])
|
||||
self.assertEqual(qux_bytes, bundle.target_payloads['qux'])
|
||||
|
||||
targets_metadata = TargetsMetadata.FromString(
|
||||
bundle.targets_metadata['targets'].serialized_targets_metadata)
|
||||
self.assertEqual(targets_metadata.common_metadata.version, 42)
|
||||
self.assertEqual(serialized_root_metadata_bytes,
|
||||
bundle.root_metadata.serialized_root_metadata)
|
||||
|
||||
def test_persist_to_disk(self):
|
||||
"""Tests persisting the TUF repo to disk for debugging"""
|
||||
|
|
|
@ -19,11 +19,6 @@ package pw.software_update;
|
|||
import "pw_software_update/tuf.proto";
|
||||
|
||||
message UpdateBundle {
|
||||
// NOTE/TODO: SignedRootMetadata is not currently included as part of the
|
||||
// UpdateBundle. Updating of the root metdata needs to be handled in a
|
||||
// separate project-specific process. A standard upstream process can be added
|
||||
// in the future.
|
||||
|
||||
// The timestamp role is used for freshness check of the snapshot. Any
|
||||
// project-specific update metadata should go in the top-level
|
||||
// targets_metadata or with the TargetFile information
|
||||
|
@ -46,6 +41,27 @@ message UpdateBundle {
|
|||
// the file lives relative to the base directory of the repository, as
|
||||
// described in the target metadata. e.g. "path/to/amber_tools/0".
|
||||
map<string, bytes> target_payloads = 4;
|
||||
|
||||
// If present, a client will attempt to upgrade its on-device trusted root
|
||||
// metadata to the root metadata included in the bundle, following the
|
||||
// standard "Update the root role" flow specified in the TUF spec, but
|
||||
// without "version climbing".
|
||||
//
|
||||
// The exact steps are:
|
||||
// 1. Check if there is a root metadata in the bundle.
|
||||
// 2. If the root metadata IS NOT included, assume on-device root metadata
|
||||
// is up-to-date and continue with the rest of metadata verification.
|
||||
// 3. If the root metadata IS included, verify the new root metadata using
|
||||
// the on-device root metadata.
|
||||
// 4. If the verification is successful, persist new root metadata and
|
||||
// continue with the rest of metadata verification. Otherwise abort the
|
||||
// update session.
|
||||
//
|
||||
// The key deviation from standard flow is the client assumes it can always
|
||||
// directly upgrade to the single new root metadata in the update bundle,
|
||||
// without any step-stone history root metadata. This works only because
|
||||
// we are not supporting (more than 1) root key rotations.
|
||||
optional SignedRootMetadata root_metadata = 5;
|
||||
}
|
||||
|
||||
// Update bundle metadata
|
||||
|
|
Loading…
Reference in New Issue
Block a user