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
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 typing import Optional
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
30from paramiko.message import Message
31from paramiko.pkey import PKey
32from paramiko.ssh_exception import SSHException
35class RSAKey(PKey):
36 """
37 Representation of an RSA key which can be used to sign and verify SSH2
38 data.
39 """
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 }
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 # 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 ]
93 @property
94 def size(self):
95 return self.key.key_size
97 @property
98 def private_key(self) -> Optional[rsa.RSAPrivateKey]:
99 return self.key if isinstance(self.key, rsa.RSAPrivateKey) else None
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()
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()
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")
121 @property
122 def _fields(self):
123 return (self.get_name(), self.public_numbers.e, self.public_numbers.n)
125 def get_name(self):
126 return self.name
128 def get_bits(self):
129 return self.size
131 def can_sign(self):
132 return isinstance(self.key, rsa.RSAPrivateKey)
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
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()
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
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
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