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 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
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()
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
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
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.
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)
189 # ...internals...
191 def _from_private_key_file(self, filename, password):
192 data = self._read_private_key_file("RSA", filename, password)
193 self._decode_key(data)
195 def _from_private_key(self, file_obj, password):
196 data = self._read_private_key("RSA", file_obj, password)
197 self._decode_key(data)
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