• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2022 Google Inc. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18`fsverity_manifest_generator` generates the a manifest file containing digests
19of target files.
20"""
21
22import argparse
23import os
24import subprocess
25import sys
26from fsverity_digests_pb2 import FSVerityDigests
27
28HASH_ALGORITHM = 'sha256'
29
30def _digest(fsverity_path, input_file):
31  cmd = [fsverity_path, 'digest', input_file]
32  cmd.extend(['--compact'])
33  cmd.extend(['--hash-alg', HASH_ALGORITHM])
34  out = subprocess.check_output(cmd, text=True).strip()
35  return bytes(bytearray.fromhex(out))
36
37if __name__ == '__main__':
38  p = argparse.ArgumentParser(fromfile_prefix_chars='@')
39  p.add_argument(
40      '--output',
41      help='Path to the output manifest',
42      required=True)
43  p.add_argument(
44      '--fsverity-path',
45      help='path to the fsverity program',
46      required=True)
47  p.add_argument(
48      '--base-dir',
49      help='directory to use as a relative root for the inputs. Also see the documentation of '
50      'inputs')
51  p.add_argument(
52      'inputs',
53      nargs='*',
54      help='input file for the build manifest. It can be in either of two forms: <file> or '
55      '<file>,<path_on_device>. If the first form is used, --base-dir must be provided, and the '
56      'path on device will be the filepath relative to the base dir')
57  args = p.parse_args()
58
59  links = {}
60  digests = FSVerityDigests()
61  for f in sorted(args.inputs):
62    if args.base_dir:
63      # f is a full path for now; make it relative so it starts with {mount_point}/
64      rel = os.path.relpath(f, args.base_dir)
65    else:
66      parts = f.split(',')
67      if len(parts) != 2 or not parts[0] or not parts[1]:
68        sys.exit("Since --base-path wasn't provided, all inputs must be pairs separated by commas "
69          "but this input wasn't: " + f)
70      f, rel = parts
71
72    # Some fsv_meta files are links to other ones. Don't read through the link, because the
73    # layout of files in the build system may not match the layout of files on the device.
74    # Instead, read its target and use it to copy the digest from the real file after all files
75    # are processed.
76    if os.path.islink(f):
77      links[rel] = os.path.normpath(os.path.join(os.path.dirname(rel), os.readlink(f)))
78    else:
79      digest = digests.digests[rel]
80      digest.digest = _digest(args.fsverity_path, f)
81      digest.hash_alg = HASH_ALGORITHM
82
83  for link_rel, real_rel in links.items():
84    if real_rel not in digests.digests:
85      sys.exit(f'There was a fsv_meta symlink to {real_rel}, but that file was not a fsv_meta file')
86    link_digest = digests.digests[link_rel]
87    real_digest = digests.digests[real_rel]
88    link_digest.CopyFrom(real_digest)
89
90  # Serialize with deterministic=True for reproducible builds and build caching.
91  # The serialized contents will still change across different versions of protobuf.
92  manifest = digests.SerializeToString(deterministic=True)
93
94  with open(args.output, "wb") as f:
95    f.write(manifest)
96