[Python-modules-commits] [python-asyncssh] 02/08: Import python-asyncssh_1.10.1.orig.tar.gz
Vincent Bernat
bernat at moszumanska.debian.org
Mon Sep 18 10:02:30 UTC 2017
This is an automated email from the git hooks/post-receive script.
bernat pushed a commit to branch debian/master
in repository python-asyncssh.
commit 0dc15b0d2a6d3a2bb983e7d42845220212169792
Author: Vincent Bernat <bernat at debian.org>
Date: Wed Jul 5 16:49:20 2017 +0200
Import python-asyncssh_1.10.1.orig.tar.gz
---
.travis.yml | 2 +-
COPYRIGHT | 2 +-
README.rst | 18 +-
appveyor.yml | 32 +
asyncssh/__init__.py | 7 +-
asyncssh/auth.py | 354 ++++-
asyncssh/channel.py | 5 +
asyncssh/client.py | 9 +-
asyncssh/connection.py | 364 +++--
asyncssh/dh.py | 461 ++++--
asyncssh/ecdh.py | 9 +-
asyncssh/gss.py | 55 +
asyncssh/gss_unix.py | 146 ++
asyncssh/gss_win32.py | 173 ++
asyncssh/kex.py | 41 +-
asyncssh/known_hosts.py | 29 +-
asyncssh/misc.py | 10 +-
asyncssh/process.py | 476 ++++--
asyncssh/public_key.py | 71 +-
asyncssh/scp.py | 832 ++++++++++
asyncssh/server.py | 42 +-
asyncssh/sftp.py | 798 +++++++---
asyncssh/stream.py | 84 +-
asyncssh/version.py | 4 +-
asyncssh/x11.py | 9 +-
docs/api.rst | 168 +-
docs/changes.rst | 85 +-
docs/conf.py | 2 +
docs/index.rst | 91 ++
examples/chat_server.py | 19 +-
examples/editor.py | 27 +-
examples/math_server.py | 16 +-
.../{simple_cert_server.py => redirect_server.py} | 18 +-
examples/scp_client.py | 23 +
examples/show_environment.py | 18 +-
examples/show_terminal.py | 29 +-
examples/simple_cert_server.py | 12 +-
examples/simple_keyed_server.py | 12 +-
...{simple_cert_server.py => simple_scp_server.py} | 9 +-
examples/simple_server.py | 12 +-
pylintrc | 4 +-
setup.py | 1 +
tests/gss_stub.py | 43 +
tests/gssapi_stub.py | 128 ++
tests/server.py | 65 +-
tests/sspi_stub.py | 125 ++
tests/test_auth.py | 238 ++-
tests/test_cipher.py | 5 +-
tests/test_connection.py | 138 +-
tests/test_connection_auth.py | 257 ++-
tests/test_forward.py | 8 +
tests/test_kex.py | 259 ++-
tests/test_known_hosts.py | 25 +-
tests/test_process.py | 556 ++++---
tests/test_public_key.py | 117 +-
tests/test_sftp.py | 1674 +++++++++++++++++---
tests/test_x11.py | 18 +-
tests/util.py | 84 +-
tests_py35/test_sftp.py | 18 +-
tox.ini | 21 +-
60 files changed, 6860 insertions(+), 1498 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index d471d7d..16c130f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
language: python
install:
- - pip install tox bcrypt libnacl
+ - pip install tox
matrix:
allow_failures:
diff --git a/COPYRIGHT b/COPYRIGHT
index 5c6c0dc..e5b8563 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
All rights reserved.
This program and the accompanying materials are made available under
diff --git a/README.rst b/README.rst
index c83c04b..47b3925 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,7 @@ __ http://asyncssh.readthedocs.io/en/stable/#client-examples
Features
--------
-* Full support for SSHv2 and SFTP client and server functions
+* Full support for SSHv2, SFTP, and SCP client and server functions
* Shell, command, and subsystem channels
* Environment variables, terminal type, and window size
@@ -36,6 +36,7 @@ Features
* Local and remote UNIX domain socket forwarding
* X11 forwarding support on both the client and the server
* SFTP protocol version 3 with OpenSSH extensions
+ * SCP protocol support, including third-party remote to remote copies
* Multiple simultaneous sessions on a single SSH connection
* Multiple SSH connections in a single event loop
@@ -73,7 +74,7 @@ License
This package is released under the following terms:
- Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+ Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
All rights reserved.
This program and the accompanying materials are made available under
@@ -111,6 +112,9 @@ functionality:
* Install bcrypt from https://pypi.python.org/pypi/bcrypt
if you want support for OpenSSH private key encryption.
+* Install gssapi from https://pypi.python.org/pypi/gssapi if you
+ want support for GSSAPI key exchange and authentication on UNIX.
+
* Install libsodium from https://github.com/jedisct1/libsodium
and libnacl from https://pypi.python.org/pypi/libnacl if you want
support for curve25519 Diffie Hellman key exchange, ed25519 keys,
@@ -120,22 +124,24 @@ functionality:
if you want support for UMAC cryptographic hashes.
* Install pypiwin32 from https://pypi.python.org/pypi/pypiwin32
- if you want support for using the Pageant agent on Windows.
+ if you want support for using the Pageant agent or support for
+ GSSAPI key exchange and authentication on Windows.
AsyncSSH defines the following optional PyPI extra packages to make it
easy to install any or all of these dependencies:
| bcrypt
+ | gssapi
| libnacl
| pypiwin32
-For example, to install bcrypt and libnacl, you can run:
+For example, to install bcrypt, gssapi, and libnacl on UNIX, you can run:
::
- pip install 'asyncssh[bcrypt,libnacl]'
+ pip install 'asyncssh[bcrypt,gssapi,libnacl]'
-To install all three of these packages on a Windows system, you can run:
+To install bcrypt, libnacl, and pypiwin32 on Windows, you can run:
::
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..6233062
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,32 @@
+environment:
+ matrix:
+ - PYTHON: "C:\\Python34"
+ ARCH: win32
+
+ - PYTHON: "C:\\Python34-x64"
+ ARCH: win64
+
+ - PYTHON: "C:\\Python35"
+ ARCH: win32
+
+ - PYTHON: "C:\\Python35-x64"
+ ARCH: win64
+
+ - PYTHON: "C:\\Python36"
+ ARCH: win32
+
+ - PYTHON: "C:\\Python36-x64"
+ ARCH: win64
+
+install:
+ - "cd %PYTHON%"
+ - "curl https://www.timeheart.net/appveyor/%ARCH%/libsodium-18.dll -O"
+ - "curl https://www.timeheart.net/appveyor/%ARCH%/libnettle-6.dll -O"
+ - "curl https://www.timeheart.net/appveyor/%ARCH%/libhogweed-4.dll -O"
+ - "cd %APPVEYOR_BUILD_FOLDER%"
+ - "%PYTHON%\\python.exe -m pip install tox"
+
+build: off
+
+test_script:
+ - "%PYTHON%\\python.exe -m tox -e py"
diff --git a/asyncssh/__init__.py b/asyncssh/__init__.py
index 0785f7c..3e13b6b 100644
--- a/asyncssh/__init__.py
+++ b/asyncssh/__init__.py
@@ -49,7 +49,8 @@ from .misc import BreakReceived, SignalReceived, TerminalSizeChanged
from .pbe import KeyEncryptionError
-from .process import SSHClientProcess, SSHCompletedProcess, ProcessError
+from .process import SSHClientProcess, SSHServerProcess
+from .process import SSHCompletedProcess, ProcessError
from .process import DEVNULL, PIPE, STDOUT
from .public_key import SSHKey, SSHKeyPair, SSHCertificate
@@ -61,12 +62,14 @@ from .public_key import read_private_key_list, read_public_key_list
from .public_key import read_certificate_list
from .public_key import load_keypairs, load_public_keys
+from .scp import scp
+
from .session import SSHClientSession, SSHServerSession
from .session import SSHTCPSession, SSHUNIXSession
from .server import SSHServer
-from .sftp import SFTPClient, SFTPServer, SFTPFile, SFTPError
+from .sftp import SFTPClient, SFTPClientFile, SFTPServer, SFTPError
from .sftp import SFTPAttrs, SFTPVFSAttrs, SFTPName
from .sftp import SEEK_SET, SEEK_CUR, SEEK_END
diff --git a/asyncssh/auth.py b/asyncssh/auth.py
index 603ebad..178a9b2 100644
--- a/asyncssh/auth.py
+++ b/asyncssh/auth.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -14,7 +14,9 @@
import asyncio
-from .constants import DISC_PROTOCOL_ERROR
+from .constants import DEFAULT_LANG, DISC_PROTOCOL_ERROR
+from .gss import GSSError
+from .logging import logger
from .misc import DisconnectError, PasswordChangeRequired
from .packet import Boolean, Byte, String, UInt32, SSHPacketHandler
from .saslprep import saslprep, SASLPrepError
@@ -22,15 +24,23 @@ from .saslprep import saslprep, SASLPrepError
# pylint: disable=bad-whitespace
+# SSH message values for GSS auth
+MSG_USERAUTH_GSSAPI_RESPONSE = 60
+MSG_USERAUTH_GSSAPI_TOKEN = 61
+MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = 63
+MSG_USERAUTH_GSSAPI_ERROR = 64
+MSG_USERAUTH_GSSAPI_ERRTOK = 65
+MSG_USERAUTH_GSSAPI_MIC = 66
+
# SSH message values for public key auth
-MSG_USERAUTH_PK_OK = 60
+MSG_USERAUTH_PK_OK = 60
# SSH message values for password auth
-MSG_USERAUTH_PASSWD_CHANGEREQ = 60
+MSG_USERAUTH_PASSWD_CHANGEREQ = 60
# SSH message values for 'keyboard-interactive' auth
-MSG_USERAUTH_INFO_REQUEST = 60
-MSG_USERAUTH_INFO_RESPONSE = 61
+MSG_USERAUTH_INFO_REQUEST = 60
+MSG_USERAUTH_INFO_RESPONSE = 61
# pylint: enable=bad-whitespace
@@ -44,9 +54,7 @@ class _Auth(SSHPacketHandler):
def __init__(self, conn, coro):
self._conn = conn
- self._coro = None
-
- self.create_task(coro)
+ self._coro = conn.create_task(coro)
def create_task(self, coro):
"""Create an asynchronous auth task"""
@@ -57,7 +65,7 @@ class _Auth(SSHPacketHandler):
def cancel(self):
"""Cancel any authentication in progress"""
- if self._coro:
+ if self._coro: # pragma: no branch
self._coro.cancel()
self._coro = None
@@ -101,6 +109,149 @@ class _ClientNullAuth(_ClientAuth):
yield from self.send_request()
+class _ClientGSSKexAuth(_ClientAuth):
+ """Client side implementation of GSS key exchange auth"""
+
+ @asyncio.coroutine
+ def _start(self):
+ """Start client GSS key exchange authentication"""
+
+ if self._conn.gss_kex_auth_requested():
+ yield from self.send_request(key=self._conn.get_gss_context())
+ else:
+ self._conn.try_next_auth()
+
+
+class _ClientGSSMICAuth(_ClientAuth):
+ """Client side implementation of GSS MIC auth"""
+
+ def __init__(self, conn, method):
+ super().__init__(conn, method)
+
+ self._gss = None
+ self._got_error = False
+
+ @asyncio.coroutine
+ def _start(self):
+ """Start client GSS MIC authentication"""
+
+ if self._conn.gss_mic_auth_requested():
+ self._gss = self._conn.get_gss_context()
+ mechs = b''.join((String(mech) for mech in self._gss.mechs))
+ yield from self.send_request(UInt32(len(self._gss.mechs)), mechs)
+ else:
+ self._conn.try_next_auth()
+
+ def _finish(self):
+ """Finish client GSS MIC authentication"""
+
+ if self._gss.provides_integrity:
+ data = self._conn.get_userauth_request_data(self._method)
+
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_MIC),
+ String(self._gss.sign(data)))
+ else:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE))
+
+ def _process_response(self, pkttype, packet):
+ """Process a GSS response from the server"""
+
+ # pylint: disable=unused-argument
+
+ mech = packet.get_string()
+ packet.check_end()
+
+ if mech not in self._gss.mechs:
+ raise DisconnectError(DISC_PROTOCOL_ERROR, 'Mechanism mismatch')
+
+ try:
+ token = self._gss.step()
+
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+ String(token))
+
+ if self._gss.complete:
+ self._finish()
+ except GSSError as exc:
+ if exc.token:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+ String(exc.token))
+
+ self._conn.try_next_auth()
+
+ return True
+
+ def _process_token(self, pkttype, packet):
+ """Process a GSS token from the server"""
+
+ # pylint: disable=unused-argument
+
+ token = packet.get_string()
+ packet.check_end()
+
+ try:
+ token = self._gss.step(token)
+
+ if token:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+ String(token))
+
+ if self._gss.complete:
+ self._finish()
+ except GSSError as exc:
+ if exc.token:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+ String(exc.token))
+
+ self._conn.try_next_auth()
+
+ return True
+
+ def _process_error(self, pkttype, packet):
+ """Process a GSS error from the server"""
+
+ # pylint: disable=unused-argument
+
+ _ = packet.get_uint32() # major_status
+ _ = packet.get_uint32() # minor_status
+ msg = packet.get_string()
+ _ = packet.get_string() # lang
+ packet.check_end()
+
+ logger.warning('GSS error from server: %s',
+ msg.decode('utf-8', errors='ignore'))
+ self._got_error = True
+
+ return True
+
+ def _process_error_token(self, pkttype, packet):
+ """Process a GSS error token from the server"""
+
+ # pylint: disable=no-self-use,unused-argument
+
+ token = packet.get_string()
+ packet.check_end()
+
+ try:
+ self._gss.step(token)
+ except GSSError as exc:
+ if not self._got_error: # pragma: no cover
+ logger.warning('GSS error from server: %s', str(exc))
+
+ return True
+
+ # pylint: disable=bad-whitespace
+
+ packet_handlers = {
+ MSG_USERAUTH_GSSAPI_RESPONSE: _process_response,
+ MSG_USERAUTH_GSSAPI_TOKEN: _process_token,
+ MSG_USERAUTH_GSSAPI_ERROR: _process_error,
+ MSG_USERAUTH_GSSAPI_ERRTOK: _process_error_token
+ }
+
+ # pylint: enable=bad-whitespace
+
+
class _ClientPublicKeyAuth(_ClientAuth):
"""Client side implementation of public key auth"""
@@ -297,8 +448,9 @@ class _ClientPasswordAuth(_ClientAuth):
class _ServerAuth(_Auth):
"""Parent class for server authentication"""
- def __init__(self, conn, username, packet):
+ def __init__(self, conn, username, method, packet):
self._username = username
+ self._method = method
super().__init__(conn, self._start(packet))
@@ -332,10 +484,176 @@ class _ServerNullAuth(_ServerAuth):
@asyncio.coroutine
def _start(self, packet):
- """Always fail null server authentication"""
+ """Supported always returns false, so we never get here"""
+
+
+class _ServerGSSKexAuth(_ServerAuth):
+ """Server side implementation of GSS key exchange auth"""
+
+ def __init__(self, conn, username, method, packet):
+ super().__init__(conn, username, method, packet)
+
+ self._gss = conn.get_gss_context()
+
+ @classmethod
+ def supported(cls, conn):
+ """Return whether GSS key exchange authentication is supported"""
+
+ return conn.gss_kex_auth_supported()
+
+ @asyncio.coroutine
+ def _start(self, packet):
+ """Start server GSS key exchange authentication"""
+
+ mic = packet.get_string()
+ packet.check_end()
+
+ data = self._conn.get_userauth_request_data(self._method)
+
+ if (self._gss.complete and self._gss.verify(data, mic) and
+ (yield from self._conn.validate_gss_principal(self._username,
+ self._gss.user,
+ self._gss.host))):
+ self.send_success()
+ else:
+ self.send_failure()
+
+
+class _ServerGSSMICAuth(_ServerAuth):
+ """Server side implementation of GSS MIC auth"""
+
+ def __init__(self, conn, username, method, packet):
+ super().__init__(conn, username, method, packet)
+
+ self._gss = conn.get_gss_context()
+
+ @classmethod
+ def supported(cls, conn):
+ """Return whether GSS MIC authentication is supported"""
+
+ return conn.gss_mic_auth_supported()
+
+ @asyncio.coroutine
+ def _start(self, packet):
+ """Start server GSS MIC authentication"""
+
+ mechs = set()
+ n = packet.get_uint32()
+ for _ in range(n):
+ mechs.add(packet.get_string())
packet.check_end()
- self.send_failure()
+
+ match = None
+
+ for mech in self._gss.mechs:
+ if mech in mechs:
+ match = mech
+ break
+
+ if not match:
+ self.send_failure()
+ return
+
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_RESPONSE),
+ String(match))
+
+ @asyncio.coroutine
+ def _finish(self):
+ """Finish server GSS MIC authentication"""
+
+ if (yield from self._conn.validate_gss_principal(self._username,
+ self._gss.user,
+ self._gss.host)):
+ self.send_success()
+ else:
+ self.send_failure()
+
+ def _process_token(self, pkttype, packet):
+ """Process a GSS token from the client"""
+
+ # pylint: disable=unused-argument
+
+ token = packet.get_string()
+ packet.check_end()
+
+ try:
+ token = self._gss.step(token)
+
+ if token:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_TOKEN),
+ String(token))
+ except GSSError as exc:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERROR),
+ UInt32(exc.maj_code), UInt32(exc.min_code),
+ String(str(exc)), String(DEFAULT_LANG))
+
+ if exc.token:
+ self._conn.send_packet(Byte(MSG_USERAUTH_GSSAPI_ERRTOK),
+ String(exc.token))
+
+ self.send_failure()
+
+ return True
+
+ def _process_exchange_complete(self, pkttype, packet):
+ """Process a GSS exchange complete message from the client"""
+
+ # pylint: disable=unused-argument
+
+ packet.check_end()
+
+ if self._gss.complete and not self._gss.provides_integrity:
+ self.create_task(self._finish())
+ else:
+ self.send_failure()
+
+ return True
+
+ def _process_error_token(self, pkttype, packet):
+ """Process a GSS error token from the client"""
+
+ # pylint: disable=unused-argument
+
+ token = packet.get_string()
+ packet.check_end()
+
+ try:
+ self._gss.step(token)
+ except GSSError as exc:
+ logger.warning('GSS error from client: %s', str(exc))
+
+ return True
+
+ def _process_mic(self, pkttype, packet):
+ """Process a GSS MIC from the client"""
+
+ # pylint: disable=unused-argument
+
+ mic = packet.get_string()
+ packet.check_end()
+
+ data = self._conn.get_userauth_request_data(self._method)
+
+ if (self._gss.complete and self._gss.provides_integrity and
+ self._gss.verify(data, mic)):
+ self.create_task(self._finish())
+ else:
+ self.send_failure()
+
+ return True
+
+ # pylint: disable=bad-whitespace
+
+ packet_handlers = {
+ MSG_USERAUTH_GSSAPI_TOKEN: _process_token,
+ MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: _process_exchange_complete,
+ MSG_USERAUTH_GSSAPI_ERRTOK: _process_error_token,
+ MSG_USERAUTH_GSSAPI_MIC: _process_mic
+ }
+
+ # pylint: enable=bad-whitespace
+
class _ServerPublicKeyAuth(_ServerAuth):
"""Server side implementation of public key auth"""
@@ -452,6 +770,7 @@ class _ServerKbdIntAuth(_ServerAuth):
packet.check_end()
self.create_task(self._validate_response(responses))
+ return True
packet_handlers = {
MSG_USERAUTH_INFO_RESPONSE: _process_info_response
@@ -534,16 +853,21 @@ def get_server_auth_methods(conn):
def lookup_server_auth(conn, username, method, packet):
"""Look up the server authentication method to use"""
- if method in _auth_methods:
- return _server_auth_handlers[method](conn, username, packet)
+ handler = _server_auth_handlers.get(method)
+
+ if handler and handler.supported(conn):
+ return handler(conn, username, method, packet)
else:
conn.send_userauth_failure(False)
return None
+
# pylint: disable=bad-whitespace
_auth_method_list = (
(b'none', _ClientNullAuth, _ServerNullAuth),
+ (b'gssapi-keyex', _ClientGSSKexAuth, _ServerGSSKexAuth),
+ (b'gssapi-with-mic', _ClientGSSMICAuth, _ServerGSSMICAuth),
(b'publickey', _ClientPublicKeyAuth, _ServerPublicKeyAuth),
(b'keyboard-interactive', _ClientKbdIntAuth, _ServerKbdIntAuth),
(b'password', _ClientPasswordAuth, _ServerPasswordAuth)
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index 12b9520..a32080b 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -111,6 +111,11 @@ class SSHChannel(SSHPacketHandler):
return self._read_datatypes
+ def get_write_datatypes(self):
+ """Return the legal write data types for this channel"""
+
+ return self._write_datatypes
+
def _cleanup(self, exc=None):
"""Clean up this channel"""
diff --git a/asyncssh/client.py b/asyncssh/client.py
index 0e38749..7ffe5ab 100644
--- a/asyncssh/client.py
+++ b/asyncssh/client.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -39,6 +39,13 @@ class SSHClient:
:meth:`password_changed` or :meth:`password_change_failed` depending
on whether the password change is successful.
+ .. note:: The authentication callbacks described here can be
+ defined as coroutines. However, they may be cancelled if
+ they are running when the SSH connection is closed by
+ the server. If they attempt to catch the CancelledError
+ exception to perform cleanup, they should make sure to
+ re-raise it to allow AsyncSSH to finish its own cleanup.
+
"""
# pylint: disable=no-self-use,unused-argument
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index 4cf01c9..753ffc2 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2017 by Ron Frederick <ronf at timeheart.net>.
# All rights reserved.
#
# This program and the accompanying materials are made available under
@@ -64,7 +64,9 @@ from .constants import OPEN_UNKNOWN_CHANNEL_TYPE
from .forward import SSHForwarder
-from .kex import get_kex_algs, get_kex
+from .gss import GSSClient, GSSServer, GSSError
+
+from .kex import get_kex_algs, expand_kex_algs, get_kex
from .known_hosts import match_known_hosts
@@ -76,24 +78,24 @@ from .logging import logger
from .mac import get_mac_algs, get_mac_params, get_mac
from .misc import ChannelOpenError, DisconnectError, PasswordChangeRequired
-from .misc import async_context_manager, ensure_future, ip_address
+from .misc import async_context_manager, create_task, ip_address
from .misc import load_default_keypairs, map_handler_name
from .packet import Boolean, Byte, NameList, String, UInt32, UInt64
from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler
-from .process import PIPE, SSHClientProcess
+from .process import PIPE, SSHClientProcess, SSHServerProcess
from .public_key import CERT_TYPE_HOST, CERT_TYPE_USER, KeyImportError
from .public_key import get_public_key_algs, get_certificate_algs
from .public_key import decode_ssh_public_key, decode_ssh_certificate
-from .public_key import load_keypairs, load_public_keys
+from .public_key import load_keypairs
from .saslprep import saslprep, SASLPrepError
from .server import SSHServer
-from .sftp import SFTPClient, SFTPServer, SFTPClientHandler
+from .sftp import SFTPServer, start_sftp_client
from .stream import SSHClientStreamSession, SSHServerStreamSession
from .stream import SSHTCPStreamSession, SSHUNIXStreamSession
@@ -244,6 +246,10 @@ class SSHConnection(SSHPacketHandler):
self._kex_complete = False
self._ignore_first_kex = False
+ self._gss = None
+ self._gss_kex_auth = False
+ self._gss_mic_auth = False
+
self._rekey_bytes = rekey_bytes
self._rekey_bytes_sent = 0
self._rekey_seconds = rekey_seconds
@@ -366,6 +372,8 @@ class SSHConnection(SSHPacketHandler):
# pylint: disable=broad-except
try:
yield from coro
+ except asyncio.CancelledError:
+ pass
except DisconnectError as exc:
self._send_disconnect(exc.code, exc.reason, exc.lang)
self._force_close(exc)
@@ -377,7 +385,7 @@ class SSHConnection(SSHPacketHandler):
def create_task(self, coro):
"""Create an asynchronous task which catches and reports errors"""
- task = ensure_future(self._run_task(coro), loop=self._loop)
+ task = create_task(self._run_task(coro), loop=self._loop)
self._tasks.add(task)
return task
@@ -416,6 +424,7 @@ class SSHConnection(SSHPacketHandler):
self._transport = transport
sock = transport.get_extra_info('socket')
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
peername = transport.get_extra_info('peername')
@@ -511,6 +520,16 @@ class SSHConnection(SSHPacketHandler):
del self._channels[recv_chan]
+ def get_gss_context(self):
+ """Return the GSS context associated with this connection"""
+
+ return self._gss
+
+ def enable_gss_kex_auth(self):
+ """Enable GSS key exchange authentication"""
+
+ self._gss_kex_auth = True
+
def _choose_alg(self, alg_type, local_algs, remote_algs):
"""Choose a common algorithm from the client & server lists
@@ -791,9 +810,13 @@ class SSHConnection(SSHPacketHandler):
self._rekey_bytes_sent = 0
self._rekey_time = time.monotonic() + self._rekey_seconds
+ gss_mechs = self._gss.mechs if self._gss else []
+ kex_algs = expand_kex_algs(self._kex_algs, gss_mechs,
+ bool(self._server_host_key_algs))
+
cookie = os.urandom(16)
- kex_algs = NameList(self._kex_algs + self._get_ext_info_kex_alg())
- host_key_algs = NameList(self._server_host_key_algs)
+ kex_algs = NameList(kex_algs + self._get_ext_info_kex_alg())
+ host_key_algs = NameList(self._server_host_key_algs or [b'null'])
enc_algs = NameList(self._enc_algs)
mac_algs = NameList(self._mac_algs)
cmp_algs = NameList(self._cmp_algs)
@@ -945,12 +968,23 @@ class SSHConnection(SSHPacketHandler):
self._next_service = service
self.send_packet(Byte(MSG_SERVICE_REQUEST), String(service))
+ def _get_userauth_request_packet(self, method, args):
+ """Get packet data for a user authentication request"""
+
+ return b''.join((Byte(MSG_USERAUTH_REQUEST), String(self._username),
+ String(_CONNECTION_SERVICE), String(method)) + args)
+
+ def get_userauth_request_data(self, method, *args):
+ """Get signature data for a user authentication request"""
+
+ return (String(self._session_id) +
+ self._get_userauth_request_packet(method, args))
+
@asyncio.coroutine
def send_userauth_request(self, method, *args, key=None):
"""Send a user authentication request"""
- packet = b''.join((Byte(MSG_USERAUTH_REQUEST), String(self._username),
- String(_CONNECTION_SERVICE), String(method)) + args)
+ packet = self._get_userauth_request_packet(method, args)
if key:
sig = key.sign(String(self._session_id) + packet)
@@ -1178,8 +1212,8 @@ class SSHConnection(SSHPacketHandler):
'Key exchange already in progress')
_ = packet.get_bytes(16) # cookie
- kex_algs = packet.get_namelist()
- server_host_key_algs = packet.get_namelist()
+ peer_kex_algs = packet.get_namelist()
+ peer_host_key_algs = packet.get_namelist()
enc_algs_cs = packet.get_namelist()
enc_algs_sc = packet.get_namelist()
mac_algs_cs = packet.get_namelist()
@@ -1195,13 +1229,7 @@ class SSHConnection(SSHPacketHandler):
if self.is_server():
self._client_kexinit = packet.get_consumed_payload()
- # This method is only in SSHServerConnection
- # pylint: disable=no-member
- if not self._choose_server_host_key(server_host_key_algs):
- raise DisconnectError(DISC_KEY_EXCHANGE_FAILED, 'Unable to '
- 'find compatible server host key')
-
- if b'ext-info-c' in kex_algs:
+ if b'ext-info-c' in peer_kex_algs:
self._can_send_ext_info = True
else:
self._server_kexinit = packet.get_consumed_payload()
@@ -1211,10 +1239,25 @@ class SSHConnection(SSHPacketHandler):
else:
self._send_kexinit()
- kex_alg = self._choose_alg('key exchange', self._kex_algs, kex_algs)
+ if self._gss:
+ self._gss.reset()
+
+ gss_mechs = self._gss.mechs if self._gss else []
+ kex_algs = expand_kex_algs(self._kex_algs, gss_mechs,
+ bool(self._server_host_key_algs))
+
+ kex_alg = self._choose_alg('key exchange', kex_algs, peer_kex_algs)
self._kex = get_kex(self, kex_alg)
self._ignore_first_kex = (first_kex_follows and
- self._kex.algorithm != kex_algs[0])
+ self._kex.algorithm != peer_kex_algs[0])
+
+ if self.is_server():
+ # This method is only in SSHServerConnection
+ # pylint: disable=no-member
+ if (not self._choose_server_host_key(peer_host_key_algs) and
+ not kex_alg.startswith(b'gss-')):
+ raise DisconnectError(DISC_KEY_EXCHANGE_FAILED, 'Unable '
+ 'to find compatible server host key')
self._enc_alg_cs = self._choose_alg('encryption', self._enc_algs,
enc_algs_cs)
@@ -1229,6 +1272,9 @@ class SSHConnection(SSHPacketHandler):
self._cmp_alg_sc = self._choose_alg('compression', self._cmp_algs,
cmp_algs_sc)
+ if self.is_client():
+ self._kex.start()
+
def _process_newkeys(self, pkttype, packet):
"""Process a new keys message, finishing a key exchange"""
@@ -1873,8 +1919,8 @@ class SSHClientConnection(SSHConnection):
def __init__(self, client_factory, loop, client_version, kex_algs,
encryption_algs, mac_algs, compression_algs, signature_algs,
rekey_bytes, rekey_seconds, host, port, known_hosts,
- username, password, client_keys, agent, agent_path,
- auth_waiter):
+ username, password, client_keys, gss_host, gss_delegate_creds,
+ agent, agent_path, auth_waiter):
super().__init__(client_factory, loop, client_version, kex_algs,
encryption_algs, mac_algs, compression_algs,
signature_algs, rekey_bytes, rekey_seconds,
@@ -1890,6 +1936,13 @@ class SSHClientConnection(SSHConnection):
self._agent_path = agent_path
self._auth_waiter = auth_waiter
+ if gss_host:
+ try:
+ self._gss = GSSClient(gss_host, gss_delegate_creds)
+ self._gss_mic_auth = True
+ except GSSError:
+ pass
+
self._server_host_keys = set()
self._server_ca_keys = set()
self._revoked_server_keys = set()
@@ -1906,24 +1959,16 @@ class SSHClientConnection(SSHConnection):
self._server_host_keys = None
self._server_ca_keys = None
self._revoked_server_keys = None
- self._server_host_key_algs = (get_public_key_algs() +
- get_certificate_algs())
+ self._server_host_key_algs = None
else:
if not self._known_hosts:
self._known_hosts = os.path.join(os.path.expanduser('~'),
'.ssh', 'known_hosts')
- if isinstance(self._known_hosts, (str, bytes)):
- server_host_keys, server_ca_keys, revoked_server_keys = \
- match_known_hosts(self._known_hosts, self._host,
- self._peer_addr, self._port)
- else:
- server_host_keys, server_ca_keys, revoked_server_keys = \
- self._known_hosts
+ known_hosts = match_known_hosts(self._known_hosts, self._host,
+ self._peer_addr, self._port)
- server_host_keys = load_public_keys(server_host_keys)
- server_ca_keys = load_public_keys(server_ca_keys)
- revoked_server_keys = load_public_keys(revoked_server_keys)
+ server_host_keys, server_ca_keys, revoked_server_keys = known_hosts
self._server_host_keys = set()
self._server_host_key_algs = []
@@ -1940,8 +1985,14 @@ class SSHClientConnection(SSHConnection):
self._server_host_key_algs.extend(key.sig_algorithms)
if not self._server_host_key_algs:
- raise DisconnectError(DISC_HOST_KEY_NOT_VERIFYABLE,
- 'No trusted server host keys available')
+ if self._known_hosts is None:
+ self._server_host_key_algs = (get_public_key_algs() +
+ get_certificate_algs())
+ elif self._gss:
+ self._server_host_key_algs = [b'null']
+ else:
+ raise DisconnectError(DISC_HOST_KEY_NOT_VERIFYABLE,
+ 'No trusted server host keys available')
def _cleanup(self, exc):
"""Clean up this client connection"""
@@ -2028,6 +2079,24 @@ class SSHClientConnection(SSHConnection):
self._force_close(DisconnectError(DISC_NO_MORE_AUTH_METHODS_AVAILABLE,
'Permission denied'))
+ def gss_kex_auth_requested(self):
+ """Return whether to allow GSS key exchange authentication or not"""
+
+ if self._gss_kex_auth:
+ self._gss_kex_auth = False
+ return True
... 12173 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-asyncssh.git
More information about the Python-modules-commits
mailing list