[Python-modules-commits] [python-asyncssh] 01/06: Import python-asyncssh_1.7.1.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Wed Oct 26 20:39:00 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 316b2e7f91c50d23cc7da8cd9b7eff7c9f64903b
Author: Vincent Bernat <bernat at debian.org>
Date:   Wed Oct 26 22:30:58 2016 +0200

    Import python-asyncssh_1.7.1.orig.tar.gz
---
 CONTRIBUTING.rst              |   2 +-
 asyncssh/__init__.py          |   3 +-
 asyncssh/agent.py             | 363 ++++++++++++++++++++++++++--
 asyncssh/connection.py        | 386 ++++++++++++------------------
 asyncssh/constants.py         |   1 +
 asyncssh/crypto/pyca/rsa.py   |  22 +-
 asyncssh/dh.py                |  20 +-
 asyncssh/dsa.py               |  22 +-
 asyncssh/ecdsa.py             |  30 ++-
 asyncssh/ed25519.py           |  22 +-
 asyncssh/misc.py              |  21 ++
 asyncssh/public_key.py        | 521 +++++++++++++++++++++++++++++++++++------
 asyncssh/rsa.py               |  29 ++-
 asyncssh/version.py           |   2 +-
 docs/api.rst                  | 111 +++++++--
 docs/changes.rst              |  51 +++-
 docs/index.rst                |   2 +-
 tests/server.py               |  40 +++-
 tests/test_agent.py           | 321 ++++++++++++++++++-------
 tests/test_auth.py            |  23 +-
 tests/test_auth_keys.py       |  30 +--
 tests/test_channel.py         |   2 +-
 tests/test_connection.py      |  56 +++++
 tests/test_connection_auth.py | 138 ++++++++++-
 tests/test_known_hosts.py     |  27 +--
 tests/test_public_key.py      | 534 ++++++++++++++++++++++++------------------
 tests/test_sftp.py            |   6 +-
 tests/util.py                 |   2 +-
 28 files changed, 2007 insertions(+), 780 deletions(-)

diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index aff4bb0..c34f797 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -72,7 +72,7 @@ There are two long-lived branches in AsyncSSH at the moment:
 
 * The master branch is intended to contain the latest stable version
   of the code. All official versions of AsyncSSH are released from
-  this branch, and a each release has a corresponding tag added
+  this branch, and each release has a corresponding tag added
   matching its release number. Bug fixes and simple improvements
   may be checked directly into this branch, but most new features
   will be added to the develop branch first.
diff --git a/asyncssh/__init__.py b/asyncssh/__init__.py
index e21d704..0785f7c 100644
--- a/asyncssh/__init__.py
+++ b/asyncssh/__init__.py
@@ -20,7 +20,7 @@ from .constants import *
 
 # pylint: enable=wildcard-import
 
-from .agent import SSHAgentClient, connect_agent
+from .agent import SSHAgentClient, SSHAgentKeyPair, connect_agent
 
 from .auth_keys import SSHAuthorizedKeys
 from .auth_keys import import_authorized_keys, read_authorized_keys
@@ -59,6 +59,7 @@ from .public_key import import_public_key, import_certificate
 from .public_key import read_private_key, read_public_key, read_certificate
 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 .session import SSHClientSession, SSHServerSession
 from .session import SSHTCPSession, SSHUNIXSession
diff --git a/asyncssh/agent.py b/asyncssh/agent.py
index 4a4aea3..2ee353c 100644
--- a/asyncssh/agent.py
+++ b/asyncssh/agent.py
@@ -15,42 +15,103 @@
 import asyncio
 import os
 
-from .misc import ChannelOpenError
+import asyncssh
+
+from .misc import ChannelOpenError, load_default_keypairs
 from .packet import Byte, String, UInt32, PacketDecodeError, SSHPacket
 from .public_key import SSHKeyPair
 
 
 # pylint: disable=bad-whitespace
 
