[Python-modules-commits] [python-asyncssh] 06/08: New upstream release.

Vincent Bernat bernat at moszumanska.debian.org
Wed Nov 22 21:11:49 UTC 2017


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

bernat pushed a commit to annotated tag debian/1.11.1-1
in repository python-asyncssh.

commit 48bc3a09f6f3639c7a8a75888c526a81740b6e3a
Author: Vincent Bernat <bernat at debian.org>
Date:   Wed Nov 22 21:52:14 2017 +0100

    New upstream release.
---
 .travis.yml                 |  39 ++++++++++---
 asyncssh/channel.py         |  11 +++-
 asyncssh/connection.py      |   2 +-
 asyncssh/crypto/__init__.py |   3 +-
 asyncssh/crypto/pyca/dsa.py |   5 +-
 asyncssh/crypto/pyca/ec.py  |   6 +-
 asyncssh/crypto/pyca/kdf.py |  28 ++++++++++
 asyncssh/dsa.py             |  47 ++++++++--------
 asyncssh/ecdsa.py           |  45 +++++++--------
 asyncssh/ed25519.py         |  36 ++++++------
 asyncssh/pbe.py             |  82 ++++++----------------------
 asyncssh/process.py         |  49 ++++++++++-------
 asyncssh/public_key.py      |  59 ++++++++++++++++----
 asyncssh/rsa.py             |  38 ++++++-------
 asyncssh/session.py         |   2 +-
 asyncssh/version.py         |   2 +-
 docs/api.rst                |   2 -
 docs/changes.rst            |  20 +++++++
 tests/test_agent.py         |   4 +-
 tests/test_process.py       | 130 ++++++++++++++++++++++++++++++++++++++++++++
 tests/test_public_key.py    |  20 ++++---
 tox.ini                     |   4 +-
 22 files changed, 422 insertions(+), 212 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 2042de3..b9731be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ install:
 
 matrix:
   allow_failures:
-    - python: 3.6-dev
+    - python: "nightly"
   fast_finish: true
   include:
     - os: linux
@@ -27,13 +27,21 @@ matrix:
     - os: linux
       sudo: required
       dist: trusty
-      python: 3.6-dev
+      python: 3.6
       before_install:
         - .travis/build-libsodium.sh
       env:
         - TOXENV=py36
+    - os: linux
+      sudo: required
+      dist: trusty
+      python: "nightly" # currently points to 3.7-rc
+      before_install:
+        - .travis/build-libsodium.sh
+      env:
+        - TOXENV=py37
     - os: osx
-      osx_image: xcode8.1
+      osx_image: xcode9.1
       language: generic
       env:
         - CPPFLAGS=-I/usr/local/opt/openssl/include
@@ -44,11 +52,11 @@ matrix:
         - brew update
         - brew install libffi libsodium
         - eval "$(pyenv init -)"
-        - pyenv install 3.4.5
-        - pyenv local 3.4.5
+        - pyenv install 3.4.6
+        - pyenv local 3.4.6
         - pyenv rehash
     - os: osx
-      osx_image: xcode8.1
+      osx_image: xcode9.1
       language: generic
       env:
         - CPPFLAGS=-I/usr/local/opt/openssl/include
@@ -59,8 +67,23 @@ matrix:
         - brew update
         - brew install libffi libsodium
         - eval "$(pyenv init -)"
-        - pyenv install 3.5.2
-        - pyenv local 3.5.2
+        - pyenv install 3.5.3
+        - pyenv local 3.5.3
+        - pyenv rehash
+    - os: osx
+      osx_image: xcode9.1
+      language: generic
+      env:
+        - CPPFLAGS=-I/usr/local/opt/openssl/include
+        - LDFLAGS=-L/usr/local/opt/openssl/lib -L/usr/local/opt/libffi/lib
+        - PATH=$HOME/.pyenv/bin:/usr/local/opt/openssl/bin:$PATH
+        - TOXENV=py36
+      before_install:
+        - brew update
+        - brew install libffi libsodium
+        - eval "$(pyenv init -)"
+        - pyenv install 3.6.1
+        - pyenv local 3.6.1
         - pyenv rehash
 
 script: travis_wait 60 tox
diff --git a/asyncssh/channel.py b/asyncssh/channel.py
index e9b4c03..8fcfe7c 100644
--- a/asyncssh/channel.py
+++ b/asyncssh/channel.py
@@ -1093,7 +1093,16 @@ class SSHClientChannel(SSHChannel):
 
            This method can be called to deliver a signal to the remote
            process or service. Signal names should be as described in
