Merge pull request #8 from garyvdm/master

Untitled
This commit is contained in:
Robey Pointer 2011-05-21 19:11:34 -07:00
commit e891a5e473
21 changed files with 50 additions and 385 deletions

5
NEWS
View File

@ -8,6 +8,11 @@ Highlights of what's new in each release:
Releases Releases
======== ========
Unreleased
----------
* Use Crypto.Random rather than Crypto.Util.RandomPool.
(Gary van der Merwe, #271791)
v1.7.6 (Fanny) 1nov09 v1.7.6 (Fanny) 1nov09
--------------------- ---------------------
* fixed bugs 411099 (sftp chdir isn't unicode-safe), 363163 & 411910 (more * fixed bugs 411099 (sftp chdir isn't unicode-safe), 363163 & 411910 (more

View File

@ -66,7 +66,7 @@ __version_info__ = (1, 7, 6)
__license__ = "GNU Lesser General Public License (LGPL)" __license__ = "GNU Lesser General Public License (LGPL)"
from transport import randpool, SecurityOptions, Transport from transport import SecurityOptions, Transport
from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy
from auth_handler import AuthHandler from auth_handler import AuthHandler
from channel import Channel, ChannelFile from channel import Channel, ChannelFile

View File

@ -139,7 +139,7 @@ class AgentKey(PKey):
def get_name(self): def get_name(self):
return self.name return self.name
def sign_ssh_data(self, randpool, data): def sign_ssh_data(self, rng, data):
msg = Message() msg = Message()
msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST)) msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
msg.add_string(self.blob) msg.add_string(self.blob)

View File

@ -206,7 +206,7 @@ class AuthHandler (object):
m.add_string(self.private_key.get_name()) m.add_string(self.private_key.get_name())
m.add_string(str(self.private_key)) m.add_string(str(self.private_key))
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
sig = self.private_key.sign_ssh_data(self.transport.randpool, blob) sig = self.private_key.sign_ssh_data(self.transport.rng, blob)
m.add_string(str(sig)) m.add_string(str(sig))
elif self.auth_method == 'keyboard-interactive': elif self.auth_method == 'keyboard-interactive':
m.add_string('') m.add_string('')

View File

@ -364,7 +364,7 @@ class Channel (object):
if auth_protocol is None: if auth_protocol is None:
auth_protocol = 'MIT-MAGIC-COOKIE-1' auth_protocol = 'MIT-MAGIC-COOKIE-1'
if auth_cookie is None: if auth_cookie is None:
auth_cookie = binascii.hexlify(self.transport.randpool.get_bytes(16)) auth_cookie = binascii.hexlify(self.transport.rng.read(16))
m = Message() m = Message()
m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_byte(chr(MSG_CHANNEL_REQUEST))

View File

@ -95,10 +95,10 @@ CONNECTION_FAILED_CODE = {
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \ DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14 DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
from rng import StrongLockingRandomPool from Crypto import Random
# keep a crypto-strong PRNG nearby # keep a crypto-strong PRNG nearby
randpool = StrongLockingRandomPool() rng = Random.new()
import sys import sys
if sys.version_info < (2, 3): if sys.version_info < (2, 3):

View File

@ -91,13 +91,13 @@ class DSSKey (PKey):
def can_sign(self): def can_sign(self):
return self.x is not None return self.x is not None
def sign_ssh_data(self, rpool, data): def sign_ssh_data(self, rng, data):
digest = SHA.new(data).digest() digest = SHA.new(data).digest()
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x))) dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
# generate a suitable k # generate a suitable k
qsize = len(util.deflate_long(self.q, 0)) qsize = len(util.deflate_long(self.q, 0))
while True: while True:
k = util.inflate_long(rpool.get_bytes(qsize), 1) k = util.inflate_long(rng.read(qsize), 1)
if (k > 2) and (k < self.q): if (k > 2) and (k < self.q):
break break
r, s = dss.sign(util.inflate_long(digest, 1), k) r, s = dss.sign(util.inflate_long(digest, 1), k)
@ -161,8 +161,7 @@ class DSSKey (PKey):
@return: new private key @return: new private key
@rtype: L{DSSKey} @rtype: L{DSSKey}
""" """
randpool.stir() dsa = DSA.generate(bits, rng.read, progress_func)
dsa = DSA.generate(bits, randpool.get_bytes, progress_func)
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y)) key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
key.x = dsa.x key.x = dsa.x
return key return key

