rsakey.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. RSA keys.
  20. """
  21. from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
  22. from cryptography.hazmat.backends import default_backend
  23. from cryptography.hazmat.primitives import hashes, serialization
  24. from cryptography.hazmat.primitives.asymmetric import rsa, padding
  25. from paramiko.message import Message
  26. from paramiko.pkey import PKey
  27. from paramiko.py3compat import PY2
  28. from paramiko.ssh_exception import SSHException
  29. class RSAKey(PKey):
  30. """
  31. Representation of an RSA key which can be used to sign and verify SSH2
  32. data.
  33. """
  34. HASHES = {
  35. "ssh-rsa": hashes.SHA1,
  36. "ssh-rsa-cert-v01@openssh.com": hashes.SHA1,
  37. "rsa-sha2-256": hashes.SHA256,
  38. "rsa-sha2-256-cert-v01@openssh.com": hashes.SHA256,
  39. "rsa-sha2-512": hashes.SHA512,
  40. "rsa-sha2-512-cert-v01@openssh.com": hashes.SHA512,
  41. }
  42. def __init__(
  43. self,
  44. msg=None,
  45. data=None,
  46. filename=None,
  47. password=None,
  48. key=None,
  49. file_obj=None,
  50. ):
  51. self.key = None
  52. self.public_blob = None
  53. if file_obj is not None:
  54. self._from_private_key(file_obj, password)
  55. return
  56. if filename is not None:
  57. self._from_private_key_file(filename, password)
  58. return
  59. if (msg is None) and (data is not None):
  60. msg = Message(data)
  61. if key is not None:
  62. self.key = key
  63. else:
  64. self._check_type_and_load_cert(
  65. msg=msg,
  66. # NOTE: this does NOT change when using rsa2 signatures; it's
  67. # purely about key loading, not exchange or verification
  68. key_type="ssh-rsa",
  69. cert_type="ssh-rsa-cert-v01@openssh.com",
  70. )
  71. self.key = rsa.RSAPublicNumbers(
  72. e=msg.get_mpint(), n=msg.get_mpint()
  73. ).public_key(default_backend())
  74. @property
  75. def size(self):
  76. return self.key.key_size
  77. @property
  78. def public_numbers(self):
  79. if isinstance(self.key, rsa.RSAPrivateKey):
  80. return self.key.private_numbers().public_numbers
  81. else:
  82. return self.key.public_numbers()
  83. def asbytes(self):
  84. m = Message()
  85. m.add_string("ssh-rsa")
  86. m.add_mpint(self.public_numbers.e)
  87. m.add_mpint(self.public_numbers.n)
  88. return m.asbytes()
  89. def __str__(self):
  90. # NOTE: as per inane commentary in #853, this appears to be the least
  91. # crummy way to get a representation that prints identical to Python
  92. # 2's previous behavior, on both interpreters.
  93. # TODO: replace with a nice clean fingerprint display or something
  94. if PY2:
  95. # Can't just return the .decode below for Py2 because stuff still
  96. # tries stuffing it into ASCII for whatever godforsaken reason
  97. return self.asbytes()
  98. else:
  99. return self.asbytes().decode("utf8", errors="ignore")
  100. @property
  101. def _fields(self):
  102. return (self.get_name(), self.public_numbers.e, self.public_numbers.n)
  103. def get_name(self):
  104. return "ssh-rsa"
  105. def get_bits(self):
  106. return self.size
  107. def can_sign(self):
  108. return isinstance(self.key, rsa.RSAPrivateKey)
  109. def sign_ssh_data(self, data, algorithm="ssh-rsa"):
  110. sig = self.key.sign(
  111. data,
  112. padding=padding.PKCS1v15(),
  113. algorithm=self.HASHES[algorithm](),
  114. )
  115. m = Message()
  116. m.add_string(algorithm.replace("-cert-v01@openssh.com", ""))
  117. m.add_string(sig)
  118. return m
  119. def verify_ssh_sig(self, data, msg):
  120. sig_algorithm = msg.get_text()
  121. if sig_algorithm not in self.HASHES:
  122. return False
  123. key = self.key
  124. if isinstance(key, rsa.RSAPrivateKey):
  125. key = key.public_key()
  126. # NOTE: pad received signature with leading zeros, key.verify()
  127. # expects a signature of key size (e.g. PuTTY doesn't pad)
  128. sign = msg.get_binary()
  129. diff = key.key_size - len(sign) * 8
  130. if diff > 0:
  131. sign = b"\x00" * ((diff + 7) // 8) + sign
  132. try:
  133. key.verify(
  134. sign, data, padding.PKCS1v15(), self.HASHES[sig_algorithm]()
  135. )
  136. except InvalidSignature:
  137. return False
  138. else:
  139. return True
  140. def write_private_key_file(self, filename, password=None):
  141. self._write_private_key_file(
  142. filename,
  143. self.key,
  144. serialization.PrivateFormat.TraditionalOpenSSL,
  145. password=password,
  146. )
  147. def write_private_key(self, file_obj, password=None):
  148. self._write_private_key(
  149. file_obj,
  150. self.key,
  151. serialization.PrivateFormat.TraditionalOpenSSL,
  152. password=password,
  153. )
  154. @staticmethod
  155. def generate(bits, progress_func=None):
  156. """
  157. Generate a new private RSA key. This factory function can be used to
  158. generate a new host key or authentication key.
  159. :param int bits: number of bits the generated key should be.
  160. :param progress_func: Unused
  161. :return: new `.RSAKey` private key
  162. """
  163. key = rsa.generate_private_key(
  164. public_exponent=65537, key_size=bits, backend=default_backend()
  165. )
  166. return RSAKey(key=key)
  167. # ...internals...
  168. def _from_private_key_file(self, filename, password):
  169. data = self._read_private_key_file("RSA", filename, password)
  170. self._decode_key(data)
  171. def _from_private_key(self, file_obj, password):
  172. data = self._read_private_key("RSA", file_obj, password)
  173. self._decode_key(data)
  174. def _decode_key(self, data):
  175. pkformat, data = data
  176. if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL:
  177. try:
  178. key = serialization.load_der_private_key(
  179. data, password=None, backend=default_backend()
  180. )
  181. except (ValueError, TypeError, UnsupportedAlgorithm) as e:
  182. raise SSHException(str(e))
  183. elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH:
  184. n, e, d, iqmp, p, q = self._uint32_cstruct_unpack(data, "iiiiii")
  185. public_numbers = rsa.RSAPublicNumbers(e=e, n=n)
  186. key = rsa.RSAPrivateNumbers(
  187. p=p,
  188. q=q,
  189. d=d,
  190. dmp1=d % (p - 1),
  191. dmq1=d % (q - 1),
  192. iqmp=iqmp,
  193. public_numbers=public_numbers,
  194. ).private_key(default_backend())
  195. else:
  196. self._got_bad_key_format_id(pkformat)
  197. assert isinstance(key, rsa.RSAPrivateKey)
  198. self.key = key