-           section 6.10 of :rfc:`4254#section-6.10`.
+           section 6.10 of :rfc:`RFC 4254 <4254#section-6.10>`.
+
+           .. note:: OpenSSH's SSH server implementation does not
+                     currently support this message, so attempts to
+                     use :meth:`send_signal`, :meth:`terminate`, or
+                     :meth:`kill` with an OpenSSH SSH server will
+                     end up being ignored. This is currently being
+                     tracked in OpenSSH `bug 1424`__.
+
+                     __ https://bugzilla.mindrot.org/show_bug.cgi?id=1424
 
            :param str signal:
                The signal to deliver
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index 80987c2..bb2cc8d 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -2498,7 +2498,7 @@ class SSHClientConnection(SSHConnection):
            :param term_modes: (optional)
                POSIX terminal modes to set for this session, where keys
                are taken from :ref:`POSIX terminal modes <PTYModes>` with
-               values defined in section 8 of :rfc:`4254#section-8`.
+               values defined in section 8 of :rfc:`RFC 4254 <4254#section-8>`.
            :param bool x11_forwarding: (optional)
                Whether or not to request X11 forwarding for this session,
                defaulting to ``False``
diff --git a/asyncssh/crypto/__init__.py b/asyncssh/crypto/__init__.py
index 9e05e8a..4636df2 100644
--- a/asyncssh/crypto/__init__.py
+++ b/asyncssh/crypto/__init__.py
@@ -16,10 +16,11 @@ from .cipher import register_cipher, lookup_cipher
 
 from .ec import lookup_ec_curve_by_params
 
-# Import PyCA versions of DSA, ECDSA, and RSA
+# Import PyCA versions of DSA, ECDSA, RSA, and PBKDF2
 from .pyca.dsa import DSAPrivateKey, DSAPublicKey
 from .pyca.ec import ECDSAPrivateKey, ECDSAPublicKey, ECDH
 from .pyca.rsa import RSAPrivateKey, RSAPublicKey
+from .pyca.kdf import pbkdf2_hmac
 
 # Import pyca module to get ciphers defined there registered
 from . import pyca
diff --git a/asyncssh/crypto/pyca/dsa.py b/asyncssh/crypto/pyca/dsa.py
index d38f5c6..2364eef 100644
--- a/asyncssh/crypto/pyca/dsa.py
+++ b/asyncssh/crypto/pyca/dsa.py
@@ -17,7 +17,6 @@ from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.hashes import SHA1
 from cryptography.hazmat.primitives.asymmetric import dsa
 
-from ...asn1 import der_encode, der_decode
 from .misc import PyCAKey
 
 # Short variable names are used here, matching names in the spec
@@ -93,7 +92,7 @@ class DSAPrivateKey(_DSAKey):
     def sign(self, data):
         """Sign a block of data"""
 
-        return der_decode(self.pyca_key.sign(data, SHA1()))
+        return self.pyca_key.sign(data, SHA1())
 
 
 class DSAPublicKey(_DSAKey):
@@ -113,7 +112,7 @@ class DSAPublicKey(_DSAKey):
         """Verify the signature on a block of data"""
 
         try:
-            self.pyca_key.verify(der_encode(sig), data, SHA1())
+            self.pyca_key.verify(sig, data, SHA1())
             return True
         except InvalidSignature:
             return False
diff --git a/asyncssh/crypto/pyca/ec.py b/asyncssh/crypto/pyca/ec.py
index e711407..edcb4de 100644
--- a/asyncssh/crypto/pyca/ec.py
+++ b/asyncssh/crypto/pyca/ec.py
@@ -17,7 +17,6 @@ from cryptography.hazmat.backends.openssl import backend
 from cryptography.hazmat.primitives.hashes import SHA256, SHA384, SHA512
 from cryptography.hazmat.primitives.asymmetric import ec
 
-from ...asn1 import der_encode, der_decode
 from .misc import PyCAKey
 
 # Short variable names are used here, matching names in the spec
@@ -119,7 +118,7 @@ class ECDSAPrivateKey(_ECKey):
     def sign(self, data):
         """Sign a block of data"""
 
-        return der_decode(self.pyca_key.sign(data, ec.ECDSA(self._hash_alg())))
+        return self.pyca_key.sign(data, ec.ECDSA(self._hash_alg()))
 
 
 class ECDSAPublicKey(_ECKey):
@@ -140,8 +139,7 @@ class ECDSAPublicKey(_ECKey):
         """Verify the signature on a block of data"""
 
         try:
-            self.pyca_key.verify(der_encode(sig), data,
-                                 ec.ECDSA(self._hash_alg()))
+            self.pyca_key.verify(sig, data, ec.ECDSA(self._hash_alg()))
             return True
         except InvalidSignature:
             return False
