From 9d5760cf45619ce5aacb567fdc42849d678d93eb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 31 May 2017 17:30:17 -0700 Subject: Additional house style formatting tweaks, mostly re: removal of line continuations --- tests/stub_sftp.py | 6 ++++-- tests/test_auth.py | 7 ++++--- tests/test_transport.py | 13 ++++++++----- tests/test_util.py | 7 ++++--- 4 files changed, 20 insertions(+), 13 deletions(-) (limited to 'tests') diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py index 5fcca386..334af561 100644 --- a/tests/stub_sftp.py +++ b/tests/stub_sftp.py @@ -22,8 +22,10 @@ A stub SFTP server for loopback SFTP testing. import os import sys -from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \ - SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED +from paramiko import ( + ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, + SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED, +) from paramiko.common import o666 diff --git a/tests/test_auth.py b/tests/test_auth.py index 23517790..96f7611c 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -24,9 +24,10 @@ import sys import threading import unittest -from paramiko import Transport, ServerInterface, RSAKey, DSSKey, \ - BadAuthenticationType, InteractiveQuery, \ - AuthenticationException +from paramiko import ( + Transport, ServerInterface, RSAKey, DSSKey, BadAuthenticationType, + InteractiveQuery, AuthenticationException, +) from paramiko import AUTH_FAILED, AUTH_PARTIALLY_SUCCESSFUL, AUTH_SUCCESSFUL from paramiko.py3compat import u from tests.loop import LoopSocket diff --git a/tests/test_transport.py b/tests/test_transport.py index d81ad8f3..2ebdf854 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -31,13 +31,16 @@ import random from hashlib import sha1 import unittest -from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \ - SSHException, ChannelException, Packetizer +from paramiko import ( + Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, SSHException, + ChannelException, Packetizer, +) from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED -from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, \ - MIN_PACKET_SIZE, MIN_WINDOW_SIZE, MAX_WINDOW_SIZE, \ - DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE +from paramiko.common import ( + MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, MIN_PACKET_SIZE, MIN_WINDOW_SIZE, + MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, +) from paramiko.py3compat import bytes from paramiko.message import Message from tests.loop import LoopSocket diff --git a/tests/test_util.py b/tests/test_util.py index a31e4507..7880e156 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -475,9 +475,10 @@ Host param3 parara safe_has_bytes = safe_string(has_bytes) expected_bytes = b("has %07%03 bytes") err = "{0!r} != {1!r}" - assert safe_vanilla == vanilla, err.format(safe_vanilla, vanilla) - assert safe_has_bytes == expected_bytes, \ - err.format(safe_has_bytes, expected_bytes) + msg = err.format(safe_vanilla, vanilla) + assert safe_vanilla == vanilla, msg + msg = err.format(safe_has_bytes, expected_bytes) + assert safe_has_bytes == expected_bytes, msg def test_proxycommand_none_issue_418(self): test_config_file = """ -- cgit v1.2.3 From 7a2c893d95651ad1f4667f7e4480da149069b7ff Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 31 May 2017 18:18:33 -0700 Subject: Even moar parentheses over backslashes --- paramiko/packet.py | 16 ++++++++++------ paramiko/primes.py | 7 +++++-- paramiko/server.py | 10 ++++++---- paramiko/sftp_file.py | 12 ++++++++---- paramiko/ssh_exception.py | 3 +-- paramiko/ssh_gss.py | 16 +++++++++++----- paramiko/transport.py | 15 +++++++++++---- paramiko/util.py | 3 +-- tests/test_gssapi.py | 8 +++++--- 9 files changed, 58 insertions(+), 32 deletions(-) (limited to 'tests') diff --git a/paramiko/packet.py b/paramiko/packet.py index 6f3cd4e2..16288a0a 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -394,9 +394,11 @@ class Packetizer (object): self.__sent_bytes += len(out) self.__sent_packets += 1 - if (self.__sent_packets >= self.REKEY_PACKETS or - self.__sent_bytes >= self.REKEY_BYTES)\ - and not self.__need_rekey: + sent_too_much = ( + self.__sent_packets >= self.REKEY_PACKETS or + self.__sent_bytes >= self.REKEY_BYTES + ) + if sent_too_much and not self.__need_rekey: # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' % (self.__sent_packets, self.__sent_bytes)) @@ -506,9 +508,11 @@ class Packetizer (object): self.__logger.log(level, msg) def _check_keepalive(self): - if (not self.__keepalive_interval) or \ - (not self.__block_engine_out) or \ - self.__need_rekey: + if ( + not self.__keepalive_interval or + not self.__block_engine_out or + self.__need_rekey + ): # wait till we're encrypting, and not in the middle of rekeying return now = time.time() diff --git a/paramiko/primes.py b/paramiko/primes.py index 50100ad5..48a34e53 100644 --- a/paramiko/primes.py +++ b/paramiko/primes.py @@ -75,8 +75,11 @@ class ModulusPack (object): # type 2 (meets basic structural requirements) # test 4 (more than just a small-prime sieve) # tries < 100 if test & 4 (at least 100 tries of miller-rabin) - if (mod_type < 2) or (tests < 4) or \ - ((tests & 4) and (tests < 8) and (tries < 100)): + if ( + mod_type < 2 or + tests < 4 or + (tests & 4 and tests < 8 and tries < 100) + ): self.discarded.append( (modulus, 'does not meet basic requirements')) return diff --git a/paramiko/server.py b/paramiko/server.py index 89278a82..953bb33f 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -22,8 +22,10 @@ import threading from paramiko import util -from paramiko.common import DEBUG, ERROR, \ - OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED, AUTH_SUCCESSFUL +from paramiko.common import ( + DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED, + AUTH_SUCCESSFUL, +) from paramiko.py3compat import string_types @@ -450,8 +452,8 @@ class ServerInterface (object): ``True`` if this channel is now hooked up to the requested subsystem; ``False`` if that subsystem can't or won't be provided. """ - handler_class, larg, kwarg = \ - channel.get_transport()._get_subsystem_handler(name) + transport = channel.get_transport() + handler_class, larg, kwarg = transport._get_subsystem_handler(name) if handler_class is None: return False handler = handler_class(channel, name, self, *larg, **kwarg) diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 13e28e28..58653c79 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -194,8 +194,10 @@ class SFTPFile (BufferedFile): data[:chunk] ) self._reqs.append(sftp_async_request) - if not self.pipelined or \ - (len(self._reqs) > 100 and self.sftp.sock.recv_ready()): + if ( + not self.pipelined or + (len(self._reqs) > 100 and self.sftp.sock.recv_ready()) + ): while len(self._reqs): req = self._reqs.popleft() t, msg = self.sftp._read_response(req) @@ -476,8 +478,10 @@ class SFTPFile (BufferedFile): read_chunks = [] for offset, size in chunks: # don't fetch data that's already in the prefetch buffer - if self._data_in_prefetch_buffers(offset) or \ - self._data_in_prefetch_requests(offset, size): + if ( + self._data_in_prefetch_buffers(offset) or + self._data_in_prefetch_requests(offset, size) + ): continue # break up anything larger than the max read size diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index eb63df4e..e3584d89 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -108,8 +108,7 @@ class BadHostKeyException (SSHException): .. versionadded:: 1.6 """ def __init__(self, hostname, got_key, expected_key): - message = 'Host key for server {} does not match : ' \ - 'got {} expected {}' + message = 'Host key for server {} does not match: got {} expected {}' message = message.format( hostname, got_key.get_base64(), expected_key.get_base64()) diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 8bb0c2e0..9c88c6fc 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -405,12 +405,16 @@ class _SSH_SSPI(_SSH_GSSAuth): _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds) if self._gss_deleg_creds: - self._gss_flags = \ - sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH | \ + self._gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH | sspicon.ISC_REQ_DELEGATE + ) else: - self._gss_flags = \ - sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH + self._gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH + ) def ssh_init_sec_context(self, target, desired_mech=None, username=None, recv_token=None): @@ -546,8 +550,10 @@ class _SSH_SSPI(_SSH_GSSAuth): :return: ``True`` if credentials are delegated, otherwise ``False`` :rtype: Boolean """ - return self._gss_flags & sspicon.ISC_REQ_DELEGATE and \ + return ( + self._gss_flags & sspicon.ISC_REQ_DELEGATE and (self._gss_srv_ctxt_status or self._gss_flags) + ) def save_client_creds(self, client_token): """ diff --git a/paramiko/transport.py b/paramiko/transport.py index 0a570977..129562d1 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -2125,8 +2125,13 @@ class Transport (threading.Thread, ClosingContextManager): self.packetizer.set_outbound_cipher( engine, block_size, mac_engine, mac_size, mac_key, sdctr) compress_out = self._compression_info[self.local_compression][0] - if (compress_out is not None) and \ - ((self.local_compression != 'zlib@openssh.com') or self.authenticated): + if ( + compress_out is not None and + ( + self.local_compression != 'zlib@openssh.com' or + self.authenticated + ) + ): self._log(DEBUG, 'Switching on outbound compression ...') self.packetizer.set_outbound_compressor(compress_out()) if not self.packetizer.need_rekey(): @@ -2275,8 +2280,10 @@ class Transport (threading.Thread, ClosingContextManager): initial_window_size = m.get_int() max_packet_size = m.get_int() reject = False - if (kind == 'auth-agent@openssh.com') and \ - (self._forward_agent_handler is not None): + if ( + kind == 'auth-agent@openssh.com' and + self._forward_agent_handler is not None + ): self._log(DEBUG, 'Incoming forward agent connection') self.lock.acquire() try: diff --git a/paramiko/util.py b/paramiko/util.py index 82157c24..de099c0c 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -248,8 +248,7 @@ def log_to_file(filename, level=DEBUG): l.setLevel(level) f = open(filename, 'a') lh = logging.StreamHandler(f) - frm = '%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d ' \ - '%(name)s: %(message)s' + frm = '%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d %(name)s: %(message)s' # noqa lh.setFormatter(logging.Formatter(frm, '%Y%m%d-%H:%M:%S')) l.addHandler(lh) diff --git a/tests/test_gssapi.py b/tests/test_gssapi.py index 96c268d9..bc220108 100644 --- a/tests/test_gssapi.py +++ b/tests/test_gssapi.py @@ -104,9 +104,11 @@ class GSSAPITest(unittest.TestCase): status = gss_srv_ctxt.verify_mic(mic_msg, mic_token) self.assertEquals(0, status) else: - gss_flags = sspicon.ISC_REQ_INTEGRITY |\ - sspicon.ISC_REQ_MUTUAL_AUTH |\ - sspicon.ISC_REQ_DELEGATE + gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH | + sspicon.ISC_REQ_DELEGATE + ) # Initialize a GSS-API context. target_name = "host/" + socket.getfqdn(targ_name) gss_ctxt = sspi.ClientAuth("Kerberos", -- cgit v1.2.3 From afbcd96fcca5aa4ccb72421d9660874586cbef4f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 1 Jun 2017 12:42:35 -0700 Subject: Remove unused value from demo/test. Honestly not sure WTF --- demos/demo_server.py | 4 +--- tests/test_ssh_gss.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/demos/demo_server.py b/demos/demo_server.py index 4867e9ca..3a7ec854 100644 --- a/demos/demo_server.py +++ b/demos/demo_server.py @@ -96,9 +96,7 @@ class Server (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = False - return UseGSSAPI + return True def get_allowed_auths(self, username): return 'gssapi-keyex,gssapi-with-mic,password,publickey' diff --git a/tests/test_ssh_gss.py b/tests/test_ssh_gss.py index e20d348f..967b3b81 100644 --- a/tests/test_ssh_gss.py +++ b/tests/test_ssh_gss.py @@ -43,9 +43,7 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = True - return UseGSSAPI + return True def check_channel_request(self, kind, chanid): return paramiko.OPEN_SUCCEEDED -- cgit v1.2.3 From 4d15d0e824d199b60f96b7ee5584d4cb5b23fc1a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 1 Jun 2017 13:08:10 -0700 Subject: Test & impl for truly functional HostKeys.__delitem__ --- paramiko/hostkeys.py | 9 ++++++++- tests/test_hostkeys.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 4be3fd42..b72abe40 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -246,7 +246,14 @@ class HostKeys (MutableMapping): return ret def __delitem__(self, key): - pass # Needed for instantiating HostKeys. + index = None + for i, entry in enumerate(self._entries): + if self._hostname_matches(key, entry): + index = i + break + if i is None: + raise KeyError(key) + self._entries.pop(i) def __setitem__(self, hostname, entry): # don't use this please. diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 2bdcad9c..2c7ceeb9 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -115,3 +115,15 @@ class HostKeysTest (unittest.TestCase): self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp) fp = hexlify(hostdict['secure.example.com']['ssh-dss'].get_fingerprint()).upper() self.assertEqual(b'4478F0B9A23CC5182009FF755BC1D26C', fp) + + def test_delitem(self): + hostdict = paramiko.HostKeys('hostfile.temp') + target = 'happy.example.com' + entry = hostdict[target] # will KeyError if not present + del hostdict[target] + try: + entry = hostdict[target] + except KeyError: + pass # Good + else: + assert False, "Entry was not deleted from HostKeys on delitem!" -- cgit v1.2.3 From a8ff22322622aff271d39ac849618f7372552619 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 26 May 2017 21:18:58 -0400 Subject: Support decrypting keys --- paramiko/__init__.py | 1 + paramiko/ed25519key.py | 50 +++++++++++++++++++++++++++++++++++------ paramiko/transport.py | 2 +- setup.py | 1 + tests/test_ed25519.key | 8 +++++++ tests/test_ed25519_password.key | 8 +++++++ tests/test_pkey.py | 19 ++++++++-------- 7 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 tests/test_ed25519.key create mode 100644 tests/test_ed25519_password.key (limited to 'tests') diff --git a/paramiko/__init__.py b/paramiko/__init__.py index 197f519a..d67ad62f 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -45,6 +45,7 @@ from paramiko.server import ServerInterface, SubsystemHandler, InteractiveQuery from paramiko.rsakey import RSAKey from paramiko.dsskey import DSSKey from paramiko.ecdsakey import ECDSAKey +from paramiko.ed25519key import Ed25519Key from paramiko.sftp import SFTPError, BaseSFTP from paramiko.sftp_client import SFTP, SFTPClient from paramiko.sftp_server import SFTPServer diff --git a/paramiko/ed25519key.py b/paramiko/ed25519key.py index 9638cc01..694b1e15 100644 --- a/paramiko/ed25519key.py +++ b/paramiko/ed25519key.py @@ -14,6 +14,11 @@ # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +import bcrypt + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher + import nacl.signing import six @@ -49,7 +54,7 @@ class Ed25519Key(PKey): elif filename is not None: with open(filename, "rb") as f: data = self._read_private_key("OPENSSH", f) - signing_key = self._parse_signing_key_data(data) + signing_key = self._parse_signing_key_data(data, password) if signing_key is None and verifying_key is None: raise ValueError("need a key") @@ -58,7 +63,8 @@ class Ed25519Key(PKey): self._verifying_key = verifying_key - def _parse_signing_key_data(self, data): + def _parse_signing_key_data(self, data, password): + from paramiko.transport import Transport # We may eventually want this to be usable for other key types, as # OpenSSH moves to it, but for now this is just for Ed25519 keys. message = Message(data) @@ -70,10 +76,22 @@ class Ed25519Key(PKey): kdfoptions = message.get_string() num_keys = message.get_int() - if ciphername != "none" or kdfname != "none" or kdfoptions: - # TODO: add support for `kdfname == "bcrypt"` as documented in: - # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key#L21-L28 - raise NotImplementedError("Encrypted keys are not implemented") + if kdfname == "none": + # kdfname of "none" must have an empty kdfoptions, the ciphername + # must be "none" and there must not be a password. + if kdfoptions or ciphername != "none" or password: + raise SSHException('Invalid key') + elif kdfname == "bcrypt": + if not password: + raise SSHException('Invalid key') + kdf = Message(kdfoptions) + bcrypt_salt = kdf.get_binary() + bcrypt_rounds = kdf.get_int() + else: + raise SSHException('Invalid key') + + if ciphername != "none" and ciphername not in Transport._cipher_info: + raise SSHException('Invalid key') public_keys = [] for _ in range(num_keys): @@ -82,7 +100,25 @@ class Ed25519Key(PKey): raise SSHException('Invalid key') public_keys.append(pubkey.get_binary()) - message = Message(unpad(message.get_binary())) + private_ciphertext = message.get_binary() + if ciphername == "none": + private_data = private_ciphertext + else: + cipher = Transport._cipher_info[ciphername] + key = bcrypt.kdf( + password=password, + salt=bcrypt_salt, + desired_key_bytes=cipher['key-size'] + cipher['block-size'], + rounds=bcrypt_rounds + ) + decryptor = Cipher( + cipher['class'](key[:cipher['key-size']]), + cipher['mode'](key[cipher['key-size']:]), + backend=default_backend() + ).decryptor() + private_data = decryptor.update(private_ciphertext) + decryptor.finalize() + + message = Message(unpad(private_data)) if message.get_int() != message.get_int(): raise SSHException('Invalid key') diff --git a/paramiko/transport.py b/paramiko/transport.py index acba0b81..7e437cc9 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -85,7 +85,7 @@ import atexit atexit.register(_join_lingering_threads) -class Transport (threading.Thread, ClosingContextManager): +class Transport(threading.Thread, ClosingContextManager): """ An SSH Transport attaches to a stream (usually a socket), negotiates an encrypted session, authenticates, and then creates stream tunnels, called diff --git a/setup.py b/setup.py index 2756a76d..458916a6 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ setup( 'Programming Language :: Python :: 3.5', ], install_requires=[ + 'bcrypt>=3.0.0', 'cryptography>=1.1', 'pynacl', 'pyasn1>=0.1.7', diff --git a/tests/test_ed25519.key b/tests/test_ed25519.key new file mode 100644 index 00000000..eb9f94c2 --- /dev/null +++ b/tests/test_ed25519.key @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXwAAAKhjwAdrY8AH +awAAAAtzc2gtZWQyNTUxOQAAACB69SvZKJh/9VgSL0G27b5xVYa8nethH3IERbi0YqJDXw +AAAEA9tGQi2IrprbOSbDCF+RmAHd6meNSXBUQ2ekKXm4/8xnr1K9komH/1WBIvQbbtvnFV +hryd62EfcgRFuLRiokNfAAAAI2FsZXhfZ2F5bm9yQEFsZXhzLU1hY0Jvb2stQWlyLmxvY2 +FsAQI= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/test_ed25519_password.key b/tests/test_ed25519_password.key new file mode 100644 index 00000000..d178aaae --- /dev/null +++ b/tests/test_ed25519_password.key @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDaKD4ac7 +kieb+UfXaLaw68AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIOQn7fjND5ozMSV3 +CvbEtIdT73hWCMRjzS/lRdUDw50xAAAAsE8kLGyYBnl9ihJNqv378y6mO3SkzrDbWXOnK6 +ij0vnuTAvcqvWHAnyu6qBbplu/W2m55ZFeAItgaEcV2/V76sh/sAKlERqrLFyXylN0xoOW +NU5+zU08aTlbSKGmeNUU2xE/xfJq12U9XClIRuVUkUpYANxNPbmTRpVrbD3fgXMhK97Jrb +DEn8ca1IqMPiYmd/hpe5+tq3OxyRljXjCUFWTnqkp9VvUdzSTdSGZHsW9i +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 24d78c3e..74330b8d 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -27,7 +27,7 @@ from binascii import hexlify from hashlib import md5 import base64 -from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util +from paramiko import RSAKey, DSSKey, ECDSAKey, Ed25519Key, Message, util from paramiko.py3compat import StringIO, byte_chr, b, bytes, PY2 from tests.util import test_path @@ -112,14 +112,7 @@ TEST_KEY_BYTESTR_2 = '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x81\x TEST_KEY_BYTESTR_3 = '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x00ӏV\x07k%<\x1fT$E#>ғfD\x18 \x0cae#̬S#VlE\x1epvo\x17M߉DUXL<\x06\x10דw\u2bd5ٿw˟0)#y{\x10l\tPru\t\x19Π\u070e/f0yFmm\x1f' -class KeyTest (unittest.TestCase): - - def setUp(self): - pass - - def tearDown(self): - pass - +class KeyTest(unittest.TestCase): def test_1_generate_key_bytes(self): key = util.generate_key_bytes(md5, x1234, 'happy birthday', 30) exp = b'\x61\xE1\xF2\x72\xF4\xC1\xC4\x56\x15\x86\xBD\x32\x24\x98\xC0\xE9\x24\x67\x27\x80\xF4\x7B\xB3\x7D\xDA\x7D\x54\x01\x9E\x64' @@ -436,3 +429,11 @@ class KeyTest (unittest.TestCase): key = RSAKey.from_private_key_file(test_path('test_rsa.key')) comparable = TEST_KEY_BYTESTR_2 if PY2 else TEST_KEY_BYTESTR_3 self.assertEqual(str(key), comparable) + + def test_ed25519(self): + key1 = Ed25519Key.from_private_key_file(test_path('test_ed25519.key')) + key2 = Ed25519Key.from_private_key_file( + test_path('test_ed25519_password.key'), 'abc123' + ) + + self.assertNotEqual(key1.asbytes(), key2.asbytes()) -- cgit v1.2.3 From 9b98d347ce2b1ba371e4689ea42c22962ed32e8e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 26 May 2017 21:36:43 -0400 Subject: py3k --- paramiko/ed25519key.py | 12 ++++++------ tests/test_pkey.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/paramiko/ed25519key.py b/paramiko/ed25519key.py index 21c69305..3e447eb7 100644 --- a/paramiko/ed25519key.py +++ b/paramiko/ed25519key.py @@ -72,9 +72,9 @@ class Ed25519Key(PKey): if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException('Invalid key') - ciphername = message.get_string() - kdfname = message.get_string() - kdfoptions = message.get_string() + ciphername = message.get_text() + kdfname = message.get_text() + kdfoptions = message.get_binary() num_keys = message.get_int() if kdfname == "none": @@ -97,7 +97,7 @@ class Ed25519Key(PKey): public_keys = [] for _ in range(num_keys): pubkey = Message(message.get_binary()) - if pubkey.get_string() != 'ssh-ed25519': + if pubkey.get_text() != 'ssh-ed25519': raise SSHException('Invalid key') public_keys.append(pubkey.get_binary()) @@ -128,7 +128,7 @@ class Ed25519Key(PKey): signing_keys = [] for i in range(num_keys): - if message.get_string() != 'ssh-ed25519': + if message.get_text() != 'ssh-ed25519': raise SSHException('Invalid key') # A copy of the public key, again, ignore. public = message.get_binary() @@ -142,7 +142,7 @@ class Ed25519Key(PKey): ) signing_keys.append(signing_key) # Comment, ignore. - message.get_string() + message.get_binary() if len(signing_keys) != 1: raise SSHException('Invalid key') diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 74330b8d..a26ff170 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -433,7 +433,7 @@ class KeyTest(unittest.TestCase): def test_ed25519(self): key1 = Ed25519Key.from_private_key_file(test_path('test_ed25519.key')) key2 = Ed25519Key.from_private_key_file( - test_path('test_ed25519_password.key'), 'abc123' + test_path('test_ed25519_password.key'), b'abc123' ) self.assertNotEqual(key1.asbytes(), key2.asbytes()) -- cgit v1.2.3 From 88600f0942f2e903590638c56373533da6e64f31 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 26 May 2017 21:52:57 -0400 Subject: integration test, with ourselves --- paramiko/client.py | 2 +- paramiko/ed25519key.py | 6 +++++- tests/test_client.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/paramiko/client.py b/paramiko/client.py index 25bbffd9..d76ca383 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -547,7 +547,7 @@ class SSHClient (ClosingContextManager): if not two_factor: for key_filename in key_filenames: - for pkey_class in (RSAKey, DSSKey, ECDSAKey): + for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key): try: key = pkey_class.from_private_key_file( key_filename, password) diff --git a/paramiko/ed25519key.py b/paramiko/ed25519key.py index 01abea97..2908ff5b 100644 --- a/paramiko/ed25519key.py +++ b/paramiko/ed25519key.py @@ -154,9 +154,13 @@ class Ed25519Key(PKey): return signing_keys[0] def asbytes(self): + if self.can_sign(): + v = self._signing_key.verify_key + else: + v = self._verifying_key m = Message() m.add_string('ssh-ed25519') - m.add_bytes(self._signing_key.verify_key.encode()) + m.add_bytes(v.encode()) return m.asbytes() def get_name(self): diff --git a/tests/test_client.py b/tests/test_client.py index 5f4f0dd5..eb6aa7b3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -43,6 +43,7 @@ FINGERPRINTS = { 'ssh-dss': b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c', 'ssh-rsa': b'\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5', 'ecdsa-sha2-nistp256': b'\x25\x19\xeb\x55\xe6\xa1\x47\xff\x4f\x38\xd2\x75\x6f\xa5\xd5\x60', + 'ssh-ed25519': b'\x1d\xf3\xefoj\x95\x99\xb7\xedq\x7f&\xba\xb0CD', } @@ -194,6 +195,9 @@ class SSHClientTest (unittest.TestCase): """ self._test_connection(key_filename=test_path('test_ecdsa_256.key')) + def test_client_ed25519(self): + self._test_connection(key_filename=test_path('test_ed25519.key')) + def test_3_multiple_key_files(self): """ verify that SSHClient accepts and tries multiple key files. -- cgit v1.2.3 From 5e103b31f12f701254ee07f61ffd482bf6e08dd4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 3 Jun 2017 01:58:24 -0400 Subject: Fixed encoding/decoding of the public key on the wire Public point was accidentally encoded as 32 bytes, with no length prefix. --- paramiko/ed25519key.py | 4 ++-- paramiko/hostkeys.py | 1 + tests/test_client.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/paramiko/ed25519key.py b/paramiko/ed25519key.py index d9c92aa2..e1a8a732 100644 --- a/paramiko/ed25519key.py +++ b/paramiko/ed25519key.py @@ -52,7 +52,7 @@ class Ed25519Key(PKey): if msg is not None: if msg.get_text() != "ssh-ed25519": raise SSHException("Invalid key") - verifying_key = nacl.signing.VerifyKey(msg.get_bytes(32)) + verifying_key = nacl.signing.VerifyKey(msg.get_binary()) elif filename is not None: with open(filename, "r") as f: data = self._read_private_key("OPENSSH", f) @@ -164,7 +164,7 @@ class Ed25519Key(PKey): v = self._verifying_key m = Message() m.add_string("ssh-ed25519") - m.add_bytes(v.encode()) + m.add_string(v.encode()) return m.asbytes() def get_name(self): diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 7586b903..f3cb29db 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -35,6 +35,7 @@ from paramiko.dsskey import DSSKey from paramiko.rsakey import RSAKey from paramiko.util import get_logger, constant_time_bytes_eq from paramiko.ecdsakey import ECDSAKey +from paramiko.ed25519key import Ed25519Key from paramiko.ssh_exception import SSHException diff --git a/tests/test_client.py b/tests/test_client.py index eb6aa7b3..a340be00 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -43,7 +43,7 @@ FINGERPRINTS = { 'ssh-dss': b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c', 'ssh-rsa': b'\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5', 'ecdsa-sha2-nistp256': b'\x25\x19\xeb\x55\xe6\xa1\x47\xff\x4f\x38\xd2\x75\x6f\xa5\xd5\x60', - 'ssh-ed25519': b'\x1d\xf3\xefoj\x95\x99\xb7\xedq\x7f&\xba\xb0CD', + 'ssh-ed25519': b'\xb3\xd5"\xaa\xf9u^\xe8\xcd\x0e\xea\x02\xb9)\xa2\x80', } -- cgit v1.2.3 From d510b1ae91978a169bb4bd2a9e5e165d2311bc6b Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 5 Jun 2017 04:02:59 -0400 Subject: test transport security options can be set to defaults ensures all defaults key/cipher/digest etc types are supported --- tests/test_transport.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'tests') diff --git a/tests/test_transport.py b/tests/test_transport.py index 2ebdf854..c426cef1 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -165,6 +165,15 @@ class TransportTest(unittest.TestCase): except TypeError: pass + def test_1b_security_options_reset(self): + o = self.tc.get_security_options() + # should not throw any exceptions + o.ciphers = o.ciphers + o.digests = o.digests + o.key_types = o.key_types + o.kex = o.kex + o.compression = o.compression + def test_2_compute_key(self): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' -- cgit v1.2.3