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
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
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.
19"""
20RSA keys.
21"""
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
28from paramiko.message import Message
29from paramiko.pkey import PKey
30from paramiko.ssh_exception import SSHException
33class RSAKey(PKey):
34 """
35 Representation of an RSA key which can be used to sign and verify SSH2
36 data.
37 """
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 }
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())
82 @classmethod
83 def identifiers(cls):
84 return list(cls.HASHES.keys())
86 @property
87 def size(self):
88 return self.key.key_size
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()
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()
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")
109 @property
110 def _fields(self):
111 return (self.get_name(), self.public_numbers.e, self.public_numbers.n)
113 def get_name(self):
114 return self.name
116 def get_bits(self):
117 return self.size
119 def can_sign(self):
120 return isinstance(self.key, rsa.RSAPrivateKey)
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
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()
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
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
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 )
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 )
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.
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)
193 # ...internals...
195 def _from_private_key_file(self, filename, password):
196 data = self._read_private_key_file("RSA", filename, password)
197 self._decode_key(data)
199 def _from_private_key(self, file_obj, password):
200 data = self._read_private_key("RSA", file_obj, password)
201 self._decode_key(data)
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