Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/securesystemslib/_gpg/rsa.py: 33%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

45 statements  

1""" 

2<Module Name> 

3 rsa.py 

4 

5<Author> 

6 Santiago Torres-Arias <santiago@nyu.edu> 

7 

8<Started> 

9 Nov 15, 2017 

10 

11<Copyright> 

12 See LICENSE for licensing information. 

13 

14<Purpose> 

15 RSA-specific handling routines for signature verification and key parsing 

16""" 

17 

18import binascii 

19 

20CRYPTO = True 

21NO_CRYPTO_MSG = "RSA key support for GPG requires the cryptography library" 

22try: 

23 from cryptography.exceptions import InvalidSignature 

24 from cryptography.hazmat import backends 

25 from cryptography.hazmat.primitives.asymmetric import padding, rsa, utils 

26except ImportError: 

27 CRYPTO = False 

28 

29# ruff: noqa: E402 

30from securesystemslib import exceptions 

31from securesystemslib._gpg import util as gpg_util 

32from securesystemslib._gpg.exceptions import PacketParsingError 

33 

34 

35def create_pubkey(pubkey_info): 

36 """ 

37 <Purpose> 

38 Create and return an RSAPublicKey object from the passed pubkey_info 

39 using pyca/cryptography. 

40 

41 <Arguments> 

42 pubkey_info: 

43 An RSA pubkey dict. 

44 

45 <Exceptions> 

46 securesystemslib.exceptions.UnsupportedLibraryError if 

47 the cryptography module is unavailable 

48 

49 <Returns> 

50 A cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey based on the 

51 passed pubkey_info. 

52 

53 """ 

54 if not CRYPTO: # pragma: no cover 

55 raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) 

56 

57 e = int(pubkey_info["keyval"]["public"]["e"], 16) 

58 n = int(pubkey_info["keyval"]["public"]["n"], 16) 

59 pubkey = rsa.RSAPublicNumbers(e, n).public_key(backends.default_backend()) 

60 

61 return pubkey 

62 

63 

64def get_pubkey_params(data): 

65 """ 

66 <Purpose> 

67 Parse the public key parameters as multi-precision-integers. 

68 

69 <Arguments> 

70 data: 

71 the RFC4880-encoded public key parameters data buffer as described 

72 in the fifth paragraph of section 5.5.2. 

73 

74 <Exceptions> 

75 securesystemslib._gpg.exceptions.PacketParsingError: 

76 if the public key parameters are malformed 

77 

78 <Side Effects> 

79 None. 

80 

81 <Returns> 

82 An RSA public key dict. 

83 """ 

84 ptr = 0 

85 

86 modulus_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 

87 ptr += 2 

88 modulus = data[ptr : ptr + modulus_length] 

89 if len(modulus) != modulus_length: # pragma: no cover 

90 raise PacketParsingError("This modulus MPI was truncated!") 

91 ptr += modulus_length 

92 

93 exponent_e_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 

94 ptr += 2 

95 exponent_e = data[ptr : ptr + exponent_e_length] 

96 if len(exponent_e) != exponent_e_length: # pragma: no cover 

97 raise PacketParsingError("This e MPI has been truncated!") 

98 

99 return { 

100 "e": binascii.hexlify(exponent_e).decode("ascii"), 

101 "n": binascii.hexlify(modulus).decode("ascii"), 

102 } 

103 

104 

105def get_signature_params(data): 

106 """ 

107 <Purpose> 

108 Parse the signature parameters as multi-precision-integers. 

109 

110 <Arguments> 

111 data: 

112 the RFC4880-encoded signature data buffer as described 

113 in the third paragraph of section 5.2.2. 

114 

115 <Exceptions> 

116 securesystemslib._gpg.exceptions.PacketParsingError: 

117 if the public key parameters are malformed 

118 

119 <Side Effects> 

120 None. 

121 

122 <Returns> 

123 The decoded signature buffer 

124 """ 

125 

126 ptr = 0 

127 signature_length = gpg_util.get_mpi_length(data[ptr : ptr + 2]) 

128 ptr += 2 

129 signature = data[ptr : ptr + signature_length] 

130 if len(signature) != signature_length: # pragma: no cover 

131 raise PacketParsingError("This signature was truncated!") 

132 

133 return signature 

134 

135 

136def verify_signature(signature_object, pubkey_info, content, hash_algorithm_id): 

137 """ 

138 <Purpose> 

139 Verify the passed signature against the passed content with the passed 

140 RSA public key using pyca/cryptography. 

141 

142 <Arguments> 

143 signature_object: 

144 A signature dict. 

145 

146 pubkey_info: 

147 The RSA public key dict. 

148 

149 content: 

150 The signed bytes against which the signature is verified 

151 

152 hash_algorithm_id: 

153 one of SHA1, SHA256, SHA512 (see securesystemslib._gpg.constants) 

154 used to verify the signature 

155 NOTE: Overrides any hash algorithm specification in "pubkey_info"'s 

156 "hashes" or "method" fields. 

157 

158 <Exceptions> 

159 securesystemslib.exceptions.UnsupportedLibraryError if: 

160 the cryptography module is unavailable 

161 

162 ValueError: 

163 if the passed hash_algorithm_id is not supported (see 

164 securesystemslib._gpg.util.get_hashing_class) 

165 

166 <Returns> 

167 True if signature verification passes and False otherwise 

168 

169 """ 

170 if not CRYPTO: # pragma: no cover 

171 raise exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) 

172 

173 hasher = gpg_util.get_hashing_class(hash_algorithm_id) 

174 

175 pubkey_object = create_pubkey(pubkey_info) 

176 

177 # zero-pad the signature due to a discrepancy between the openssl backend 

178 # and the gnupg interpretation of PKCSv1.5. Read more at: 

179 # https://github.com/in-toto/in-toto/issues/171#issuecomment-440039256 

180 # we are skipping this if on the tests because well, how would one test this 

181 # deterministically. 

182 pubkey_length = len(pubkey_info["keyval"]["public"]["n"]) 

183 signature_length = len(signature_object["signature"]) 

184 if pubkey_length != signature_length: # pragma: no cover 

185 zero_pad = "0" * (pubkey_length - signature_length) 

186 signature_object["signature"] = "{}{}".format( 

187 zero_pad, signature_object["signature"] 

188 ) 

189 

190 digest = gpg_util.hash_object( 

191 binascii.unhexlify(signature_object["other_headers"]), hasher(), content 

192 ) 

193 

194 try: 

195 pubkey_object.verify( 

196 binascii.unhexlify(signature_object["signature"]), 

197 digest, 

198 padding.PKCS1v15(), 

199 utils.Prehashed(hasher()), 

200 ) 

201 return True 

202 except InvalidSignature: 

203 return False