diff --git a/asyncssh/crypto/pyca/kdf.py b/asyncssh/crypto/pyca/kdf.py
new file mode 100644
index 0000000..7c18025
--- /dev/null
+++ b/asyncssh/crypto/pyca/kdf.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2017 by Ron Frederick <ronf at timeheart.net>.
+# All rights reserved.
+#
+# This program and the accompanying materials are made available under
+# the terms of the Eclipse Public License v1.0 which accompanies this
+# distribution and is available at:
+#
+#     http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     Ron Frederick - initial implementation, API, and documentation
+
+"""A shim around PyCA for key derivation functions"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.hashes import SHA1, SHA224, SHA256
+from cryptography.hazmat.primitives.hashes import SHA384, SHA512
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+
+
+_hashes = {h.name: h for h in (SHA1, SHA224, SHA256, SHA384, SHA512)}
+
+
+def pbkdf2_hmac(hash_name, passphrase, salt, count, key_size):
+    """A shim around PyCA for PBKDF2 HMAC key derivation"""
+
+    return PBKDF2HMAC(_hashes[hash_name](), key_size, salt, count,
+                      default_backend()).derive(passphrase)
diff --git a/asyncssh/dsa.py b/asyncssh/dsa.py
index b649c0f..66a24ae 100644
--- a/asyncssh/dsa.py
+++ b/asyncssh/dsa.py
@@ -15,7 +15,7 @@
 from .asn1 import ASN1DecodeError, ObjectIdentifier, der_encode, der_decode
 from .crypto import DSAPrivateKey, DSAPublicKey
 from .misc import all_ints
-from .packet import MPInt, String, PacketDecodeError, SSHPacket
+from .packet import MPInt
 from .public_key import SSHKey, SSHOpenSSHCertificateV01, KeyExportError
 from .public_key import register_public_key_alg, register_certificate_alg
 from .public_key import register_x509_certificate_alg
@@ -32,6 +32,7 @@ class _DSAKey(SSHKey):
     pkcs8_oid = ObjectIdentifier('1.2.840.10040.4.1')
     sig_algorithms = (algorithm,)
     x509_algorithms = (b'x509v3-' + algorithm,)
+    all_sig_algorithms = set(sig_algorithms)
 
     def __eq__(self, other):
         # This isn't protected access - both objects are _DSAKey instances
@@ -196,42 +197,40 @@ class _DSAKey(SSHKey):
 
         return MPInt(self._key.x)
 
-    def sign(self, data, algorithm):
-        """Return a signature of the specified data using this key"""
+    def sign_der(self, data, sig_algorithm):
+        """Compute a DER-encoded signature of the specified data"""
+
+        # pylint: disable=unused-argument
 
         if not self._key.x:
             raise ValueError('Private key needed for signing')
 
-        if algorithm not in self.sig_algorithms:
-            raise ValueError('Unrecognized signature algorithm')
+        return self._key.sign(data)
 
-        r, s = self._key.sign(data)
-        return b''.join((String(algorithm),
-                         String(r.to_bytes(20, 'big') +
-                                s.to_bytes(20, 'big'))))
+    def verify_der(self, data, sig_algorithm, sig):
+        """Verify a DER-encoded signature of the specified data"""
 
-    def verify(self, data, sig):
-        """Verify a signature of the specified data using this key"""
-
-        try:
-            packet = SSHPacket(sig)
+        # pylint: disable=unused-argument
 
-            if packet.get_string() not in self.sig_algorithms:
-                return False
+        return self._key.verify(data, sig)
 
-            sig = packet.get_string()
-            packet.check_end()
+    def sign_ssh(self, data, sig_algorithm):
+        """Compute an SSH-encoded signature of the specified data"""
 
-            if len(sig) != 40:
-                return False
+        r, s = der_decode(self.sign_der(data, sig_algorithm))
+        return r.to_bytes(20, 'big') + s.to_bytes(20, 'big')
 
-            r = int.from_bytes(sig[:20], 'big')
-            s = int.from_bytes(sig[20:], 'big')
+    def verify_ssh(self, data, sig_algorithm, sig):
+        """Verify an SSH-encoded signature of the specified data"""
 
-            return self._key.verify(data, (r, s))
-        except PacketDecodeError:
+        if len(sig) != 40:
             return False
 
+        r = int.from_bytes(sig[:20], 'big')
+        s = int.from_bytes(sig[20:], 'big')
+
+        return self.verify_der(data, sig_algorithm, der_encode((r, s)))
+
 
 register_public_key_alg(b'ssh-dss', _DSAKey)
 
diff --git a/asyncssh/ecdsa.py b/asyncssh/ecdsa.py
index ec8da9f..890bb55 100644
--- a/asyncssh/ecdsa.py
+++ b/asyncssh/ecdsa.py
@@ -16,7 +16,7 @@ from .asn1 import ASN1DecodeError, BitString, ObjectIdentifier, TaggedDERObject
 from .asn1 import der_encode, der_decode
 from .crypto import lookup_ec_curve_by_params
 from .crypto import ECDSAPrivateKey, ECDSAPublicKey
-from .packet import MPInt, String, SSHPacket, PacketDecodeError
+from .packet import MPInt, String, SSHPacket
 from .public_key import SSHKey, SSHOpenSSHCertificateV01
 from .public_key import KeyImportError, KeyExportError
 from .public_key import register_public_key_alg, register_certificate_alg
@@ -44,6 +44,7 @@ class _ECKey(SSHKey):
         self.algorithm = b'ecdsa-sha2-' + key.curve_id
         self.sig_algorithms = (self.algorithm,)
         self.x509_algorithms = (b'x509v3-' + self.algorithm,)
+        self.all_sig_algorithms = set(self.sig_algorithms)
 
         self._alg_oid = _alg_oids[key.curve_id]
 
@@ -260,38 +261,38 @@ class _ECKey(SSHKey):
 
         return MPInt(self._key.d)
 
-    def sign(self, data, algorithm):
-        """Return a signature of the specified data using this key"""
+    def sign_der(self, data, sig_algorithm):
+        """Compute a DER-encoded signature of the specified data"""
+
+        # pylint: disable=unused-argument
 
         if not self._key.private_value:
             raise ValueError('Private key needed for signing')
 
-        if algorithm not in self.sig_algorithms:
-            raise ValueError('Unrecognized signature algorithm')
+        return self._key.sign(data)
 
-        r, s = self._key.sign(data)
-        return b''.join((String(algorithm), String(MPInt(r) + MPInt(s))))
+    def verify_der(self, data, sig_algorithm, sig):
+        """Verify a DER-encoded signature of the specified data"""
 
-    def verify(self, data, sig):
-        """Verify a signature of the specified data using this key"""
+        # pylint: disable=unused-argument
 
-        try:
-            packet = SSHPacket(sig)
+        return self._key.verify(data, sig)
+
+    def sign_ssh(self, data, sig_algorithm):
+        """Compute an SSH-encoded signature of the specified data"""
 
-            if packet.get_string() not in self.sig_algorithms:
-                return False
+        r, s = der_decode(self.sign_der(data, sig_algorithm))
+        return MPInt(r) + MPInt(s)
 
-            sig = packet.get_string()
-            packet.check_end()
+    def verify_ssh(self, data, sig_algorithm, sig):
+        """Verify an SSH-encoded signature of the specified data"""
 
-            packet = SSHPacket(sig)
-            r = packet.get_mpint()
-            s = packet.get_mpint()
-            packet.check_end()
+        packet = SSHPacket(sig)
+        r = packet.get_mpint()
+        s = packet.get_mpint()
+        packet.check_end()
 
-            return self._key.verify(data, (r, s))
-        except PacketDecodeError:
-            return False
+        return self.verify_der(data, sig_algorithm, der_encode((r, s)))
 
 
 for _curve_id, _oid in ((b'nistp521', '1.3.132.0.35'),
diff --git a/asyncssh/ed25519.py b/asyncssh/ed25519.py
index 158a1ea..ec4061a 100644
--- a/asyncssh/ed25519.py
+++ b/asyncssh/ed25519.py
@@ -12,7 +12,7 @@
 
 """Ed25519 public key encryption handler"""
 
-from .packet import String, SSHPacket
+from .packet import String
 from .public_key import SSHKey, SSHOpenSSHCertificateV01, KeyExportError
 from .public_key import register_public_key_alg, register_certificate_alg
 
@@ -25,6 +25,7 @@ class _Ed25519Key(SSHKey):
 
     algorithm = b'ssh-ed25519'
     sig_algorithms = (algorithm,)
+    all_sig_algorithms = set(sig_algorithms)
 
     def __init__(self, vk, sk):
         super().__init__()
@@ -97,36 +98,37 @@ class _Ed25519Key(SSHKey):
 
         return self.encode_ssh_private()
 
-    def sign(self, data, algorithm):
-        """Return a signature of the specified data using this key"""
+    def sign_der(self, data, sig_algorithm):
+        """Return a DER-encoded signature of the specified data"""
 
         # pylint: disable=unused-argument
 
         if self._sk is None:
             raise ValueError('Private key needed for signing')
 
-        if algorithm not in self.sig_algorithms:
-            raise ValueError('Unrecognized signature algorithm')
-
         sig = libnacl.crypto_sign(data, self._sk)
-        return b''.join((String(algorithm), String(sig[:-len(data)])))
-
-    def verify(self, data, sig):
-        """Verify a signature of the specified data using this key"""
+        return sig[:-len(data)]
 
-        try:
-            packet = SSHPacket(sig)
+    def verify_der(self, data, sig_algorithm, sig):
+        """Verify a DER-encoded signature of the specified data"""
 
-            if packet.get_string() not in self.sig_algorithms:
-                return False
-
-            sig = packet.get_string()
-            packet.check_end()
+        # pylint: disable=unused-argument
 
+        try:
             return libnacl.crypto_sign_open(sig + data, self._vk) == data
         except ValueError:
             return False
 
+    def sign_ssh(self, data, sig_algorithm):
+        """Return an SSH-encoded signature of the specified data"""
+
+        return self.sign_der(data, sig_algorithm)
+
+    def verify_ssh(self, data, sig_algorithm, sig):
+        """Verify an SSH-encoded signature of the specified data"""
+
+        return self.verify_der(data, sig_algorithm, sig)
+
 
 try:
     # pylint: disable=wrong-import-position,wrong-import-order
diff --git a/asyncssh/pbe.py b/asyncssh/pbe.py
index 3630972..2c00489 100644
--- a/asyncssh/pbe.py
+++ b/asyncssh/pbe.py
@@ -12,13 +12,12 @@
 
 """Asymmetric key password based encryption functions"""
 
-import hmac
 import os
 
-from hashlib import md5, sha1, sha224, sha256, sha384, sha512
+from hashlib import md5, sha1
 
 from .asn1 import ASN1DecodeError, ObjectIdentifier, der_encode, der_decode
-from .crypto import lookup_cipher
+from .crypto import lookup_cipher, pbkdf2_hmac
 
 
 # pylint: disable=bad-whitespace
@@ -48,8 +47,6 @@ _ES2_SHA224     = ObjectIdentifier('1.2.840.113549.2.8')
 _ES2_SHA256     = ObjectIdentifier('1.2.840.113549.2.9')
 _ES2_SHA384     = ObjectIdentifier('1.2.840.113549.2.10')
 _ES2_SHA512     = ObjectIdentifier('1.2.840.113549.2.11')
-_ES2_SHA512_224 = ObjectIdentifier('1.2.840.113549.2.12')
-_ES2_SHA512_256 = ObjectIdentifier('1.2.840.113549.2.13')
 
 # pylint: enable=bad-whitespace
 
@@ -66,13 +63,6 @@ _pbes2_kdf_names = {}
 _pbes2_prf_names = {}
 
 
-def strxor(a, b):
-    """Return the byte-wise XOR of two strings"""
-
-    c = int.from_bytes(a, 'little') ^ int.from_bytes(b, 'little')
-    return int.to_bytes(c, max(len(a), len(b)), 'little')
-
-
 class KeyEncryptionError(ValueError):
     """Key encryption error
 
