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:
Ali Zhang 2021-09-30 17:18:27 -07:00 committed by CQ Bot Account
parent 8990f7fad0
commit e059107b73
3 changed files with 58 additions and 20 deletions

View File

@ -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__)
@ -76,16 +76,17 @@ 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: Dict[Path, str],
persist: Optional[Path] = None,
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],
out: Path,
persist: Path = None,
targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION
) -> None:
def main(targets: Iterable[str],
out: Path,
persist: Path = 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())

View File

@ -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"""

View File

@ -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