-# Generic agent replies
-SSH_AGENT_FAILURE              = 5
-
-# Protocol 2 key operations
-SSH2_AGENTC_REQUEST_IDENTITIES = 11
-SSH2_AGENT_IDENTITIES_ANSWER   = 12
-SSH2_AGENTC_SIGN_REQUEST       = 13
-SSH2_AGENT_SIGN_RESPONSE       = 14
+# Client request message numbers
+SSH_AGENTC_REQUEST_IDENTITIES            = 11
+SSH_AGENTC_SIGN_REQUEST                  = 13
+SSH_AGENTC_ADD_IDENTITY                  = 17
+SSH_AGENTC_REMOVE_IDENTITY               = 18
+SSH_AGENTC_REMOVE_ALL_IDENTITIES         = 19
+SSH_AGENTC_ADD_SMARTCARD_KEY             = 20
+SSH_AGENTC_REMOVE_SMARTCARD_KEY          = 21
+SSH_AGENTC_LOCK                          = 22
+SSH_AGENTC_UNLOCK                        = 23
+SSH_AGENTC_ADD_ID_CONSTRAINED            = 25
+SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26
+SSH_AGENTC_EXTENSION                     = 27
+
+# Agent response message numbers
+SSH_AGENT_FAILURE                        = 5
+SSH_AGENT_SUCCESS                        = 6
+SSH_AGENT_IDENTITIES_ANSWER              = 12
+SSH_AGENT_SIGN_RESPONSE                  = 14
+SSH_AGENT_EXTENSION_FAILURE              = 28
+
+# SSH agent constraint numbers
+SSH_AGENT_CONSTRAIN_LIFETIME             = 1
+SSH_AGENT_CONSTRAIN_CONFIRM              = 2
+SSH_AGENT_CONSTRAIN_EXTENSION            = 3
+
+# SSH agent signature flags
+SSH_AGENT_RSA_SHA2_256                   = 2
+SSH_AGENT_RSA_SHA2_512                   = 4
 
 # pylint: enable=bad-whitespace
 
 
-class _SSHAgentKeyPair(SSHKeyPair):
+class SSHAgentKeyPair(SSHKeyPair):
     """Surrogate for a key managed by the SSH agent"""
 
-    def __init__(self, agent, public_data, comment):
-        self._agent = agent
+    _key_type = 'agent'
 
-        packet = SSHPacket(public_data)
-        self.algorithm = packet.get_string()
+    def __init__(self, agent, algorithm, public_data, comment):
+        super().__init__(algorithm, comment)
 
+        self._agent = agent
         self.public_data = public_data
-        self.comment = comment
+
+        self._cert = algorithm.endswith(b'-cert-v01 at openssh.com')
+        self._flags = 0
+
+        if self._cert:
+            self.sig_algorithm = algorithm[:-21]
+        else:
+            self.sig_algorithm = algorithm
+
+        if self.sig_algorithm == b'ssh-rsa':
+            self.sig_algorithms = (b'rsa-sha2-256', b'rsa-sha2-512',
+                                   b'ssh-rsa')
+        else:
+            self.sig_algorithms = (self.sig_algorithm,)
+
+        if self._cert:
+            self.host_key_algorithms = (algorithm,)
+        else:
+            self.host_key_algorithms = self.sig_algorithms
+
+    def set_sig_algorithm(self, sig_algorithm):
+        """Set the signature algorithm to use when signing data"""
+
+        self.sig_algorithm = sig_algorithm
+
+        if not self._cert:
+            self.algorithm = sig_algorithm
+
+        if sig_algorithm == b'rsa-sha2-256':
+            self._flags |= SSH_AGENT_RSA_SHA2_256
+        elif sig_algorithm == b'rsa-sha2-512':
+            self._flags |= SSH_AGENT_RSA_SHA2_512
 
     @asyncio.coroutine
     def sign(self, data):
         """Sign a block of data with this private key"""
 
-        return (yield from self._agent.sign(self.public_data, data))
+        return (yield from self._agent.sign(self.public_data,
+                                            data, self._flags))
+
+    @asyncio.coroutine
+    def remove(self):
+        """Remove this key pair from the agent"""
+
+        yield from self._agent.remove_keys([self])
 
 
 class SSHAgentClient:
@@ -71,6 +132,20 @@ class SSHAgentClient:
             self._reader = None
             self._writer = None
 
+    @staticmethod
+    def encode_constraints(lifetime, confirm):
+        """Encode key constraints"""
+
+        result = b''
+
+        if lifetime:
+            result += Byte(SSH_AGENT_CONSTRAIN_LIFETIME) + UInt32(lifetime)
+
+        if confirm:
+            result += Byte(SSH_AGENT_CONSTRAIN_CONFIRM)
+
+        return result
+
     @asyncio.coroutine
     def connect(self):
         """Connect to the SSH agent"""
@@ -122,9 +197,9 @@ class SSHAgentClient:
         """
 
         resptype, resp = \
-            yield from self._make_request(SSH2_AGENTC_REQUEST_IDENTITIES)
+            yield from self._make_request(SSH_AGENTC_REQUEST_IDENTITIES)
 
-        if resptype == SSH2_AGENT_IDENTITIES_ANSWER:
+        if resptype == SSH_AGENT_IDENTITIES_ANSWER:
             result = []
 
             num_keys = resp.get_uint32()
@@ -132,7 +207,11 @@ class SSHAgentClient:
                 key_blob = resp.get_string()
                 comment = resp.get_string()
 
-                result.append(_SSHAgentKeyPair(self, key_blob, comment))
+                packet = SSHPacket(key_blob)
+                algorithm = packet.get_string()
+
+                result.append(SSHAgentKeyPair(self, algorithm,
+                                              key_blob, comment))
 
             resp.check_end()
             return result
@@ -140,20 +219,254 @@ class SSHAgentClient:
             raise ValueError('Unknown SSH agent response: %d' % resptype)
 
     @asyncio.coroutine
-    def sign(self, key_blob, data):
-        """Sign a block of data with this private key"""
+    def sign(self, key_blob, data, flags=0):
+        """Sign a block of data with the requested key"""
 
         resptype, resp = \
-            yield from self._make_request(SSH2_AGENTC_SIGN_REQUEST,
+            yield from self._make_request(SSH_AGENTC_SIGN_REQUEST,
                                           String(key_blob), String(data),
-                                          UInt32(0))
+                                          UInt32(flags))
 
-        if resptype == SSH2_AGENT_SIGN_RESPONSE:
+        if resptype == SSH_AGENT_SIGN_RESPONSE:
             sig = resp.get_string()
             resp.check_end()
             return sig
         elif resptype == SSH_AGENT_FAILURE:
-            raise ValueError('Unknown key passed to SSH agent')
+            raise ValueError('Unable to sign with requested key')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def add_keys(self, keylist=(), passphrase=None,
+                 lifetime=None, confirm=False):
+        """Add keys to the agent
+
+           This method adds a list of local private keys and optional
+           matching certificates to the agent.
+
+           :param keylist: (optional)
+               The list of keys to add. If not specified, an attempt will
+               be made to load keys from the files :file:`.ssh/id_ed25519`,
+               :file:`.ssh/id_ecdsa`, :file:`.ssh/id_rsa` and
+               :file:`.ssh/id_dsa` in the user's home directory with
+               optional matching certificates loaded from the files
+               :file:`.ssh/id_ed25519-cert.pub`,
+               :file:`.ssh/id_ecdsa-cert.pub`, :file:`.ssh/id_rsa-cert.pub`,
+               and :file:`.ssh/id_dsa-cert.pub`.
+           :param str passphrase: (optional)
+               The passphrase to use to decrypt the keys.
+           :param lifetime: (optional)
+               The time in seconds after which the keys should be
+               automatically deleted, or ``None`` to store these keys
+               indefinitely (the default).
+           :param bool confirm: (optional)
+               Whether or not to require confirmation for each private
+               key operation which uses these keys, defaulting to ``False``.
+           :type keylist: *see* :ref:`SpecifyingPrivateKeys`
+           :type lifetime: `int` or ``None``
+
+           :raises: ::exc::`ValueError` if the keys cannot be added
+
+        """
+
+        if keylist:
+            keypairs = asyncssh.load_keypairs(keylist, passphrase)
+        else:
+            keypairs = load_default_keypairs(passphrase)
+
+        constraints = self.encode_constraints(lifetime, confirm)
+        msgtype = SSH_AGENTC_ADD_ID_CONSTRAINED if constraints else \
+                      SSH_AGENTC_ADD_IDENTITY
+
+        for keypair in keypairs:
+            comment = keypair.get_comment()
+            resptype, resp = \
+                yield from self._make_request(msgtype,
+                                              keypair.get_agent_private_key(),
+                                              String(comment or ''),
+                                              constraints)
+
+            if resptype == SSH_AGENT_SUCCESS:
+                resp.check_end()
+            elif resptype == SSH_AGENT_FAILURE:
+                raise ValueError('Unable to add key')
+            else:
+                raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def add_smartcard_keys(self, provider, pin=None,
+                           lifetime=None, confirm=False):
+        """Store keys associated with a smart card in the agent
+
+           :param str provider:
+               The name of the smart card provider
+           :param pin: (optional)
+               The PIN to use to unlock the smart card
+           :param lifetime: (optional)
+               The time in seconds after which the keys should be
+               automatically deleted, or ``None`` to store these keys
+               indefinitely (the default).
+           :param bool confirm: (optional)
+               Whether or not to require confirmation for each private
+               key operation which uses these keys, defaulting to ``False``.
+           :type pin: `str` or ``None``
+           :type lifetime: `int` or ``None``
+
+           :raises: ::exc::`ValueError` if the keys cannot be added
+
+        """
+
+        constraints = self.encode_constraints(lifetime, confirm)
+        msgtype = SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED \
+                      if constraints else SSH_AGENTC_ADD_SMARTCARD_KEY
+
+        resptype, resp = \
+            yield from self._make_request(msgtype, String(provider),
+                                          String(pin or ''), constraints)
+
+        if resptype == SSH_AGENT_SUCCESS:
+            resp.check_end()
+        elif resptype == SSH_AGENT_FAILURE:
+            raise ValueError('Unable to add keys')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def remove_keys(self, keylist):
+        """Remove a key stored in the agent
+
+           :param keylist:
+               The list of keys to remove.
+           :type keylist: list of :class:`SSHKeyPair`
+
+           :raises: ::exc::`ValueError` if any keys are not found
+
+        """
+
+        for keypair in keylist:
+            resptype, resp = \
+                yield from self._make_request(SSH_AGENTC_REMOVE_IDENTITY,
+                                              String(keypair.public_data))
+
+            if resptype == SSH_AGENT_SUCCESS:
+                resp.check_end()
+            elif resptype == SSH_AGENT_FAILURE:
+                raise ValueError('Key not found')
+            else:
+                raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def remove_smartcard_keys(self, provider, pin=None):
+        """Remove keys associated with a smart card stored in the agent
+
+           :param str provider:
+               The name of the smart card provider
+           :param pin: (optional)
+               The PIN to use to unlock the smart card
+           :type pin: `str` or ``None``
+
+           :raises: ::exc::`ValueError` if the keys are not found
+
+        """
+
+        resptype, resp = \
+            yield from self._make_request(SSH_AGENTC_REMOVE_SMARTCARD_KEY,
+                                          String(provider), String(pin or ''))
+
+        if resptype == SSH_AGENT_SUCCESS:
+            resp.check_end()
+        elif resptype == SSH_AGENT_FAILURE:
+            raise ValueError('Keys not found')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def remove_all(self):
+        """Remove all keys stored in the agent
+
+           :raises: ::exc::`ValueError` if the keys can't be removed
+
+        """
+
+        resptype, resp = \
+            yield from self._make_request(SSH_AGENTC_REMOVE_ALL_IDENTITIES)
+
+        if resptype == SSH_AGENT_SUCCESS:
+            resp.check_end()
+        elif resptype == SSH_AGENT_FAILURE:
+            raise ValueError('Unable to remove all keys')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def lock(self, passphrase):
+        """Lock the agent using the specified passphrase
+
+           :param str passphrase:
+               The passphrase required to later unlock the agent
+
+           :raises: ::exc::`ValueError` if the agent can't be locked
+
+        """
+
+        resptype, resp = yield from self._make_request(SSH_AGENTC_LOCK,
+                                                       String(passphrase))
+
+        if resptype == SSH_AGENT_SUCCESS:
+            resp.check_end()
+        elif resptype == SSH_AGENT_FAILURE:
+            raise ValueError('Unable to lock SSH agent')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def unlock(self, passphrase):
+        """Unlock the agent using the specified passphrase
+
+           :param str passphrase:
+               The passphrase to use to unlock the agent
+
+           :raises: ::exc::`ValueError` if the agent can't be unlocked
+
+        """
+
+        resptype, resp = yield from self._make_request(SSH_AGENTC_UNLOCK,
+                                                       String(passphrase))
+
+        if resptype == SSH_AGENT_SUCCESS:
+            resp.check_end()
+        elif resptype == SSH_AGENT_FAILURE:
+            raise ValueError('Unable to unlock SSH agent')
+        else:
+            raise ValueError('Unknown SSH agent response: %d' % resptype)
+
+    @asyncio.coroutine
+    def query_extensions(self):
+        """Return a list of extensions supported by the agent
+
+           :returns: A list of strings of supported extension names
+
+        """
+
+        resptype, resp = yield from self._make_request(SSH_AGENTC_EXTENSION,
+                                                       String('query'))
+
+        if resptype == SSH_AGENT_SUCCESS:
+            result = []
+
+            while resp:
+                exttype = resp.get_string()
+
+                try:
+                    exttype = exttype.decode('utf-8')
+                except UnicodeDecodeError:
+                    raise ValueError('Invalid extension type name')
+
+                result.append(exttype)
+
+            return result
+        elif resptype == SSH_AGENT_FAILURE:
+            return []
         else:
             raise ValueError('Unknown SSH agent response: %d' % resptype)
 
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index b2fe2c7..c3f2a01 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -45,8 +45,8 @@ from .constants import DISC_KEY_EXCHANGE_FAILED, DISC_HOST_KEY_NOT_VERIFYABLE
 from .constants import DISC_MAC_ERROR, DISC_NO_MORE_AUTH_METHODS_AVAILABLE
 from .constants import DISC_PROTOCOL_ERROR, DISC_SERVICE_NOT_AVAILABLE
 from .constants import EXTENDED_DATA_STDERR
-from .constants import MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED
-from .constants import MSG_DEBUG, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT
+from .constants import MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG
+from .constants import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, MSG_EXT_INFO
 from .constants import MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_CONFIRMATION
 from .constants import MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_WINDOW_ADJUST
 from .constants import MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA
@@ -76,21 +76,17 @@ 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 map_handler_name
+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 .public_key import CERT_TYPE_HOST, CERT_TYPE_USER
+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 import_private_key, import_public_key
-from .public_key import read_private_key, read_public_key
-from .public_key import read_private_key_list, read_public_key_list
-from .public_key import import_certificate, read_certificate
-from .public_key import SSHKeyPair, SSHLocalKeyPair, KeyImportError
+from .public_key import load_keypairs, load_public_keys
 
 from .saslprep import saslprep, SASLPrepError
 
@@ -110,9 +106,6 @@ _DEFAULT_PORT = 22
 _USERAUTH_SERVICE = b'ssh-userauth'
 _CONNECTION_SERVICE = b'ssh-connection'
 
-# Default file names in .ssh directory to read private keys from
-_DEFAULT_KEY_FILES = ('id_ed25519', 'id_ecdsa', 'id_rsa', 'id_dsa')
-
 # Default rekey parameters
 _DEFAULT_REKEY_BYTES = 1 << 30      # 1 GiB
 _DEFAULT_REKEY_SECONDS = 3600       # 1 hour
@@ -128,173 +121,6 @@ _DEFAULT_MAX_PKTSIZE = 32768        # 32 kiB
 _DEFAULT_LINE_HISTORY = 1000        # 1000 lines
 
 
-def _load_private_key(key, passphrase=None):
-    """Load a private key
-
-       This function loads a private key and an optional certificate.
-       The key argument can be either a key reference or a tuple
-       with a reference to a key and a reference to a matching
-       certificate.
-
-       Key references can either be the name of a file to load the
-       key from, a byte string to import as a private key, or an
-       already loaded :class:`SSHKey` private key.
-
-       Certificate references can be the name of a file to load the
-       certificate from, a byte string to import as a certificate,
-       an already loaded :class:`SSHCertificate`, or ``None`` if
-       no certificate should be associated with the key.
-
-       When a filename is provided in as a reference outside of a
-       tuple, an attempt is made to load a private key from that
-       file and a certificate from a file constructed by appending
-       '-cert.pub' to the end of the filename.
-
-       This function returns a tuple of a :class:`SSHKey` private
-       key and either an :class:`SSHCertificate` or ``None``
-       depending on whether an associated certificate was loaded.
-
-    """
-
-    if isinstance(key, str):
-        cert = key + '-cert.pub'
-        ignore_missing_cert = True
-    elif isinstance(key, tuple):
-        key, cert = key
-        ignore_missing_cert = False
-    else:
-        cert = None
-
-    if isinstance(key, str):
-        key = read_private_key(key, passphrase)
-    elif isinstance(key, bytes):
-        key = import_private_key(key, passphrase)
-
-    if isinstance(cert, str):
-        try:
-            cert = read_certificate(cert)
-        except OSError:
-            if ignore_missing_cert:
-                cert = None
-            else:
-                raise
-    elif isinstance(cert, bytes):
-        cert = import_certificate(cert)
-
-    if cert and key.get_ssh_public_key() != cert.key.get_ssh_public_key():
-        raise ValueError('Certificate key mismatch')
-
-    return key, cert
-
-
-def _load_private_keypair(key, passphrase=None):
-    """Load an SSH key pair
-
-       This function loads an SSH key pair. The key argument can be
-       either an already loaded :class:`SSHKeyPair`, a reference
-       to a key and optional certificate as described in
-       :func:`_load_private_key`, or ``None``.
-
-       It returns an :class:`SSHKeyPair` or ``None``.
-
-    """
-
-    if isinstance(key, SSHKeyPair):
-        return key
-    elif key:
-        return SSHLocalKeyPair(*_load_private_key(key, passphrase))
-    else:
-        return None
-
-
-def _load_private_keypair_list(keylist, passphrase=None):
-    """Load list of SSH key pairs
-
-       This function loads a collection of SSH key pairs. The keylist
-       argument can be either a filename to load private keys from
-       (without any certificates) or a list of values representing
-       key pairs as described in ::func::`_load_private_keypair`.
-
-       In cases where a private key is provided with a certificate,
-       the private key is added to the list both with and without
-       the certificate.
-
-       This function returns a list of :class:`SSHKeyPair` objects.
-
-    """
-
-    if isinstance(keylist, str):
-        keys = read_private_key_list(keylist, passphrase)
-        return [SSHLocalKeyPair(key) for key in keys]
-    else:
-        result = []
-
-        for key in keylist:
-            if isinstance(key, SSHKeyPair):
-                result.append(key)
-            else:
-                key, cert = _load_private_key(key, passphrase)
-
-                if cert:
-                    result.append(SSHLocalKeyPair(key, cert))
-
-                result.append(SSHLocalKeyPair(key))
-
-        return result
-
-
-def _load_public_key(key):
-    """Load a public key
-
-       This function loads a public key. The key argument can be
-       the name of a file to load the key from, a byte string to
-       import the key from, or an already loaded :class:`SSHKey`
-       public key.
-
-    """
-
-    if isinstance(key, str):
-        key = read_public_key(key)
-    elif isinstance(key, bytes):
-        key = import_public_key(key)
-
-    return key
-
-
-def _load_public_key_list(keylist):
-    """Load public key list
-
-       This function loads a collection of public keys. The keylist
-       argument can be either a filename to load keys from or a
-       list of values representing keys as described in
-       :func:`_load_public_key`.
-
-       It returns a list of loaded :class:`SSHKey` public keys.
-
-    """
-
-    if isinstance(keylist, str):
-        return read_public_key_list(keylist)
-    else:
-        return [_load_public_key(key) for key in keylist]
-
-
-def _load_authorized_keys(authorized_keys):
-    """Load authorized keys list
-
-       This function loads authorized client keys. The authorized_keys
-       argument can be either a filename to load keys from or an
-       already imported :class:`SSHAuthorizedKeys` object
-       containing the authorized keys and their associated options.
-
-    """
-
-    if isinstance(authorized_keys, str):
-        return read_authorized_keys(authorized_keys)
-    else:
-        return authorized_keys
-
-
 def _validate_version(version):
     """Validate requested SSH version"""
 
@@ -340,7 +166,7 @@ def _select_algs(alg_type, algs, possible_algs, none_value=None):
         raise ValueError('No %s algorithms selected' % alg_type)
 
 
-def _validate_algs(kex_algs, enc_algs, mac_algs, cmp_algs):
+def _validate_algs(kex_algs, enc_algs, mac_algs, cmp_algs, sig_algs):
     """Validate requested algorithms"""
 
     kex_algs = _select_algs('key exchange', kex_algs, get_kex_algs())
@@ -348,16 +174,17 @@ def _validate_algs(kex_algs, enc_algs, mac_algs, cmp_algs):
     mac_algs = _select_algs('MAC', mac_algs, get_mac_algs())
     cmp_algs = _select_algs('compression', cmp_algs,
                             get_compression_algs(), b'none')
+    sig_algs = _select_algs('signature', sig_algs, get_public_key_algs())
 
-    return kex_algs, enc_algs, mac_algs, cmp_algs
+    return kex_algs, enc_algs, mac_algs, cmp_algs, sig_algs
 
 
 class SSHConnection(SSHPacketHandler):
     """Parent class for SSH connections"""
 
     def __init__(self, protocol_factory, loop, version, kex_algs,
-                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
-                 rekey_seconds, server):
+                 encryption_algs, mac_algs, compression_algs, signature_algs,
+                 rekey_bytes, rekey_seconds, server):
         self._protocol_factory = protocol_factory
         self._loop = loop
         self._tasks = set()
@@ -407,6 +234,7 @@ class SSHConnection(SSHPacketHandler):
         self._enc_algs = encryption_algs
         self._mac_algs = mac_algs
         self._cmp_algs = compression_algs
+        self._sig_algs = signature_algs
 
         self._kex = None
         self._kexinit_sent = False
@@ -427,6 +255,11 @@ class SSHConnection(SSHPacketHandler):
         self._cmp_alg_cs = None
         self._cmp_alg_sc = None
 
+        self._can_send_ext_info = False
+        self._extensions_sent = OrderedDict()
+
+        self._server_sig_algs = ()
+
         self._next_service = None
 
         self._agent = None
@@ -693,6 +526,11 @@ class SSHConnection(SSHPacketHandler):
         raise DisconnectError(DISC_KEY_EXCHANGE_FAILED,
                               'No matching %s algorithm found' % alg_type)
 
+    def _get_ext_info_kex_alg(self):
+        """Return the kex alg to add if any to request extension info"""
+
+        return [b'ext-info-c'] if self.is_client() else []
+
     def _send(self, data):
         """Send data to the SSH connection"""
 
@@ -950,7 +788,7 @@ class SSHConnection(SSHPacketHandler):
         self._rekey_time = time.monotonic() + self._rekey_seconds
 
         cookie = os.urandom(16)
-        kex_algs = NameList(self._kex_algs)
+        kex_algs = NameList(self._kex_algs + self._get_ext_info_kex_alg())
         host_key_algs = NameList(self._server_host_key_algs)
         enc_algs = NameList(self._enc_algs)
         mac_algs = NameList(self._mac_algs)
@@ -968,6 +806,17 @@ class SSHConnection(SSHPacketHandler):
 
         self.send_packet(packet)
 
+    def _send_ext_info(self):
+        """Send extension information"""
+
+        packet = b''.join((Byte(MSG_EXT_INFO),
+                           UInt32(len(self._extensions_sent))))
+
+        for name, value in self._extensions_sent.items():
+            packet += String(name) + String(value)
+
+        self.send_packet(packet)
+
     def send_newkeys(self, k, h):
         """Finish a key exchange and send a new keys message"""
 
@@ -1078,6 +927,11 @@ class SSHConnection(SSHPacketHandler):
 
             self._next_service = _USERAUTH_SERVICE
 
+            if self._can_send_ext_info:
+                self._extensions_sent['server-sig-algs'] = \
+                    b','.join(self._sig_algs)
+                self._send_ext_info()
+
         self._kex_complete = True
         self._send_deferred_packets()
 
@@ -1291,6 +1145,25 @@ class SSHConnection(SSHPacketHandler):
             raise DisconnectError(DISC_SERVICE_NOT_AVAILABLE,
                                   'Unexpected service accept received')
 
+    def _process_ext_info(self, pkttype, packet):
+        """Process extension information"""
+
+        # pylint: disable=unused-argument
+
+        extensions = {}
+
+        num_extensions = packet.get_uint32()
+        for _ in range(num_extensions):
+            name = packet.get_string()
+            value = packet.get_string()
+            extensions[name] = value
+
+        packet.check_end()
+
+        if self.is_client():
+            self._server_sig_algs = \
+                extensions.get(b'server-sig-algs').split(b',')
+
     def _process_kexinit(self, pkttype, packet):
         """Process a key exchange request"""
 
@@ -1323,6 +1196,9 @@ class SSHConnection(SSHPacketHandler):
             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:
+                self._can_send_ext_info = True
         else:
             self._server_kexinit = packet.get_consumed_payload()
 
@@ -1615,6 +1491,7 @@ class SSHConnection(SSHPacketHandler):
         MSG_DEBUG:                      _process_debug,
         MSG_SERVICE_REQUEST:            _process_service_request,
         MSG_SERVICE_ACCEPT:             _process_service_accept,
+        MSG_EXT_INFO:                   _process_ext_info,
 
         MSG_KEXINIT:                    _process_kexinit,
         MSG_NEWKEYS:                    _process_newkeys,
@@ -1977,12 +1854,14 @@ class SSHClientConnection(SSHConnection):
     """
 
     def __init__(self, client_factory, loop, client_version, kex_algs,
-                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
-                 rekey_seconds, host, port, known_hosts, username, password,
-                 client_keys, agent, agent_path, auth_waiter):
+                 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):
         super().__init__(client_factory, loop, client_version, kex_algs,
                          encryption_algs, mac_algs, compression_algs,
-                         rekey_bytes, rekey_seconds, server=False)
+                         signature_algs, rekey_bytes, rekey_seconds,
+                         server=False)
 
         self._host = host
         self._port = port if port != _DEFAULT_PORT else None