View File

@ -303,7 +303,7 @@ class HostKeys (UserDict.DictMixin):
@rtype: str @rtype: str
""" """
if salt is None: if salt is None:
salt = randpool.get_bytes(SHA.digest_size) salt = rng.read(SHA.digest_size)
else: else:
if salt.startswith('|1|'): if salt.startswith('|1|'):
salt = salt.split('|')[2] salt = salt.split('|')[2]

View File

@ -101,8 +101,7 @@ class KexGex (object):
qhbyte <<= 1 qhbyte <<= 1
qmask >>= 1 qmask >>= 1
while True: while True:
self.transport.randpool.stir() x_bytes = self.transport.rng.read(bytes)
x_bytes = self.transport.randpool.get_bytes(bytes)
x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:] x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
x = util.inflate_long(x_bytes, 1) x = util.inflate_long(x_bytes, 1)
if (x > 1) and (x < q): if (x > 1) and (x < q):
@ -207,7 +206,7 @@ class KexGex (object):
H = SHA.new(str(hm)).digest() H = SHA.new(str(hm)).digest()
self.transport._set_K_H(K, H) self.transport._set_K_H(K, H)
# sign it # sign it
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H) sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
# send reply # send reply
m = Message() m = Message()
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY)) m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))

View File

@ -79,8 +79,7 @@ class KexGroup1(object):
# potential x where the first 63 bits are 1, because some of those will be # potential x where the first 63 bits are 1, because some of those will be
# larger than q (but this is a tiny tiny subset of potential x). # larger than q (but this is a tiny tiny subset of potential x).
while 1: while 1:
self.transport.randpool.stir() x_bytes = self.transport.rng.read(128)
x_bytes = self.transport.randpool.get_bytes(128)
x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:] x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:]
if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \ if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'): (x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
@ -125,7 +124,7 @@ class KexGroup1(object):
H = SHA.new(str(hm)).digest() H = SHA.new(str(hm)).digest()
self.transport._set_K_H(K, H) self.transport._set_K_H(K, H)
# sign it # sign it
sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H) sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
# send reply # send reply
m = Message() m = Message()
m.add_byte(chr(_MSG_KEXDH_REPLY)) m.add_byte(chr(_MSG_KEXDH_REPLY))

View File

@ -311,9 +311,6 @@ class Packetizer (object):
self.__sent_bytes += len(out) self.__sent_bytes += len(out)
self.__sent_packets += 1 self.__sent_packets += 1
if (self.__sent_packets % 100) == 0:
# stirring the randpool takes 30ms on my ibook!!
randpool.stir()
if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \ if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
and not self.__need_rekey: and not self.__need_rekey:
# only ask once for rekeying # only ask once for rekeying
@ -359,7 +356,7 @@ class Packetizer (object):
raise SSHException('Mismatched MAC') raise SSHException('Mismatched MAC')
padding = ord(packet[0]) padding = ord(packet[0])
payload = packet[1:packet_size - padding] payload = packet[1:packet_size - padding]
randpool.add_event()
if self.__dump_packets: if self.__dump_packets:
self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
@ -476,7 +473,7 @@ class Packetizer (object):
packet = struct.pack('>IB', len(payload) + padding + 1, padding) packet = struct.pack('>IB', len(payload) + padding + 1, padding)
packet += payload packet += payload
if self.__block_engine_out is not None: if self.__block_engine_out is not None:
packet += randpool.get_bytes(padding) packet += rng.read(padding)
else: else:
# cute trick i caught openssh doing: if we're not encrypting, # cute trick i caught openssh doing: if we're not encrypting,
# don't waste random bytes for the padding # don't waste random bytes for the padding

