| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Metadata signing facilities.""" |
| |
| import argparse |
| from pathlib import Path |
| |
| from pw_software_update import keys |
| from pw_software_update.tuf_pb2 import SignedRootMetadata |
| from pw_software_update.update_bundle_pb2 import UpdateBundle |
| |
| |
| def sign_root_metadata(root_metadata: SignedRootMetadata, |
| root_key_pem: bytes) -> SignedRootMetadata: |
| """Signs or re-signs a Root Metadata. |
| |
| Args: |
| root_metadata: A SignedRootMetadata to be signed/re-signed. |
| root_key_pem: The Root signing key in PEM. |
| """ |
| |
| signature = keys.create_ecdsa_signature( |
| root_metadata.serialized_root_metadata, root_key_pem) |
| root_metadata.signatures.append(signature) |
| |
| return root_metadata |
| |
| |
| def sign_update_bundle(bundle: UpdateBundle, |
| targets_key_pem: bytes) -> UpdateBundle: |
| """Signs or re-signs an update bundle. |
| |
| Args: |
| bundle: An UpdateBundle to be signed/re-signed. |
| targets_key_pem: The targets signing key in PEM. |
| """ |
| bundle.targets_metadata['targets'].signatures.append( |
| keys.create_ecdsa_signature( |
| bundle.targets_metadata['targets'].serialized_targets_metadata, |
| targets_key_pem)) |
| return bundle |
| |
| |
| def parse_args(): |
| """Parse CLI arguments.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| |
| parser.add_argument('--root-metadata', |
| type=Path, |
| required=False, |
| help='Path to the root metadata to be signed') |
| |
| parser.add_argument('--bundle', |
| type=Path, |
| required=False, |
| help='Path to the bundle to be signed') |
| |
| parser.add_argument( |
| '--output', |
| type=Path, |
| required=False, |
| help=('Path to save the signed root metadata or bundle ' |
| 'to; Defaults to the input path if unspecified')) |
| |
| parser.add_argument('--key', |
| type=Path, |
| required=True, |
| help='Path to the signing key') |
| |
| args = parser.parse_args() |
| |
| if not (args.root_metadata or args.bundle): |
| parser.error( |
| 'either "--root-metadata" or "--bundle" must be specified') |
| if args.root_metadata and args.bundle: |
| parser.error('"--root-metadata" and "--bundle" are mutually exclusive') |
| |
| return args |
| |
| |
| def main(root_metadata: Path, bundle: Path, key: Path, output: Path) -> None: |
| """Signs or re-signs a root metadata or an update bundle.""" |
| if root_metadata: |
| signed_root_metadata = sign_root_metadata( |
| SignedRootMetadata.FromString(root_metadata.read_bytes()), |
| key.read_bytes()) |
| |
| if not output: |
| output = root_metadata |
| output.write_bytes(signed_root_metadata.SerializeToString()) |
| else: |
| signed_bundle = sign_update_bundle( |
| UpdateBundle.FromString(bundle.read_bytes()), key.read_bytes()) |
| |
| if not output: |
| output = bundle |
| output.write_bytes(signed_bundle.SerializeToString()) |
| |
| |
| if __name__ == '__main__': |
| main(**vars(parse_args())) |