Coverage for admin/verify_ledger_attestation.py: 90%

93 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-07-10 13:43 +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 re 

24from .misc import info, head, AdminError 

25from .attestation_utils import PowHsmAttestationMessage, load_pubkeys, \ 

26 compute_pubkeys_hash, compute_pubkeys_output 

27from .utils import is_nonempty_hex_string 

28from .certificate import HSMCertificate, HSMCertificateRoot 

29 

30 

31UI_MESSAGE_HEADER_REGEX = re.compile(b"^HSM:UI:([2345].[0-9])") 

32SIGNER_LEGACY_MESSAGE_HEADER_REGEX = re.compile(b"^HSM:SIGNER:([2345].[0-9])") 

33UI_DERIVATION_PATH = "m/44'/0'/0'/0/0" 

34UD_VALUE_LENGTH = 32 

35PUBLIC_KEYS_HASH_LENGTH = 32 

36PUBKEY_COMPRESSED_LENGTH = 33 

37SIGNER_HASH_LENGTH = 32 

38SIGNER_ITERATION_LENGTH = 2 

39 

40# Ledger's root authority 

41# (according to 

42# https://github.com/LedgerHQ/blue-loader-python/blob/master/ledgerblue/ 

43# endorsementSetup.py#L138) 

44DEFAULT_ROOT_AUTHORITY = "0490f5c9d15a0134bb019d2afd0bf297149738459706e7ac5be4abc350a1f8"\ 

45 "18057224fce12ec9a65de18ec34d6e8c24db927835ea1692b14c32e9836a75"\ 

46 "dad609" 

47 

48 

49def do_verify_attestation(options): 

50 head("### -> Verify UI and Signer attestations", fill="#") 

51 

52 if options.attestation_certificate_file_path is None: 

53 raise AdminError("No attestation certificate file given") 

54 

55 if options.pubkeys_file_path is None: 

56 raise AdminError("No public keys file given") 

57 

58 root_authority = DEFAULT_ROOT_AUTHORITY 

59 if options.root_authority is not None: 

60 if not is_nonempty_hex_string(options.root_authority): 

61 raise AdminError("Invalid root authority") 

62 root_authority = options.root_authority 

63 try: 

64 root_authority = HSMCertificateRoot(root_authority) 

65 except ValueError: 

66 raise AdminError("Invalid root authority") 

67 info(f"Using {root_authority} as root authority") 

68 

69 # Load public keys, compute their hash and format them for output 

70 pubkeys_map = load_pubkeys(options.pubkeys_file_path) 

71 pubkeys_hash = compute_pubkeys_hash(pubkeys_map) 

72 pubkeys_output = compute_pubkeys_output(pubkeys_map) 

73 

74 # Find the expected UI public key 

75 expected_ui_public_key = next(filter( 

76 lambda pair: pair[0] == UI_DERIVATION_PATH, pubkeys_map.items()), (None, None))[1] 

77 if expected_ui_public_key is None: 

78 raise AdminError( 

79 f"Public key with path {UI_DERIVATION_PATH} not present in public key file") 

80 expected_ui_public_key = expected_ui_public_key.serialize(compressed=True).hex() 

81 

82 # Load the given attestation key certificate 

83 try: 

84 att_cert = HSMCertificate.from_jsonfile(options.attestation_certificate_file_path) 

85 except Exception as e: 

86 raise AdminError(f"While loading the attestation certificate file: {str(e)}") 

87 

88 # Validate the certificate using the given root authority 

89 # (this should be *one of* Ledger's public keys) 

90 result = att_cert.validate_and_get_values(root_authority) 

91 

92 # UI 

93 if "ui" not in result: 

94 raise AdminError("Certificate does not contain a UI attestation") 

95 

96 ui_result = result["ui"] 

97 if not ui_result[0]: 

98 raise AdminError(f"Invalid UI attestation: error validating '{ui_result[1]}'") 

99 

100 ui_message = bytes.fromhex(ui_result[1]) 

101 ui_hash = bytes.fromhex(ui_result[2]) 

102 mh_match = UI_MESSAGE_HEADER_REGEX.match(ui_message) 

103 if mh_match is None: 

104 raise AdminError( 

105 f"Invalid UI attestation message header: {ui_message.hex()}") 

106 mh_len = len(mh_match.group(0)) 

107 

108 # Extract UI version, UD value, UI public key and signer version from message 

109 ui_version = mh_match.group(1) 

110 ud_value = ui_message[mh_len:mh_len + UD_VALUE_LENGTH].hex() 

