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 if algorithm is None: 

136 algorithm = self.name 

137 sig = self.key.sign( 

138 data, 

139 padding=padding.PKCS1v15(), 

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

141 # is not truly relevant. 

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

143 ) 

144 m = Message() 

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

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

147 m.add_string(sig) 

148 return m 

149 

150 def verify_ssh_sig(self, data, msg): 

151 sig_algorithm = msg.get_text() 

152 if sig_algorithm not in self.HASHES: 

153 return False 

154 key = self.key 

155 if isinstance(key, rsa.RSAPrivateKey): 

156 key = key.public_key() 

157 

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

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

160 sign = msg.get_binary() 

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

162 if diff > 0: 

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

164 

165 try: 

166 key.verify( 

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

168 ) 

169 except InvalidSignature: 

170 return False 

171 else: 

172 return True 

173 

174 @staticmethod 

175 def generate(bits, progress_func=None): 

176 """ 

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

178 generate a new host key or authentication key. 

179 

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

181 :param progress_func: Unused 

182 :return: new `.RSAKey` private key 

183 """ 

184 key = rsa.generate_private_key( 

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

186 ) 

187 return RSAKey(key=key) 

188 

189 # ...internals... 

190 

191 def _from_private_key_file(self, filename, password): 

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

193 self._decode_key(data) 

194 

195 def _from_private_key(self, file_obj, password): 

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

197 self._decode_key(data) 

198 

199 def _decode_key(self, data): 

200 pkformat, data = data 

201 if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL: 

202 try: 

203 key = serialization.load_der_private_key( 

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

205 ) 

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

207 raise SSHException(str(e)) 

208 elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH: 

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

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

211 key = rsa.RSAPrivateNumbers( 

212 p=p, 

213 q=q, 

214 d=d, 

215 dmp1=d % (p - 1), 

216 dmq1=d % (q - 1), 

217 iqmp=iqmp, 

218 public_numbers=public_numbers, 

219 ).private_key(default_backend()) 

220 else: 

221 self._got_bad_key_format_id(pkformat) 

222 assert isinstance(key, rsa.RSAPrivateKey) 

223 self.key = key