[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-68]

added Transport.get_security_options()
just something i wanted to play with:
added Transport.get_security_options() which returns a SecurityOptions object.
this object is a kind of proxy for the 4 "preferred_*" fields in Transport,
and lets me avoid exposing those fields directly in case i change my mind
later about how they should be stored.

added some docs to Channel explaining that the request methods now return
True/False, and fixed up docs in a few other places.
This commit is contained in:
Robey Pointer 2004-08-30 20:22:10 +00:00
parent 5598a8f88f
commit aebe186c3e
5 changed files with 114 additions and 29 deletions

View File

@ -77,9 +77,11 @@ Message = message.Message
PasswordRequiredException = ssh_exception.PasswordRequiredException PasswordRequiredException = ssh_exception.PasswordRequiredException
SFTP = sftp.SFTP SFTP = sftp.SFTP
ServerInterface = server.ServerInterface ServerInterface = server.ServerInterface
SecurityOptions = transport.SecurityOptions
__all__ = [ 'Transport', __all__ = [ 'Transport',
'SecurityOptions',
'Channel', 'Channel',
'RSAKey', 'RSAKey',
'DSSKey', 'DSSKey',

View File

@ -116,6 +116,8 @@ class Channel (object):
@type width: int @type width: int
@param height: height (in characters) of the terminal screen @param height: height (in characters) of the terminal screen
@type height: int @type height: int
@return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool
""" """
if self.closed or self.eof_received or self.eof_sent or not self.active: if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open') raise SSHException('Channel is not open')
@ -144,6 +146,9 @@ class Channel (object):
Request an interactive shell session on this channel. If the server Request an interactive shell session on this channel. If the server
allows it, the channel will then be directly connected to the stdin allows it, the channel will then be directly connected to the stdin
and stdout of the shell. and stdout of the shell.
@return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool
""" """
if self.closed or self.eof_received or self.eof_sent or not self.active: if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open') raise SSHException('Channel is not open')
@ -169,6 +174,8 @@ class Channel (object):
@param command: a shell command to execute. @param command: a shell command to execute.
@type command: string @type command: string
@return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool
""" """
if self.closed or self.eof_received or self.eof_sent or not self.active: if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open') raise SSHException('Channel is not open')
@ -195,6 +202,8 @@ class Channel (object):
@param subsystem: name of the subsystem being requested. @param subsystem: name of the subsystem being requested.
@type subsystem: string @type subsystem: string
@return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool
""" """
if self.closed or self.eof_received or self.eof_sent or not self.active: if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open') raise SSHException('Channel is not open')
@ -222,6 +231,8 @@ class Channel (object):
@type width: int @type width: int
@param height: new height (in characters) of the terminal screen @param height: new height (in characters) of the terminal screen
@type height: int @type height: int
@return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool
""" """
if self.closed or self.eof_received or self.eof_sent or not self.active: if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open') raise SSHException('Channel is not open')

View File

@ -28,6 +28,10 @@ class ServerInterface (object):
""" """
This class defines an interface for controlling the behavior of paramiko This class defines an interface for controlling the behavior of paramiko
in server mode. in server mode.
Methods on this class are called from paramiko's primary thread, so you
shouldn't do too much work in them. (Certainly nothing that blocks or
sleeps.)
""" """
def check_channel_request(self, kind, chanid): def check_channel_request(self, kind, chanid):

View File

@ -123,8 +123,10 @@ class SFTPError (Exception):
class SFTPFile (BufferedFile): class SFTPFile (BufferedFile):
# some sftp servers will choke if you send read/write requests larger than """
# this size. Some sftp servers will choke if you send read/write requests larger than
this size.
"""
MAX_REQUEST_SIZE = 32768 MAX_REQUEST_SIZE = 32768
def __init__(self, sftp, handle, mode='r', bufsize=-1): def __init__(self, sftp, handle, mode='r', bufsize=-1):
@ -245,10 +247,20 @@ class SFTP (object):
# raise SFTPError('Incompatible sftp protocol') # raise SFTPError('Incompatible sftp protocol')
def from_transport(selfclass, t): def from_transport(selfclass, t):
"""
Create an SFTP client channel from an open L{Transport}.
@param t: an open L{Transport} which is already authenticated.
@type t: L{Transport}
@return: a new L{SFTP} object, referring to an sftp session (channel)
across the transport.
@rtype: L{SFTP}
"""
chan = t.open_session() chan = t.open_session()
if chan is None: if chan is None:
return None return None
chan.invoke_subsystem('sftp') if not chan.invoke_subsystem('sftp'):
raise SFTPError('Failed to invoke sftp subsystem')
return selfclass(chan) return selfclass(chan)
from_transport = classmethod(from_transport) from_transport = classmethod(from_transport)

View File

@ -53,6 +53,50 @@ import atexit
atexit.register(_join_lingering_threads) atexit.register(_join_lingering_threads)
class SecurityOptions (object):
"""
Simple object containing the security preferences of an ssh transport.
These are lists of acceptable ciphers, digests, key types, and key
exchange algorithms, listed in order of preference.
"""
__slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', '_transport' ]
def __init__(self, transport):
self._transport = transport
def _get_ciphers(self):
return self._transport._preferred_ciphers
def _set_ciphers(self, x):
self._transport._preferred_ciphers = x
def _get_digests(self):
return self._transport._preferred_macs
def _set_digests(self, x):
self._transport._preferred_macs = x
def _get_key_types(self):
return self._transport._preferred_keys
def _set_key_types(self, x):
self._transport._preferred_keys = x
def _get_kex(self):
return self._transport._preferred_kex
def _set_kex(self, x):
self._transport._preferred_kex = x
ciphers = property(_get_ciphers, _set_ciphers, None,
"Symmetric encryption ciphers")
digests = property(_get_digests, _set_digests, None,
"Digest (one-way hash) algorithms")
key_types = property(_get_key_types, _set_key_types, None,
"Public-key algorithms")
kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
class BaseTransport (threading.Thread): class BaseTransport (threading.Thread):
""" """
Handles protocol negotiation, key exchange, encryption, and the creation Handles protocol negotiation, key exchange, encryption, and the creation
@ -62,10 +106,10 @@ class BaseTransport (threading.Thread):
_PROTO_ID = '2.0' _PROTO_ID = '2.0'
_CLIENT_ID = 'pyssh_1.1' _CLIENT_ID = 'pyssh_1.1'
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ] _preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ] _preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
preferred_keys = [ 'ssh-rsa', 'ssh-dss' ] _preferred_keys = [ 'ssh-rsa', 'ssh-dss' ]
preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ] _preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ]
_cipher_info = { _cipher_info = {
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 }, 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
@ -205,6 +249,18 @@ class BaseTransport (threading.Thread):
out += '>' out += '>'
return out return out
def get_security_options(self):
"""
Return a L{SecurityOptions} object which can be used to tweak the
encryption algorithms this transport will permit, and the order of
preference for them.
@return: an object that can be used to change the preferred algorithms
for encryption, digest (hash), public key, and key exchange.
@rtype: L{SecurityOptions}
"""
return SecurityOptions(self)
def start_client(self, event=None): def start_client(self, event=None):
""" """
Negotiate a new SSH2 session as a client. This is the first step after Negotiate a new SSH2 session as a client. This is the first step after
@ -640,7 +696,7 @@ class BaseTransport (threading.Thread):
@since: doduo @since: doduo
""" """
if hostkeytype is not None: if hostkeytype is not None:
self.preferred_keys = [ hostkeytype ] self._preferred_keys = [ hostkeytype ]
event = threading.Event() event = threading.Event()
self.start_client(event) self.start_client(event)
@ -1048,23 +1104,23 @@ class BaseTransport (threading.Thread):
""" """
self.clear_to_send.clear() self.clear_to_send.clear()
if self.server_mode: if self.server_mode:
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex): if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex):
# can't do group-exchange if we don't have a pack of potential primes # can't do group-exchange if we don't have a pack of potential primes
self.preferred_kex.remove('diffie-hellman-group-exchange-sha1') self._preferred_kex.remove('diffie-hellman-group-exchange-sha1')
available_server_keys = filter(self.server_key_dict.keys().__contains__, available_server_keys = filter(self.server_key_dict.keys().__contains__,
self.preferred_keys) self._preferred_keys)
else: else:
available_server_keys = self.preferred_keys available_server_keys = self._preferred_keys
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(randpool.get_bytes(16))
m.add(','.join(self.preferred_kex)) m.add(','.join(self._preferred_kex))
m.add(','.join(available_server_keys)) m.add(','.join(available_server_keys))
m.add(','.join(self.preferred_ciphers)) m.add(','.join(self._preferred_ciphers))
m.add(','.join(self.preferred_ciphers)) m.add(','.join(self._preferred_ciphers))
m.add(','.join(self.preferred_macs)) m.add(','.join(self._preferred_macs))
m.add(','.join(self.preferred_macs)) m.add(','.join(self._preferred_macs))
m.add('none') m.add('none')
m.add('none') m.add('none')
m.add('') m.add('')
@ -1105,19 +1161,19 @@ class BaseTransport (threading.Thread):
# as a server, we pick the first item in the client's list that we support. # as a server, we pick the first item in the client's list that we support.
# as a client, we pick the first item in our list that the server supports. # as a client, we pick the first item in our list that the server supports.
if self.server_mode: if self.server_mode:
agreed_kex = filter(self.preferred_kex.__contains__, kex_algo_list) agreed_kex = filter(self._preferred_kex.__contains__, kex_algo_list)
else: else:
agreed_kex = filter(kex_algo_list.__contains__, self.preferred_kex) agreed_kex = filter(kex_algo_list.__contains__, self._preferred_kex)
if len(agreed_kex) == 0: if len(agreed_kex) == 0:
raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)')
self.kex_engine = self._kex_info[agreed_kex[0]](self) self.kex_engine = self._kex_info[agreed_kex[0]](self)
if self.server_mode: if self.server_mode:
available_server_keys = filter(self.server_key_dict.keys().__contains__, available_server_keys = filter(self.server_key_dict.keys().__contains__,
self.preferred_keys) self._preferred_keys)
agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list) agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list)
else: else:
agreed_keys = filter(server_key_algo_list.__contains__, self.preferred_keys) agreed_keys = filter(server_key_algo_list.__contains__, self._preferred_keys)
if len(agreed_keys) == 0: if len(agreed_keys) == 0:
raise SSHException('Incompatible ssh peer (no acceptable host key)') raise SSHException('Incompatible ssh peer (no acceptable host key)')
self.host_key_type = agreed_keys[0] self.host_key_type = agreed_keys[0]
@ -1125,15 +1181,15 @@ class BaseTransport (threading.Thread):
raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') raise SSHException('Incompatible ssh peer (can\'t match requested host key type)')
if self.server_mode: if self.server_mode:
agreed_local_ciphers = filter(self.preferred_ciphers.__contains__, agreed_local_ciphers = filter(self._preferred_ciphers.__contains__,
server_encrypt_algo_list) server_encrypt_algo_list)
agreed_remote_ciphers = filter(self.preferred_ciphers.__contains__, agreed_remote_ciphers = filter(self._preferred_ciphers.__contains__,
client_encrypt_algo_list) client_encrypt_algo_list)
else: else:
agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__, agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__,
self.preferred_ciphers) self._preferred_ciphers)
agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__, agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__,
self.preferred_ciphers) self._preferred_ciphers)
if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0): if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0):
raise SSHException('Incompatible ssh server (no acceptable ciphers)') raise SSHException('Incompatible ssh server (no acceptable ciphers)')
self.local_cipher = agreed_local_ciphers[0] self.local_cipher = agreed_local_ciphers[0]
@ -1141,11 +1197,11 @@ class BaseTransport (threading.Thread):
self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher)) self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher))
if self.server_mode: if self.server_mode:
agreed_remote_macs = filter(self.preferred_macs.__contains__, client_mac_algo_list) agreed_remote_macs = filter(self._preferred_macs.__contains__, client_mac_algo_list)
agreed_local_macs = filter(self.preferred_macs.__contains__, server_mac_algo_list) agreed_local_macs = filter(self._preferred_macs.__contains__, server_mac_algo_list)
else: else:
agreed_local_macs = filter(client_mac_algo_list.__contains__, self.preferred_macs) agreed_local_macs = filter(client_mac_algo_list.__contains__, self._preferred_macs)
agreed_remote_macs = filter(server_mac_algo_list.__contains__, self.preferred_macs) agreed_remote_macs = filter(server_mac_algo_list.__contains__, self._preferred_macs)
if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0): if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0):
raise SSHException('Incompatible ssh server (no acceptable macs)') raise SSHException('Incompatible ssh server (no acceptable macs)')
self.local_mac = agreed_local_macs[0] self.local_mac = agreed_local_macs[0]