123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
- #
- # This file is part of paramiko.
- #
- # Paramiko is free software; you can redistribute it and/or modify it under the
- # terms of the GNU Lesser General Public License as published by the Free
- # Software Foundation; either version 2.1 of the License, or (at your option)
- # any later version.
- #
- # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- # details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- """
- Utility functions for dealing with primes.
- """
- import os
- from paramiko import util
- from paramiko.py3compat import byte_mask, long
- from paramiko.ssh_exception import SSHException
- def _roll_random(n):
- """returns a random # from 0 to N-1"""
- bits = util.bit_length(n - 1)
- byte_count = (bits + 7) // 8
- hbyte_mask = pow(2, bits % 8) - 1
- # so here's the plan:
- # we fetch as many random bits as we'd need to fit N-1, and if the
- # generated number is >= N, we try again. in the worst case (N-1 is a
- # power of 2), we have slightly better than 50% odds of getting one that
- # fits, so i can't guarantee that this loop will ever finish, but the odds
- # of it looping forever should be infinitesimal.
- while True:
- x = os.urandom(byte_count)
- if hbyte_mask > 0:
- x = byte_mask(x[0], hbyte_mask) + x[1:]
- num = util.inflate_long(x, 1)
- if num < n:
- break
- return num
- class ModulusPack(object):
- """
- convenience object for holding the contents of the /etc/ssh/moduli file,
- on systems that have such a file.
- """
- def __init__(self):
- # pack is a hash of: bits -> [ (generator, modulus) ... ]
- self.pack = {}
- self.discarded = []
- def _parse_modulus(self, line):
- (
- timestamp,
- mod_type,
- tests,
- tries,
- size,
- generator,
- modulus,
- ) = line.split()
- mod_type = int(mod_type)
- tests = int(tests)
- tries = int(tries)
- size = int(size)
- generator = int(generator)
- modulus = long(modulus, 16)
- # weed out primes that aren't at least:
- # type 2 (meets basic structural requirements)
- # test 4 (more than just a small-prime sieve)
- # tries < 100 if test & 4 (at least 100 tries of miller-rabin)
- if (
- mod_type < 2
- or tests < 4
- or (tests & 4 and tests < 8 and tries < 100)
- ):
- self.discarded.append(
- (modulus, "does not meet basic requirements")
- )
- return
- if generator == 0:
- generator = 2
- # there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay!
- # call cnn!) where it understates the bit lengths of these primes by 1.
- # this is okay.
- bl = util.bit_length(modulus)
- if (bl != size) and (bl != size + 1):
- self.discarded.append(
- (modulus, "incorrectly reported bit length {}".format(size))
- )
- return
- if bl not in self.pack:
- self.pack[bl] = []
- self.pack[bl].append((generator, modulus))
- def read_file(self, filename):
- """
- :raises IOError: passed from any file operations that fail.
- """
- self.pack = {}
- with open(filename, "r") as f:
- for line in f:
- line = line.strip()
- if (len(line) == 0) or (line[0] == "#"):
- continue
- try:
- self._parse_modulus(line)
- except:
- continue
- def get_modulus(self, min, prefer, max):
- bitsizes = sorted(self.pack.keys())
- if len(bitsizes) == 0:
- raise SSHException("no moduli available")
- good = -1
- # find nearest bitsize >= preferred
- for b in bitsizes:
- if (b >= prefer) and (b <= max) and (b < good or good == -1):
- good = b
- # if that failed, find greatest bitsize >= min
- if good == -1:
- for b in bitsizes:
- if (b >= min) and (b <= max) and (b > good):
- good = b
- if good == -1:
- # their entire (min, max) range has no intersection with our range.
- # if their range is below ours, pick the smallest. otherwise pick
- # the largest. it'll be out of their range requirement either way,
- # but we'll be sending them the closest one we have.
- good = bitsizes[0]
- if min > good:
- good = bitsizes[-1]
- # now pick a random modulus of this bitsize
- n = _roll_random(len(self.pack[good]))
- return self.pack[good][n]
|