• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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