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