diff options
-rw-r--r-- | .travis.yml | 7 | ||||
-rw-r--r-- | paramiko/agent.py | 4 | ||||
-rw-r--r-- | paramiko/buffered_pipe.py | 10 | ||||
-rw-r--r-- | paramiko/channel.py | 43 | ||||
-rw-r--r-- | paramiko/client.py | 20 | ||||
-rw-r--r-- | paramiko/dsskey.py | 2 | ||||
-rw-r--r-- | paramiko/ecdsakey.py | 2 | ||||
-rw-r--r-- | paramiko/file.py | 20 | ||||
-rw-r--r-- | paramiko/hostkeys.py | 4 | ||||
-rw-r--r-- | paramiko/kex_gss.py | 4 | ||||
-rw-r--r-- | paramiko/packet.py | 12 | ||||
-rw-r--r-- | paramiko/pkey.py | 42 | ||||
-rw-r--r-- | paramiko/rsakey.py | 2 | ||||
-rw-r--r-- | paramiko/server.py | 67 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 19 | ||||
-rw-r--r-- | paramiko/sftp_file.py | 24 | ||||
-rw-r--r-- | paramiko/sftp_handle.py | 10 | ||||
-rw-r--r-- | paramiko/sftp_server.py | 2 | ||||
-rw-r--r-- | paramiko/sftp_si.py | 20 | ||||
-rw-r--r-- | paramiko/ssh_exception.py | 18 | ||||
-rw-r--r-- | paramiko/ssh_gss.py | 43 | ||||
-rw-r--r-- | paramiko/transport.py | 119 | ||||
-rw-r--r-- | sites/www/changelog.rst | 49 | ||||
-rw-r--r-- | tests/test_pkey.py | 28 | ||||
-rw-r--r-- | tests/test_transport.py | 9 |
25 files changed, 318 insertions, 262 deletions
diff --git a/.travis.yml b/.travis.yml index 4cacb017..c8faf0a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,8 @@ install: script: # Main tests, w/ coverage! - inv test --coverage - # Ensure documentation & invoke pipeline run OK. - # Run 'docs' first since its objects.inv is referred to by 'www'. - # Also force warnings to be errors since most of them tend to be actual - # problems. - - invoke docs -o -W www -o -W + # Ensure documentation builds, both sites, maxxed nitpicking + - inv sites # flake8 is now possible! - flake8 notifications: diff --git a/paramiko/agent.py b/paramiko/agent.py index a7cab4d8..bc857efa 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -253,7 +253,7 @@ class AgentServerProxy(AgentSSH): """ :param .Transport t: Transport used for SSH Agent communication forwarding - :raises SSHException: mostly if we lost the agent + :raises: `.SSHException` -- mostly if we lost the agent """ def __init__(self, t): AgentSSH.__init__(self) @@ -347,7 +347,7 @@ class Agent(AgentSSH): opened, if one is running. If no agent is running, initialization will succeed, but `get_keys` will return an empty tuple. - :raises SSHException: + :raises: `.SSHException` -- if an SSH agent is found, but speaks an incompatible protocol """ def __init__(self): diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py index 9a65cd95..d9f5149d 100644 --- a/paramiko/buffered_pipe.py +++ b/paramiko/buffered_pipe.py @@ -90,7 +90,7 @@ class BufferedPipe (object): Feed new data into this pipe. This method is assumed to be called from a separate thread, so synchronization is done. - :param data: the data to add, as a `str` or `bytes` + :param data: the data to add, as a ``str`` or ``bytes`` """ self._lock.acquire() try: @@ -134,11 +134,11 @@ class BufferedPipe (object): :param int nbytes: maximum number of bytes to read :param float timeout: maximum seconds to wait (or ``None``, the default, to wait forever) - :return: the read data, as a `bytes` + :return: the read data, as a ``str`` or ``bytes`` - :raises PipeTimeout: - if a timeout was specified and no data was ready before that - timeout + :raises: + `.PipeTimeout` -- if a timeout was specified and no data was ready + before that timeout """ out = bytes() self._lock.acquire() diff --git a/paramiko/channel.py b/paramiko/channel.py index f2ecc4c0..1f603cf0 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -46,8 +46,9 @@ def open_only(func): """ Decorator for `.Channel` methods which performs an openness check. - :raises SSHException: - If the wrapped method is called on an unopened `.Channel`. + :raises: + `.SSHException` -- If the wrapped method is called on an unopened + `.Channel`. """ @wraps(func) def _check(self, *args, **kwds): @@ -165,8 +166,9 @@ class Channel (ClosingContextManager): :param int width_pixels: width (in pixels) of the terminal screen :param int height_pixels: height (in pixels) of the terminal screen - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -197,7 +199,8 @@ class Channel (ClosingContextManager): When the shell exits, the channel will be closed and can't be reused. You must open a new channel if you wish to open another shell. - :raises SSHException: if the request was rejected or the channel was + :raises: + `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() @@ -222,7 +225,8 @@ class Channel (ClosingContextManager): :param str command: a shell command to execute. - :raises SSHException: if the request was rejected or the channel was + :raises: + `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() @@ -247,8 +251,9 @@ class Channel (ClosingContextManager): :param str subsystem: name of the subsystem being requested. - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -271,8 +276,9 @@ class Channel (ClosingContextManager): :param int width_pixels: new width (in pixels) of the terminal screen :param int height_pixels: new height (in pixels) of the terminal screen - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -313,11 +319,11 @@ class Channel (ClosingContextManager): `.Transport` or session's ``window_size`` (e.g. that set by the ``default_window_size`` kwarg for `.Transport.__init__`) will cause `.recv_exit_status` to hang indefinitely if it is called prior to a - sufficiently large `~Channel..read` (or if there are no threads - calling `~Channel.read` in the background). + sufficiently large `.Channel.recv` (or if there are no threads + calling `.Channel.recv` in the background). In these cases, ensuring that `.recv_exit_status` is called *after* - `~Channel.read` (or, again, using threads) can avoid the hang. + `.Channel.recv` (or, again, using threads) can avoid the hang. :return: the exit code (as an `int`) of the process on the server. @@ -391,8 +397,8 @@ class Channel (ClosingContextManager): if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) - :param function handler: - an optional handler to use for incoming X11 connections + :param handler: + an optional callable handler to use for incoming X11 connections :return: the auth_cookie used """ if auth_protocol is None: @@ -421,8 +427,9 @@ class Channel (ClosingContextManager): Request for a forward SSH Agent on this channel. This is only valid for an ssh-agent from OpenSSH !!! - :param function handler: - a required handler to use for incoming SSH Agent connections + :param handler: + a required callable handler to use for incoming SSH Agent + connections :return: True if we are ok, else False (at that time we always return ok) @@ -613,7 +620,7 @@ class Channel (ClosingContextManager): length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. - :return: received data, as a `bytes` + :return: received data, as a ``str``/``bytes``. :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. diff --git a/paramiko/client.py b/paramiko/client.py index d947e1bc..224109bf 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -90,7 +90,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to read, or ``None`` - :raises IOError: + :raises: ``IOError`` -- if a filename was provided and the file could not be read """ if filename is None: @@ -117,7 +117,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to read - :raises IOError: if the filename could not be read + :raises: ``IOError`` -- if the filename could not be read """ self._host_keys_filename = filename self._host_keys.load(filename) @@ -130,7 +130,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to save to - :raises IOError: if the file could not be written + :raises: ``IOError`` -- if the file could not be written """ # update local host keys from file (in case other SSH clients @@ -281,10 +281,12 @@ class SSHClient (ClosingContextManager): :param float banner_timeout: an optional timeout (in seconds) to wait for the SSH banner to be presented. - :raises BadHostKeyException: if the server's host key could not be + :raises: + `.BadHostKeyException` -- if the server's host key could not be verified - :raises AuthenticationException: if authentication failed - :raises SSHException: if there was any other error connecting or + :raises: `.AuthenticationException` -- if authentication failed + :raises: + `.SSHException` -- if there was any other error connecting or establishing an SSH session :raises socket.error: if a socket error occurred while connecting @@ -413,12 +415,12 @@ class SSHClient (ClosingContextManager): interpreted the same way as by the built-in ``file()`` function in Python :param int timeout: - set command's channel timeout. See `Channel.settimeout`.settimeout + set command's channel timeout. See `.Channel.settimeout` :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple - :raises SSHException: if the server fails to execute the command + :raises: `.SSHException` -- if the server fails to execute the command """ chan = self._transport.open_session(timeout=timeout) if get_pty: @@ -445,7 +447,7 @@ class SSHClient (ClosingContextManager): :param int height_pixels: the height (in pixels) of the terminal window :return: a new `.Channel` connected to the remote shell - :raises SSHException: if the server fails to invoke a shell + :raises: `.SSHException` -- if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py index ac6875bc..55ef1e9b 100644 --- a/paramiko/dsskey.py +++ b/paramiko/dsskey.py @@ -208,7 +208,7 @@ class DSSKey(PKey): generate a new host key or authentication key. :param int bits: number of bits the generated key should be. - :param function progress_func: Unused + :param progress_func: Unused :return: new `.DSSKey` private key """ numbers = dsa.generate_private_key( diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 51f8d8ce..f5dacac8 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -231,7 +231,7 @@ class ECDSAKey(PKey): Generate a new private ECDSA key. This factory function can be used to generate a new host key or authentication key. - :param function progress_func: Not used for this type of key. + :param progress_func: Not used for this type of key. :returns: A new private key (`.ECDSAKey`) object """ if bits is not None: diff --git a/paramiko/file.py b/paramiko/file.py index e31ad9dd..5212091a 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -67,7 +67,7 @@ class BufferedFile (ClosingContextManager): file. This iterator happens to return the file itself, since a file is its own iterator. - :raises ValueError: if the file is closed. + :raises: ``ValueError`` -- if the file is closed. """ if self._closed: raise ValueError('I/O operation on closed file') @@ -93,10 +93,10 @@ class BufferedFile (ClosingContextManager): def next(self): """ Returns the next line from the input, or raises - `~exceptions.StopIteration` when EOF is hit. Unlike Python file + ``StopIteration`` when EOF is hit. Unlike Python file objects, it's okay to mix calls to `next` and `readline`. - :raises StopIteration: when the end of the file is reached. + :raises: ``StopIteration`` -- when the end of the file is reached. :returns: a line (`str`) read from the file. """ @@ -107,11 +107,11 @@ class BufferedFile (ClosingContextManager): else: def __next__(self): """ - Returns the next line from the input, or raises `.StopIteration` + Returns the next line from the input, or raises ``StopIteration`` when EOF is hit. Unlike python file objects, it's okay to mix calls to `.next` and `.readline`. - :raises StopIteration: when the end of the file is reached. + :raises: ``StopIteration`` -- when the end of the file is reached. :returns: a line (`str`) read from the file. """ @@ -152,8 +152,8 @@ class BufferedFile (ClosingContextManager): def readinto(self, buff): """ - Read up to ``len(buff)`` bytes into :class:`bytearray` *buff* and - return the number of bytes read. + Read up to ``len(buff)`` bytes into ``bytearray`` *buff* and return the + number of bytes read. :returns: The number of bytes read. @@ -368,7 +368,7 @@ class BufferedFile (ClosingContextManager): type of movement: 0 = absolute; 1 = relative to the current position; 2 = relative to the end of the file. - :raises IOError: if the file doesn't support random access. + :raises: ``IOError`` -- if the file doesn't support random access. """ raise IOError('File does not support seeking.') @@ -389,7 +389,7 @@ class BufferedFile (ClosingContextManager): written yet. (Use `flush` or `close` to force buffered data to be written out.) - :param str/bytes data: data to write + :param data: ``str``/``bytes`` data to write """ data = b(data) if self._closed: @@ -423,7 +423,7 @@ class BufferedFile (ClosingContextManager): name is intended to match `readlines`; `writelines` does not add line separators.) - :param iterable sequence: an iterable sequence of strings. + :param sequence: an iterable sequence of strings. """ for line in sequence: self.write(line) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 18a0d333..008ba592 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -90,7 +90,7 @@ class HostKeys (MutableMapping): :param str filename: name of the file to read host keys from - :raises IOError: if there was an error reading the file + :raises: ``IOError`` -- if there was an error reading the file """ with open(filename, 'r') as f: for lineno, line in enumerate(f, 1): @@ -118,7 +118,7 @@ class HostKeys (MutableMapping): :param str filename: name of the file to write - :raises IOError: if there was an error writing the file + :raises: ``IOError`` -- if there was an error writing the file .. versionadded:: 1.6.1 """ diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 40ceb5cd..ba24c0a0 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -108,7 +108,7 @@ class KexGSSGroup1(object): """ Parse the next packet. - :param char ptype: The type of the incoming packet + :param ptype: The (string) type of the incoming packet :param `.Message` m: The paket content """ if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT): @@ -345,7 +345,7 @@ class KexGSSGex(object): """ Parse the next packet. - :param char ptype: The type of the incoming packet + :param ptype: The (string) type of the incoming packet :param `.Message` m: The paket content """ if ptype == MSG_KEXGSS_GROUPREQ: diff --git a/paramiko/packet.py b/paramiko/packet.py index 16288a0a..95a26c6e 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -43,6 +43,9 @@ def compute_hmac(key, message, digest_class): class NeedRekeyException (Exception): + """ + Exception indicating a rekey is needed. + """ pass @@ -253,8 +256,9 @@ class Packetizer (object): :param int n: number of bytes to read :return: the data read, as a `str` - :raises EOFError: - if the socket was closed before all the bytes could be read + :raises: + ``EOFError`` -- if the socket was closed before all the bytes could + be read """ out = bytes() # handle over-reading from reading the banner line @@ -413,8 +417,8 @@ class Packetizer (object): Only one thread should ever be in this function (no other locking is done). - :raises SSHException: if the packet is mangled - :raises NeedRekeyException: if the transport should rekey + :raises: `.SSHException` -- if the packet is mangled + :raises: `.NeedRekeyException` -- if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not None: diff --git a/paramiko/pkey.py b/paramiko/pkey.py index af9370fc..35a26fc7 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -48,6 +48,12 @@ class PKey(object): 'blocksize': 16, 'mode': modes.CBC }, + 'AES-256-CBC': { + 'cipher': algorithms.AES, + 'keysize': 32, + 'blocksize': 16, + 'mode': modes.CBC + }, 'DES-EDE3-CBC': { 'cipher': algorithms.TripleDES, 'keysize': 24, @@ -68,7 +74,7 @@ class PKey(object): :param str data: an optional string containing a public key of this type - :raises SSHException: + :raises: `.SSHException` -- if a key cannot be created from the ``data`` or ``msg`` given, or no key was passed in. """ @@ -95,7 +101,7 @@ class PKey(object): of the key are compared, so a public key will compare equal to its corresponding private key. - :param .Pkey other: key to compare to. + :param .PKey other: key to compare to. """ hs = hash(self) ho = hash(other) @@ -191,10 +197,10 @@ class PKey(object): encrypted :return: a new `.PKey` based on the given private key - :raises IOError: if there was an error reading the file - :raises PasswordRequiredException: if the private key file is + :raises: ``IOError`` -- if there was an error reading the file + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None`` - :raises SSHException: if the key file is invalid + :raises: `.SSHException` -- if the key file is invalid """ key = cls(filename=filename, password=password) return key @@ -212,10 +218,10 @@ class PKey(object): an optional password to use to decrypt the key, if it's encrypted :return: a new `.PKey` based on the given private key - :raises IOError: if there was an error reading the key - :raises PasswordRequiredException: + :raises: ``IOError`` -- if there was an error reading the key + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None`` - :raises SSHException: if the key file is invalid + :raises: `.SSHException` -- if the key file is invalid """ key = cls(file_obj=file_obj, password=password) return key @@ -229,8 +235,8 @@ class PKey(object): :param str password: an optional password to use to encrypt the key file - :raises IOError: if there was an error writing the file - :raises SSHException: if the key is invalid + :raises: ``IOError`` -- if there was an error writing the file + :raises: `.SSHException` -- if the key is invalid """ raise Exception('Not implemented in PKey') @@ -242,8 +248,8 @@ class PKey(object): :param file_obj: the file-like object to write into :param str password: an optional password to use to encrypt the key - :raises IOError: if there was an error writing to the file - :raises SSHException: if the key is invalid + :raises: ``IOError`` -- if there was an error writing to the file + :raises: `.SSHException` -- if the key is invalid """ raise Exception('Not implemented in PKey') @@ -263,10 +269,10 @@ class PKey(object): encrypted. :return: data blob (`str`) that makes up the private key. - :raises IOError: if there was an error reading the file. - :raises PasswordRequiredException: if the private key file is + :raises: ``IOError`` -- if there was an error reading the file. + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None``. - :raises SSHException: if the key file is invalid. + :raises: `.SSHException` -- if the key file is invalid. """ with open(filename, 'r') as f: data = self._read_private_key(tag, f, password) @@ -340,17 +346,17 @@ class PKey(object): :param str data: data blob that makes up the private key. :param str password: an optional password to use to encrypt the file. - :raises IOError: if there was an error writing the file. + :raises: ``IOError`` -- if there was an error writing the file. """ with open(filename, 'w') as f: os.chmod(filename, o600) - self._write_private_key(f, key, format) + self._write_private_key(f, key, format, password=password) def _write_private_key(self, f, key, format, password=None): if password is None: encryption = serialization.NoEncryption() else: - encryption = serialization.BestEncryption(password) + encryption = serialization.BestAvailableEncryption(b(password)) f.write(key.private_bytes( serialization.Encoding.PEM, diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index 8ccf4c30..f6d11a09 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -160,7 +160,7 @@ class RSAKey(PKey): generate a new host key or authentication key. :param int bits: number of bits the generated key should be. - :param function progress_func: Unused + :param progress_func: Unused :return: new `.RSAKey` private key """ key = rsa.generate_private_key( diff --git a/paramiko/server.py b/paramiko/server.py index b2a07916..adc606bf 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -106,15 +106,15 @@ class ServerInterface (object): Determine if a client may open channels with no (further) authentication. - Return `.AUTH_FAILED` if the client must authenticate, or - `.AUTH_SUCCESSFUL` if it's okay for the client to not + Return ``AUTH_FAILED`` if the client must authenticate, or + ``AUTH_SUCCESSFUL`` if it's okay for the client to not authenticate. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the client. :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if it succeeds. :rtype: int """ @@ -125,21 +125,21 @@ class ServerInterface (object): Determine if a given username and password supplied by the client is acceptable for use in authentication. - Return `.AUTH_FAILED` if the password is not accepted, - `.AUTH_SUCCESSFUL` if the password is accepted and completes - the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the password is not accepted, + ``AUTH_SUCCESSFUL`` if the password is accepted and completes + the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this key is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what options it has for continuing the authentication.) - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client. :param str password: the password given by the client. :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if - it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the password auth is + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if + it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the password auth is successful, but authentication must continue. :rtype: int """ @@ -152,9 +152,9 @@ class ServerInterface (object): check the username and key and decide if you would accept a signature made using this key. - Return `.AUTH_FAILED` if the key is not accepted, - `.AUTH_SUCCESSFUL` if the key is accepted and completes the - authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the key is not accepted, + ``AUTH_SUCCESSFUL`` if the key is accepted and completes the + authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this password is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what @@ -164,13 +164,13 @@ class ServerInterface (object): If you're willing to accept the key, Paramiko will do the work of verifying the client's signature. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client :param .PKey key: the key object provided by the client :return: - `.AUTH_FAILED` if the client can't authenticate with this key; - `.AUTH_SUCCESSFUL` if it can; `.AUTH_PARTIALLY_SUCCESSFUL` if it + ``AUTH_FAILED`` if the client can't authenticate with this key; + ``AUTH_SUCCESSFUL`` if it can; ``AUTH_PARTIALLY_SUCCESSFUL`` if it can authenticate with this key but must continue with authentication :rtype: int @@ -184,19 +184,19 @@ class ServerInterface (object): ``"keyboard-interactive"`` auth type, which requires you to send a series of questions for the client to answer. - Return `.AUTH_FAILED` if this auth method isn't supported. Otherwise, + Return ``AUTH_FAILED`` if this auth method isn't supported. Otherwise, you should return an `.InteractiveQuery` object containing the prompts and instructions for the user. The response will be sent via a call to `check_auth_interactive_response`. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client :param str submethods: a comma-separated list of methods preferred by the client (usually empty) :return: - `.AUTH_FAILED` if this auth method isn't supported; otherwise an + ``AUTH_FAILED`` if this auth method isn't supported; otherwise an object containing queries for the user :rtype: int or `.InteractiveQuery` """ @@ -208,9 +208,9 @@ class ServerInterface (object): supported. You should override this method in server mode if you want to support the ``"keyboard-interactive"`` auth type. - Return `.AUTH_FAILED` if the responses are not accepted, - `.AUTH_SUCCESSFUL` if the responses are accepted and complete - the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the responses are not accepted, + ``AUTH_SUCCESSFUL`` if the responses are accepted and complete + the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this set of responses is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what @@ -221,12 +221,12 @@ class ServerInterface (object): client to respond with more answers, calling this method again. This cycle can continue indefinitely. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param list responses: list of `str` responses from the client :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if - it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the interactive auth + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if + it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the interactive auth is successful, but authentication must continue; otherwise an object containing queries for the user :rtype: int or `.InteractiveQuery` @@ -243,8 +243,8 @@ class ServerInterface (object): :param str username: The username of the authenticating client :param int gss_authenticated: The result of the krb5 authentication :param str cc_filename: The krb5 client credentials cache filename - :return: `.AUTH_FAILED` if the user is not authenticated otherwise - `.AUTH_SUCCESSFUL` + :return: ``AUTH_FAILED`` if the user is not authenticated otherwise + ``AUTH_SUCCESSFUL`` :rtype: int :note: Kerberos credential delegation is not supported. :see: `.ssh_gss` @@ -257,7 +257,7 @@ class ServerInterface (object): your local kerberos library to make sure that the krb5_principal has an account on the server and is allowed to log in as a user. - :see: `http://www.unix.com/man-page/all/3/krb5_kuserok/` + :see: http://www.unix.com/man-page/all/3/krb5_kuserok/ """ if gss_authenticated == AUTH_SUCCESSFUL: return AUTH_SUCCESSFUL @@ -275,8 +275,8 @@ class ServerInterface (object): :param str username: The username of the authenticating client :param int gss_authenticated: The result of the krb5 authentication :param str cc_filename: The krb5 client credentials cache filename - :return: `.AUTH_FAILED` if the user is not authenticated otherwise - `.AUTH_SUCCESSFUL` + :return: ``AUTH_FAILED`` if the user is not authenticated otherwise + ``AUTH_SUCCESSFUL`` :rtype: int :note: Kerberos credential delegation is not supported. :see: `.ssh_gss` `.kex_gss` @@ -289,7 +289,7 @@ class ServerInterface (object): your local kerberos library to make sure that the krb5_principal has an account on the server and is allowed to log in as a user. - :see: `http://www.unix.com/man-page/all/3/krb5_kuserok/` + :see: http://www.unix.com/man-page/all/3/krb5_kuserok/ """ if gss_authenticated == AUTH_SUCCESSFUL: return AUTH_SUCCESSFUL @@ -301,9 +301,8 @@ class ServerInterface (object): authentication. The default implementation always returns false. - :return: True if GSSAPI authentication is enabled otherwise false - :rtype: Boolean - :see: : `.ssh_gss` + :returns bool: Whether GSSAPI authentication is enabled. + :see: `.ssh_gss` """ UseGSSAPI = False return UseGSSAPI diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index ea6f88a9..12fccb2f 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -83,8 +83,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem - :raises SSHException: if there's an exception while negotiating - sftp + :raises: + `.SSHException` -- if there's an exception while negotiating sftp """ BaseSFTP.__init__(self) self.sock = sock @@ -321,7 +321,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int bufsize: desired buffering (-1 = default buffer size) :return: an `.SFTPFile` object representing the open file - :raises IOError: if the file could not be opened. + :raises: ``IOError`` -- if the file could not be opened. """ filename = self._adjust_cwd(filename) self._log(DEBUG, 'open(%r, %r)' % (filename, mode)) @@ -356,7 +356,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: path (absolute or relative) of the file to remove - :raises IOError: if the path refers to a folder (directory) + :raises: ``IOError`` -- if the path refers to a folder (directory) """ path = self._adjust_cwd(path) self._log(DEBUG, 'remove(%r)' % path) @@ -371,7 +371,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str oldpath: existing name of the file or folder :param str newpath: new name for the file or folder - :raises IOError: if ``newpath`` is a folder, or something else goes + :raises: + ``IOError`` -- if ``newpath`` is a folder, or something else goes wrong """ oldpath = self._adjust_cwd(oldpath) @@ -522,8 +523,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): method on Python file objects. :param str path: path of the file to modify - :param size: the new size of the file - :type size: int or long + :param int size: the new size of the file """ path = self._adjust_cwd(path) self._log(DEBUG, 'truncate(%r, %r)' % (path, size)) @@ -562,7 +562,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: path to be normalized :return: normalized form of the given path (as a `str`) - :raises IOError: if the path can't be resolved on the server + :raises: ``IOError`` -- if the path can't be resolved on the server """ path = self._adjust_cwd(path) self._log(DEBUG, 'normalize(%r)' % path) @@ -585,7 +585,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: new current working directory - :raises IOError: if the requested path doesn't exist on the server + :raises: + ``IOError`` -- if the requested path doesn't exist on the server .. versionadded:: 1.4 """ diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 58653c79..337cdbeb 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -251,6 +251,11 @@ class SFTPFile (BufferedFile): return True def seek(self, offset, whence=0): + """ + Set the file's current position. + + See `file.seek` for details. + """ self.flush() if whence == self.SEEK_SET: self._realpos = self._pos = offset @@ -267,8 +272,8 @@ class SFTPFile (BufferedFile): exactly like `.SFTPClient.stat`, except that it operates on an already-open file. - :return: an `.SFTPAttributes` object containing attributes about this - file. + :returns: + an `.SFTPAttributes` object containing attributes about this file. """ t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: @@ -332,7 +337,6 @@ class SFTPFile (BufferedFile): Python file objects. :param size: the new size of the file - :type size: int or long """ self.sftp._log( DEBUG, @@ -370,21 +374,18 @@ class SFTPFile (BufferedFile): :param offset: offset into the file to begin hashing (0 means to start from the beginning) - :type offset: int or long :param length: number of bytes to hash (0 means continue to the end of the file) - :type length: int or long :param int block_size: number of bytes to hash per result (must not be less than 256; 0 means to compute only one hash of the entire segment) - :type block_size: int :return: `str` of bytes representing the hash of each block, concatenated together - :raises IOError: if the server doesn't support the "check-file" - extension, or possibly doesn't support the hash algorithm - requested + :raises: + ``IOError`` -- if the server doesn't support the "check-file" + extension, or possibly doesn't support the hash algorithm requested .. note:: Many (most?) servers don't support this extension yet. @@ -466,9 +467,8 @@ class SFTPFile (BufferedFile): once. :param chunks: - a list of (offset, length) tuples indicating which sections of the - file to read - :type chunks: list(tuple(long, int)) + a list of ``(offset, length)`` tuples indicating which sections of + the file to read :return: a list of blocks read, in the same order as in ``chunks`` .. versionadded:: 1.5.4 diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py index 2d2e621c..ca473900 100644 --- a/paramiko/sftp_handle.py +++ b/paramiko/sftp_handle.py @@ -77,7 +77,7 @@ class SFTPHandle (ClosingContextManager): to be 64 bits. If the end of the file has been reached, this method may return an - empty string to signify EOF, or it may also return `.SFTP_EOF`. + empty string to signify EOF, or it may also return ``SFTP_EOF``. The default implementation checks for an attribute on ``self`` named ``readfile``, and if present, performs the read operation on the Python @@ -85,7 +85,6 @@ class SFTPHandle (ClosingContextManager): common case where you are wrapping a Python file object.) :param offset: position in the file to start reading from. - :type offset: int or long :param int length: number of bytes to attempt to read. :return: data read from the file, or an SFTP error code, as a `str`. """ @@ -120,9 +119,8 @@ class SFTPHandle (ClosingContextManager): refer to the same file. :param offset: position in the file to start reading from. - :type offset: int or long :param str data: data to write into the file. - :return: an SFTP error code like `.SFTP_OK`. + :return: an SFTP error code like ``SFTP_OK``. """ writefile = getattr(self, 'writefile', None) if writefile is None: @@ -152,7 +150,7 @@ class SFTPHandle (ClosingContextManager): :return: an attributes object for the given file, or an SFTP error code - (like `.SFTP_PERMISSION_DENIED`). + (like ``SFTP_PERMISSION_DENIED``). :rtype: `.SFTPAttributes` or error code """ return SFTP_OP_UNSUPPORTED @@ -164,7 +162,7 @@ class SFTPHandle (ClosingContextManager): check for the presence of fields before using them. :param .SFTPAttributes attr: the attributes to change on this file. - :return: an `int` error code like `.SFTP_OK`. + :return: an `int` error code like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index f94c5e39..1cfe286b 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -72,7 +72,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler): :param str name: name of the requested subsystem. :param .ServerInterface server: the server object associated with this channel and subsystem - :param class sftp_si: + :param sftp_si: a subclass of `.SFTPServerInterface` to use for handling individual requests. """ diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py index c335eaec..09e7025c 100644 --- a/paramiko/sftp_si.py +++ b/paramiko/sftp_si.py @@ -72,7 +72,7 @@ class SFTPServerInterface (object): on that file. On success, a new object subclassed from `.SFTPHandle` should be returned. This handle will be used for future operations on the file (read, write, etc). On failure, an error code such as - `.SFTP_PERMISSION_DENIED` should be returned. + ``SFTP_PERMISSION_DENIED`` should be returned. ``flags`` contains the requested mode for opening (read-only, write-append, etc) as a bitset of flags from the ``os`` module: @@ -120,7 +120,7 @@ class SFTPServerInterface (object): `.SFTPAttributes.from_stat` will usually do what you want. In case of an error, you should return one of the ``SFTP_*`` error - codes, such as `.SFTP_PERMISSION_DENIED`. + codes, such as ``SFTP_PERMISSION_DENIED``. :param str path: the requested path (relative or absolute) to be listed. @@ -150,7 +150,7 @@ class SFTPServerInterface (object): for. :return: an `.SFTPAttributes` object for the given file, or an SFTP error - code (like `.SFTP_PERMISSION_DENIED`). + code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED @@ -168,7 +168,7 @@ class SFTPServerInterface (object): :type path: str :return: an `.SFTPAttributes` object for the given file, or an SFTP error - code (like `.SFTP_PERMISSION_DENIED`). + code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED @@ -178,7 +178,7 @@ class SFTPServerInterface (object): :param str path: the requested path (relative or absolute) of the file to delete. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -197,7 +197,7 @@ class SFTPServerInterface (object): :param str oldpath: the requested path (relative or absolute) of the existing file. :param str newpath: the requested new path of the file. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -214,7 +214,7 @@ class SFTPServerInterface (object): :param str path: requested path (relative or absolute) of the new folder. :param .SFTPAttributes attr: requested attributes of the new folder. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -226,7 +226,7 @@ class SFTPServerInterface (object): :param str path: requested path (relative or absolute) of the folder to remove. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -241,7 +241,7 @@ class SFTPServerInterface (object): :param attr: requested attributes to change on the file (an `.SFTPAttributes` object) - :return: an error code `int` like `.SFTP_OK`. + :return: an error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -277,7 +277,7 @@ class SFTPServerInterface (object): :param str path: path (relative or absolute) of the symbolic link. :return: the target `str` path of the symbolic link, or an error code like - `.SFTP_NO_SUCH_FILE`. + ``SFTP_NO_SUCH_FILE``. """ return SFTP_OP_UNSUPPORTED diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 280a7f39..e9ab8d66 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -50,12 +50,10 @@ class BadAuthenticationType (AuthenticationException): the server isn't allowing that type. (It may only allow public-key, for example.) - :ivar list allowed_types: - list of allowed authentication types provided by the server (possible - values are: ``"none"``, ``"password"``, and ``"publickey"``). - .. versionadded:: 1.1 """ + #: list of allowed authentication types provided by the server (possible + #: values are: ``"none"``, ``"password"``, and ``"publickey"``). allowed_types = [] def __init__(self, explanation, types): @@ -87,7 +85,7 @@ class ChannelException (SSHException): """ Exception raised when an attempt to open a new `.Channel` fails. - :ivar int code: the error code returned by the server + :param int code: the error code returned by the server .. versionadded:: 1.6 """ @@ -102,9 +100,9 @@ class BadHostKeyException (SSHException): """ The host key given by the SSH server did not match what we were expecting. - :ivar str hostname: the hostname of the SSH server - :ivar PKey got_key: the host key presented by the server - :ivar PKey expected_key: the host key expected + :param str hostname: the hostname of the SSH server + :param PKey got_key: the host key presented by the server + :param PKey expected_key: the host key expected .. versionadded:: 1.6 """ @@ -125,8 +123,8 @@ class ProxyCommandFailure (SSHException): """ The "ProxyCommand" found in the .ssh/config file returned an error. - :ivar str command: The command line that is generating this exception. - :ivar str error: The error captured from the proxy command output. + :param str command: The command line that is generating this exception. + :param str error: The error captured from the proxy command output. """ def __init__(self, command, error): SSHException.__init__(self, diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 9c88c6fc..414485f9 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -72,9 +72,8 @@ def GSSAuth(auth_method, gss_deleg_creds=True): We delegate credentials by default. :return: Either an `._SSH_GSSAPI` (Unix) object or an `_SSH_SSPI` (Windows) object - :rtype: Object - :raise ImportError: If no GSS-API / SSPI module could be imported. + :raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported. :see: `RFC 4462 <http://www.ietf.org/rfc/rfc4462.txt>`_ :note: Check for the available API and return either an `._SSH_GSSAPI` @@ -131,7 +130,6 @@ class _SSH_GSSAuth(object): as the only service value. :param str service: The desired SSH service - :rtype: Void """ if service.find("ssh-"): self._service = service @@ -142,7 +140,6 @@ class _SSH_GSSAuth(object): username is not set by C{ssh_init_sec_context}. :param str username: The name of the user who attempts to login - :rtype: Void """ self._username = username @@ -155,7 +152,6 @@ class _SSH_GSSAuth(object): :return: A byte sequence containing the number of supported OIDs, the length of the OID and the actual OID encoded with DER - :rtype: Bytes :note: In server mode we just return the OID length and the DER encoded OID. """ @@ -172,7 +168,6 @@ class _SSH_GSSAuth(object): :param str desired_mech: The desired GSS-API mechanism of the client :return: ``True`` if the given OID is supported, otherwise C{False} - :rtype: Boolean """ mech, __ = decoder.decode(desired_mech) if mech.__str__() != self._krb5_mech: @@ -187,7 +182,6 @@ class _SSH_GSSAuth(object): :param int integer: The integer value to convert :return: The byte sequence of an 32 bit integer - :rtype: Bytes """ return struct.pack("!I", integer) @@ -207,7 +201,6 @@ class _SSH_GSSAuth(object): string service (ssh-connection), string authentication-method (gssapi-with-mic or gssapi-keyex) - :rtype: Bytes """ mic = self._make_uint32(len(session_id)) mic += session_id @@ -256,11 +249,11 @@ class _SSH_GSSAPI(_SSH_GSSAuth): ("pseudo negotiated" mechanism, because we support just the krb5 mechanism :-)) :param str recv_token: The GSS-API token received from the Server - :raise SSHException: Is raised if the desired mechanism of the client - is not supported + :raises: + `.SSHException` -- Is raised if the desired mechanism of the client + is not supported :return: A ``String`` if the GSS-API has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._username = username self._gss_host = target @@ -304,8 +297,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): gssapi-keyex: Returns the MIC token from GSS-API with the SSH session ID as message. - :rtype: String - :see: `._ssh_build_mic` """ self._session_id = session_id if not gss_kex: @@ -329,7 +320,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): if it's not the initial call. :return: A ``String`` if the GSS-API has returned a token or ``None`` if no token was returned - :rtype: String or None """ # hostname and username are not required for GSSAPI, but for SSPI self._gss_host = hostname @@ -348,7 +338,7 @@ class _SSH_GSSAPI(_SSH_GSSAuth): :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login :return: None if the MIC check was successful - :raises gssapi.GSSException: if the MIC check failed + :raises: ``gssapi.GSSException`` -- if the MIC check failed """ self._session_id = session_id self._username = username @@ -371,7 +361,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): Checks if credentials are delegated (server mode). :return: ``True`` if credentials are delegated, otherwise ``False`` - :rtype: bool """ if self._gss_srv_ctxt.delegated_cred is not None: return True @@ -384,8 +373,9 @@ class _SSH_GSSAPI(_SSH_GSSAuth): (server mode). :param str client_token: The GSS-API token received form the client - :raise NotImplementedError: Credential delegation is currently not - supported in server mode + :raises: + ``NotImplementedError`` -- Credential delegation is currently not + supported in server mode """ raise NotImplementedError @@ -427,11 +417,11 @@ class _SSH_SSPI(_SSH_GSSAuth): ("pseudo negotiated" mechanism, because we support just the krb5 mechanism :-)) :param recv_token: The SSPI token received from the Server - :raise SSHException: Is raised if the desired mechanism of the client - is not supported + :raises: + `.SSHException` -- Is raised if the desired mechanism of the client + is not supported :return: A ``String`` if the SSPI has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._username = username self._gss_host = target @@ -476,8 +466,6 @@ class _SSH_SSPI(_SSH_GSSAuth): gssapi-keyex: Returns the MIC token from SSPI with the SSH session ID as message. - :rtype: String - :see: `._ssh_build_mic` """ self._session_id = session_id if not gss_kex: @@ -501,7 +489,6 @@ class _SSH_SSPI(_SSH_GSSAuth): if it's not the initial call. :return: A ``String`` if the SSPI has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._gss_host = hostname self._username = username @@ -522,7 +509,7 @@ class _SSH_SSPI(_SSH_GSSAuth): :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login :return: None if the MIC check was successful - :raises sspi.error: if the MIC check failed + :raises: ``sspi.error`` -- if the MIC check failed """ self._session_id = session_id self._username = username @@ -548,7 +535,6 @@ class _SSH_SSPI(_SSH_GSSAuth): Checks if credentials are delegated (server mode). :return: ``True`` if credentials are delegated, otherwise ``False`` - :rtype: Boolean """ return ( self._gss_flags & sspicon.ISC_REQ_DELEGATE and @@ -562,7 +548,8 @@ class _SSH_SSPI(_SSH_GSSAuth): (server mode). :param str client_token: The SSPI token received form the client - :raise NotImplementedError: Credential delegation is currently not - supported in server mode + :raises: + ``NotImplementedError`` -- Credential delegation is currently not + supported in server mode """ raise NotImplementedError diff --git a/paramiko/transport.py b/paramiko/transport.py index a5fcd047..d219550d 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -71,7 +71,6 @@ from paramiko.ssh_exception import ( from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value - # for thread cleanup _active_threads = [] @@ -79,11 +78,12 @@ def _join_lingering_threads(): for thr in _active_threads: thr.stop_thread() + import atexit atexit.register(_join_lingering_threads) -class Transport (threading.Thread, ClosingContextManager): +class Transport(threading.Thread, ClosingContextManager): """ An SSH Transport attaches to a stream (usually a socket), negotiates an encrypted session, authenticates, and then creates stream tunnels, called @@ -110,21 +110,22 @@ class Transport (threading.Thread, ClosingContextManager): 'aes192-cbc', 'aes256-cbc', '3des-cbc', - 'arcfour128', - 'arcfour256', ) _preferred_macs = ( 'hmac-sha2-256', 'hmac-sha2-512', + 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', - 'hmac-sha1', ) _preferred_keys = ( + 'ecdsa-sha2-nistp256', + 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', 'ssh-rsa', 'ssh-dss', - ) + tuple(ECDSAKey.supported_key_format_identifiers()) + ) _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', @@ -182,18 +183,6 @@ class Transport (threading.Thread, ClosingContextManager): 'block-size': 8, 'key-size': 24 }, - 'arcfour128': { - 'class': algorithms.ARC4, - 'mode': None, - 'block-size': 8, - 'key-size': 16 - }, - 'arcfour256': { - 'class': algorithms.ARC4, - 'mode': None, - 'block-size': 8, - 'key-size': 32 - }, } @@ -210,6 +199,8 @@ class Transport (threading.Thread, ClosingContextManager): 'ssh-rsa': RSAKey, 'ssh-dss': DSSKey, 'ecdsa-sha2-nistp256': ECDSAKey, + 'ecdsa-sha2-nistp384': ECDSAKey, + 'ecdsa-sha2-nistp521': ECDSAKey, } _kex_info = { @@ -453,7 +444,6 @@ class Transport (threading.Thread, ClosingContextManager): :param str gss_host: The targets name in the kerberos database Default: The name of the host to connect to - :rtype: Void """ # We need the FQDN to get this working with SSPI self.gss_host = socket.getfqdn(gss_host) @@ -486,8 +476,9 @@ class Transport (threading.Thread, ClosingContextManager): :param .threading.Event event: an event to trigger when negotiation is complete (optional) - :raises SSHException: if negotiation fails (and no ``event`` was passed - in) + :raises: + `.SSHException` -- if negotiation fails (and no ``event`` was + passed in) """ self.active = True if event is not None: @@ -547,8 +538,9 @@ class Transport (threading.Thread, ClosingContextManager): an object used to perform authentication and create `channels <.Channel>` - :raises SSHException: if negotiation fails (and no ``event`` was passed - in) + :raises: + `.SSHException` -- if negotiation fails (and no ``event`` was + passed in) """ if server is None: server = ServerInterface() @@ -670,7 +662,7 @@ class Transport (threading.Thread, ClosingContextManager): string)``. You can get the same effect by calling `.PKey.get_name` for the key type, and ``str(key)`` for the key string. - :raises SSHException: if no session is currently active. + :raises: `.SSHException` -- if no session is currently active. :return: public key (`.PKey`) of the remote server """ @@ -710,7 +702,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` - :raises SSHException: if the request is rejected or the session ends + :raises: + `.SSHException` -- if the request is rejected or the session ends prematurely .. versionchanged:: 1.13.4/1.14.3/1.15.3 @@ -733,7 +726,8 @@ class Transport (threading.Thread, ClosingContextManager): x11 port, ie. 6010) :return: a new `.Channel` - :raises SSHException: if the request is rejected or the session ends + :raises: + `.SSHException` -- if the request is rejected or the session ends prematurely """ return self.open_channel('x11', src_addr=src_addr) @@ -747,7 +741,7 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` - :raises SSHException: + :raises: `.SSHException` -- if the request is rejected or the session ends prematurely """ return self.open_channel('auth-agent@openssh.com') @@ -799,7 +793,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` on success - :raises SSHException: if the request is rejected, the session ends + :raises: + `.SSHException` -- if the request is rejected, the session ends prematurely or there is a timeout openning a channel .. versionchanged:: 1.15 @@ -886,7 +881,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: the port number (`int`) allocated by the server - :raises SSHException: if the server refused the TCP forward request + :raises: + `.SSHException` -- if the server refused the TCP forward request """ if not self.active: raise SSHException('SSH session not active') @@ -960,8 +956,9 @@ class Transport (threading.Thread, ClosingContextManager): traffic both ways as the two sides swap keys and do computations. This method returns when the session has switched to new keys. - :raises SSHException: if the key renegotiation failed (which causes the - session to end) + :raises: + `.SSHException` -- if the key renegotiation failed (which causes + the session to end) """ self.completion_event = threading.Event() self._send_kex_init() @@ -1102,7 +1099,7 @@ class Transport (threading.Thread, ClosingContextManager): :param bool gss_deleg_creds: Whether to delegate GSS-API client credentials. - :raises SSHException: if the SSH2 negotiation fails, the host key + :raises: `.SSHException` -- if the SSH2 negotiation fails, the host key supplied by the server is incorrect, or authentication fails. """ if hostkey is not None: @@ -1176,7 +1173,7 @@ class Transport (threading.Thread, ClosingContextManager): passed to the `.SubsystemHandler` constructor later. :param str name: name of the subsystem. - :param class handler: + :param handler: subclass of `.SubsystemHandler` that handles this subsystem. """ try: @@ -1237,9 +1234,11 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if "none" authentication isn't allowed + :raises: + `.BadAuthenticationType` -- if "none" authentication isn't allowed by the server for this user - :raises SSHException: if the authentication failed due to a network + :raises: + `.SSHException` -- if the authentication failed due to a network error .. versionadded:: 1.5 @@ -1290,11 +1289,13 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if password authentication isn't + :raises: + `.BadAuthenticationType` -- if password authentication isn't allowed by the server for this user (and no event was passed in) - :raises AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to send the password unless we're on a secure @@ -1359,11 +1360,13 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if public-key authentication isn't + :raises: + `.BadAuthenticationType` -- if public-key authentication isn't allowed by the server for this user (and no event was passed in) - :raises AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1415,10 +1418,10 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty). - :raises BadAuthenticationType: if public-key authentication isn't + :raises: `.BadAuthenticationType` -- if public-key authentication isn't allowed by the server for this user - :raises AuthenticationException: if the authentication failed - :raises SSHException: if there was a network error + :raises: `.AuthenticationException` -- if the authentication failed + :raises: `.SSHException` -- if there was a network error .. versionadded:: 1.5 """ @@ -1463,11 +1466,12 @@ class Transport (threading.Thread, ClosingContextManager): :return: list of auth types permissible for the next stage of authentication (normally empty) :rtype: list - :raise BadAuthenticationType: if gssapi-with-mic isn't + :raises: `.BadAuthenticationType` -- if gssapi-with-mic isn't allowed by the server (and no event was passed in) - :raise AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raise SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1487,12 +1491,12 @@ class Transport (threading.Thread, ClosingContextManager): :returns: a `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: + :raises: `.BadAuthenticationType` -- if GSS-API Key Exchange was not performed (and no event was passed in) - :raises AuthenticationException: + :raises: `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1714,21 +1718,6 @@ class Transport (threading.Thread, ClosingContextManager): def _get_cipher(self, name, key, iv, operation): if name not in self._cipher_info: raise SSHException('Unknown client cipher ' + name) - if name in ('arcfour128', 'arcfour256'): - # arcfour cipher - cipher = Cipher( - self._cipher_info[name]['class'](key), - None, - backend=default_backend() - ) - if operation is self._ENCRYPT: - engine = cipher.encryptor() - else: - engine = cipher.decryptor() - # as per RFC 4345, the first 1536 bytes of keystream - # generated by the cipher MUST be discarded - engine.encrypt(" " * 1536) - return engine else: cipher = Cipher( self._cipher_info[name]['class'](key), diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 553b98d1..dfa2fedc 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -6,11 +6,41 @@ Changelog a connection problem or protocol error, even if ``Transport.close()`` is called. Thanks Kyle Agronick for the discovery and investigation, and Pierce Lopez for assistance. +* :bug:`741` (also :issue:`809`, :issue:`772`; all via :issue:`912`) Writing + encrypted/password-protected private key files was silently broken since 2.0 + due to an incorrect API call; this has been fixed. + + Includes a directly related fix, namely adding the ability to read + ``AES-256-CBC`` ciphered private keys (which is now what we tend to write out + as it is Cryptography's default private key cipher.) + + Thanks to ``@virlos`` for the original report, Chris Harris and ``@ibuler`` + for initial draft PRs, and ``@jhgorrell`` for the final patch. +* :bug:`983` Move ``sha1`` above the now-arguably-broken ``md5`` in the list of + preferred MAC algorithms, as an incremental security improvement for users + whose target systems offer both. Credit: Pierce Lopez. +* :bug:`667` The RC4/arcfour family of ciphers has been broken since version + 2.0; but since the algorithm is now known to be completely insecure, we are + opting to remove support outright instead of fixing it. Thanks to Alex Gaynor + for catch & patch. +* :support:`- backported` A big formatting pass to clean up an enormous number + of invalid Sphinx reference links, discovered by switching to a modern, + rigorous nitpicking doc-building mode. +* :bug:`900` (via :issue:`911`) Prefer newer ``ecdsa-sha2-nistp`` keys over RSA + and DSA keys during host key selection. This improves compatibility with + OpenSSH, both in terms of general behavior, and also re: ability to properly + leverage OpenSSH-modified ``known_hosts`` files. Credit: ``@kasdoe`` for + original report/PR and Pierce Lopez for the second draft. +* :bug:`794` (via :issue:`981`) Prior support for ``ecdsa-sha2-nistp(384|521)`` + algorithms didn't fully extend to covering host keys, preventing connection + to hosts which only offer these key types and no others. This is now fixed. + Thanks to ``@ncoult`` and ``@kasdoe`` for reports and Pierce Lopez for the + patch. * :support:`974 backported` Overhaul the codebase to be PEP-8, etc, compliant (i.e. passes the maintainer's preferred `flake8 <http://flake8.pycqa.org/>`_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to Dorian Pula! -* :bug:`683` Make `util.log_to_file()` append instead of replace. Thanks +* :bug:`683` Make ``util.log_to_file`` append instead of replace. Thanks to ``@vlcinsky`` for the report. * :release:`2.0.5 <2017-02-20>` * :release:`1.18.2 <2017-02-20>` @@ -86,7 +116,7 @@ Changelog * :bug:`334 (1.17+)` Make the ``subprocess`` import in ``proxy.py`` lazy so users on platforms without it (such as Google App Engine) can import Paramiko successfully. (Relatedly, make it easier to tweak an active socket check - timeout [in `Transport <paramko.transport.Transport>`] which was previously + timeout [in `Transport <paramiko.transport.Transport>`] which was previously hardcoded.) Credit: Shinya Okano. * :support:`854 backported (1.17+)` Fix incorrect docstring/param-list for `Transport.auth_gssapi_keyex @@ -141,10 +171,10 @@ Changelog ``proxycommand`` key in parsed config structures). Thanks to Pat Brisbin for the catch. * :bug:`676` (via :issue:`677`) Fix a backwards incompatibility issue that - cropped up in `SFTPFile.prefetch <~paramiko.sftp_file.prefetch>` re: the - erroneously non-optional ``file_size`` parameter. Should only affect users - who manually call ``prefetch``. Thanks to ``@stevevanhooser`` for catch & - patch. + cropped up in `SFTPFile.prefetch <paramiko.sftp_file.SFTPFile.prefetch>` re: + the erroneously non-optional ``file_size`` parameter. Should only affect + users who manually call ``prefetch``. Thanks to ``@stevevanhooser`` for catch + & patch. * :feature:`394` Replace PyCrypto with the Python Cryptographic Authority (PyCA) 'Cryptography' library suite. This improves security, installability, and performance; adds PyPy support; and much more. @@ -234,7 +264,7 @@ Changelog * :release:`1.15.4 <2015-11-02>` * :release:`1.14.3 <2015-11-02>` * :release:`1.13.4 <2015-11-02>` -* :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string +* :bug:`366` Fix `~paramiko.sftp_attr.SFTPAttributes` so its string representation doesn't raise exceptions on empty/initialized instances. Patch by Ulrich Petri. * :bug:`359` Use correct attribute name when trying to use Python 3's @@ -345,8 +375,9 @@ Changelog * :release:`1.15.1 <2014-09-22>` * :bug:`399` SSH agent forwarding (potentially other functionality as well) would hang due to incorrect values passed into the new window size - arguments for `.Transport` (thanks to a botched merge). This has been - corrected. Thanks to Dylan Thacker-Smith for the report & patch. + arguments for `~paramiko.transport.Transport` (thanks to a botched merge). + This has been corrected. Thanks to Dylan Thacker-Smith for the report & + patch. * :feature:`167` Add `~paramiko.config.SSHConfig.get_hostnames` for easier introspection of a loaded SSH config file or object. Courtesy of Søren Løvborg. diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 24d78c3e..394a2cf4 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -120,6 +120,18 @@ class KeyTest (unittest.TestCase): def tearDown(self): pass + def assert_keyfile_is_encrypted(self, keyfile): + """ + A quick check that filename looks like an encrypted key. + """ + with open(keyfile, "r") as fh: + self.assertEqual( + fh.readline()[:-1], + "-----BEGIN RSA PRIVATE KEY-----" + ) + self.assertEqual(fh.readline()[:-1], "Proc-Type: 4,ENCRYPTED") + self.assertEqual(fh.readline()[0:10], "DEK-Info: ") + def test_1_generate_key_bytes(self): key = util.generate_key_bytes(md5, x1234, 'happy birthday', 30) exp = b'\x61\xE1\xF2\x72\xF4\xC1\xC4\x56\x15\x86\xBD\x32\x24\x98\xC0\xE9\x24\x67\x27\x80\xF4\x7B\xB3\x7D\xDA\x7D\x54\x01\x9E\x64' @@ -426,6 +438,7 @@ class KeyTest (unittest.TestCase): # When the bug under test exists, this will ValueError. try: key.write_private_key_file(newfile, password=newpassword) + self.assert_keyfile_is_encrypted(newfile) # Verify the inner key data still matches (when no ValueError) key2 = RSAKey(filename=newfile, password=newpassword) self.assertEqual(key, key2) @@ -436,3 +449,18 @@ class KeyTest (unittest.TestCase): key = RSAKey.from_private_key_file(test_path('test_rsa.key')) comparable = TEST_KEY_BYTESTR_2 if PY2 else TEST_KEY_BYTESTR_3 self.assertEqual(str(key), comparable) + + def test_keyfile_is_actually_encrypted(self): + # Read an existing encrypted private key + file_ = test_path('test_rsa_password.key') + password = 'television' + newfile = file_ + '.new' + newpassword = 'radio' + key = RSAKey(filename=file_, password=password) + # Write out a newly re-encrypted copy with a new password. + # When the bug under test exists, this will ValueError. + try: + key.write_private_key_file(newfile, password=newpassword) + self.assert_keyfile_is_encrypted(newfile) + finally: + os.remove(newfile) diff --git a/tests/test_transport.py b/tests/test_transport.py index 2ebdf854..c426cef1 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -165,6 +165,15 @@ class TransportTest(unittest.TestCase): except TypeError: pass + def test_1b_security_options_reset(self): + o = self.tc.get_security_options() + # should not throw any exceptions + o.ciphers = o.ciphers + o.digests = o.digests + o.key_types = o.key_types + o.kex = o.kex + o.compression = o.compression + def test_2_compute_key(self): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' |