dsskey.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. 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,
  27. encode_dss_signature,
  28. )
  29. from paramiko import util
  30. from paramiko.common import zero_byte
  31. from paramiko.ssh_exception import SSHException
  32. from paramiko.message import Message
  33. from paramiko.ber import BER, BERException
  34. from paramiko.pkey import PKey
  35. class DSSKey(PKey):
  36. """
  37. Representation of a DSS key which can be used to sign an verify SSH2
  38. data.
  39. """
  40. def __init__(
  41. self,
  42. msg=None,
  43. data=None,
  44. filename=None,
  45. password=None,
  46. vals=None,
  47. file_obj=None,
  48. ):
  49. self.p = None
  50. self.q = None
  51. self.g = None
  52. self.y = None
  53. self.x = None
  54. self.public_blob = None
  55. if file_obj is not None:
  56. self._from_private_key(file_obj, password)
  57. return
  58. if filename is not None:
  59. self._from_private_key_file(filename, password)
  60. return
  61. if (msg is None) and (data is not None):
  62. msg = Message(data)
  63. if vals is not None:
  64. self.p, self.q, self.g, self.y = vals
  65. else:
  66. self._check_type_and_load_cert(
  67. msg=msg,
  68. key_type="ssh-dss",
  69. cert_type="ssh-dss-cert-v01@openssh.com",
  70. )
  71. self.p = msg.get_mpint()
  72. self.q = msg.get_mpint()
  73. self.g = msg.get_mpint()
  74. self.y = msg.get_mpint()
  75. self.size = util.bit_length(self.p)
  76. def asbytes(self):
  77. m = Message()
  78. m.add_string("ssh-dss")
  79. m.add_mpint(self.p)
  80. m.add_mpint(self.q)
  81. m.add_mpint(self.g)
  82. m.add_mpint(self.y)
  83. return m.asbytes()
  84. def __str__(self):
  85. return self.asbytes()
  86. @property
  87. def _fields(self):
  88. return (self.get_name(), self.p, self.q, self.g, self.y)
  89. def get_name(self):
  90. return "ssh-dss"
  91. def get_bits(self):
  92. return self.size
  93. def can_sign(self):
  94. return self.x is not None
  95. def sign_ssh_data(self, data, algorithm=None):
  96. key = dsa.DSAPrivateNumbers(
  97. x=self.x,
  98. public_numbers=dsa.DSAPublicNumbers(
  99. y=self.y,
  100. parameter_numbers=dsa.DSAParameterNumbers(
  101. p=self.p, q=self.q, g=self.g
  102. ),
  103. ),
  104. ).private_key(backend=default_backend())
  105. sig = key.sign(data, hashes.SHA1())
  106. r, s = decode_dss_signature(sig)
  107. m = Message()
  108. m.add_string("ssh-dss")
  109. # apparently, in rare cases, r or s may be shorter than 20 bytes!
  110. rstr = util.deflate_long(r, 0)
  111. sstr = util.deflate_long(s, 0)
  112. if len(rstr) < 20:
  113. rstr = zero_byte * (20 - len(rstr)) + rstr
  114. if len(sstr) < 20:
  115. sstr = zero_byte * (20 - len(sstr)) + sstr
  116. m.add_string(rstr + sstr)
  117. return m
  118. def verify_ssh_sig(self, data, msg):
  119. if len(msg.asbytes()) == 40:
  120. # spies.com bug: signature has no header
  121. sig = msg.asbytes()
  122. else:
  123. kind = msg.get_text()
  124. if kind != "ssh-dss":
  125. return 0
  126. sig = msg.get_binary()
  127. # pull out (r, s) which are NOT encoded as mpints
  128. sigR = util.inflate_long(sig[:20], 1)
  129. sigS = util.inflate_long(sig[20:], 1)
  130. signature = encode_dss_signature(sigR, sigS)
  131. key = dsa.DSAPublicNumbers(
  132. y=self.y,
  133. parameter_numbers=dsa.DSAParameterNumbers(
  134. p=self.p, q=self.q, g=self.g
  135. ),
  136. ).public_key(backend=default_backend())
  137. try:
  138. key.verify(signature, data, hashes.SHA1())
  139. except InvalidSignature:
  140. return False
  141. else:
  142. return True
  143. def write_private_key_file(self, filename, password=None):
  144. key = dsa.DSAPrivateNumbers(
  145. x=self.x,
  146. public_numbers=dsa.DSAPublicNumbers(
  147. y=self.y,
  148. parameter_numbers=dsa.DSAParameterNumbers(
  149. p=self.p, q=self.q, g=self.g
  150. ),
  151. ),
  152. ).private_key(backend=default_backend())
  153. self._write_private_key_file(
  154. filename,
  155. key,
  156. serialization.PrivateFormat.TraditionalOpenSSL,
  157. password=password,
  158. )
  159. def write_private_key(self, file_obj, password=None):
  160. key = dsa.DSAPrivateNumbers(
  161. x=self.x,
  162. public_numbers=dsa.DSAPublicNumbers(
  163. y=self.y,
  164. parameter_numbers=dsa.DSAParameterNumbers(
  165. p=self.p, q=self.q, 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(
  188. vals=(
  189. numbers.public_numbers.parameter_numbers.p,
  190. numbers.public_numbers.parameter_numbers.q,
  191. numbers.public_numbers.parameter_numbers.g,
  192. numbers.public_numbers.y,
  193. )
  194. )
  195. key.x = numbers.x
  196. return key
  197. # ...internals...
  198. def _from_private_key_file(self, filename, password):
  199. data = self._read_private_key_file("DSA", filename, password)
  200. self._decode_key(data)
  201. def _from_private_key(self, file_obj, password):
  202. data = self._read_private_key("DSA", file_obj, password)
  203. self._decode_key(data)
  204. def _decode_key(self, data):
  205. pkformat, data = data
  206. # private key file contains:
  207. # DSAPrivateKey = { version = 0, p, q, g, y, x }
  208. if pkformat == self._PRIVATE_KEY_FORMAT_ORIGINAL:
  209. try:
  210. keylist = BER(data).decode()
  211. except BERException as e:
  212. raise SSHException("Unable to parse key file: {}".format(e))
  213. elif pkformat == self._PRIVATE_KEY_FORMAT_OPENSSH:
  214. keylist = self._uint32_cstruct_unpack(data, "iiiii")
  215. keylist = [0] + list(keylist)
  216. else:
  217. self._got_bad_key_format_id(pkformat)
  218. if type(keylist) is not list or len(keylist) < 6 or keylist[0] != 0:
  219. raise SSHException(
  220. "not a valid DSA private key file (bad ber encoding)"
  221. )
  222. self.p = keylist[1]
  223. self.q = keylist[2]
  224. self.g = keylist[3]
  225. self.y = keylist[4]
  226. self.x = keylist[5]
  227. self.size = util.bit_length(self.p)