merge dwayne litzenberger's fixes for randpool on windows.
This commit is contained in:
Robey Pointer 2008-05-18 15:45:25 -07:00
parent c52b11ba17
commit d21d384509
5 changed files with 332 additions and 161 deletions

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 osrandom import OSRandomPool from rng import StrongLockingRandomPool
# keep a crypto-strong PRNG nearby # keep a crypto-strong PRNG nearby
randpool = OSRandomPool() randpool = StrongLockingRandomPool()
import sys import sys
if sys.version_info < (2, 3): if sys.version_info < (2, 3):

View File

@ -1,159 +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
##
## Find potential random number sources
##
# Try to open /dev/urandom now so that paramiko will be able to access
# it even if os.chroot() is invoked later.
try:
_dev_urandom = open("/dev/urandom", "rb", 0)
except EnvironmentError:
_dev_urandom = None
# Try to import the "winrandom" module
try:
from Crypto.Util import winrandom
except ImportError:
winrandom = None
# Lastly, try to get the plain "RandomPool"
# (sometimes windows doesn't even have winrandom!)
try:
from Crypto.Util.randpool import RandomPool
except ImportError:
RandomPool = None
##
## Define RandomPool classes
##
def _workaround_windows_cryptgenrandom_bug(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.get_bytes(128*1024) # discard 128 KiB of output
class BaseOSRandomPool(object):
def __init__(self, numbytes=160, cipher=None, hash=None):
pass
def stir(self, s=''):
pass
def randomize(self, N=0):
self.stir()
def add_event(self, s=None):
pass
class WinRandomPool(BaseOSRandomPool):
"""RandomPool that uses the C{winrandom} module for input"""
def __init__(self, numbytes=160, cipher=None, hash=None):
self._wr = winrandom.new()
self.get_bytes = self._wr.get_bytes
self.randomize()
def stir(self, s=''):
_workaround_windows_cryptgenrandom_bug(self)
class DevUrandomPool(BaseOSRandomPool):
"""RandomPool that uses the C{/dev/urandom} special device node for input"""
def __init__(self, numbytes=160, cipher=None, hash=None):
self.randomize()
def get_bytes(self, n):
bytes = ""
while len(bytes) < n:
bytes += _dev_urandom.read(n - len(bytes))
return bytes
class FallbackRandomPool (BaseOSRandomPool):
def __init__(self):
self._wr = RandomPool()
self.randomize()
def get_bytes(self, n):
return self._wr.get_bytes(n)
##
## Detect default random number source
##
osrandom_source = None
# Try /dev/urandom
if osrandom_source is None and _dev_urandom is not None:
osrandom_source = "/dev/urandom"
DefaultRandomPoolClass = DevUrandomPool
# Try winrandom
if osrandom_source is None and winrandom is not None:
osrandom_source = "winrandom"
DefaultRandomPoolClass = WinRandomPool
# Try final fallback
if osrandom_source is None and RandomPool is not None:
osrandom_source = "randompool"
DefaultRandomPoolClass = FallbackRandomPool
# Give up
if osrandom_source is None:
raise ImportError("Cannot find OS entropy source")
##
## Define wrapper class
##
class OSRandomPool(object):
"""RandomPool wrapper.
The C{randpool} attribute of this object may be modified by users of this class at runtime.
"""
def __init__(self, instance=None):
if instance is None:
instance = DefaultRandomPoolClass()
self.randpool = instance
def stir(self, s=''):
self.randpool.stir(s)
def randomize(self, N=0):
self.randpool.randomize(N)
def add_event(self, s=None):
self.randpool.add_event(s)
def get_bytes(self, N):
return self.randpool.get_bytes(N)
# vim:set ts=4 sw=4 sts=4 expandtab:

112
paramiko/rng.py Normal file
View File

@ -0,0 +1,112 @@
#!/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:

97
paramiko/rng_posix.py Normal file
View File

@ -0,0 +1,97 @@
#!/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:

121
paramiko/rng_win32.py Normal file
View File

@ -0,0 +1,121 @@
#!/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: