Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/rsakey.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

107 statements  

1# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com> 

2# 

3# This file is part of paramiko. 

4# 

5# Paramiko is free software; you can redistribute it and/or modify it under the 

6# terms of the GNU Lesser General Public License as published by the Free 

7# Software Foundation; either version 2.1 of the License, or (at your option) 

8# any later version. 

9# 

10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 

11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 

12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 

13# details. 

14# 

15# You should have received a copy of the GNU Lesser General Public License 

16# along with Paramiko; if not, write to the Free Software Foundation, Inc., 

17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 

18 

19""" 

20RSA keys. 

21""" 

22 

23from typing import Optional 

24 

25from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm 

26from cryptography.hazmat.backends import default_backend 

27from cryptography.hazmat.primitives import hashes, serialization 

28from cryptography.hazmat.primitives.asymmetric import padding, rsa 

29 

30from paramiko.message import Message 

31from paramiko.pkey import PKey 

32from paramiko.ssh_exception import SSHException 

33 

34 

35class RSAKey(PKey): 

36 """ 

37 Representation of an RSA key which can be used to sign and verify SSH2 

38 data. 

39 """ 

40 

41 name = "ssh-rsa" 

42 HASHES = { 

43 "rsa-sha2-256": hashes.SHA256, 

44 "rsa-sha2-256-cert-v01@openssh.com": hashes.SHA256, 

45 "rsa-sha2-512": hashes.SHA512, 

46 "rsa-sha2-512-cert-v01@openssh.com": hashes.SHA512, 

47 } 

48 

49 def __init__( 

50 self, 

51 msg=None, 

52 data=None, 

53 filename=None, 

54 password=None, 

55 key=None, 

56 file_obj=None, 

57 ): 

58 self.key = None 

59 self.public_blob = None 

60 if file_obj is not None: 

61 self._from_private_key(file_obj, password) 

62 return 

63 if filename is not None: 

64 self._from_private_key_file(filename, password) 

65 return 

66 if (msg is None) and (data is not None): 

67 msg = Message(data) 

68 if key is not None: 

69 self.key = key 

70 else: 

71 self._check_type_and_load_cert( 

72 msg=msg, 

73 # NOTE: this does NOT change when using rsa2 signatures; it's 

74 # purely about key loading, not exchange or verification 

75 key_type=self.name, 

76 cert_type="ssh-rsa-cert-v01@openssh.com", 

77 ) 

78 self.key = rsa.RSAPublicNumbers( 

79 e=msg.get_mpint(), n=msg.get_mpint() 

80 ).public_key(default_backend()) 

81 

82 @classmethod 

83 def identifiers(cls): 

84 # NOTE: we no longer want to have ssh-rsa+SHA1 in HASHES but we still 

85 # need to advertise we can be used to read ssh-rsa keys (w/ assumption 

86 # other parts of system will enforce the use of SHA2 signing algos). 

87 # Thus, just say so here. 

88 return list(cls.HASHES.keys()) + [ 

89 "ssh-rsa", 

90 "ssh-rsa-cert-v01@openssh.com", 

91 ] 

92 

93 @property 

94 def size(self): 

95 return self.key.key_size 

96 

97 @property 

98 def private_key(self) -> Optional[rsa.RSAPrivateKey]: 

99 return self.key if isinstance(self.key, rsa.RSAPrivateKey) else None 

100 

101 @property 

102 def public_numbers(self): 

103 if isinstance(self.key, rsa.RSAPrivateKey): 

104 return self.key.private_numbers().public_numbers 

105 else: 

106 return self.key.public_numbers() 

107 

108 def asbytes(self): 

109 m = Message() 

110 m.add_string(self.name) 

111 m.add_mpint(self.public_numbers.e) 

112 m.add_mpint(self.public_numbers.n) 

113 return m.asbytes() 

114 

115 def __str__(self): 

116 # NOTE: see #853 to explain some legacy behavior. 

117 # TODO (backwards incompat): replace with a nice clean fingerprint 

118 # display or something 

119 return self.asbytes().decode("utf8", errors="ignore") 

120 

121 @property 

122 def _fields(self): 

123 return (self.get_name(), self.public_numbers.e, self.public_numbers.n) 

124 

125 def get_name(self): 

126 return self.name 

127 

128 def get_bits(self): 

129 return self.size 

130 

131 def can_sign(self): 

132 return isinstance(self.key, rsa.RSAPrivateKey) 

133 

134 def sign_ssh_data(self, data, algorithm=None): 

135 # Default to strongest implemented algorithm when unspecified. (We used 

136 # to have an 'obvious' default prior to the SHA2 era, but that's no 

137 # longer the case, but here it's better to guess and default to 

138 # best-possible, than to just blow up. 

139 if algorithm is None: 

140 algorithm = "rsa-sha2-512" 

141 sig = self.key.sign( 

142 data, 

143 padding=padding.PKCS1v15(), 

144 # HASHES being just a map from long identifier to algo; cert'ness 

145 # is not truly relevant. 

146 algorithm=self.HASHES[algorithm](), 

147 ) 

148 m = Message() 

149 # And here again, cert'ness is irrelevant, so it is stripped out. 

150 m.add_string(algorithm.replace("-cert-v01@openssh.com", "")) 

151 m.add_string(sig) 

152 return m 

153 

154 def verify_ssh_sig(self, data, msg): 

155 sig_algorithm = msg.get_text() 

156 if sig_algorithm not in self.HASHES: 

157 return False 

158 key = self.key 

159 if isinstance(key, rsa.RSAPrivateKey): 

160 key = key.public_key() 

161 

162 # NOTE: pad received signature with leading zeros, key.verify() 

163 # expects a signature of key size (e.g. PuTTY doesn't pad) 

164 sign = msg.get_binary() 

165 diff = key.key_size - len(sign) * 8 

166 if diff > 0: 

167 sign = b"\x00" * ((diff + 7) // 8) + sign 

168 

169 try: 

170 key.verify( 

171 sign, data, padding.PKCS1v15(), self.HASHES[sig_algorithm]() 

172 ) 

173 except InvalidSignature: 

174 return False 

175 else: 

176 return True 

177 

178 @staticmethod 

179 def generate(bits, progress_func=None): 

180 """ 

181 Generate a new private RSA key. This factory function can be used to 

182 generate a new host key or authentication key. 

183 

184 :param int bits: number of bits the generated key should be. 

185 :param progress_func: Unused 

186 :return: new `.RSAKey` private key 

187 """ 

188 key = rsa.generate_private_key( 

189 public_exponent=65537, key_size=bits, backend=default_backend() 

190 ) 

191 return RSAKey(key=key) 

192 

193 # ...internals... 

194 

195 def _from_private_key_file(self, filename, password): 

196 data = self._read_private_key_file("RSA", filename, password) 

197 self._decode_key(data) 

198 

199 def _from_private_key(self, file_obj, password): 

200 data = self._read_private_key("RSA", file_obj, password) 

201 self._decode_key(data) 

202 

203 def _decode_key(self, data): 

204 pkformat, data = data 

205 if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL: 

206 try: 

207 key = serialization.load_der_private_key( 

208 data, password=None, backend=default_backend() 

209 ) 

210 except (ValueError, TypeError, UnsupportedAlgorithm) as e: 

211 raise SSHException(str(e)) 

212 elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH: 

213 n, e, d, iqmp, p, q = self._uint32_cstruct_unpack(data, "iiiiii") 

214 public_numbers = rsa.RSAPublicNumbers(e=e, n=n) 

215 key = rsa.RSAPrivateNumbers( 

216 p=p, 

217 q=q, 

218 d=d, 

219 dmp1=d % (p - 1), 

220 dmq1=d % (q - 1), 

221 iqmp=iqmp, 

222 public_numbers=public_numbers, 

223 ).private_key(default_backend()) 

224 else: 

225 self._got_bad_key_format_id(pkformat) 

226 assert isinstance(key, rsa.RSAPrivateKey) 

227 self.key = key