[Python-modules-commits] [python-asyncssh] 01/08: Import asyncssh-1.3.2.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Sun Jan 3 17:16:22 UTC 2016


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to branch master
in repository python-asyncssh.

commit 9a16010d1a050309e546d35f0996837f75780394
Author: Vincent Bernat <bernat at debian.org>
Date:   Sun Jan 3 17:39:51 2016 +0100

    Import asyncssh-1.3.2.orig.tar.gz
---
 README.rst                    |   2 +-
 asyncssh/__init__.py          |   1 +
 asyncssh/asn1.py              |   8 +-
 asyncssh/auth.py              |  74 +++---
 asyncssh/auth_keys.py         |   3 +
 asyncssh/channel.py           |  16 +-
 asyncssh/connection.py        | 195 ++++++++++++----
 asyncssh/crypto/__init__.py   |   8 +-
 asyncssh/crypto/curve25519.py |   2 -
 asyncssh/crypto/ec.py         | 207 +----------------
 asyncssh/crypto/ecdh.py       |  48 ----
 asyncssh/crypto/pyca/ec.py    |  44 +++-
 asyncssh/dsa.py               |   6 +-
 asyncssh/ecdsa.py             |   8 +-
 asyncssh/ed25519.py           |   2 +-
 asyncssh/misc.py              |  38 ++--
 asyncssh/public_key.py        |  46 +---
 asyncssh/rsa.py               |   6 +-
 asyncssh/sftp.py              |   2 +-
 asyncssh/stream.py            |  25 ++-
 asyncssh/version.py           |   2 +-
 docs/api.rst                  |   8 +-
 docs/changes.rst              |  50 ++++-
 docs/conf.py                  |   4 +-
 setup.py                      |   2 +-
 tests/test_auth.py            | 511 ++++++++++++++++++++++++++++++++++++++++++
 tests/test_auth_keys.py       | 146 ++++++++++++
 tests/test_cipher.py          |  37 +--
 tests/test_kex.py             | 135 +++++------
 tests/test_native_ec.py       | 138 ------------
 tests/test_public_key.py      |  61 +++--
 tests/util.py                 | 116 +++++++++-
 32 files changed, 1258 insertions(+), 693 deletions(-)

diff --git a/README.rst b/README.rst
index 25fbf3c..31e3eb7 100644
--- a/README.rst
+++ b/README.rst
@@ -78,7 +78,7 @@ Prerequisites
 To use ``asyncssh``, you need the following:
 
 * Python 3.4 or later
-* cryptography (PyCA) 1.0.0 or later
+* cryptography (PyCA) 1.1 or later
 
 Installation
 ------------
diff --git a/asyncssh/__init__.py b/asyncssh/__init__.py
index 842ac10..f0d8414 100644
--- a/asyncssh/__init__.py
+++ b/asyncssh/__init__.py
@@ -34,6 +34,7 @@ from .listener import SSHListener
 from .logging import logger
 
 from .misc import Error, DisconnectError, ChannelOpenError
+from .misc import PasswordChangeRequired
 from .misc import BreakReceived, SignalReceived, TerminalSizeChanged
 
 from .pbe import KeyEncryptionError
diff --git a/asyncssh/asn1.py b/asyncssh/asn1.py
index 38aedbf..f26654b 100644
--- a/asyncssh/asn1.py
+++ b/asyncssh/asn1.py
@@ -141,7 +141,7 @@ class RawDERObject:
                 (_asn1_class[self.asn1_class], self.tag, self.content))
 
     def __eq__(self, other):
-        return (isinstance(other, self.__class__) and
+        return (isinstance(other, type(self)) and
                 self.asn1_class == other.asn1_class and
                 self.tag == other.tag and self.content == other.content)
 
@@ -183,7 +183,7 @@ class TaggedDERObject:
                     (_asn1_class[self.asn1_class], self.tag, self.value))
 
     def __eq__(self, other):
-        return (isinstance(other, self.__class__) and
+        return (isinstance(other, type(self)) and
                 self.asn1_class == other.asn1_class and
                 self.tag == other.tag and self.value == other.value)
 
@@ -433,7 +433,7 @@ class BitString:
         return "BitString('%s')" % self
 
     def __eq__(self, other):
-        return (isinstance(other, self.__class__) and
+        return (isinstance(other, type(self)) and
                 self.value == other.value and self.unused == other.unused)
 
     def __hash__(self):
@@ -481,7 +481,7 @@ class ObjectIdentifier:
         return "ObjectIdentifier('%s')" % self.value
 
     def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.value == other.value
+        return isinstance(other, type(self)) and self.value == other.value
 
     def __hash__(self):
         return hash(self.value)
diff --git a/asyncssh/auth.py b/asyncssh/auth.py
index 1fd0a71..cb32362 100644
--- a/asyncssh/auth.py
+++ b/asyncssh/auth.py
@@ -15,7 +15,7 @@
 import asyncio
 
 from .constants import DISC_PROTOCOL_ERROR
-from .misc import DisconnectError
+from .misc import DisconnectError, PasswordChangeRequired
 from .packet import Boolean, Byte, String, UInt32, SSHPacketHandler
 from .saslprep import saslprep, SASLPrepError
 
@@ -42,9 +42,27 @@ _server_auth_handlers = {}
 class _Auth(SSHPacketHandler):
     """Parent class for authentication"""
 
-    def __init__(self):
+    def __init__(self, conn, coro):
+        self._conn = conn
         self._coro = None
 
+        self.create_task(coro)
+
+    def create_task(self, coro):
+        """Create an asynchronous auth task"""
+
+        self.cancel()
+        self._coro = asyncio.async(self.run_task(coro))
+
+    @asyncio.coroutine
+    def run_task(self, coro):
+        """Run an async auth task, catching disconnect errors"""
+
+        try:
+            yield from coro
+        except DisconnectError as exc:
+            self._conn.connection_lost(exc)
+
     def cancel(self):
         """Cancel any authentication in progress"""
 
@@ -52,15 +70,14 @@ class _Auth(SSHPacketHandler):
             self._coro.cancel()
             self._coro = None
 
+
 class _ClientAuth(_Auth):
     """Parent class for client authentication"""
 
     def __init__(self, conn, method):
-        super().__init__()
-
-        self._conn = conn
         self._method = method
-        self._coro = asyncio.async(self._start())
+
+        super().__init__(conn, self._start())
 
     @asyncio.coroutine
     def _start(self):
@@ -90,8 +107,6 @@ class _ClientNullAuth(_ClientAuth):
 
         self.send_request()
 
-    packet_handlers = {}
-
 
 class _ClientPublicKeyAuth(_ClientAuth):
     """Client side implementation of public key auth"""
@@ -193,9 +208,8 @@ class _ClientKbdIntAuth(_ClientAuth):
 
             prompts.append((prompt, echo))
 
-        self.cancel()
-        self._coro = asyncio.async(self._receive_challenge(name, instruction,
-                                                           lang, prompts))
+        self.create_task(self._receive_challenge(name, instruction,
+                                                 lang, prompts))
 
         return True
 
@@ -225,10 +239,10 @@ class _ClientPasswordAuth(_ClientAuth):
         self.send_request(Boolean(False), String(password))
 
     @asyncio.coroutine
-    def _change_password(self):
+    def _change_password(self, prompt, lang):
         """Start password change"""
 
-        result = yield from self._conn.password_change_requested()
+        result = yield from self._conn.password_change_requested(prompt, lang)
 
         if result == NotImplemented:
             # Password change not supported - move on to the next auth method
@@ -268,8 +282,8 @@ class _ClientPasswordAuth(_ClientAuth):
             raise DisconnectError(DISC_PROTOCOL_ERROR,
                                   'Invalid password change request') from None
 
-        self.cancel()
-        self._coro = asyncio.async(self._change_password())
+        self.auth_failed()
+        self.create_task(self._change_password(prompt, lang))
 
         return True
 
@@ -282,11 +296,9 @@ class _ServerAuth(_Auth):
     """Parent class for server authentication"""
 
     def __init__(self, conn, username, packet):
-        super().__init__()
-
-        self._conn = conn
         self._username = username
-        self._coro = asyncio.async(self._start(packet))
+
+        super().__init__(conn, self._start(packet))
 
     @asyncio.coroutine
     def _start(self, packet):
@@ -437,8 +449,7 @@ class _ServerKbdIntAuth(_ServerAuth):
 
         packet.check_end()
 
-        self.cancel()
-        self._coro = asyncio.async(self._validate_response(responses))
+        self.create_task(self._validate_response(responses))
 
     packet_handlers = {
         MSG_USERAUTH_INFO_RESPONSE: _process_info_response
@@ -470,12 +481,23 @@ class _ServerPasswordAuth(_ServerAuth):
             raise DisconnectError(DISC_PROTOCOL_ERROR, 'Invalid password auth '
                                   'request') from None
 
-        # TODO: Handle password change request
+        try:
+            if password_change:
+                result = yield from self._conn.change_password(self._username,
+                                                               password,
+                                                               new_password)
+            else:
+                result = \
+                    yield from self._conn.validate_password(self._username,
+                                                            password)
 
-        if (yield from self._conn.validate_password(self._username, password)):
-            self.send_success()
-        else:
-            self.send_failure()
+            if result:
+                self.send_success()
+            else:
+                self.send_failure()
+        except PasswordChangeRequired as exc:
+            self._conn.send_packet(Byte(MSG_USERAUTH_PASSWD_CHANGEREQ),
+                                   String(exc.prompt), String(exc.lang))
 
 
 def register_auth_method(alg, client_handler, server_handler):
diff --git a/asyncssh/auth_keys.py b/asyncssh/auth_keys.py
index c48a0b8..d0502c5 100644
--- a/asyncssh/auth_keys.py
+++ b/asyncssh/auth_keys.py
@@ -157,6 +157,9 @@ class SSHAuthorizedKeys:
             else:
                 self._user_entries.append(entry)
 
+        if not self._user_entries and not self._ca_entries:
+            raise ValueError('No valid keys found')
+
     def validate(self, key, client_addr, cert_principals=None, ca=False):
         """Return whether a public key or CA is valid for authentication"""
 
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index b4a1923..cb96c75 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -103,13 +103,17 @@ class SSHChannel(SSHPacketHandler):
         """Clean up this channel"""
 
         if self._open_waiter:
-            self._open_waiter.set_exception(
-                ChannelOpenError(OPEN_CONNECT_FAILED, 'SSH connection closed'))
+            if not self._open_waiter.cancelled():
+                self._open_waiter.set_exception(
+                    ChannelOpenError(OPEN_CONNECT_FAILED,
+                                     'SSH connection closed'))
+
             self._open_waiter = None
 
         if self._request_waiters:
             for waiter in self._request_waiters:
-                waiter.set_exception(exc)
+                if not waiter.cancelled():
+                    waiter.set_exception(exc)
 
             self._request_waiters = []
 
@@ -312,6 +316,7 @@ class SSHChannel(SSHPacketHandler):
 
         if not self._open_waiter.cancelled():
             self._open_waiter.set_result(packet)
+
         self._open_waiter = None
 
     def process_open_failure(self, code, reason, lang):
@@ -321,7 +326,10 @@ class SSHChannel(SSHPacketHandler):
             raise DisconnectError(DISC_PROTOCOL_ERROR,
                                   'Channel not being opened')
 
-        self._open_waiter.set_exception(ChannelOpenError(code, reason, lang))
+        if not self._open_waiter.cancelled():
+            self._open_waiter.set_exception(
+                ChannelOpenError(code, reason, lang))
+
         self._open_waiter = None
         self._loop.call_soon(self._cleanup)
 
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index 55ecacc..37e5ca1 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -65,7 +65,8 @@ from .listener import SSHClientListener, SSHForwardListener
 
 from .mac import get_mac_algs, get_mac_params, get_mac
 
-from .misc import ChannelOpenError, DisconnectError, ip_address
+from .misc import ChannelOpenError, DisconnectError, PasswordChangeRequired
+from .misc import ip_address
 
 from .packet import Boolean, Byte, NameList, String, UInt32, UInt64
 from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler
@@ -106,7 +107,7 @@ _DEFAULT_WINDOW = 2*1024*1024       # 2 MiB
 _DEFAULT_MAX_PKTSIZE = 32768        # 32 kiB
 
 
-def _load_private_key(key):
+def _load_private_key(key, passphrase=None):
     """Load a private key
 
        This function loads a private key and an optional certificate.
@@ -144,9 +145,9 @@ def _load_private_key(key):
         cert = None
 
     if isinstance(key, str):
-        key = read_private_key(key)
+        key = read_private_key(key, passphrase)
     elif isinstance(key, bytes):
-        key = import_private_key(key)
+        key = import_private_key(key, passphrase)
 
     if isinstance(cert, str):
         try:
@@ -183,7 +184,7 @@ def _load_public_key(key):
     return key
 
 
-def _load_private_key_list(keylist):
+def _load_private_key_list(keylist, passphrase=None):
     """Load list of private keys and optional associated certificates
 
        This function loads a collection of private keys, each with
@@ -200,10 +201,10 @@ def _load_private_key_list(keylist):
     """
 
     if isinstance(keylist, str):
-        keys = read_private_key_list(keylist)
+        keys = read_private_key_list(keylist, passphrase)
         return [(key, None) for key in keys]
     else:
-        return [_load_private_key(key) for key in keylist]
+        return [_load_private_key(key, passphrase) for key in keylist]
 
 def _load_public_key_list(keylist):
     """Load public key list
@@ -1322,7 +1323,8 @@ class SSHConnection(SSHPacketHandler):
         packet.check_end()
 
         if self.is_client() and self._auth:
-            if partial_success:
+            if partial_success: # pragma: no cover
+                # Partial success not implemented yet
                 self._auth.auth_succeeded()
             else:
                 self._auth.auth_failed()
@@ -1342,6 +1344,7 @@ class SSHConnection(SSHPacketHandler):
         packet.check_end()
 
         if self.is_client() and self._auth:
+            self._auth.auth_succeeded()
             self._auth.cancel()
             self._auth = None
             self._auth_in_progress = False
@@ -1354,6 +1357,7 @@ class SSHConnection(SSHPacketHandler):
             if self._auth_waiter:
                 if not self._auth_waiter.cancelled():
                     self._auth_waiter.set_result(None)
+
                 self._auth_waiter = None
         else:
             raise DisconnectError(DISC_PROTOCOL_ERROR,
@@ -1691,9 +1695,9 @@ class SSHClientConnection(SSHConnection):
     """
 
     def __init__(self, client_factory, loop, host, port, known_hosts,
-                 username, client_keys, password, kex_algs, encryption_algs,
-                 mac_algs, compression_algs, rekey_bytes, rekey_seconds,
-                 auth_waiter):
+                 username, password, client_keys, passphrase, kex_algs,
+                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
+                 rekey_seconds, auth_waiter):
         super().__init__(client_factory, loop, kex_algs, encryption_algs,
                          mac_algs, compression_algs, rekey_bytes,
                          rekey_seconds, server=False)
@@ -1711,7 +1715,7 @@ class SSHClientConnection(SSHConnection):
         self._username = saslprep(username)
 
         if client_keys:
-            self._client_keys = _load_private_key_list(client_keys)
+            self._client_keys = _load_private_key_list(client_keys, passphrase)
         else:
             self._client_keys = []
 
@@ -1719,7 +1723,8 @@ class SSHClientConnection(SSHConnection):
                 for file in _DEFAULT_KEY_FILES:
                     try:
                         file = os.path.join(os.environ['HOME'], '.ssh', file)
-                        self._client_keys.append(_load_private_key(file))
+                        client_key = _load_private_key(file, passphrase)
+                        self._client_keys.append(client_key)
                     except OSError:
                         pass
 
@@ -1787,7 +1792,9 @@ class SSHClientConnection(SSHConnection):
             self._dynamic_remote_listeners = {}
 
         if self._auth_waiter:
-            self._auth_waiter.set_exception(exc)
+            if not self._auth_waiter.cancelled():
+                self._auth_waiter.set_exception(exc)
+
             self._auth_waiter = None
 
         super()._cleanup(exc)
@@ -1900,10 +1907,10 @@ class SSHClientConnection(SSHConnection):
         return result
 
     @asyncio.coroutine
-    def password_change_requested(self):
+    def password_change_requested(self, prompt, lang):
         """Return a password to authenticate with and what to change it to"""
 
-        result = self._owner.password_change_requested()
+        result = self._owner.password_change_requested(prompt, lang)
 
         if asyncio.iscoroutine(result):
             result = yield from result
@@ -1958,6 +1965,10 @@ class SSHClientConnection(SSHConnection):
                 result = []
             elif len(prompts) == 1 and 'password' in prompts[0][0].lower():
                 password = self.password_auth_requested()
+
+                if asyncio.iscoroutine(password):
+                    password = yield from password
+
                 result = [password] if password is not None else None
             else:
                 result = None
@@ -2255,6 +2266,13 @@ class SSHClientConnection(SSHConnection):
                 listen_port = packet.get_uint32()
                 dynamic = True
             else:
+                # OpenSSH 6.8 introduced a bug which causes the reply
+                # to contain an extra uint32 value of 0 when non-dynamic
+                # ports are requested, causing the check_end() call below
+                # to fail. This check works around this problem.
+                if len(packet.get_remaining_payload()) == 4:
+                    packet.get_uint32()
+
                 dynamic = False
 
             packet.check_end()
@@ -2455,7 +2473,7 @@ class SSHServerConnection(SSHConnection):
 
     """
 
-    def __init__(self, server_factory, loop, server_host_keys,
+    def __init__(self, server_factory, loop, server_host_keys, passphrase,
                  authorized_client_keys, kex_algs, encryption_algs, mac_algs,
                  compression_algs, allow_pty, session_factory,
                  session_encoding, sftp_factory, window, max_pktsize,
@@ -2471,7 +2489,7 @@ class SSHServerConnection(SSHConnection):
         self._window = window
         self._max_pktsize = max_pktsize
 
-        server_host_keys = _load_private_key_list(server_host_keys)
+        server_host_keys = _load_private_key_list(server_host_keys, passphrase)
 
         self._server_host_keys = OrderedDict()
 
@@ -2655,13 +2673,28 @@ class SSHServerConnection(SSHConnection):
 
         return result
 
+    @asyncio.coroutine
+    def change_password(self, username, old_password, new_password):
+        """Handle a password change request for a user"""
+
+        result = self._owner.change_password(username, old_password,
+                                             new_password)
+
+        if asyncio.iscoroutine(result):
+            result = yield from result
+
+        return result
+
     def kbdint_auth_supported(self):
         """Return whether or not keyboard-interactive authentication
            is supported"""
 
-        if self._owner.kbdint_auth_supported():
+        result = self._owner.kbdint_auth_supported()
+
+        if result is True:
             return True
-        elif self._owner.password_auth_supported():
+        elif (result is NotImplemented and
+              self._owner.password_auth_supported()):
             self._kbdint_password_auth = True
             return True
         else:
@@ -2691,12 +2724,20 @@ class SSHServerConnection(SSHConnection):
             if len(responses) != 1:
                 return False
 
-            result = self._owner.validate_password(username, responses[0])
+            try:
+                result = self._owner.validate_password(username, responses[0])
+
+                if asyncio.iscoroutine(result):
+                    result = yield from result
+            except PasswordChangeRequired:
+                # Don't support password change requests for now in
+                # keyboard-interactive auth
+                result = False
         else:
             result = self._owner.validate_kbdint_response(username, responses)
 
-        if asyncio.iscoroutine(result):
-            result = yield from result
+            if asyncio.iscoroutine(result):
+                result = yield from result
 
         return result
 
@@ -3350,7 +3391,7 @@ class SSHClient:
            :param string prompt:
                The prompt requesting that the user enter a new password
            :param string lang:
-               the language that the prompt is in
+               The language that the prompt is in
 
            :returns: A tuple of two strings containing the old and new
                      passwords or ``NotImplemented`` if password changes
@@ -3664,6 +3705,13 @@ class SSHServer:
            be overridden by applications wishing to support password
            authentication.
 
+           If the password provided is valid but expired, this method
+           may raise :exc:`PasswordChangeRequired` to request that the
+           client provide a new password before authentication is
+           allowed to complete. In this case, the application must
+           override :meth:`change_password` to handle the password
+           change request.
+
            This method may be called multiple times with different
            passwords provided by the client. Applications may wish
            to limit the number of attempts which are allowed. This
@@ -3685,6 +3733,50 @@ class SSHServer:
            :returns: A boolean indicating if the specified password is
                      valid for the user being authenticated
 
+           :raises: :exc:`PasswordChangeRequired` if the password
+                    provided is expired and needs to be changed
+
+        """
+
+        return False
+
+    def change_password(self, username, old_password, new_password):
+        """Handle a request to change a user's password
+
+           This method is called when a user makes a request to
+           change their password. It should first validate that
+           the old password provided is correct and then attempt
+           to change the user's password to the new value.
+
+           If the old password provided is valid and the change to
+           the new password is successful, this method should
+           return ``True``. If the old password is not valid or
+           password changes are not supported, it should return
+           ``False``. It may also raise :exc:`PasswordChangeRequired`
+           to request that the client try again if the new password
+           is not acceptable for some reason.
+
+           If blocking operations need to be performed to determine the
+           validity of the old password or to change to the new password,
+           this method may be defined as a coroutine.
+
+           By default, this method returns ``False``, rejecting all
+           password changes.
+
+           :param string username:
+               The user whose password should be changed
+           :param string old_password:
+               The user's current password
+           :param string new_password:
+               The new password being requested
+
+           :returns: A boolean indicating if the password change
+                     is successful or not
+
+           :raises: :exc:`PasswordChangeRequired` if the new password
+                    is not acceptable and the client should be asked
+                    to provide another
+
         """
 
         return False
@@ -3700,12 +3792,19 @@ class SSHServer:
            to generate the apporiate challenges and validate the responses
            for the user being authenticated.
 
+           By default, this method returns ``NotImplemented`` tying
+           this authentication to password authentication. If the
+           application implements password authentication and this
+           method is not overridden, keyboard-interactive authentication
+           will be supported by prompting for a password and passing
+           that to the password authentication callbacks.
+
            :returns: A boolean indicating if keyboard-interactive
                      authentication is supported or not
 
         """
 
-        return False
+        return NotImplemented
 
     def get_kbdint_challenge(self, username, lang, submethods):
         """Return a keyboard-interactive auth challenge
@@ -3934,8 +4033,9 @@ class SSHServer:
 @asyncio.coroutine
 def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
                       loop=None, family=0, flags=0, local_addr=None,
-                      known_hosts=(), username=None, client_keys=(),
-                      password=None, kex_algs=(), encryption_algs=(),
+                      known_hosts=(), username=None, password=None,
+                      client_keys=(), passphrase=None,
+                      kex_algs=(), encryption_algs=(),
                       mac_algs=(), compression_algs=(),
                       rekey_bytes=_DEFAULT_REKEY_BYTES,
                       rekey_seconds=_DEFAULT_REKEY_SECONDS):
@@ -3998,6 +4098,11 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
        :param string username: (optional)
            Username to authenticate as on the server. If not specified,
            the currently logged in user on the local machine will be used.
+       :param string password: (optional)
+           The password to use for client password authentication or
+           keyboard-interactive authentication which prompts for a password.
+           If this is not specified, client password authentication will
+           not be performed.
        :param client_keys: (optional)
            A list of keys which will be used to authenticate this client
            via public key authentication. If no client keys are specified,
@@ -4009,11 +4114,11 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
            :file:`.ssh/id_rsa-cert.pub`, and :file:`.ssh/id_dsa-cert.pub`.
            If this argument is explicitly set to ``None``, client public
            key authentication will not be performed.
-       :param string password: (optional)
-           The password to use for client password authentication or
-           keyboard-interactive authentication which prompts for a password.
-           If this is not specified, client password authentication will
-           not be performed.
+       :param string passphrase: (optional)
+           The passphrase to use to decrypt client keys when loading them,
+           if they are encrypted. If this is not specified, only unencrypted
+           client keys can be loaded. If the keys passed into client_keys
+           are already loaded, this argument is ignored.
        :param kex_algs: (optional)
            A list of allowed key exchange algorithms in the SSH handshake,
            taken from :ref:`key exchange algorithms <KexAlgs>`
@@ -4052,9 +4157,10 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
         """Return an SSH client connection handler"""
 
         return SSHClientConnection(client_factory, loop, host, port,
-                                   known_hosts, username, client_keys,
-                                   password, kex_algs, encryption_algs,
-                                   mac_algs, compression_algs, rekey_bytes,
+                                   known_hosts, username, password,
+                                   client_keys, passphrase, kex_algs,
+                                   encryption_algs, mac_algs,
+                                   compression_algs, rekey_bytes,
                                    rekey_seconds, auth_waiter)
 
     if not client_factory:
@@ -4077,7 +4183,7 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
 @asyncio.coroutine
 def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
                   loop=None, family=0, flags=socket.AI_PASSIVE, backlog=100,
-                  reuse_address=None, server_host_keys,
+                  reuse_address=None, server_host_keys, passphrase=None,
                   authorized_client_keys=None, kex_algs=(),
                   encryption_algs=(), mac_algs=(), compression_algs=(),
                   allow_pty=True, session_factory=None,
@@ -4119,6 +4225,12 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
            A list of private keys and optional certificates which can be
            used by the server as a host key. This argument must be
            specified.
+       :param string passphrase: (optional)
+           The passphrase to use to decrypt server host keys when loading
+           them, if they are encrypted. If this is not specified, only
+           unencrypted server host keys can be loaded. If the keys passed
+           into server_host_keys are already loaded, this argument is
+           ignored.
        :param authorized_client_keys: (optional)
            A list of authorized user and CA public keys which should be
            trusted for certifcate-based client public key authentication.
@@ -4194,11 +4306,12 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
         """Return an SSH server connection handler"""
 
         return SSHServerConnection(server_factory, loop, server_host_keys,
-                                   authorized_client_keys, kex_algs,
-                                   encryption_algs, mac_algs, compression_algs,
-                                   allow_pty, session_factory,
-                                   session_encoding, sftp_factory, window,
-                                   max_pktsize, rekey_bytes, rekey_seconds)
+                                   passphrase, authorized_client_keys,
+                                   kex_algs, encryption_algs, mac_algs,
+                                   compression_algs, allow_pty,
+                                   session_factory, session_encoding,
+                                   sftp_factory, window, max_pktsize,
+                                   rekey_bytes, rekey_seconds)
 
     return (yield from loop.create_server(conn_factory, host, port,
                                           family=family, flags=flags,
diff --git a/asyncssh/crypto/__init__.py b/asyncssh/crypto/__init__.py
index a111d7c..58a05c0 100644
--- a/asyncssh/crypto/__init__.py
+++ b/asyncssh/crypto/__init__.py
@@ -14,12 +14,11 @@
 
 from .cipher import register_cipher, lookup_cipher
 
-from .ec import decode_ec_point, encode_ec_point
-from .ec import get_ec_curve_params, lookup_ec_curve_by_params
+from .ec import lookup_ec_curve_by_params
 
 # Import PyCA versions of DSA, ECDSA, and RSA
 from .pyca.dsa import DSAPrivateKey, DSAPublicKey
-from .pyca.ec import ECDSAPrivateKey, ECDSAPublicKey
+from .pyca.ec import ECDSAPrivateKey, ECDSAPublicKey, ECDH
 from .pyca.rsa import RSAPrivateKey, RSAPublicKey
 
 # Import pyca module to get ciphers defined there registered
@@ -33,6 +32,3 @@ try:
     from .curve25519 import Curve25519DH
 except ImportError: # pragma: no cover
     pass
-
-# Import native Python ECDH module
-from .ecdh import ECDH
diff --git a/asyncssh/crypto/curve25519.py b/asyncssh/crypto/curve25519.py
index f41a821..be563de 100644
--- a/asyncssh/crypto/curve25519.py
+++ b/asyncssh/crypto/curve25519.py
@@ -15,8 +15,6 @@
 import ctypes
 import os
 
-_found = None
-
 try:
     from libnacl import nacl
 
diff --git a/asyncssh/crypto/ec.py b/asyncssh/crypto/ec.py
index 5dc20c2..8bdb1d0 100644
--- a/asyncssh/crypto/ec.py
+++ b/asyncssh/crypto/ec.py
@@ -10,144 +10,15 @@
 # Contributors:
 #     Ron Frederick - initial implementation, API, and documentation
 
-"""Elliptic curve public key encryption primitives"""
+"""Elliptic curve public key utility functions"""
 
-from ..misc import mod_inverse
-
-_curve_params = {}
 _curve_param_map = {}
 
 # Short variable names are used here, matching names in the spec
 # pylint: disable=invalid-name
 
 
-class PrimeCurve:
-    """An elliptic curve over a prime finite field F(p)"""
-
-    def __init__(self, p, a, b):
-        self.p = p
-        self.a = a
-        self.b = b
-        self.keylen = (p.bit_length() + 7) // 8
-
-    def __eq__(self, other):
-        return (isinstance(other, self.__class__) and
-                self.p == other.p and
-                self.a % self.p == other.a % other.p and
-                self.b % self.p == other.b % other.p)
-
-    def __hash__(self):
-        return hash((self.p, self.a % self.p, self.b % self.p))
-
-
-class PrimePoint:
-    """A point on an elliptic curve over a prime finite field F(p)"""
-
-    def __init__(self, curve, x, y):
-        self.curve = curve
-        self.x = x
-        self.y = y
-
-    def __eq__(self, other):
-        return (isinstance(other, self.__class__) and
-                self.curve == other.curve and
-                self.x == other.x and self.y == other.y)
-
-    def __hash__(self):
-        return hash((self.curve, self.x, self.y))
-
-    def __bool__(self):
-        return self.curve is not None
-
-    def __neg__(self):
-        """Negate an elliptic curve point"""
-
-        if self.y:
-            return PrimePoint(self.curve, self.x, self.curve.p - self.y)
-        else:
-            return self
-
-    def __add__(self, other):
-        """Add two elliptic curve points"""
-
-        if self.curve is None:
-            return other
-        elif other.curve is None:
-            return self
-        elif self.curve != other.curve:
-            raise ValueError('Can\'t add points from different curves')
-
-        p = self.curve.p
-
-        if self.x == other.x:
-            if (self.y + other.y) % p == 0:
-                return _INFINITY
-            else:
-                l = ((3 * self.x * self.x + self.curve.a) *
-                     mod_inverse(2 * self.y, p)) % p
-        else:
-            l = ((other.y - self.y) * mod_inverse(other.x - self.x, p)) % p
-
-        x = (l * l - self.x - other.x) % p
-        y = (l * (self.x - x) - self.y) % p
-        return PrimePoint(self.curve, x, y)
-
-    def __sub__(self, other):
-        """Subtract one elliptic curve point from another"""
-
-        return self + (-other)
-
-    def __rmul__(self, k):
-        """Multiply an elliptic curve point by a scalar value"""
-
-        result = _INFINITY
-        P = self
-
-        while k:
-            if k & 1:
-                if k & 2:
-                    result -= P
-                    while k & 2:
-                        k >>= 1
-                        P += P
-                    k |= 2
-                else:
-                    result += P
-
-            k >>= 1
-            P += P
-
-        return result
-
-    @classmethod
-    def construct(cls, curve, x, y):
-        """Construct an elliptic curve point from a curve and x, y values"""
-
-        if x is None and y is None:
-            return _INFINITY
-        elif (0 <= x < curve.p and 0 <= y < curve.p and
-              (y*y - (x*x*x + curve.a*x + curve.b)) % curve.p == 0):
-            return cls(curve, x, y)
-        else:
-            raise ValueError('Point not on curve')
-
-    @classmethod
-    def decode(cls, curve, data):
-        """Decode an octet string into an elliptic curve point"""
-
-        return cls.construct(curve, *decode_ec_point(curve.keylen, data))
-
-    def encode(self):
-        """Encode an elliptic curve point as an octet string"""
-
-        return encode_ec_point(self.curve.keylen, self.x, self.y)
-
-
-# Define the point "infinity" which exists on all elliptic curves
-_INFINITY = PrimePoint(None, None, None)
-
-
-def register_prime_curve(curve_id, p, a, b, Gx, Gy, n):
+def register_prime_curve(curve_id, p, a, b, point, n):
     """Register an elliptic curve prime domain
 
        This function registers an elliptic curve prime domain by
@@ -158,61 +29,7 @@ def register_prime_curve(curve_id, p, a, b, Gx, Gy, n):
 
     """
 
-    if p % 2 == 0 or (4*a*a*a + 27*b*b) % p == 0:
-        raise ValueError('Invalid curve parameters')
-
-    G = PrimePoint.construct(PrimeCurve(p, a, b), Gx, Gy)
-
-    if n * G:
-        raise ValueError('Invalid order for curve %s' % curve_id.decode())
-
-    pb = p % n
-    for b in range(100):
-        if pb == 1:
-            raise ValueError('Invalid prime for curve %s' % curve_id.decode())
-
-        pb = (pb * p) % n
-
-    _curve_params[curve_id] = (G, n)
-    _curve_param_map[G, n] = curve_id
-
-
-def decode_ec_point(keylen, data):
-    """Decode an octet string into an elliptic curve point"""
-
-    if data == b'\x00':
-        return None, None
-    elif data.startswith(b'\x04'):
-        if len(data) == 2*keylen + 1:
-            return (int.from_bytes(data[1:keylen+1], 'big'),
-                    int.from_bytes(data[keylen+1:], 'big'))
-        else:
-            raise ValueError('Invalid elliptic curve point data length')
-    else:
-        raise ValueError('Unsupported elliptic curve point type')
-
-
-def encode_ec_point(keylen, x, y):
-    """Encode an elliptic curve point as an octet string"""
-
-    if x is None:
-        return b'\x00'
-    else:
-        return b'\x04' + x.to_bytes(keylen, 'big') + y.to_bytes(keylen, 'big')
-
-
-def get_ec_curve_params(curve_id):
-    """Return the parameters for a named elliptic curve
-
-       This function looks up an elliptic curve by name and returns the
-       curve's generator point and order.
-
-    """
-
-    try:
-        return _curve_params[curve_id]
-    except KeyError:
-        raise ValueError('Unknown EC curve %s' % curve_id.decode())
+    _curve_param_map[p, a % p, b % p, point, n] = curve_id
... 2084 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