View File

@ -143,13 +143,13 @@ class PKey (object):
""" """
return base64.encodestring(str(self)).replace('\n', '') return base64.encodestring(str(self)).replace('\n', '')
def sign_ssh_data(self, randpool, data): def sign_ssh_data(self, rng, data):
""" """
Sign a blob of data with this private key, and return a L{Message} Sign a blob of data with this private key, and return a L{Message}
representing an SSH signature message. representing an SSH signature message.
@param randpool: a secure random number generator. @param rng: a secure random number generator.
@type randpool: L{Crypto.Util.randpool.RandomPool} @type rng: L{Crypto.Util.rng.RandomPool}
@param data: the data to sign. @param data: the data to sign.
@type data: str @type data: str
@return: an SSH signature message. @return: an SSH signature message.
@ -360,11 +360,11 @@ class PKey (object):
keysize = self._CIPHER_TABLE[cipher_name]['keysize'] keysize = self._CIPHER_TABLE[cipher_name]['keysize']
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize'] blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
mode = self._CIPHER_TABLE[cipher_name]['mode'] mode = self._CIPHER_TABLE[cipher_name]['mode']
salt = randpool.get_bytes(8) salt = rng.read(8)
key = util.generate_key_bytes(MD5, salt, password, keysize) key = util.generate_key_bytes(MD5, salt, password, keysize)
if len(data) % blocksize != 0: if len(data) % blocksize != 0:
n = blocksize - len(data) % blocksize n = blocksize - len(data) % blocksize
#data += randpool.get_bytes(n) #data += rng.read(n)
# that would make more sense ^, but it confuses openssh. # that would make more sense ^, but it confuses openssh.
data += '\0' * n data += '\0' * n
data = cipher.new(key, mode, salt).encrypt(data) data = cipher.new(key, mode, salt).encrypt(data)

View File

