[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