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, NewType 19 20from pw_software_update import keys, metadata 21from pw_software_update.tuf_pb2 import ( 22 RootMetadata, 23 SignedRootMetadata, 24 SignatureRequirement, 25) 26 27RootKeys = NewType('RootKeys', list[bytes]) 28TargetsKeys = NewType('TargetsKeys', list[bytes]) 29 30 31def gen_root_metadata( 32 root_key_pems: RootKeys, targets_key_pems: TargetsKeys, version: int = 1 33) -> RootMetadata: 34 """Generates a RootMetadata. 35 36 Args: 37 root_key_pems: list of root public keys in PEM format. 38 targets_key_pems: list of targets keys in PEM format. 39 version: Version number for rollback checks. 40 """ 41 common = metadata.gen_common_metadata( 42 metadata.RoleType.ROOT, version=version 43 ) 44 45 root_keys = [keys.import_ecdsa_public_key(pem) for pem in root_key_pems] 46 targets_keys = [ 47 keys.import_ecdsa_public_key(pem) for pem in targets_key_pems 48 ] 49 50 return RootMetadata( 51 common_metadata=common, 52 consistent_snapshot=False, 53 keys=root_keys + targets_keys, 54 root_signature_requirement=SignatureRequirement( 55 key_ids=[k.key_id for k in root_keys], threshold=1 56 ), 57 targets_signature_requirement=SignatureRequirement( 58 key_ids=[k.key_id for k in targets_keys], threshold=1 59 ), 60 ) 61 62 63def parse_args(): 64 """Parse CLI arguments.""" 65 parser = argparse.ArgumentParser(description=__doc__) 66 67 parser.add_argument( 68 '-o', 69 '--out', 70 type=Path, 71 required=True, 72 help='Output path for the generated root metadata', 73 ) 74 75 parser.add_argument( 76 '--version', 77 type=int, 78 default=1, 79 help='Canonical version number for rollback checks', 80 ) 81 82 parser.add_argument( 83 '--root-key', 84 type=Path, 85 required=True, 86 nargs='+', 87 help='Public key filename for the "Root" role', 88 ) 89 90 parser.add_argument( 91 '--targets-key', 92 type=Path, 93 required=True, 94 nargs='+', 95 help='Public key filename for the "Targets" role', 96 ) 97 return parser.parse_args() 98 99 100def main( 101 out: Path, 102 root_key: Iterable[Path], 103 targets_key: Iterable[Path], 104 version: int, 105) -> None: 106 """Generates and writes to disk an unsigned SignedRootMetadata.""" 107 108 root_metadata = gen_root_metadata( 109 RootKeys([k.read_bytes() for k in root_key]), 110 TargetsKeys([k.read_bytes() for k in targets_key]), 111 version, 112 ) 113 signed = SignedRootMetadata( 114 serialized_root_metadata=root_metadata.SerializeToString() 115 ) 116 117 out.write_bytes(signed.SerializeToString()) 118 119 120if __name__ == '__main__': 121 main(**vars(parse_args())) 122