@@ -144,35 +134,6 @@ def _pbkdf1(hash_alg, passphrase, salt, count, key_size):
         return key[:key_size]
 
 
-def _pbkdf2(prf, passphrase, salt, count, key_size):
-    """PKCS#5 v2.0 key derivation function for password-based encryption
-
-       This function implements the PKCS#5 v2.0 algorithm for deriving
-       an encryption key from a passphrase and salt.
-
-    """
-
-    # Short variable names are used here, matching names in the spec
-    # pylint: disable=invalid-name
-
-    if isinstance(passphrase, str):
-        passphrase = passphrase.encode('utf-8')
-
-    key = b''
-    i = 1
-    while len(key) < key_size:
-        u = prf(passphrase, salt + i.to_bytes(4, 'big'))
-        f = u
-        for _ in range(1, count):
-            u = prf(passphrase, u)
-            f = strxor(f, u)
-
-        key += f
-        i += 1
-
-    return key[:key_size]
-
-
 def _pbkdf_p12(hash_alg, passphrase, salt, count, key_size, idx):
     """PKCS#12 key derivation function for password-based encryption
 
@@ -285,17 +246,6 @@ def _pbes2_iv(params, key, cipher):
     return cipher.new(key, params[0])
 
 
-def _pbes2_hmac_prf(hash_alg, digest_size=None):
-    """PKCS#5 v2.0 handler for PBKDF2 psuedo-random function
-
-       This function returns the appropriate PBKDF2 pseudo-random function
-       to use for key derivation.
-
-    """
-
-    return lambda key, msg: hmac.new(key, msg, hash_alg).digest()[:digest_size]
-
-
 def _pbes2_pbkdf2(params, passphrase, default_key_size):
     """PKCS#5 v2.0 handler for PBKDF2 key derivation
 
@@ -326,8 +276,7 @@ def _pbes2_pbkdf2(params, passphrase, default_key_size):
                 isinstance(params[0][0], ObjectIdentifier)):
             prf_alg = params[0][0]
             if prf_alg in _pbes2_prfs:
-                handler, args = _pbes2_prfs[prf_alg]
-                prf = handler(*args)
+                hash_name = _pbes2_prfs[prf_alg]
             else:
                 raise KeyEncryptionError('Unknown PBES2 pseudo-random '
                                          'function')
@@ -335,9 +284,12 @@ def _pbes2_pbkdf2(params, passphrase, default_key_size):
             raise KeyEncryptionError('Invalid PBES2 pseudo-random function '
                                      'parameters')
     else:
-        prf = _pbes2_hmac_prf(sha1)
+        hash_name = 'sha1'
+
+    if isinstance(passphrase, str):
+        passphrase = passphrase.encode('utf-8')
 
-    return _pbkdf2(prf, passphrase, salt, count, key_size)
+    return pbkdf2_hmac(hash_name, passphrase, salt, count, key_size)
 
 
 def _pbes2(params, passphrase):
@@ -412,10 +364,10 @@ def register_pbes2_kdf(kdf_name, alg, handler, *args):
     _pbes2_kdf_names[kdf_name] = alg
 
 
-def register_pbes2_prf(hash_name, alg, handler, *args):
+def register_pbes2_prf(hash_name, alg):
     """Register a PBES2 pseudo-random function"""
 
-    _pbes2_prfs[alg] = (handler, args)
+    _pbes2_prfs[alg] = hash_name
     _pbes2_prf_names[hash_name] = alg
 
 
@@ -474,7 +426,7 @@ def pkcs8_encrypt(data, cipher_name, hash_name, version, passphrase):
 
        Available hashes include:
 
-           md5, sha1, sha256, sha384, sha512, sha512-224, sha512-256
+           md5, sha1, sha256, sha384, sha512
 
        Available versions include 1 for PBES1 and 2 for PBES2.
 
@@ -580,13 +532,11 @@ _pbes2_kdf_list = (
 )
 
 _pbes2_prf_list = (
-    ('sha1',       _ES2_SHA1,       _pbes2_hmac_prf, sha1),
-    ('sha224',     _ES2_SHA224,     _pbes2_hmac_prf, sha224),
-    ('sha256',     _ES2_SHA256,     _pbes2_hmac_prf, sha256),
-    ('sha384',     _ES2_SHA384,     _pbes2_hmac_prf, sha384),
-    ('sha512',     _ES2_SHA512,     _pbes2_hmac_prf, sha512),
-    ('sha512-224', _ES2_SHA512_224, _pbes2_hmac_prf, sha512, 28),
-    ('sha512-256', _ES2_SHA512_256, _pbes2_hmac_prf, sha512, 32)
+    ('sha1',       _ES2_SHA1),
+    ('sha224',     _ES2_SHA224),
+    ('sha256',     _ES2_SHA256),
+    ('sha384',     _ES2_SHA384),
+    ('sha512',     _ES2_SHA512)
 )
 
 for _args in _pkcs1_cipher_list:
diff --git a/asyncssh/process.py b/asyncssh/process.py
index 2a1408d..13a75f9 100644
--- a/asyncssh/process.py
+++ b/asyncssh/process.py
@@ -26,17 +26,27 @@ from .stream import SSHClientStreamSession, SSHServerStreamSession
 from .stream import SSHReader, SSHWriter
 
 
+def _is_regular_file(file):
+    """Return if argument is a regular file or file-like object"""
+
+    try:
+        return stat.S_ISREG(os.fstat(file.fileno()).st_mode)
+    except OSError:
+        return True
+
+
 class _UnicodeReader:
     """Handle buffering partial Unicode data"""
 
-    def __init__(self, encoding):
+    def __init__(self, encoding, textmode=False):
         self._encoding = encoding
+        self._textmode = textmode
         self._partial = b''
 
     def decode(self, data):
-        """Decode received bytes into Unicode"""
+        """Decode Unicode bytes when reading from binary sources"""
 
-        if self._encoding:
+        if self._encoding and not self._textmode:
             data = self._partial + data
             self._partial = b''
 
@@ -72,13 +82,14 @@ class _UnicodeReader:
 class _UnicodeWriter:
     """Handle encoding Unicode data before writing it"""
 
-    def __init__(self, encoding):
+    def __init__(self, encoding, textmode=False):
         self._encoding = encoding
+        self._textmode = textmode
 
     def encode(self, data):
-        """Encode Unicode bytes before writing them"""
+        """Encode Unicode bytes when writing to binary targets"""
 
-        if self._encoding:
+        if self._encoding and not self._textmode:
             data = data.encode(self._encoding)
 
         return data
@@ -88,7 +99,7 @@ class _FileReader(_UnicodeReader):
     """Forward data from a file"""
 
     def __init__(self, process, file, bufsize, datatype, encoding):
-        super().__init__(encoding)
+        super().__init__(encoding, hasattr(file, 'encoding'))
 
         self._process = process
         self._file = file
@@ -130,7 +141,7 @@ class _FileWriter(_UnicodeWriter):
     """Forward data to a file"""
 
     def __init__(self, file, encoding):
-        super().__init__(encoding)
+        super().__init__(encoding, hasattr(file, 'encoding'))
 
         self._file = file
 
@@ -523,18 +534,17 @@ class SSHProcess:
                 file = os.fdopen(source, 'rb', buffering=bufsize)
             elif isinstance(source, socket.socket):
                 file = os.fdopen(source.detach(), 'rb', buffering=bufsize)