@@ -2025,10 +1904,9 @@ class SSHClientConnection(SSHConnection):
                 server_host_keys, server_ca_keys, revoked_server_keys = \
                     self._known_hosts
 
-                server_host_keys = _load_public_key_list(server_host_keys)
-                server_ca_keys = _load_public_key_list(server_ca_keys)
-                revoked_server_keys = \
-                    _load_public_key_list(revoked_server_keys)
+                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)
 
             self._server_host_keys = set()
             self._server_host_key_algs = []
@@ -2042,7 +1920,7 @@ class SSHClientConnection(SSHConnection):
             for key in server_host_keys:
                 self._server_host_keys.add(key)
                 if key.algorithm not in self._server_host_key_algs:
-                    self._server_host_key_algs.append(key.algorithm)
+                    self._server_host_key_algs.extend(key.sig_algorithms)
 
         if not self._server_host_key_algs:
             raise DisconnectError(DISC_HOST_KEY_NOT_VERIFYABLE,
@@ -2137,15 +2015,28 @@ class SSHClientConnection(SSHConnection):
     def public_key_auth_requested(self):
         """Return a client key pair to authenticate with"""
 
-        if self._client_keys:
-            return self._client_keys.pop(0)
-        else:
-            result = self._owner.public_key_auth_requested()
+        while True:
+            if not self._client_keys:
+                result = self._owner.public_key_auth_requested()
 
-            if asyncio.iscoroutine(result):
-                result = yield from result
+                if asyncio.iscoroutine(result):
+                    result = yield from result
+
+                if not result:
+                    return None
 
-            return _load_private_keypair(result)
+                self._client_keys = load_keypairs(result)
+
+            keypair = self._client_keys.pop(0)
+
+            if self._server_sig_algs:
+                for alg in keypair.sig_algorithms:
+                    if alg in self._sig_algs and alg in self._server_sig_algs:
+                        keypair.set_sig_algorithm(alg)
+                        return keypair
+
+            if keypair.sig_algorithms[-1] in self._sig_algs:
+                return keypair
 
     @asyncio.coroutine
     def password_auth_requested(self):
@@ -3087,14 +2978,15 @@ class SSHServerConnection(SSHConnection):
     """
 
     def __init__(self, server_factory, loop, server_version, kex_algs,
-                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
-                 rekey_seconds, server_host_keys, authorized_client_keys,
-                 allow_pty, line_editor, line_history, agent_forwarding,
-                 session_factory, session_encoding, sftp_factory, window,
-                 max_pktsize, login_timeout):
+                 encryption_algs, mac_algs, compression_algs, signature_algs,
+                 rekey_bytes, rekey_seconds, server_host_keys,
+                 authorized_client_keys, allow_pty, line_editor, line_history,
+                 agent_forwarding, session_factory, session_encoding,
+                 sftp_factory, window, max_pktsize, login_timeout):
         super().__init__(server_factory, loop, server_version, kex_algs,
                          encryption_algs, mac_algs, compression_algs,
-                         rekey_bytes, rekey_seconds, server=True)
+                         signature_algs, rekey_bytes, rekey_seconds,
+                         server=True)
 
         self._server_host_keys = server_host_keys
         self._server_host_key_algs = server_host_keys.keys()
@@ -3157,8 +3049,12 @@ class SSHServerConnection(SSHConnection):
         """
 
         for alg in peer_host_key_algs:
-            if alg in self._server_host_keys:
-                self._server_host_key = self._server_host_keys[alg]
+            keypair = self._server_host_keys.get(alg)
+            if keypair:
+                if alg != keypair.algorithm:
+                    keypair.set_sig_algorithm(alg)
+
+                self._server_host_key = keypair
                 return True
 
         return False
@@ -3166,8 +3062,9 @@ class SSHServerConnection(SSHConnection):
     def get_server_host_key(self):
         """Return the chosen server host key
 
-           This method returns the chosen server host private key and a
-           corresponding public key or certificate which contains it.
+           This method returns a keypair object containing the
+           chosen server host key and a corresponding public key
+           or certificate.
 
         """
 
@@ -3673,7 +3570,10 @@ class SSHServerConnection(SSHConnection):
 
         """
... 3803 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