@ -26,12 +26,12 @@ from paramiko import util
from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import SSHException
def _generate_prime(bits, randpool): def _generate_prime(bits, rng):
"primtive attempt at prime generation" "primtive attempt at prime generation"
hbyte_mask = pow(2, bits % 8) - 1 hbyte_mask = pow(2, bits % 8) - 1
while True: while True:
# loop catches the case where we increment n into a higher bit-range # loop catches the case where we increment n into a higher bit-range
x = randpool.get_bytes((bits+7) // 8) x = rng.read((bits+7) // 8)
if hbyte_mask > 0: if hbyte_mask > 0:
x = chr(ord(x[0]) & hbyte_mask) + x[1:] x = chr(ord(x[0]) & hbyte_mask) + x[1:]
n = util.inflate_long(x, 1) n = util.inflate_long(x, 1)
@ -43,7 +43,7 @@ def _generate_prime(bits, randpool):
break break
return n return n
def _roll_random(rpool, n): def _roll_random(rng, n):
"returns a random # from 0 to N-1" "returns a random # from 0 to N-1"
bits = util.bit_length(n-1) bits = util.bit_length(n-1)
bytes = (bits + 7) // 8 bytes = (bits + 7) // 8
@ -56,7 +56,7 @@ def _roll_random(rpool, n):
# fits, so i can't guarantee that this loop will ever finish, but the odds # fits, so i can't guarantee that this loop will ever finish, but the odds
# of it looping forever should be infinitesimal. # of it looping forever should be infinitesimal.
while True: while True:
x = rpool.get_bytes(bytes) x = rng.read(bytes)
if hbyte_mask > 0: if hbyte_mask > 0:
x = chr(ord(x[0]) & hbyte_mask) + x[1:] x = chr(ord(x[0]) & hbyte_mask) + x[1:]
num = util.inflate_long(x, 1) num = util.inflate_long(x, 1)
@ -75,7 +75,7 @@ class ModulusPack (object):
# pack is a hash of: bits -> [ (generator, modulus) ... ] # pack is a hash of: bits -> [ (generator, modulus) ... ]
self.pack = {} self.pack = {}
self.discarded = [] self.discarded = []
self.randpool = rpool self.rng = rpool
def _parse_modulus(self, line): def _parse_modulus(self, line):
timestamp, mod_type, tests, tries, size, generator, modulus = line.split() timestamp, mod_type, tests, tries, size, generator, modulus = line.split()
@ -147,5 +147,5 @@ class ModulusPack (object):
if min > good: if min > good:
good = bitsizes[-1] good = bitsizes[-1]
# now pick a random modulus of this bitsize # now pick a random modulus of this bitsize
n = _roll_random(self.randpool, len(self.pack[good])) n = _roll_random(self.rng, len(self.pack[good]))
return self.pack[good][n] return self.pack[good][n]

View File

@ -1,112 +0,0 @@
#!/usr/bin/python
# -*- coding: ascii -*-
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
#
# 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 distrubuted 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.
import sys
import threading
from Crypto.Util.randpool import RandomPool as _RandomPool
try:
import platform
except ImportError:
platform = None # Not available using Python 2.2
def _strxor(a, b):
assert len(a) == len(b)
return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), a, b))
##
## Find a strong random entropy source, depending on the detected platform.
## WARNING TO DEVELOPERS: This will fail on some systems, but do NOT use
## Crypto.Util.randpool.RandomPool as a fall-back. RandomPool will happily run
## with very little entropy, thus _silently_ defeating any security that
## Paramiko attempts to provide. (This is current as of PyCrypto 2.0.1).
## See http://www.lag.net/pipermail/paramiko/2008-January/000599.html
## and http://www.lag.net/pipermail/paramiko/2008-April/000678.html
##
if ((platform is not None and platform.system().lower() == 'windows') or
sys.platform == 'win32'):
# MS Windows
from paramiko import rng_win32
rng_device = rng_win32.open_rng_device()
else:
# Assume POSIX (any system where /dev/urandom exists)
from paramiko import rng_posix
rng_device = rng_posix.open_rng_device()
class StrongLockingRandomPool(object):
"""Wrapper around RandomPool guaranteeing strong random numbers.
Crypto.Util.randpool.RandomPool will silently operate even if it is seeded
with little or no entropy, and it provides no prediction resistance if its
state is ever compromised throughout its runtime. It is also not thread-safe.
This wrapper augments RandomPool by XORing its output with random bits from
the operating system, and by controlling access to the underlying
RandomPool using an exclusive lock.
"""
def __init__(self, instance=None):
if instance is None:
instance = _RandomPool()
self.randpool = instance
self.randpool_lock = threading.Lock()
self.entropy = rng_device
# Stir 256 bits of entropy from the RNG device into the RandomPool.
self.randpool.stir(self.entropy.read(32))
self.entropy.randomize()
def stir(self, s=''):
self.randpool_lock.acquire()
try:
self.randpool.stir(s)
finally:
self.randpool_lock.release()
self.entropy.randomize()
def randomize(self, N=0):
self.randpool_lock.acquire()
try:
self.randpool.randomize(N)
finally:
self.randpool_lock.release()
self.entropy.randomize()
def add_event(self, s=''):
self.randpool_lock.acquire()
try:
self.randpool.add_event(s)
finally:
self.randpool_lock.release()
def get_bytes(self, N):
self.randpool_lock.acquire()
try:
randpool_data = self.randpool.get_bytes(N)
finally:
self.randpool_lock.release()
entropy_data = self.entropy.read(N)
result = _strxor(randpool_data, entropy_data)
assert len(randpool_data) == N and len(entropy_data) == N and len(result) == N
return result
# vim:set ts=4 sw=4 sts=4 expandtab:

View File