-            elif hasattr(source, 'encoding'):
-                # If file provided was opened in text mode, remove that wrapper
-                file = source.buffer
             else:
                 file = source
 
-            mode = os.fstat(file.fileno()).st_mode
-
-            if stat.S_ISREG(mode):
+            if _is_regular_file(file):
                 reader = _FileReader(self, file, bufsize,
                                      datatype, self._encoding)
             else:
+                if hasattr(source, 'buffer'):
+                    # If file was opened in text mode, remove that wrapper
+                    file = source.buffer
+
                 _, reader = \
                     yield from self._loop.connect_read_pipe(pipe_factory, file)
 
@@ -572,17 +582,16 @@ class SSHProcess:
                 file = os.fdopen(target, 'wb', buffering=bufsize)
             elif isinstance(target, socket.socket):
                 file = os.fdopen(target.detach(), 'wb', buffering=bufsize)
-            elif hasattr(target, 'encoding'):
-                # If file was opened in text mode, remove that wrapper
-                file = target.buffer
             else:
                 file = target
 
-            mode = os.fstat(file.fileno()).st_mode
-
-            if stat.S_ISREG(mode):
+            if _is_regular_file(file):
                 writer = _FileWriter(file, self._encoding)
             else:
+                if hasattr(target, 'buffer'):
+                    # If file was opened in text mode, remove that wrapper
+                    file = target.buffer
+
                 _, writer = \
                     yield from self._loop.connect_write_pipe(pipe_factory,
                                                              file)
diff --git a/asyncssh/public_key.py b/asyncssh/public_key.py
index 9a8744d..08284d6 100644
--- a/asyncssh/public_key.py
+++ b/asyncssh/public_key.py
@@ -142,6 +142,7 @@ class SSHKey:
     algorithm = None
     sig_algorithms = None
     x509_algorithms = None
+    all_sig_algorithms = None
     pem_name = None
     pkcs8_oid = None
 
@@ -236,6 +237,51 @@ class SSHKey:
 
         self._comment = comment or None
 
+    def sign_der(self, data, sig_algorithm):
+        """Abstract method to compute a DER-encoded signature"""
+
+        raise NotImplementedError
+
+    def verify_der(self, data, sig_algorithm, sig):
+        """Abstract method to verify a DER-encoded signature"""
+
+        raise NotImplementedError
+
+    def sign_ssh(self, data, sig_algorithm):
+        """Abstract method to compute an SSH-encoded signature"""
+
+        raise NotImplementedError
+
+    def verify_ssh(self, data, sig_algorithm, sig):
+        """Abstract method to verify an SSH-encoded signature"""
+
+        raise NotImplementedError
+
+    def sign(self, data, sig_algorithm):
+        """Return an SSH-encoded signature of the specified data"""
+
+        if sig_algorithm not in self.all_sig_algorithms:
+            raise ValueError('Unrecognized signature algorithm')
+
+        return b''.join((String(sig_algorithm),
+                         String(self.sign_ssh(data, sig_algorithm))))
+
+    def verify(self, data, sig):
+        """Verify an SSH signature of the specified data using this key"""
+
+        try:
+            packet = SSHPacket(sig)
+            sig_algorithm = packet.get_string()
+            sig = packet.get_string()
+            packet.check_end()
+
+            if sig_algorithm not in self.all_sig_algorithms:
+                return False
+
+            return self.verify_ssh(data, sig_algorithm, sig)
+        except PacketDecodeError:
+            return False
+
     def encode_pkcs1_private(self):
         """Export parameters associated with a PKCS#1 private key"""
 
@@ -683,7 +729,7 @@ class SSHKey:
 
            Available hashes include:
 
-               md5, sha1, sha256, sha384, sha512, sha512-224, sha512-256
+               md5, sha1, sha256, sha384, sha512
 
            Available PBE versions include 1 for PBES1 and 2 for PBES2.
 
@@ -1607,14 +1653,7 @@ class SSHKeyPair:
         raise NotImplementedError
 
     def sign(self, data):
-        """Sign a block of data with this private key
-
-           :param str data:
-               The data to be signed.
-
-           :returns: bytes containing the signature.
-
-        """
+        """Sign a block of data with this private key"""
 
         raise NotImplementedError
 
@@ -2350,7 +2389,7 @@ def import_private_key_and_certs(data, passphrase=None):
         key, end = _decode_pem_private(lines, passphrase)
 
         lines = lines[end:]
-        certs = _decode_pem_certificate_list(lines) if lines else None
+        certs = _decode_pem_certificate_list(lines) if any(lines) else None
     else:
         key, end = _decode_der_private(data, passphrase)
 
diff --git a/asyncssh/rsa.py b/asyncssh/rsa.py
index f547ac9..e9e2829 100644
--- a/asyncssh/rsa.py
+++ b/asyncssh/rsa.py
@@ -15,7 +15,7 @@
 from .asn1 import ASN1DecodeError, ObjectIdentifier, der_encode, der_decode
 from .crypto import RSAPrivateKey, RSAPublicKey
 from .misc import all_ints
