summaryrefslogtreecommitdiff
path: root/paramiko
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko')
-rw-r--r--paramiko/__init__.py14
-rw-r--r--paramiko/agent.py6
-rw-r--r--paramiko/auth_handler.py2
-rw-r--r--paramiko/channel.py2
-rw-r--r--paramiko/client.py20
-rw-r--r--paramiko/common.py4
-rw-r--r--paramiko/dsskey.py7
-rw-r--r--paramiko/hostkeys.py2
-rw-r--r--paramiko/kex_gex.py5
-rw-r--r--paramiko/kex_group1.py5
-rw-r--r--paramiko/packet.py53
-rw-r--r--paramiko/pkey.py31
-rw-r--r--paramiko/primes.py12
-rw-r--r--paramiko/rng.py112
-rw-r--r--paramiko/rng_posix.py97
-rw-r--r--paramiko/rng_win32.py121
-rw-r--r--paramiko/rsakey.py3
-rw-r--r--paramiko/sftp_attr.py10
-rw-r--r--paramiko/sftp_client.py15
-rw-r--r--paramiko/transport.py39
20 files changed, 122 insertions, 438 deletions
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index ac0d559..96b5943 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2003-2009 Robey Pointer <robeypointer@gmail.com>
+# Copyright (C) 2003-2011 Robey Pointer <robeypointer@gmail.com>
#
# This file is part of paramiko.
#
@@ -20,7 +20,7 @@
I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
is a module for python 2.3 or greater that implements the SSH2 protocol for
secure (encrypted and authenticated) connections to remote machines. Unlike
-SSL (aka TLS), the SSH2 protocol does not require heirarchical certificates
+SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates
signed by a powerful central authority. You may know SSH2 as the protocol that
replaced C{telnet} and C{rsh} for secure access to remote shells, but the
protocol also includes the ability to open arbitrary channels to remote
@@ -47,7 +47,7 @@ released under the GNU Lesser General Public License (LGPL).
Website: U{http://www.lag.net/paramiko/}
-@version: 1.7.6 (Fanny)
+@version: 1.7.7.1 (George)
@author: Robey Pointer
@contact: robeypointer@gmail.com
@license: GNU Lesser General Public License (LGPL)
@@ -60,13 +60,13 @@ if sys.version_info < (2, 2):
__author__ = "Robey Pointer <robeypointer@gmail.com>"
-__date__ = "1 Nov 2009"
-__version__ = "1.7.6 (Fanny)"
-__version_info__ = (1, 7, 6)
+__date__ = "21 May 2011"
+__version__ = "1.7.7.1 (George)"
+__version_info__ = (1, 7, 7, 1)
__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 auth_handler import AuthHandler
from channel import Channel, ChannelFile
diff --git a/paramiko/agent.py b/paramiko/agent.py
index 71de8b8..3bb9426 100644
--- a/paramiko/agent.py
+++ b/paramiko/agent.py
@@ -55,6 +55,7 @@ class Agent:
@raise SSHException: if an SSH agent is found, but speaks an
incompatible protocol
"""
+ self.conn = None
self.keys = ()
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -87,7 +88,8 @@ class Agent:
"""
Close the SSH agent connection.
"""
- self.conn.close()
+ if self.conn is not None:
+ self.conn.close()
self.conn = None
self.keys = ()
@@ -139,7 +141,7 @@ class AgentKey(PKey):
def get_name(self):
return self.name
- def sign_ssh_data(self, randpool, data):
+ def sign_ssh_data(self, rng, data):
msg = Message()
msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST))
msg.add_string(self.blob)
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
index 0f2e4f6..e3bd82d 100644
--- a/paramiko/auth_handler.py
+++ b/paramiko/auth_handler.py
@@ -206,7 +206,7 @@ class AuthHandler (object):
m.add_string(self.private_key.get_name())
m.add_string(str(self.private_key))
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))
elif self.auth_method == 'keyboard-interactive':
m.add_string('')
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 4694eef..6d895fe 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -364,7 +364,7 @@ class Channel (object):
if auth_protocol is None:
auth_protocol = 'MIT-MAGIC-COOKIE-1'
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.add_byte(chr(MSG_CHANNEL_REQUEST))
diff --git a/paramiko/client.py b/paramiko/client.py
index 023b405..4a65477 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -123,6 +123,7 @@ class SSHClient (object):
self._log_channel = None
self._policy = RejectPolicy()
self._transport = None
+ self._agent = None
def load_system_host_keys(self, filename=None):
"""
@@ -226,7 +227,8 @@ class SSHClient (object):
self._policy = policy
def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
- key_filename=None, timeout=None, allow_agent=True, look_for_keys=True):
+ key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
+ compress=False):
"""
Connect to an SSH server and authenticate to it. The server's host key
is checked against the system host keys (see L{load_system_host_keys})
@@ -267,6 +269,8 @@ class SSHClient (object):
@param look_for_keys: set to False to disable searching for discoverable
private key files in C{~/.ssh/}
@type look_for_keys: bool
+ @param compress: set to True to turn on compression
+ @type compress: bool
@raise BadHostKeyException: if the server's host key could not be
verified
@@ -281,7 +285,8 @@ class SSHClient (object):
addr = sockaddr
break
else:
- raise SSHException('No suitable address family for %s' % hostname)
+ # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
+ af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
sock = socket.socket(af, socket.SOCK_STREAM)
if timeout is not None:
try:
@@ -290,7 +295,7 @@ class SSHClient (object):
pass
sock.connect(addr)
t = self._transport = Transport(sock)
-
+ t.use_compression(compress=compress)
if self._log_channel is not None:
t.set_log_channel(self._log_channel)
t.start_client()
@@ -335,6 +340,10 @@ class SSHClient (object):
self._transport.close()
self._transport = None
+ if self._agent != None:
+ self._agent.close()
+ self._agent = None
+
def exec_command(self, command, bufsize=-1):
"""
Execute a command on the SSH server. A new L{Channel} is opened and
@@ -432,7 +441,10 @@ class SSHClient (object):
saved_exception = e
if allow_agent:
- for key in Agent().get_keys():
+ if self._agent == None:
+ self._agent = Agent()
+
+ for key in self._agent.get_keys():
try:
self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
self._transport.auth_publickey(username, key)
diff --git a/paramiko/common.py b/paramiko/common.py
index 7a37463..3323f0a 100644
--- a/paramiko/common.py
+++ b/paramiko/common.py
@@ -95,10 +95,10 @@ CONNECTION_FAILED_CODE = {
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
-from rng import StrongLockingRandomPool
+from Crypto import Random
# keep a crypto-strong PRNG nearby
-randpool = StrongLockingRandomPool()
+rng = Random.new()
import sys
if sys.version_info < (2, 3):
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index eecfa69..53ca92b 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -91,13 +91,13 @@ class DSSKey (PKey):
def can_sign(self):
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()
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
# generate a suitable k
qsize = len(util.deflate_long(self.q, 0))
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):
break
r, s = dss.sign(util.inflate_long(digest, 1), k)
@@ -161,8 +161,7 @@ class DSSKey (PKey):
@return: new private key
@rtype: L{DSSKey}
"""
- randpool.stir()
- dsa = DSA.generate(bits, randpool.get_bytes, progress_func)
+ dsa = DSA.generate(bits, rng.read, progress_func)
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
key.x = dsa.x
return key
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index 9ceef43..70ccf43 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -303,7 +303,7 @@ class HostKeys (UserDict.DictMixin):
@rtype: str
"""
if salt is None:
- salt = randpool.get_bytes(SHA.digest_size)
+ salt = rng.read(SHA.digest_size)
else:
if salt.startswith('|1|'):
salt = salt.split('|')[2]
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index c6be638..9c98339 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -101,8 +101,7 @@ class KexGex (object):
qhbyte <<= 1
qmask >>= 1
while True:
- self.transport.randpool.stir()
- x_bytes = self.transport.randpool.get_bytes(bytes)
+ x_bytes = self.transport.rng.read(bytes)
x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
x = util.inflate_long(x_bytes, 1)
if (x > 1) and (x < q):
@@ -207,7 +206,7 @@ class KexGex (object):
H = SHA.new(str(hm)).digest()
self.transport._set_K_H(K, H)
# 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
m = Message()
m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index 4228dd9..1386cf3 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -79,8 +79,7 @@ class KexGroup1(object):
# 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).
while 1:
- self.transport.randpool.stir()
- x_bytes = self.transport.randpool.get_bytes(128)
+ x_bytes = self.transport.rng.read(128)
x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:]
if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \
(x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'):
@@ -125,7 +124,7 @@ class KexGroup1(object):
H = SHA.new(str(hm)).digest()
self.transport._set_K_H(K, H)
# 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
m = Message()
m.add_byte(chr(_MSG_KEXDH_REPLY))
diff --git a/paramiko/packet.py b/paramiko/packet.py
index 9072fbe..391c5d5 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -59,7 +59,7 @@ class Packetizer (object):
# they should probably be lower.
REKEY_PACKETS = pow(2, 30)
REKEY_BYTES = pow(2, 30)
-
+
def __init__(self, socket):
self.__socket = socket
self.__logger = None
@@ -68,14 +68,14 @@ class Packetizer (object):
self.__need_rekey = False
self.__init_count = 0
self.__remainder = ''
-
+
# used for noticing when to re-key:
self.__sent_bytes = 0
self.__sent_packets = 0
self.__received_bytes = 0
self.__received_packets = 0
self.__received_packets_overflow = 0
-
+
# current inbound/outbound ciphering:
self.__block_size_out = 8
self.__block_size_in = 8
@@ -99,13 +99,13 @@ class Packetizer (object):
self.__keepalive_interval = 0
self.__keepalive_last = time.time()
self.__keepalive_callback = None
-
+
def set_log(self, log):
"""
Set the python log object to use for logging.
"""
self.__logger = log
-
+
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
"""
Switch outbound data cipher.
@@ -122,7 +122,7 @@ class Packetizer (object):
if self.__init_count == 3:
self.__init_count = 0
self.__need_rekey = False
-
+
def set_inbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
"""
Switch inbound data cipher.
@@ -140,26 +140,26 @@ class Packetizer (object):
if self.__init_count == 3:
self.__init_count = 0
self.__need_rekey = False
-
+
def set_outbound_compressor(self, compressor):
self.__compress_engine_out = compressor
-
+
def set_inbound_compressor(self, compressor):
self.__compress_engine_in = compressor
-
+
def close(self):
self.__closed = True
self.__socket.close()
def set_hexdump(self, hexdump):
self.__dump_packets = hexdump
-
+
def get_hexdump(self):
return self.__dump_packets
-
+
def get_mac_size_in(self):
return self.__mac_size_in
-
+
def get_mac_size_out(self):
return self.__mac_size_out
@@ -168,11 +168,11 @@ class Packetizer (object):
Returns C{True} if a new set of keys needs to be negotiated. This
will be triggered during a packet read or write, so it should be
checked after every read or write, or at least after every few.
-
+
@return: C{True} if a new set of keys needs to be negotiated
"""
return self.__need_rekey
-
+
def set_keepalive(self, interval, callback):
"""
Turn on/off the callback keepalive. If C{interval} seconds pass with
@@ -182,11 +182,11 @@ class Packetizer (object):
self.__keepalive_interval = interval
self.__keepalive_callback = callback
self.__keepalive_last = time.time()
-
+
def read_all(self, n, check_rekey=False):
"""
Read as close to N bytes as possible, blocking as long as necessary.
-
+
@param n: number of bytes to read
@type n: int
@return: the data read
@@ -262,7 +262,7 @@ class Packetizer (object):
break
out = out[n:]
return
-
+
def readline(self, timeout):
"""
Read a line from the socket. We assume no data is pending after the
@@ -277,7 +277,7 @@ class Packetizer (object):
if (len(buf) > 0) and (buf[-1] == '\r'):
buf = buf[:-1]
return buf
-
+
def send_message(self, data):
"""
Write a block of data using the current cipher, as an SSH block.
@@ -311,9 +311,6 @@ class Packetizer (object):
self.__sent_bytes += len(out)
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)) \
and not self.__need_rekey:
# only ask once for rekeying
@@ -328,7 +325,7 @@ class Packetizer (object):
"""
Only one thread should ever be in this function (no other locking is
done).
-
+
@raise SSHException: if the packet is mangled
@raise NeedRekeyException: if the transport should rekey
"""
@@ -359,7 +356,7 @@ class Packetizer (object):
raise SSHException('Mismatched MAC')
padding = ord(packet[0])
payload = packet[1:packet_size - padding]
- randpool.add_event()
+
if self.__dump_packets:
self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding))
@@ -369,7 +366,7 @@ class Packetizer (object):
msg = Message(payload[1:])
msg.seqno = self.__sequence_number_in
self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL
-
+
# check for rekey
self.__received_bytes += packet_size + self.__mac_size_in + 4
self.__received_packets += 1
@@ -398,8 +395,8 @@ class Packetizer (object):
########## protected
-
-
+
+
def _log(self, level, msg):
if self.__logger is None:
return
@@ -418,7 +415,7 @@ class Packetizer (object):
if now > self.__keepalive_last + self.__keepalive_interval:
self.__keepalive_callback()
self.__keepalive_last = now
-
+
def _py22_read_all(self, n, out):
while n > 0:
r, w, e = select.select([self.__socket], [], [], 0.1)
@@ -476,7 +473,7 @@ class Packetizer (object):
packet = struct.pack('>IB', len(payload) + padding + 1, padding)
packet += payload
if self.__block_engine_out is not None:
- packet += randpool.get_bytes(padding)
+ packet += rng.read(padding)
else:
# cute trick i caught openssh doing: if we're not encrypting,
# don't waste random bytes for the padding
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index bb8c83c..3e71222 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -25,7 +25,7 @@ from binascii import hexlify, unhexlify
import os
from Crypto.Hash import MD5
-from Crypto.Cipher import DES3
+from Crypto.Cipher import DES3, AES
from paramiko.common import *
from paramiko import util
@@ -40,7 +40,8 @@ class PKey (object):
# known encryption types for private key files:
_CIPHER_TABLE = {
- 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }
+ 'AES-128-CBC': { 'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC },
+ 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC },
}
@@ -143,13 +144,13 @@ class PKey (object):
"""
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}
representing an SSH signature message.
- @param randpool: a secure random number generator.
- @type randpool: L{Crypto.Util.randpool.RandomPool}
+ @param rng: a secure random number generator.
+ @type rng: L{Crypto.Util.rng.RandomPool}
@param data: the data to sign.
@type data: str
@return: an SSH signature message.
@@ -171,7 +172,7 @@ class PKey (object):
@rtype: boolean
"""
return False
-
+
def from_private_key_file(cls, filename, password=None):
"""
Create a key object by reading a private key file. If the private
@@ -204,7 +205,7 @@ class PKey (object):
object. If the private key is encrypted and C{password} is not C{None},
the given password will be used to decrypt the key (otherwise
L{PasswordRequiredException} is thrown).
-
+
@param file_obj: the file to read from
@type file_obj: file
@param password: an optional password to use to decrypt the key, if it's
@@ -212,7 +213,7 @@ class PKey (object):
@type password: str
@return: a new key object based on the given private key
@rtype: L{PKey}
-
+
@raise IOError: if there was an error reading the key
@raise PasswordRequiredException: if the private key file is encrypted,
and C{password} is C{None}
@@ -236,17 +237,17 @@ class PKey (object):
@raise SSHException: if the key is invalid
"""
raise Exception('Not implemented in PKey')
-
+
def write_private_key(self, file_obj, password=None):
"""
Write private key contents into a file (or file-like) object. If the
password is not C{None}, the key is encrypted before writing.
-
+
@param file_obj: the file object to write into
@type file_obj: file
@param password: an optional password to use to encrypt the key
@type password: str
-
+
@raise IOError: if there was an error writing to the file
@raise SSHException: if the key is invalid
"""
@@ -279,7 +280,7 @@ class PKey (object):
data = self._read_private_key(tag, f, password)
f.close()
return data
-
+
def _read_private_key(self, tag, f, password=None):
lines = f.readlines()
start = 0
@@ -350,7 +351,7 @@ class PKey (object):
os.chmod(filename, 0600)
self._write_private_key(tag, f, data, password)
f.close()
-
+
def _write_private_key(self, tag, f, data, password=None):
f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag)
if password is not None:
@@ -360,11 +361,11 @@ class PKey (object):
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
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)
if len(data) % blocksize != 0:
n = blocksize - len(data) % blocksize
- #data += randpool.get_bytes(n)
+ #data += rng.read(n)
# that would make more sense ^, but it confuses openssh.
data += '\0' * n
data = cipher.new(key, mode, salt).encrypt(data)
diff --git a/paramiko/primes.py b/paramiko/primes.py
index 1cf7905..9ebfec1 100644
--- a/paramiko/primes.py
+++ b/paramiko/primes.py
@@ -26,12 +26,12 @@ from paramiko import util
from paramiko.ssh_exception import SSHException
-def _generate_prime(bits, randpool):
+def _generate_prime(bits, rng):
"primtive attempt at prime generation"
hbyte_mask = pow(2, bits % 8) - 1
while True:
# 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:
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
n = util.inflate_long(x, 1)
@@ -43,7 +43,7 @@ def _generate_prime(bits, randpool):
break
return n
-def _roll_random(rpool, n):
+def _roll_random(rng, n):
"returns a random # from 0 to N-1"
bits = util.bit_length(n-1)
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
# of it looping forever should be infinitesimal.
while True:
- x = rpool.get_bytes(bytes)
+ x = rng.read(bytes)
if hbyte_mask > 0:
x = chr(ord(x[0]) & hbyte_mask) + x[1:]
num = util.inflate_long(x, 1)
@@ -75,7 +75,7 @@ class ModulusPack (object):
# pack is a hash of: bits -> [ (generator, modulus) ... ]
self.pack = {}
self.discarded = []
- self.randpool = rpool
+ self.rng = rpool
def _parse_modulus(self, line):
timestamp, mod_type, tests, tries, size, generator, modulus = line.split()
@@ -147,5 +147,5 @@ class ModulusPack (object):
if min > good:
good = bitsizes[-1]
# 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]
diff --git a/paramiko/rng.py b/paramiko/rng.py
deleted file mode 100644
index 46329d1..0000000
--- a/paramiko/rng.py
+++ /dev/null
@@ -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:
diff --git a/paramiko/rng_posix.py b/paramiko/rng_posix.py
deleted file mode 100644
index c4c9691..0000000
--- a/paramiko/rng_posix.py
+++ /dev/null
@@ -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:
-
diff --git a/paramiko/rng_win32.py b/paramiko/rng_win32.py
deleted file mode 100644
index 3cb8b84..0000000
--- a/paramiko/rng_win32.py
+++ /dev/null
@@ -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:
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index a665279..1e2d8f9 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -137,8 +137,7 @@ class RSAKey (PKey):
@return: new private key
@rtype: L{RSAKey}
"""
- randpool.stir()
- rsa = RSA.generate(bits, randpool.get_bytes, progress_func)
+ rsa = RSA.generate(bits, rng.read, progress_func)
key = RSAKey(vals=(rsa.e, rsa.n))
key.d = rsa.d
key.p = rsa.p
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index 26290be..1f09421 100644
--- a/paramiko/sftp_attr.py
+++ b/paramiko/sftp_attr.py
@@ -39,7 +39,7 @@ class SFTPAttributes (object):
are stored in a dict named C{attr}. Occasionally, the filename is also
stored, in C{filename}.
"""
-
+
FLAG_SIZE = 1
FLAG_UIDGID = 2
FLAG_PERMISSIONS = 4
@@ -89,7 +89,7 @@ class SFTPAttributes (object):
### internals...
-
+
def _from_msg(cls, msg, filename=None, longname=None):
attr = cls()
attr._unpack(msg)
@@ -200,7 +200,7 @@ class SFTPAttributes (object):
else:
ks = '?---------'
# compute display date
- if (self.st_mtime is None) or (self.st_mtime == 0xffffffff):
+ if (self.st_mtime is None) or (self.st_mtime == 0xffffffffL):
# shouldn't really happen
datestr = '(unknown date)'
else:
@@ -210,7 +210,7 @@ class SFTPAttributes (object):
else:
datestr = time.strftime('%d %b %H:%M', time.localtime(self.st_mtime))
filename = getattr(self, 'filename', '?')
-
+
# not all servers support uid/gid
uid = self.st_uid
gid = self.st_gid
@@ -218,6 +218,6 @@ class SFTPAttributes (object):
uid = 0
if gid is None:
gid = 0
-
+
return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename)
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 1f11075..79a7761 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -533,7 +533,7 @@ class SFTPClient (BaseSFTP):
"""
return self._cwd
- def put(self, localpath, remotepath, callback=None):
+ def put(self, localpath, remotepath, callback=None, confirm=True):
"""
Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
Any exception raised by operations will be passed through. This
@@ -549,6 +549,10 @@ class SFTPClient (BaseSFTP):
transferred so far and the total bytes to be transferred
(since 1.7.4)
@type callback: function(int, int)
+ @param confirm: whether to do a stat() on the file afterwards to
+ confirm the file size (since 1.7.7)
+ @type confirm: bool
+
@return: an object containing attributes about the given file
(since 1.7.4)
@rtype: SFTPAttributes
@@ -574,9 +578,12 @@ class SFTPClient (BaseSFTP):
fr.close()
finally:
fl.close()
- s = self.stat(remotepath)
- if s.st_size != size:
- raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
+ if confirm:
+ s = self.stat(remotepath)
+ if s.st_size != size:
+ raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
+ else:
+ s = SFTPAttributes()
return s
def get(self, remotepath, localpath, callback=None):
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 50e78e7..30de295 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -45,11 +45,7 @@ from paramiko.server import ServerInterface
from paramiko.sftp_client import SFTPClient
from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelException
-# these come from PyCrypt
-# http://www.amk.ca/python/writing/pycrypt/
-# i believe this on the standards track.
-# PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
-# http://nitace.bsd.uchicago.edu:8080/hashtar
+from Crypto import Random
from Crypto.Cipher import Blowfish, AES, DES3, ARC4
from Crypto.Hash import SHA, MD5
try:
@@ -198,7 +194,7 @@ class Transport (threading.Thread):
"""
_PROTO_ID = '2.0'
- _CLIENT_ID = 'paramiko_1.7.6'
+ _CLIENT_ID = 'paramiko_1.7.7.1'
_preferred_ciphers = ( 'aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc',
'arcfour128', 'arcfour256' )
@@ -285,19 +281,25 @@ class Transport (threading.Thread):
if type(sock) is tuple:
# connect to the given (host, port)
hostname, port = sock
+ reason = 'No suitable address family'
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
if socktype == socket.SOCK_STREAM:
af = family
addr = sockaddr
- break
+ sock = socket.socket(af, socket.SOCK_STREAM)
+ try:
+ sock.connect((hostname, port))
+ except socket.error, e:
+ reason = str(e)
+ else:
+ break
else:
- raise SSHException('No suitable address family for %s' % hostname)
- sock = socket.socket(af, socket.SOCK_STREAM)
- sock.connect((hostname, port))
+ raise SSHException(
+ 'Unable to connect to %s: %s' % (hostname, reason))
# okay, normal socket-ish flow here...
threading.Thread.__init__(self)
self.setDaemon(True)
- self.randpool = randpool
+ self.rng = rng
self.sock = sock
# Python < 2.3 doesn't have the settimeout method - RogerB
try:
@@ -450,6 +452,7 @@ class Transport (threading.Thread):
# synchronous, wait for a result
self.completion_event = event = threading.Event()
self.start()
+ Random.atfork()
while True:
event.wait(0.1)
if not self.active:
@@ -585,7 +588,7 @@ class Transport (threading.Thread):
@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
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
if filename is not None:
@@ -706,10 +709,8 @@ class Transport (threading.Thread):
@raise SSHException: if the request is rejected or the session ends
prematurely
"""
- chan = None
if not self.active:
- # don't bother trying to allocate a channel
- return None
+ raise SSHException('SSH session not active')
self.lock.acquire()
try:
chanid = self._next_channel()
@@ -839,10 +840,9 @@ class Transport (threading.Thread):
"""
m = Message()
m.add_byte(chr(MSG_IGNORE))
- randpool.stir()
if bytes is None:
- bytes = (ord(randpool.get_bytes(1)) % 32) + 10
- m.add_bytes(randpool.get_bytes(bytes))
+ bytes = (ord(rng.read(1)) % 32) + 10
+ m.add_bytes(rng.read(bytes))
self._send_user_message(m)
def renegotiate_keys(self):
@@ -1676,10 +1676,9 @@ class Transport (threading.Thread):
else:
available_server_keys = self._preferred_keys
- randpool.stir()
m = Message()
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(available_server_keys)
m.add_list(self._preferred_ciphers)