@ -1,97 +0,0 @@
#!/usr/bin/python
# -*- coding: ascii -*-
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
# Copyright (C) 2008 Open Systems Canada Limited
#
# 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 distrubuted 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.
import os
import stat
class error(Exception):
pass
class _RNG(object):
def __init__(self, file):
self.file = file
def read(self, bytes):
return self.file.read(bytes)
def close(self):
return self.file.close()
def randomize(self):
return
def open_rng_device(device_path=None):
"""Open /dev/urandom and perform some sanity checks."""
f = None
g = None
if device_path is None:
device_path = "/dev/urandom"
try:
# Try to open /dev/urandom now so that paramiko will be able to access
# it even if os.chroot() is invoked later.
try:
f = open(device_path, "rb", 0)
except EnvironmentError:
raise error("Unable to open /dev/urandom")
# Open a second file descriptor for sanity checking later.
try:
g = open(device_path, "rb", 0)
except EnvironmentError:
raise error("Unable to open /dev/urandom")
# Check that /dev/urandom is a character special device, not a regular file.
st = os.fstat(f.fileno()) # f
if stat.S_ISREG(st.st_mode) or not stat.S_ISCHR(st.st_mode):
raise error("/dev/urandom is not a character special device")
st = os.fstat(g.fileno()) # g
if stat.S_ISREG(st.st_mode) or not stat.S_ISCHR(st.st_mode):
raise error("/dev/urandom is not a character special device")
# Check that /dev/urandom always returns the number of bytes requested
x = f.read(20)
y = g.read(20)
if len(x) != 20 or len(y) != 20:
raise error("Error reading from /dev/urandom: input truncated")
# Check that different reads return different data
if x == y:
raise error("/dev/urandom is broken; returning identical data: %r == %r" % (x, y))
# Close the duplicate file object
g.close()
# Return the first file object
return _RNG(f)
except error:
if f is not None:
f.close()
if g is not None:
g.close()
raise
# vim:set ts=4 sw=4 sts=4 expandtab:

View File

@ -1,121 +0,0 @@
#!/usr/bin/python
# -*- coding: ascii -*-
# Copyright (C) 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
# Copyright (C) 2008 Open Systems Canada Limited
#
# 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 distrubuted 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.
class error(Exception):
pass
# Try to import the "winrandom" module
try:
from Crypto.Util import winrandom as _winrandom
except ImportError:
_winrandom = None
# Try to import the "urandom" module
try:
from os import urandom as _urandom
except ImportError:
_urandom = None
class _RNG(object):
def __init__(self, readfunc):
self.read = readfunc
def randomize(self):
# According to "Cryptanalysis of the Random Number Generator of the
# Windows Operating System", by Leo Dorrendorf and Zvi Gutterman
# and Benny Pinkas <http://eprint.iacr.org/2007/419>,
# CryptGenRandom only updates its internal state using kernel-provided
# random data every 128KiB of output.
self.read(128*1024) # discard 128 KiB of output
def _open_winrandom():
if _winrandom is None:
raise error("Crypto.Util.winrandom module not found")
# Check that we can open the winrandom module
try:
r0 = _winrandom.new()
r1 = _winrandom.new()
except Exception, exc:
raise error("winrandom.new() failed: %s" % str(exc), exc)
# Check that we can read from the winrandom module
try:
x = r0.get_bytes(20)
y = r1.get_bytes(20)
except Exception, exc:
raise error("winrandom get_bytes failed: %s" % str(exc), exc)
# Check that the requested number of bytes are returned
if len(x) != 20 or len(y) != 20:
raise error("Error reading from winrandom: input truncated")
# Check that different reads return different data
if x == y:
raise error("winrandom broken: returning identical data")
return _RNG(r0.get_bytes)
def _open_urandom():
if _urandom is None:
raise error("os.urandom function not found")
# Check that we can read from os.urandom()
try:
x = _urandom(20)
y = _urandom(20)
except Exception, exc:
raise error("os.urandom failed: %s" % str(exc), exc)
# Check that the requested number of bytes are returned
if len(x) != 20 or len(y) != 20:
raise error("os.urandom failed: input truncated")
# Check that different reads return different data
if x == y:
raise error("os.urandom failed: returning identical data")
return _RNG(_urandom)
def open_rng_device():
# Try using the Crypto.Util.winrandom module
try:
return _open_winrandom()
except error:
pass
# Several versions of PyCrypto do not contain the winrandom module, but
# Python >= 2.4 has os.urandom, so try to use that.
try:
return _open_urandom()
except error:
pass
# SECURITY NOTE: DO NOT USE Crypto.Util.randpool.RandomPool HERE!
# If we got to this point, RandomPool will silently run with very little
# entropy. (This is current as of PyCrypto 2.0.1).
# See http://www.lag.net/pipermail/paramiko/2008-January/000599.html
# and http://www.lag.net/pipermail/paramiko/2008-April/000678.html
raise error("Unable to find a strong random entropy source. You cannot run this software securely under the current configuration.")
# vim:set ts=4 sw=4 sts=4 expandtab:

View File

@ -137,8 +137,7 @@ class RSAKey (PKey):
@return: new private key @return: new private key
@rtype: L{RSAKey} @rtype: L{RSAKey}
""" """
randpool.stir() rsa = RSA.generate(bits, rng.read, progress_func)
rsa = RSA.generate(bits, randpool.get_bytes, progress_func)
key = RSAKey(vals=(rsa.e, rsa.n)) key = RSAKey(vals=(rsa.e, rsa.n))
key.d = rsa.d key.d = rsa.d
key.p = rsa.p key.p = rsa.p

View File

@ -303,7 +303,7 @@ class Transport (threading.Thread):
# okay, normal socket-ish flow here... # okay, normal socket-ish flow here...
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.randpool = randpool self.rng = rng
self.sock = sock self.sock = sock
# Python < 2.3 doesn't have the settimeout method - RogerB # Python < 2.3 doesn't have the settimeout method - RogerB
try: try:
@ -591,7 +591,7 @@ class Transport (threading.Thread):
@note: This has no effect when used in client mode. @note: This has no effect when used in client mode.
""" """
Transport._modulus_pack = ModulusPack(randpool) Transport._modulus_pack = ModulusPack(rng)
# places to look for the openssh "moduli" file # places to look for the openssh "moduli" file
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ] file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
if filename is not None: if filename is not None:
@ -843,10 +843,9 @@ class Transport (threading.Thread):
""" """
m = Message() m = Message()
m.add_byte(chr(MSG_IGNORE)) m.add_byte(chr(MSG_IGNORE))
randpool.stir()
if bytes is None: if bytes is None:
bytes = (ord(randpool.get_bytes(1)) % 32) + 10 bytes = (ord(rng.read(1)) % 32) + 10
m.add_bytes(randpool.get_bytes(bytes)) m.add_bytes(rng.read(bytes))
self._send_user_message(m) self._send_user_message(m)
def renegotiate_keys(self): def renegotiate_keys(self):
@ -1680,10 +1679,9 @@ class Transport (threading.Thread):
else: else:
available_server_keys = self._preferred_keys available_server_keys = self._preferred_keys
randpool.stir()
m = Message() m = Message()
m.add_byte(chr(MSG_KEXINIT)) m.add_byte(chr(MSG_KEXINIT))
m.add_bytes(randpool.get_bytes(16)) m.add_bytes(rng.read(16))
m.add_list(self._preferred_kex) m.add_list(self._preferred_kex)
m.add_list(available_server_keys) m.add_list(available_server_keys)
m.add_list(self._preferred_ciphers) m.add_list(self._preferred_ciphers)

View File

