1#!/usr/bin/env python3

2"""

3 Test that the computer booted in EFI mode, with Secure Boot active.

4

5 Copyright (C) 2016 Canonical Ltd.

6

7 Authors:

8 Rod Smith <rod.smith@canonical.com>

9

10 This program is free software: you can redistribute it and/or modify

11 it under the terms of the GNU General Public License version 3,

12 as published by the Free Software Foundation.

13

14 This program is distributed in the hope that it will be useful,

15 but WITHOUT ANY WARRANTY; without even the implied warranty of

16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

17 GNU General Public License for more details.

18

19 You should have received a copy of the GNU General Public License

20 along with this program. If not, see <http://www.gnu.org/licenses/>.

21"""

22

23 import os

24 import sys

25 import logging

26 from argparse import ArgumentParser

27

28

29 def do_tests(check):

30 """Dispatcher for the requested tests.

31

32 :returns:

33 return code from requested check.

34 """

35 if check == "efi":

36 return efi_boot_check()

37 elif check == "reboot_firmware":

38 return reboot_to_firmware_check()

39 else:

40 return secure_boot_check()

41

42

43 def efi_boot_check():

44 """Test that the computer booted in EFI mode

45

46 :returns:

47 0 if /sys/firmware/efivars exists meaning booted in EFI mode

48 1 if booted in BIOS mode

49 """

50 efi_dir = "/sys/firmware/efi/"

51 if os.path.isdir(efi_dir):

52 logging.info("PASS: System booted in EFI mode")

53 return 0

54 else:

55 logging.error("FAIL: System did not boot in EFI mode")

56 return 1

57

58

59 def reboot_to_firmware_check():

60 """Test that the computer supports the reboot-to-firmware feature.

61

62 :returns:

63 0 if the feature IS supported (pass)

64 1 if the feature is NOT supported (fail)

65 """

66 osis_dir = "/sys/firmware/efi/efivars/"

67 osis_var = (

68 osis_dir

69 + "OsIndicationsSupported-8be4df61-93ca-11d2-aa0d-00e098032b8c"

70 )

71 if os.path.isdir(osis_dir):

72 if os.path.isfile(osis_var):

73 with open(osis_var) as fh:

74 try:

75 fw_info = fh.read()

76 except IOError:

77 logging.error("FAIL: I/O error reading EFI data")

78 return 1

79 if ord(fw_info[4]) & 1:

80 logging.info("PASS: Reboot-to-firmware feature is present.")

81 return 0

82 else:

83 logging.error("FAIL: Reboot-to-firmware feature is missing.")

84 return 1

85 else:

86 logging.info("FAIL: OsIndicationsSupported variable not present.")

87 return 1

88 else:

89 logging.info("FAIL: System did NOT boot in EFI mode.")

90 return 1

91

92

93 def secure_boot_check():

94 """Test that the computer booted with Secure Boot active.

95

96 :returns:

97 0 if Secure Boot is active

98 1 if Secure Boot is inactive (could be disabled, not supported,

99 or not booted in EFI mode)

100 """

101 sb_dir = "/sys/firmware/efi/efivars/"

102 sb_var = sb_dir + "SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"

103 if os.path.isdir(sb_dir):

104 if os.path.isfile(sb_var):

105 with open(sb_var) as fh:

106 try:

107 sb_info = fh.read()

108 except IOError:

109 logging.error("FAIL: I/O error reading EFI data")

110 return 1

111 if ord(sb_info[4]) == 1:

112 logging.info("PASS: System booted with Secure Boot active.")

113 return 0

114 else:

115 logging.error(

116 "FAIL: System booted with "

117 "Secure Boot available but inactive."

118 )

119 return 1

120 else:

121 # NOTE: Normally, lack of sb_var indicates that the system

122 # doesn't support SB, as on many pre-Windows 8 UEFI systems.

123 # Below is therefore a bit harsh, but is done to ensure that

124 # no system slips through because it supports Secure Boot but

125 # does not create the sb_var when SB is inactive or has never

126 # been activated.

127 logging.error(

128 "FAIL: System does not appear to support " "Secure Boot."

129 )

130 return 1

131 else:

132 logging.info("FAIL: System did NOT boot in EFI mode.")

133 return 1

134

135

136 def main():

137 parser = ArgumentParser()

138 parser.add_argument(

139 "check",

140 choices=["efi", "secureboot", "reboot_firmware"],

141 help="The type of check to perform",

142 )

143 args = parser.parse_args()

144

145 FORMAT = "%(levelname)s: %(message)s"

146 logging.basicConfig(level=logging.INFO, format=FORMAT)

147 return do_tests(args.check)

148

149

150 if __name__ == "__main__":

151 sys.exit(main())