dsskey.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. if file_obj is not None:
  47. self._from_private_key(file_obj, password)
  48. return
  49. if filename is not None:
  50. self._from_private_key_file(filename, password)
  51. return
  52. if (msg is None) and (data is not None):
  53. msg = Message(data)
  54. if vals is not None:
  55. self.p, self.q, self.g, self.y = vals
  56. else:
  57. if msg is None:
  58. raise SSHException('Key object may not be empty')
  59. if msg.get_text() != 'ssh-dss':
  60. raise SSHException('Invalid key')
  61. self.p = msg.get_mpint()
  62. self.q = msg.get_mpint()
  63. self.g = msg.get_mpint()
  64. self.y = msg.get_mpint()
  65. self.size = util.bit_length(self.p)
  66. def asbytes(self):
  67. m = Message()
  68. m.add_string('ssh-dss')
  69. m.add_mpint(self.p)
  70. m.add_mpint(self.q)
  71. m.add_mpint(self.g)
  72. m.add_mpint(self.y)
  73. return m.asbytes()
  74. def __str__(self):
  75. return self.asbytes()
  76. def __hash__(self):
  77. return hash((self.get_name(), self.p, self.q, self.g, self.y))
  78. def get_name(self):
  79. return 'ssh-dss'
  80. def get_bits(self):
  81. return self.size
  82. def can_sign(self):
  83. return self.x is not None
  84. def sign_ssh_data(self, data):
  85. key = dsa.DSAPrivateNumbers(
  86. x=self.x,
  87. public_numbers=dsa.DSAPublicNumbers(
  88. y=self.y,
  89. parameter_numbers=dsa.DSAParameterNumbers(
  90. p=self.p,
  91. q=self.q,
  92. g=self.g
  93. )
  94. )
  95. ).private_key(backend=default_backend())
  96. signer = key.signer(hashes.SHA1())
  97. signer.update(data)
  98. r, s = decode_dss_signature(signer.finalize())
  99. m = Message()
  100. m.add_string('ssh-dss')
  101. # apparently, in rare cases, r or s may be shorter than 20 bytes!
  102. rstr = util.deflate_long(r, 0)
  103. sstr = util.deflate_long(s, 0)
  104. if len(rstr) < 20:
  105. rstr = zero_byte * (20 - len(rstr)) + rstr
  106. if len(sstr) < 20:
  107. sstr = zero_byte * (20 - len(sstr)) + sstr
  108. m.add_string(rstr + sstr)
  109. return m
  110. def verify_ssh_sig(self, data, msg):
  111. if len(msg.asbytes()) == 40:
  112. # spies.com bug: signature has no header
  113. sig = msg.asbytes()
  114. else:
  115. kind = msg.get_text()
  116. if kind != 'ssh-dss':
  117. return 0
  118. sig = msg.get_binary()
  119. # pull out (r, s) which are NOT encoded as mpints
  120. sigR = util.inflate_long(sig[:20], 1)
  121. sigS = util.inflate_long(sig[20:], 1)
  122. signature = encode_dss_signature(sigR, sigS)
  123. key = dsa.DSAPublicNumbers(
  124. y=self.y,
  125. parameter_numbers=dsa.DSAParameterNumbers(
  126. p=self.p,
  127. q=self.q,
  128. g=self.g
  129. )
  130. ).public_key(backend=default_backend())
  131. verifier = key.verifier(signature, hashes.SHA1())
  132. verifier.update(data)
  133. try:
  134. verifier.verify()
  135. except InvalidSignature:
  136. return False
  137. else:
  138. return True
  139. def write_private_key_file(self, filename, password=None):
  140. key = dsa.DSAPrivateNumbers(
  141. x=self.x,
  142. public_numbers=dsa.DSAPublicNumbers(
  143. y=self.y,
  144. parameter_numbers=dsa.DSAParameterNumbers(
  145. p=self.p,
  146. q=self.q,
  147. g=self.g
  148. )
  149. )
  150. ).private_key(backend=default_backend())
  151. self._write_private_key_file(
  152. filename,
  153. key,
  154. serialization.PrivateFormat.TraditionalOpenSSL,
  155. password=password
  156. )
  157. def write_private_key(self, file_obj, password=None):
  158. key = dsa.DSAPrivateNumbers(
  159. x=self.x,
  160. public_numbers=dsa.DSAPublicNumbers(
  161. y=self.y,
  162. parameter_numbers=dsa.DSAParameterNumbers(
  163. p=self.p,
  164. q=self.q,
  165. g=self.g
  166. )
  167. )
  168. ).private_key(backend=default_backend())
  169. self._write_private_key(
  170. file_obj,
  171. key,
  172. serialization.PrivateFormat.TraditionalOpenSSL,
  173. password=password
  174. )
  175. @staticmethod
  176. def generate(bits=1024, progress_func=None):
  177. """
  178. Generate a new private DSS key. This factory function can be used to
  179. 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 `.DSSKey` private key
  183. """
  184. numbers = dsa.generate_private_key(
  185. bits, backend=default_backend()
  186. ).private_numbers()
  187. key = DSSKey(vals=(
  188. numbers.public_numbers.parameter_numbers.p,
  189. numbers.public_numbers.parameter_numbers.q,
  190. numbers.public_numbers.parameter_numbers.g,
  191. numbers.public_numbers.y
  192. ))
  193. key.x = numbers.x
  194. return key
  195. # ...internals...
  196. def _from_private_key_file(self, filename, password):
  197. data = self._read_private_key_file('DSA', filename, password)
  198. self._decode_key(data)
  199. def _from_private_key(self, file_obj, password):
  200. data = self._read_private_key('DSA', file_obj, password)
  201. self._decode_key(data)
  202. def _decode_key(self, data):
  203. # private key file contains:
  204. # DSAPrivateKey = { version = 0, p, q, g, y, x }
  205. try:
  206. keylist = BER(data).decode()
  207. except BERException as e:
  208. raise SSHException('Unable to parse key file: ' + str(e))
  209. if (
  210. type(keylist) is not list or
  211. len(keylist) < 6 or
  212. keylist[0] != 0
  213. ):
  214. raise SSHException(
  215. 'not a valid DSA private key file (bad ber encoding)')
  216. self.p = keylist[1]
  217. self.q = keylist[2]
  218. self.g = keylist[3]
  219. self.y = keylist[4]
  220. self.x = keylist[5]
  221. self.size = util.bit_length(self.p)