@ -28,17 +28,15 @@ from paramiko.kex_gex import KexGex
from paramiko import Message from paramiko import Message
class FakeRandpool (object): class FakeRng (object):
def stir(self): def read(self, n):
pass
def get_bytes(self, n):
return chr(0xcc) * n return chr(0xcc) * n
class FakeKey (object): class FakeKey (object):
def __str__(self): def __str__(self):
return 'fake-key' return 'fake-key'
def sign_ssh_data(self, randpool, H): def sign_ssh_data(self, rng, H):
return 'fake-sig' return 'fake-sig'
@ -50,7 +48,7 @@ class FakeModulusPack (object):
class FakeTransport (object): class FakeTransport (object):
randpool = FakeRandpool() rng = FakeRng()
local_version = 'SSH-2.0-paramiko_1.0' local_version = 'SSH-2.0-paramiko_1.0'
remote_version = 'SSH-2.0-lame' remote_version = 'SSH-2.0-lame'
local_kex_init = 'local-kex-init' local_kex_init = 'local-kex-init'

View File

@ -23,7 +23,8 @@ Some unit tests for public/private key objects.
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import StringIO import StringIO
import unittest import unittest
from paramiko import RSAKey, DSSKey, Message, util, randpool from paramiko import RSAKey, DSSKey, Message, util
from paramiko.common import rng
# from openssh's ssh-keygen # from openssh's ssh-keygen
PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c=' PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c='
@ -151,7 +152,7 @@ class KeyTest (unittest.TestCase):
def test_8_sign_rsa(self): def test_8_sign_rsa(self):
# verify that the rsa private key can sign and verify # verify that the rsa private key can sign and verify
key = RSAKey.from_private_key_file('tests/test_rsa.key') key = RSAKey.from_private_key_file('tests/test_rsa.key')
msg = key.sign_ssh_data(randpool, 'ice weasels') msg = key.sign_ssh_data(rng, 'ice weasels')
self.assert_(type(msg) is Message) self.assert_(type(msg) is Message)
msg.rewind() msg.rewind()
self.assertEquals('ssh-rsa', msg.get_string()) self.assertEquals('ssh-rsa', msg.get_string())
@ -164,7 +165,7 @@ class KeyTest (unittest.TestCase):
def test_9_sign_dss(self): def test_9_sign_dss(self):
# verify that the dss private key can sign and verify # verify that the dss private key can sign and verify
key = DSSKey.from_private_key_file('tests/test_dss.key') key = DSSKey.from_private_key_file('tests/test_dss.key')
msg = key.sign_ssh_data(randpool, 'ice weasels') msg = key.sign_ssh_data(rng, 'ice weasels')
self.assert_(type(msg) is Message) self.assert_(type(msg) is Message)
msg.rewind() msg.rewind()
self.assertEquals('ssh-dss', msg.get_string()) self.assertEquals('ssh-dss', msg.get_string())
@ -178,12 +179,12 @@ class KeyTest (unittest.TestCase):
def test_A_generate_rsa(self): def test_A_generate_rsa(self):
key = RSAKey.generate(1024) key = RSAKey.generate(1024)
msg = key.sign_ssh_data(randpool, 'jerri blank') msg = key.sign_ssh_data(rng, 'jerri blank')
msg.rewind() msg.rewind()
self.assert_(key.verify_ssh_sig('jerri blank', msg)) self.assert_(key.verify_ssh_sig('jerri blank', msg))
def test_B_generate_dss(self): def test_B_generate_dss(self):
key = DSSKey.generate(1024) key = DSSKey.generate(1024)
msg = key.sign_ssh_data(randpool, 'jerri blank') msg = key.sign_ssh_data(rng, 'jerri blank')
msg.rewind() msg.rewind()
self.assert_(key.verify_ssh_sig('jerri blank', msg)) self.assert_(key.verify_ssh_sig('jerri blank', msg))

View File

@ -147,8 +147,8 @@ class UtilTest (unittest.TestCase):
os.unlink('hostfile.temp') os.unlink('hostfile.temp')
def test_6_random(self): def test_6_random(self):
from paramiko.common import randpool from paramiko.common import rng
# just verify that we can pull out 32 bytes and not get an exception. # just verify that we can pull out 32 bytes and not get an exception.
x = randpool.get_bytes(32) x = rng.read(32)
self.assertEquals(len(x), 32) self.assertEquals(len(x), 32)