Coverage for admin/onboard.py: 93%

84 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-05 20:41 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2021 RSK Labs Ltd 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy of 

6# this software and associated documentation files (the "Software"), to deal in 

7# the Software without restriction, including without limitation the rights to 

8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 

9# of the Software, and to permit persons to whom the Software is furnished to do 

10# so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in all 

13# copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

21# SOFTWARE. 

22 

23import sys 

24import os 

25from ledger.hsm2dongle import HSM2Dongle 

26from ledger.pin import BasePin 

27from .misc import ( 

28 info, 

29 head, 

30 bls, 

31 get_hsm, 

32 get_admin_hsm, 

33 dispose_hsm, 

34 PIN_ERROR_MESSAGE, 

35 AdminError, 

36 ask_for_pin, 

37 wait_for_reconnection, 

38) 

39from .dongle_admin import DongleAdmin 

40from .unlock import do_unlock 

41from .certificate import HSMCertificate, HSMCertificateElement 

42 

43# TODO: this could perhaps be done with a different value. 

44# Currently unused but necessary for the attestation setup process. 

45ENDORSEMENT_CERTIFICATE = b"RSK_ENDORSEMENT_OK" 

46SEED_SIZE = 32 

47 

48 

49def do_onboard(options): 

50 head("### -> Onboarding and attestation setup", fill="#") 

51 hsm = None 

52 

53 # Require an output file 

54 if options.output_file_path is None: 

55 raise AdminError("No output file path given") 

56 

57 # Validate pin (if given) 

58 pin = None 

59 if options.pin is not None: 

60 if not BasePin.is_valid(options.pin.encode()): 

61 raise AdminError(PIN_ERROR_MESSAGE) 

62 pin = options.pin.encode() 

63 

64 # Connection 

65 hsm = get_hsm(options.verbose) 

66 

67 # Mode check 

68 info("Finding mode... ", options.verbose) 

69 mode = hsm.get_current_mode() 

70 info(f"Mode: {mode.name.capitalize()}") 

71 

72 # Require bootloader mode for onboarding 

73 if mode != HSM2Dongle.MODE.BOOTLOADER: 

74 raise AdminError("Device not in bootloader mode. Disconnect and re-connect the " 

75 "ledger and try again") 

76 

77 # Echo check 

78 info("Sending echo... ", options.verbose) 

79 if not hsm.echo(): 

80 raise AdminError("Echo error") 

81 info("Echo OK") 

82 

83 info("Is device onboarded? ... ", options.verbose) 

84 is_onboarded = hsm.is_onboarded() 

85 info(f"Onboarded: {bls(is_onboarded)}") 

86 

87 if is_onboarded: 

88 raise AdminError("Device already onboarded") 

89 

90 message = "The following operation will onboard the device." 

91 head([ 

92 message, 

93 "Do you want to proceed? Yes/No", 

94 ]) 

95 while True: 

96 info("> ", False) 

97 answer = sys.stdin.readline().rstrip() 

98 if answer.lower() in ["n", "no"]: 

99 raise AdminError("Cancelled by user") 

100 if answer.lower() == "yes": 

101 break 

102 info("Please answer 'Yes' or 'No'") 

103 

104 # If we get here, then it means we need to onboard 

105 

106 # Ask the user for a pin if one not given 

107 if pin is None: 

108 info("Please select a pin for the device.") 

109 pin = ask_for_pin(any_pin=options.any_pin) 

110 

111 # Generate a random seed 

112 info("Generating a random seed... ", options.verbose) 

113 seed = gen_seed() 

114 info("Seed generated") 

115 

116 # Onboard 

117 info("Onboarding... ", options.verbose) 

118 hsm.onboard(seed, pin) 

119 info("Onboarded") 

120 

121 dispose_hsm(hsm) 

122 

123 head( 

124 [ 

125 "Onboarding done", 

126 "Please disconnect and re-connect the ledger to proceed with the attestation setup", # noqa E501 

127 "Press [Enter] to continue", 

128 ], 

129 nl=False, 

130 ) 

131 sys.stdin.readline() 

132 

133 # Wait for the dongle 

134 wait_for_reconnection() 

135 

136 # Unlock without executing the signer 

137 try: 

138 do_unlock(options, no_exec=True, label=False) 

139 except Exception as e: 

140 raise AdminError(f"Failed to unlock device: {str(e)}") 

141 

142 # Wait for the UI 

143 wait_for_reconnection() 

144 

145 # Connection for admin operations 

146 hsm = get_admin_hsm(options.verbose) 

147 

148 # Attestation setup 

149 info("Handshaking... ", options.verbose) 

150 hsm.handshake() 

151 info("Handshaking done") 

152 

153 info("Gathering device key... ", options.verbose) 

154 device_key_info = hsm.get_device_key() 

155 info("Device key gathered") 

156 

157 info("Setting up the attestation key... ", options.verbose) 

158 attestation_key_info = hsm.setup_endorsement_key( 

159 DongleAdmin.ENDORSEMENT_SCHEME.SCHEME_TWO, ENDORSEMENT_CERTIFICATE) 

160 info("Attestation key setup complete") 

161 

162 dispose_hsm(hsm) 

163 

164 # Generate and save the attestation certificate 

165 info("Generating the attestation certificate... ", options.verbose) 

166 

167 att_cert = HSMCertificate() 

168 att_cert.add_element( 

169 HSMCertificateElement({ 

170 "name": "attestation", 

171 "message": attestation_key_info["message"], 

172 "signature": attestation_key_info["signature"], 

173 "signed_by": "device", 

174 })) 

175 att_cert.add_element( 

176 HSMCertificateElement({ 

177 "name": "device", 

178 "message": device_key_info["message"], 

179 "signature": device_key_info["signature"], 

180 "signed_by": "root", 

181 })) 

182 att_cert.add_target("attestation") 

183 att_cert.save_to_jsonfile(options.output_file_path) 

184 

185 info(f"Attestation certificate saved to {options.output_file_path}") 

186 

187 head([ 

188 "Onboarding and attestation setup done", 

189 "Please disconnect and re-connect the ledger before the first use", 

190 ]) 

191 

192 

193def gen_seed(): 

194 return os.urandom(SEED_SIZE)