-from .packet import MPInt, String, PacketDecodeError, SSHPacket
+from .packet import MPInt
 from .public_key import SSHKey, SSHOpenSSHCertificateV01, KeyExportError
 from .public_key import register_public_key_alg, register_certificate_alg
 from .public_key import register_x509_certificate_alg
@@ -33,8 +33,7 @@ class _RSAKey(SSHKey):
     sig_algorithms = (b'rsa-sha2-256', b'rsa-sha2-512', b'ssh-rsa')
     x509_sig_algorithms = (b'rsa2048-sha256', b'ssh-rsa')
     x509_algorithms = tuple(b'x509v3-' + alg for alg in x509_sig_algorithms)
-
-    _all_sig_algorithms = set(x509_sig_algorithms + sig_algorithms)
+    all_sig_algorithms = set(x509_sig_algorithms + sig_algorithms)
 
     def __eq__(self, other):
         # This isn't protected access - both objects are _RSAKey instances
@@ -187,35 +186,32 @@ class _RSAKey(SSHKey):
         return b''.join((MPInt(self._key.d), MPInt(self._key.iqmp),
                          MPInt(self._key.p), MPInt(self._key.q)))
 
-    def sign(self, data, algorithm):
-        """Return a signature of the specified data using this key"""
+    def sign_der(self, data, sig_algorithm):
+        """Compute a DER-encoded signature of the specified data"""
+
+        # pylint: disable=unused-argument
 
         if not self._key.d:
             raise ValueError('Private key needed for signing')
 
-        if algorithm not in self._all_sig_algorithms:
-            raise ValueError('Unrecognized signature algorithm')
+        return self._key.sign(data, sig_algorithm)
 
-        sig = self._key.sign(data, algorithm)
-        return b''.join((String(algorithm), String(sig)))
+    def verify_der(self, data, sig_algorithm, sig):
+        """Verify a DER-encoded signature of the specified data"""
 
-    def verify(self, data, sig):
-        """Verify a signature of the specified data using this key"""
+        # pylint: disable=unused-argument
 
-        try:
-            packet = SSHPacket(sig)
+        return self._key.verify(data, sig, sig_algorithm)
 
-            algorithm = packet.get_string()
+    def sign_ssh(self, data, sig_algorithm):
+        """Compute an SSH-encoded signature of the specified data"""
 
-            if algorithm not in self._all_sig_algorithms:
-                return False
+        return self.sign_der(data, sig_algorithm)
 
-            sig = packet.get_string()
-            packet.check_end()
+    def verify_ssh(self, data, sig_algorithm, sig):
+        """Verify an SSH-encoded signature of the specified data"""
 
-            return self._key.verify(data, sig, algorithm)
-        except PacketDecodeError:
-            return False
+        return self.verify_der(data, sig_algorithm, sig)
 
 
 register_public_key_alg(b'ssh-rsa', _RSAKey)
diff --git a/asyncssh/session.py b/asyncssh/session.py
index e960fda..66dc64a 100644
--- a/asyncssh/session.py
+++ b/asyncssh/session.py
@@ -255,7 +255,7 @@ class SSHServerSession(SSHSession):
            :param dictionary term_modes:
                POSIX terminal modes to set for this session, where keys
                are taken from :ref:`POSIX terminal modes <PTYModes>` with
-               values defined in section 8 of :rfc:`4254#section-8`.
+               values defined in section 8 of :rfc:`RFC 4254 <4254#section-8>`.
 
            :returns: A bool indicating if the request for a
                      pseudo-terminal was allowed or not
diff --git a/asyncssh/version.py b/asyncssh/version.py
index 4d118e1..d50d301 100644
--- a/asyncssh/version.py
+++ b/asyncssh/version.py
@@ -18,4 +18,4 @@ __author_email__ = 'ronf at timeheart.net'
 
 __url__ = 'http://asyncssh.timeheart.net'
 
-__version__ = '1.11.0'
+__version__ = '1.11.1'
diff --git a/docs/api.rst b/docs/api.rst
index 735d161..ed55137 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -1213,7 +1213,6 @@ SSHKeyPair
    .. automethod:: get_algorithm
    .. automethod:: get_comment
    .. automethod:: set_comment
-   .. automethod:: sign
    ============================= =
 
 SSHCertificate
@@ -1357,7 +1356,6 @@ SSHAgentKeyPair
    .. automethod:: get_algorithm
    .. automethod:: get_comment
    .. automethod:: set_comment
-   .. automethod:: sign
    .. automethod:: remove
    ============================= =
 
diff --git a/docs/changes.rst b/docs/changes.rst
index 2bb7a88..d218b11 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -3,6 +3,26 @@
 Change Log
 ==========
 
... 284 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