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"""Metadata signing facilities.""" 15 16import argparse 17from pathlib import Path 18 19from pw_software_update import keys 20from pw_software_update.tuf_pb2 import SignedRootMetadata 21from pw_software_update.update_bundle_pb2 import UpdateBundle 22 23 24def sign_root_metadata(root_metadata: SignedRootMetadata, 25 root_key_pem: bytes) -> SignedRootMetadata: 26 """Signs or re-signs a Root Metadata. 27 28 Args: 29 root_metadata: A SignedRootMetadata to be signed/re-signed. 30 root_key_pem: The Root signing key in PEM. 31 """ 32 33 signature = keys.create_ecdsa_signature( 34 root_metadata.serialized_root_metadata, root_key_pem) 35 root_metadata.signatures.append(signature) 36 37 return root_metadata 38 39 40def sign_update_bundle(bundle: UpdateBundle, 41 targets_key_pem: bytes) -> UpdateBundle: 42 """Signs or re-signs an update bundle. 43 44 Args: 45 bundle: An UpdateBundle to be signed/re-signed. 46 targets_key_pem: The targets signing key in PEM. 47 """ 48 bundle.targets_metadata['targets'].signatures.append( 49 keys.create_ecdsa_signature( 50 bundle.targets_metadata['targets'].serialized_targets_metadata, 51 targets_key_pem)) 52 return bundle 53 54 55def parse_args(): 56 """Parse CLI arguments.""" 57 parser = argparse.ArgumentParser(description=__doc__) 58 59 parser.add_argument('--root-metadata', 60 type=Path, 61 required=False, 62 help='Path to the root metadata to be signed') 63 64 parser.add_argument('--bundle', 65 type=Path, 66 required=False, 67 help='Path to the bundle to be signed') 68 69 parser.add_argument( 70 '--output', 71 type=Path, 72 required=False, 73 help=('Path to save the signed root metadata or bundle ' 74 'to; Defaults to the input path if unspecified')) 75 76 parser.add_argument('--key', 77 type=Path, 78 required=True, 79 help='Path to the signing key') 80 81 args = parser.parse_args() 82 83 if not (args.root_metadata or args.bundle): 84 parser.error( 85 'either "--root-metadata" or "--bundle" must be specified') 86 if args.root_metadata and args.bundle: 87 parser.error('"--root-metadata" and "--bundle" are mutually exclusive') 88 89 return args 90 91 92def main(root_metadata: Path, bundle: Path, key: Path, output: Path) -> None: 93 """Signs or re-signs a root metadata or an update bundle.""" 94 if root_metadata: 95 signed_root_metadata = sign_root_metadata( 96 SignedRootMetadata.FromString(root_metadata.read_bytes()), 97 key.read_bytes()) 98 99 if not output: 100 output = root_metadata 101 output.write_bytes(signed_root_metadata.SerializeToString()) 102 else: 103 signed_bundle = sign_update_bundle( 104 UpdateBundle.FromString(bundle.read_bytes()), key.read_bytes()) 105 106 if not output: 107 output = bundle 108 output.write_bytes(signed_bundle.SerializeToString()) 109 110 111if __name__ == '__main__': 112 main(**vars(parse_args())) 113