Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/rsakey.py: 32%

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 cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm 

24from cryptography.hazmat.backends import default_backend 

25from cryptography.hazmat.primitives import hashes, serialization 

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

27 

28from paramiko.message import Message 

29from paramiko.pkey import PKey 

30from paramiko.ssh_exception import SSHException 

31 

32 

33class RSAKey(PKey): 

34 """ 

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

36 data. 

37 """ 

38 

39 name = "ssh-rsa" 

40 HASHES = { 

41 "ssh-rsa": hashes.SHA1, 

42 "ssh-rsa-cert-v01@openssh.com": hashes.SHA1, 

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 return list(cls.HASHES.keys()) 

85 

86 @property 

87 def size(self): 

88 return self.key.key_size 

89 

90 @property 

91 def public_numbers(self): 

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

93 return self.key.private_numbers().public_numbers 

94 else: 

95 return self.key.public_numbers() 

96 

97 def asbytes(self): 

98 m = Message() 

99 m.add_string(self.name) 

100 m.add_mpint(self.public_numbers.e) 

101 m.add_mpint(self.public_numbers.n) 

102 return m.asbytes() 

103 

104 def __str__(self): 

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

106 # TODO 4.0: replace with a nice clean fingerprint display or something 

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

108 

109 @property 

110 def _fields(self): 

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

112 

113 def get_name(self): 

114 return self.name 

115 

116 def get_bits(self): 

117 return self.size 

118 

119 def can_sign(self): 

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

121 

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

123 if algorithm is None: 

124 algorithm = self.name 

125 sig = self.key.sign( 

126 data, 

127 padding=padding.PKCS1v15(), 

128 # HASHES being just a map from long identifier to either SHA1 or 

129 # SHA256 - cert'ness is not truly relevant. 

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

131 ) 

132 m = Message() 

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

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

135 m.add_string(sig) 

136 return m 

137 

138 def verify_ssh_sig(self, data, msg): 

139 sig_algorithm = msg.get_text() 

140 if sig_algorithm not in self.HASHES: 

141 return False 

142 key = self.key 

143 if isinstance(key, rsa.RSAPrivateKey): 

144 key = key.public_key() 

145 

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

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

148 sign = msg.get_binary() 

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

150 if diff > 0: 

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

152 

153 try: 

154 key.verify( 

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

156 ) 

157 except InvalidSignature: 

158 return False 

159 else: 

160 return True 

161 

162 def write_private_key_file(self, filename, password=None): 

163 self._write_private_key_file( 

164 filename, 

165 self.key, 

166 serialization.PrivateFormat.TraditionalOpenSSL, 

167 password=password, 

168 ) 

169 

170 def write_private_key(self, file_obj, password=None): 

171 self._write_private_key( 

172 file_obj, 

173 self.key, 

174 serialization.PrivateFormat.TraditionalOpenSSL, 

175 password=password, 

176 ) 

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