111 ui_public_key = ui_message[mh_len + UD_VALUE_LENGTH:mh_len + UD_VALUE_LENGTH + 

112 PUBKEY_COMPRESSED_LENGTH].hex() 

113 signer_hash = ui_message[mh_len + UD_VALUE_LENGTH + PUBKEY_COMPRESSED_LENGTH: 

114 mh_len + UD_VALUE_LENGTH + PUBKEY_COMPRESSED_LENGTH + 

115 SIGNER_HASH_LENGTH].hex() 

116 signer_iteration = ui_message[mh_len + UD_VALUE_LENGTH + PUBKEY_COMPRESSED_LENGTH + 

117 SIGNER_HASH_LENGTH: 

118 mh_len + UD_VALUE_LENGTH + PUBKEY_COMPRESSED_LENGTH + 

119 SIGNER_HASH_LENGTH + SIGNER_ITERATION_LENGTH] 

120 signer_iteration = int.from_bytes(signer_iteration, byteorder='big', signed=False) 

121 

122 if ui_public_key != expected_ui_public_key: 

123 raise AdminError("Invalid UI attestation: unexpected public key reported. " 

124 f"Expected {expected_ui_public_key} but got {ui_public_key}") 

125 

126 head( 

127 [ 

128 "UI verified with:", 

129 f"UD value: {ud_value}", 

130 f"Derived public key ({UI_DERIVATION_PATH}): {ui_public_key}", 

131 f"Authorized signer hash: {signer_hash}", 

132 f"Authorized signer iteration: {signer_iteration}", 

133 f"Installed UI hash: {ui_hash.hex()}", 

134 f"Installed UI version: {ui_version.decode()}", 

135 ], 

136 fill="-", 

137 ) 

138 

139 # Signer 

140 if "signer" not in result: 

141 raise AdminError("Certificate does not contain a Signer attestation") 

142 

143 signer_result = result["signer"] 

144 if not signer_result[0]: 

145 raise AdminError( 

146 f"Invalid Signer attestation: error validating '{signer_result[1]}'") 

147 

148 signer_message = bytes.fromhex(signer_result[1]) 

149 signer_hash = bytes.fromhex(signer_result[2]) 

150 lmh_match = SIGNER_LEGACY_MESSAGE_HEADER_REGEX.match(signer_message) 

151 if lmh_match is None and not PowHsmAttestationMessage.is_header(signer_message): 

152 raise AdminError( 

153 f"Invalid Signer attestation message header: {signer_message.hex()}") 

154 

155 if lmh_match is not None: 

156 # Legacy header 

157 powhsm_message = None 

158 hlen = len(lmh_match.group(0)) 

159 signer_version = lmh_match.group(1).decode() 

160 offset = hlen 

161 reported_pubkeys_hash = signer_message[offset:] 

162 offset += PUBLIC_KEYS_HASH_LENGTH 

163 if signer_message[offset:] != b'': 

164 raise AdminError(f"Signer attestation message longer " 

165 f"than expected: {signer_message.hex()}") 

166 else: 

167 # New header 

168 try: 

169 powhsm_message = PowHsmAttestationMessage(signer_message, name="Signer") 

170 except ValueError as e: 

171 raise AdminError(str(e)) 

172 signer_version = powhsm_message.version 

173 reported_pubkeys_hash = powhsm_message.public_keys_hash 

174 

175 # Validations on extracted values 

176 if reported_pubkeys_hash != pubkeys_hash: 

177 raise AdminError( 

178 f"Signer attestation public keys hash mismatch: expected {pubkeys_hash.hex()}" 

179 f" but attestation reports {reported_pubkeys_hash.hex()}" 

180 ) 

181 

182 signer_info = [ 

183 f"Hash: {pubkeys_hash.hex()}", 

184 "", 

185 f"Installed Signer hash: {signer_hash.hex()}", 

186 f"Installed Signer version: {signer_version}", 

187 ] 

188 

189 if powhsm_message is not None: 

190 signer_info += [ 

191 f"Platform: {powhsm_message.platform}", 

192 f"UD value: {powhsm_message.ud_value.hex()}", 

193 f"Best block: {powhsm_message.best_block.hex()}", 

194 f"Last transaction signed: {powhsm_message.last_signed_tx.hex()}", 

195 f"Timestamp: {powhsm_message.timestamp}", 

196 ] 

197 

198 head( 

199 ["Signer verified with public keys:"] + pubkeys_output + signer_info, 

200 fill="-", 

201 )