xref: /aosp_15_r20/external/pigweed/pw_software_update/py/pw_software_update/root_metadata.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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