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