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.

139 lines
4.9 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. Utility functions for dealing with primes.
  20. """
  21. import os
  22. from paramiko import util
  23. from paramiko.py3compat import byte_mask, long
  24. from paramiko.ssh_exception import SSHException
  25. def _roll_random(n):
  26. """returns a random # from 0 to N-1"""
  27. bits = util.bit_length(n - 1)
  28. byte_count = (bits + 7) // 8
  29. hbyte_mask = pow(2, bits % 8) - 1
  30. # so here's the plan:
  31. # we fetch as many random bits as we'd need to fit N-1, and if the
  32. # generated number is >= N, we try again. in the worst case (N-1 is a
  33. # power of 2), we have slightly better than 50% odds of getting one that
  34. # fits, so i can't guarantee that this loop will ever finish, but the odds
  35. # of it looping forever should be infinitesimal.
  36. while True:
  37. x = os.urandom(byte_count)
  38. if hbyte_mask > 0:
  39. x = byte_mask(x[0], hbyte_mask) + x[1:]
  40. num = util.inflate_long(x, 1)
  41. if num < n:
  42. break
  43. return num
  44. class ModulusPack (object):
  45. """
  46. convenience object for holding the contents of the /etc/ssh/moduli file,
  47. on systems that have such a file.
  48. """
  49. def __init__(self):
  50. # pack is a hash of: bits -> [ (generator, modulus) ... ]
  51. self.pack = {}
  52. self.discarded = []
  53. def _parse_modulus(self, line):
  54. timestamp, mod_type, tests, tries, size, generator, modulus = \
  55. line.split()
  56. mod_type = int(mod_type)
  57. tests = int(tests)
  58. tries = int(tries)
  59. size = int(size)
  60. generator = int(generator)
  61. modulus = long(modulus, 16)
  62. # weed out primes that aren't at least:
  63. # type 2 (meets basic structural requirements)
  64. # test 4 (more than just a small-prime sieve)
  65. # tries < 100 if test & 4 (at least 100 tries of miller-rabin)
  66. if (
  67. mod_type < 2 or
  68. tests < 4 or
  69. (tests & 4 and tests < 8 and tries < 100)
  70. ):
  71. self.discarded.append(
  72. (modulus, 'does not meet basic requirements'))
  73. return
  74. if generator == 0:
  75. generator = 2
  76. # there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay!
  77. # call cnn!) where it understates the bit lengths of these primes by 1.
  78. # this is okay.
  79. bl = util.bit_length(modulus)
  80. if (bl != size) and (bl != size + 1):
  81. self.discarded.append(
  82. (modulus, 'incorrectly reported bit length {}'.format(size)))
  83. return
  84. if bl not in self.pack:
  85. self.pack[bl] = []
  86. self.pack[bl].append((generator, modulus))
  87. def read_file(self, filename):
  88. """
  89. :raises IOError: passed from any file operations that fail.
  90. """
  91. self.pack = {}
  92. with open(filename, 'r') as f:
  93. for line in f:
  94. line = line.strip()
  95. if (len(line) == 0) or (line[0] == '#'):
  96. continue
  97. try:
  98. self._parse_modulus(line)
  99. except:
  100. continue
  101. def get_modulus(self, min, prefer, max):
  102. bitsizes = sorted(self.pack.keys())
  103. if len(bitsizes) == 0:
  104. raise SSHException('no moduli available')
  105. good = -1
  106. # find nearest bitsize >= preferred
  107. for b in bitsizes:
  108. if (b >= prefer) and (b <= max) and (b < good or good == -1):
  109. good = b
  110. # if that failed, find greatest bitsize >= min
  111. if good == -1:
  112. for b in bitsizes:
  113. if (b >= min) and (b <= max) and (b > good):
  114. good = b
  115. if good == -1:
  116. # their entire (min, max) range has no intersection with our range.
  117. # if their range is below ours, pick the smallest. otherwise pick
  118. # the largest. it'll be out of their range requirement either way,
  119. # but we'll be sending them the closest one we have.
  120. good = bitsizes[0]
  121. if min > good:
  122. good = bitsizes[-1]
  123. # now pick a random modulus of this bitsize
  124. n = _roll_random(len(self.pack[good]))
  125. return self.pack[good][n]

Powered by TurnKey Linux.