1#!/usr/bin/env python3

2# Copyright 2018-2019 Canonical Ltd.

3# Written by:

4# Jonathan Cave <jonathan.cave@canonical.com>

5# Sylvain Pineau <sylvain.pineau@canonical.com>

6

7 import io

8 import os

9 import re

10 import shutil

11 import sys

12 import subprocess as sp

13 import tempfile

14

15 import yaml

16

17 from checkbox_support.snap_utils.system import get_lk_bootimg_path

18

19

20 def fitdumpimage(filename):

21 cmd = "dumpimage -l {}".format(filename)

22 try:

23 out = sp.check_output(cmd, shell=True).decode(sys.stdout.encoding)

24 except Exception:

25 raise SystemExit(1)

26 buf = io.StringIO(out)

27

28 # first line should identify FIT file

29 if not buf.readline().startswith("FIT description"):

30 raise SystemExit("ERROR: expected FIT image description")

31

32 # second line contains some metadata, skip it

33 buf.readline()

34

35 # from then on should get blocks of text describing the objects that were

36 # combined in to the FIT image e.g. kernel, ramdisk, device tree

37 image_config_re = re.compile(

38 r"(?:^\ Image|Configuration)\ \d+\ \((\S+)\)$"

39 )

40 configuration_re = re.compile(r"^\ Default Configuration")

41 objects = {}

42 name = ""

43 while True:

44 line = buf.readline()

45 # stop at end

46 if line == "":

47 break

48 # interested in storing image information

49 match = image_config_re.search(line)

50 if match:

51 name = match.group(1)

52 objects[name] = {}

53 continue

54 # not interested in the default configuration

55 if configuration_re.search(line):

56 name = ""

57 continue

58 # while in an image/config section store the info

59 if name != "":

60 entries = [s.strip() for s in line.split(":", 1)]

61 objects[name][entries[0]] = entries[1]

62 return objects

63

64

65 def get_bootloader(gadget_yaml):

66 """Parse gadget.yaml to get bootloader"""

67 with open(gadget_yaml) as f:

68 data = yaml.load(f, Loader=yaml.SafeLoader)

69 for k in data["volumes"].keys():

70 if "bootloader" not in data["volumes"][k]: 70 ↛ 71 line 70 didn't jump to line 71 because the condition on line 70 was never true

71 continue

72 bootloader = data["volumes"][k]["bootloader"]

73 if not bootloader: 73 ↛ 74 line 73 didn't jump to line 74 because the condition on line 73 was never true

74 raise SystemExit("ERROR: could not find name of bootloader")

75

76 if bootloader not in ("u-boot", "grub", "lk"): 76 ↛ 77 line 76 didn't jump to line 77 because the condition on line 76 was never true

77 raise SystemExit(

78 "ERROR: Unexpected bootloader name {}".format(bootloader)

79 )

80 print("Bootloader is {}\n".format(bootloader))

81 return bootloader

82

83

84 def get_uboot_kernel(kernel):

85 """Get u-boot kernel path"""

86 kernel_rev = os.path.basename(

87 os.path.realpath("/snap/{}/current".format(kernel))

88 )

89 # update boot kernel path according to

90 # https://snapcraft.io/docs/the-system-backup-interface

91 return "/var/lib/snapd/hostfs/boot/uboot/{}_{}.snap/kernel.img".format(

92 kernel, kernel_rev

93 )

94

95

96 def main():

97 if len(sys.argv) != 3:

98 raise SystemExit("ERROR: please supply gadget & kernel name")

99 gadget = sys.argv[1]

100 kernel = sys.argv[2]

101

102 gadget_yaml = os.path.join("/snap", gadget, "current/meta/gadget.yaml")

103

104 if not os.path.exists(gadget_yaml):

105 raise SystemExit(

106 "ERROR: failed to find gadget.yaml at {}".format(gadget_yaml)

107 )

108

109 bootloader = get_bootloader(gadget_yaml)

110

111 if bootloader == "u-boot":

112 print("Parsing FIT image information...\n")

113

114 boot_kernel = get_uboot_kernel(kernel)

115 boot_objects = fitdumpimage(boot_kernel)

116

117 for obj, attrs in boot_objects.items():

118 if obj == "conf":

119 continue

120 print("Checking object {}".format(obj))

121 if "Sign value" not in attrs:

122 raise SystemExit("ERROR: no sign value found for object")

123 print('Found "Sign value"')

124 if len(attrs["Sign value"]) != 512:

125 raise SystemExit("ERROR: unexpected sign value size")

126 if all(s in attrs["Sign algo"] for s in ["sha256", "rsa2048"]):

127 print("Found expected signing algorithms")

128 else:

129 raise SystemExit(

130 "ERROR: unexpected signing algorithms {}".format(

131 attrs["Sign algo"]

132 )

133 )

134 print()

135

136 # check that all parts of the fit image have

137 snap_kernel = "/snap/{}/current/kernel.img".format(kernel)

138 snap_objects = fitdumpimage(snap_kernel)

139 if snap_objects != boot_objects:

140 raise SystemExit(

141 "ERROR: boot kernel and current snap kernel do not match"

142 )

143 print("Kernel images in current snap and u-boot match\n")

144

145 print("Secure Boot appears to be enabled on this system")

146

147 if bootloader == "lk":

148 bootimg_path = get_lk_bootimg_path()

149 if bootimg_path == "unknown":

150 raise SystemExit("ERROR: lk-boot-env not found")

151

152 # XXX: Assuming FIT format

153 bootimg = os.path.basename(bootimg_path)

154 print("Parsing FIT image information ({})...\n".format(bootimg))

155

156 with tempfile.TemporaryDirectory() as tmpdirname:

157 shutil.copy2(bootimg_path, tmpdirname)

158 boot_kernel = os.path.join(tmpdirname, bootimg)

159 boot_objects = fitdumpimage(boot_kernel)

160

161 for obj, attrs in boot_objects.items():

162 if obj != "conf":

163 continue

164 print("Checking object {}".format(obj))

165 if "Sign value" not in attrs:

166 raise SystemExit("ERROR: no sign value found for object")

167 print('Found "Sign value"')

168 if len(attrs["Sign value"]) != 512:

169 raise SystemExit("ERROR: unexpected sign value size")

170 if all(s in attrs["Sign algo"] for s in ["sha256", "rsa2048"]):

171 print("Found expected signing algorithms")

172 else:

173 raise SystemExit(

174 "ERROR: unexpected signing algorithms {}".format(

175 attrs["Sign algo"]

176 )

177 )

178 print()

179

180 # check that all parts of the fit image have

181 snap_kernel = "/snap/{}/current/boot.img".format(kernel)

182 snap_objects = fitdumpimage(snap_kernel)

183 if snap_objects != boot_objects:

184 raise SystemExit(

185 "ERROR: boot kernel and current snap kernel do not match"

186 )

187 print("Kernel images in current snap and lk snapbootsel match\n")

188

189 print("Secure Boot appears to be enabled on this system")

190

191 if bootloader == "grub":

192 cmd = "mokutil --sb-state"

193 print("+", cmd, flush=True)

194 try:

195 out = sp.check_output(cmd, shell=True).decode(sys.stdout.encoding)

196 except Exception:

197 raise SystemExit(1)

198 print(out, flush=True)

199 if out != "SecureBoot enabled\n":

200 raise SystemExit("ERROR: mokutil reports Secure Boot not in use")

201

202 print("Secure Boot appears to be enabled on this system")

203

204

205 if __name__ == "__main__":

206 main()