You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
7.7 KiB

7 years ago
  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. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  18. """
  19. DSS keys.
  20. """
  21. from cryptography.exceptions import InvalidSignature
  22. from cryptography.hazmat.backends import default_backend
  23. from cryptography.hazmat.primitives import hashes, serialization
  24. from cryptography.hazmat.primitives.asymmetric import dsa
  25. from cryptography.hazmat.primitives.asymmetric.utils import (
  26. decode_dss_signature, encode_dss_signature
  27. )
  28. from paramiko import util
  29. from paramiko.common import zero_byte
  30. from paramiko.ssh_exception import SSHException
  31. from paramiko.message import Message
  32. from paramiko.ber import BER, BERException
  33. from paramiko.pkey import PKey
  34. class DSSKey(PKey):
  35. """
  36. Representation of a DSS key which can be used to sign an verify SSH2
  37. data.
  38. """
  39. def __init__(self, msg=None, data=None, filename=None, password=None,
  40. vals=None, file_obj=None):
  41. self.p = None
  42. self.q = None
  43. self.g = None
  44. self.y = None
  45. self.x = None
  46. self.public_blob = None
  47. if file_obj is not None:
  48. self._from_private_key(file_obj, password)
  49. return
  50. if filename is not None:
  51. self._from_private_key_file(filename, password)
  52. return
  53. if (msg is None) and (data is not None):
  54. msg = Message(data)
  55. if vals is not None:
  56. self.p, self.q, self.g, self.y = vals
  57. else:
  58. self._check_type_and_load_cert(
  59. msg=msg,
  60. key_type='ssh-dss',
  61. cert_type='ssh-dss-cert-v01@openssh.com',
  62. )
  63. self.p = msg.get_mpint()
  64. self.q = msg.get_mpint()
  65. self.g = msg.get_mpint()
  66. self.y = msg.get_mpint()
  67. self.size = util.bit_length(self.p)
  68. def asbytes(self):
  69. m = Message()
  70. m.add_string('ssh-dss')
  71. m.add_mpint(self.p)
  72. m.add_mpint(self.q)
  73. m.add_mpint(self.g)
  74. m.add_mpint(self.y)
  75. return m.asbytes()
  76. def __str__(self):
  77. return self.asbytes()
  78. def __hash__(self):
  79. return hash((self.get_name(), self.p, self.q, self.g, self.y))
  80. def get_name(self):
  81. return 'ssh-dss'
  82. def get_bits(self):
  83. return self.size
  84. def can_sign(self):
  85. return self.x is not None
  86. def sign_ssh_data(self, data):
  87. key = dsa.DSAPrivateNumbers(
  88. x=self.x,
  89. public_numbers=dsa.DSAPublicNumbers(
  90. y=self.y,
  91. parameter_numbers=dsa.DSAParameterNumbers(
  92. p=self.p,
  93. q=self.q,
  94. g=self.g
  95. )
  96. )
  97. ).private_key(backend=default_backend())
  98. sig = key.sign(data, hashes.SHA1())
  99. r, s = decode_dss_signature(sig)
  100. m = Message()
  101. m.add_string('ssh-dss')
  102. # apparently, in rare cases, r or s may be shorter than 20 bytes!
  103. rstr = util.deflate_long(r, 0)
  104. sstr = util.deflate_long(s, 0)
  105. if len(rstr) < 20:
  106. rstr = zero_byte * (20 - len(rstr)) + rstr
  107. if len(sstr) < 20:
  108. sstr = zero_byte * (20 - len(sstr)) + sstr
  109. m.add_string(rstr + sstr)
  110. return m
  111. def verify_ssh_sig(self, data, msg):
  112. if len(msg.asbytes()) == 40:
  113. # spies.com bug: signature has no header
  114. sig = msg.asbytes()
  115. else:
  116. kind = msg.get_text()
  117. if kind != 'ssh-dss':
  118. return 0
  119. sig = msg.get_binary()
  120. # pull out (r, s) which are NOT encoded as mpints
  121. sigR = util.inflate_long(sig[:20], 1)
  122. sigS = util.inflate_long(sig[20:], 1)
  123. signature = encode_dss_signature(sigR, sigS)
  124. key = dsa.DSAPublicNumbers(
  125. y=self.y,
  126. parameter_numbers=dsa.DSAParameterNumbers(
  127. p=self.p,
  128. q=self.q,
  129. g=self.g
  130. )
  131. ).public_key(backend=default_backend())
  132. try:
  133. key.verify(signature, data, hashes.SHA1())
  134. except InvalidSignature:
  135. return False
  136. else:
  137. return True
  138. def write_private_key_file(self, filename, password=None):
  139. key = dsa.DSAPrivateNumbers(
  140. x=self.x,
  141. public_numbers=dsa.DSAPublicNumbers(
  142. y=self.y,
  143. parameter_numbers=dsa.DSAParameterNumbers(
  144. p=self.p,
  145. q=self.q,
  146. g=self.g
  147. )
  148. )
  149. ).private_key(backend=default_backend())
  150. self._write_private_key_file(
  151. filename,
  152. key,
  153. serialization.PrivateFormat.TraditionalOpenSSL,
  154. password=password
  155. )
  156. def write_private_key(self, file_obj, password=None):
  157. key = dsa.DSAPrivateNumbers(
  158. x=self.x,
  159. public_numbers=dsa.DSAPublicNumbers(
  160. y=self.y,
  161. parameter_numbers=dsa.DSAParameterNumbers(
  162. p=self.p,
  163. q=self.q,
  164. g=self.g
  165. )
  166. )
  167. ).private_key(backend=default_backend())
  168. self._write_private_key(
  169. file_obj,
  170. key,
  171. serialization.PrivateFormat.TraditionalOpenSSL,
  172. password=password
  173. )
  174. @staticmethod
  175. def generate(bits=1024, progress_func=None):
  176. """
  177. Generate a new private DSS key. This factory function can be used to
  178. generate a new host key or authentication key.
  179. :param int bits: number of bits the generated key should be.
  180. :param progress_func: Unused
  181. :return: new `.DSSKey` private key
  182. """
  183. numbers = dsa.generate_private_key(
  184. bits, backend=default_backend()
  185. ).private_numbers()
  186. key = DSSKey(vals=(
  187. numbers.public_numbers.parameter_numbers.p,
  188. numbers.public_numbers.parameter_numbers.q,
  189. numbers.public_numbers.parameter_numbers.g,
  190. numbers.public_numbers.y
  191. ))
  192. key.x = numbers.x
  193. return key
  194. # ...internals...
  195. def _from_private_key_file(self, filename, password):
  196. data = self._read_private_key_file('DSA', filename, password)
  197. self._decode_key(data)
  198. def _from_private_key(self, file_obj, password):
  199. data = self._read_private_key('DSA', file_obj, password)
  200. self._decode_key(data)
  201. def _decode_key(self, data):
  202. # private key file contains:
  203. # DSAPrivateKey = { version = 0, p, q, g, y, x }
  204. try:
  205. keylist = BER(data).decode()
  206. except BERException as e:
  207. raise SSHException('Unable to parse key file: ' + str(e))
  208. if (
  209. type(keylist) is not list or
  210. len(keylist) < 6 or
  211. keylist[0] != 0
  212. ):
  213. raise SSHException(
  214. 'not a valid DSA private key file (bad ber encoding)')
  215. self.p = keylist[1]
  216. self.q = keylist[2]
  217. self.g = keylist[3]
  218. self.y = keylist[4]
  219. self.x = keylist[5]
  220. self.size = util.bit_length(self.p)

Powered by TurnKey Linux.