1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Facilities for generating the 'root' metadata.""" 15 16import argparse 17from pathlib import Path 18from typing import Iterable, List, NewType 19 20from pw_software_update import keys, metadata 21from pw_software_update.tuf_pb2 import (RootMetadata, SignedRootMetadata, 22 SignatureRequirement) 23 24RootKeys = NewType('RootKeys', List[bytes]) 25TargetsKeys = NewType('TargetsKeys', List[bytes]) 26 27 28def gen_root_metadata(root_key_pems: RootKeys, 29 targets_key_pems: TargetsKeys, 30 version: int = 1) -> RootMetadata: 31 """Generates a RootMetadata. 32 33 Args: 34 root_key_pems: list of root public keys in PEM format. 35 targets_key_pems: list of targets keys in PEM format. 36 version: Version number for rollback checks. 37 """ 38 common = metadata.gen_common_metadata(metadata.RoleType.ROOT, 39 version=version) 40 41 root_keys = [keys.import_ecdsa_public_key(pem) for pem in root_key_pems] 42 targets_keys = [ 43 keys.import_ecdsa_public_key(pem) for pem in targets_key_pems 44 ] 45 46 return RootMetadata(common_metadata=common, 47 consistent_snapshot=False, 48 keys=root_keys + targets_keys, 49 root_signature_requirement=SignatureRequirement( 50 key_ids=[k.key_id for k in root_keys], 51 threshold=1), 52 targets_signature_requirement=SignatureRequirement( 53 key_ids=[k.key_id for k in targets_keys], 54 threshold=1)) 55 56 57def parse_args(): 58 """Parse CLI arguments.""" 59 parser = argparse.ArgumentParser(description=__doc__) 60 61 parser.add_argument('-o', 62 '--out', 63 type=Path, 64 required=True, 65 help='Output path for the generated root metadata') 66 67 parser.add_argument('--version', 68 type=int, 69 default=1, 70 help='Canonical version number for rollback checks') 71 72 parser.add_argument('--root-key', 73 type=Path, 74 required=True, 75 nargs='+', 76 help='Public key filename for the "Root" role') 77 78 parser.add_argument('--targets-key', 79 type=Path, 80 required=True, 81 nargs='+', 82 help='Public key filename for the "Targets" role') 83 return parser.parse_args() 84 85 86def main(out: Path, root_key: Iterable[Path], targets_key: Iterable[Path], 87 version: int) -> None: 88 """Generates and writes to disk an unsigned SignedRootMetadata.""" 89 90 root_metadata = gen_root_metadata( 91 RootKeys([k.read_bytes() for k in root_key]), 92 TargetsKeys([k.read_bytes() for k in targets_key]), version) 93 signed = SignedRootMetadata( 94 serialized_root_metadata=root_metadata.SerializeToString()) 95 out.write_bytes(signed.SerializeToString()) 96 97 98if __name__ == '__main__': 99 main(**vars(parse_args())) 100