diff options
author | Jeff Forcier <jeff@bitprophet.org> | 2017-08-28 20:56:50 -0700 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2017-08-28 20:56:50 -0700 |
commit | e0babd7a2da93501fed8a83da0cfb70ce6a90bbd (patch) | |
tree | 3c24fca4c541d7f83908a49f7d2e85b0c5cc4903 | |
parent | 696c6ff18ff539a407fef9b93c3255309e4f7aee (diff) |
Implement ECDSA certs.
So mad at that frickin typo'd specification...
-rw-r--r-- | paramiko/ecdsakey.py | 27 | ||||
-rw-r--r-- | paramiko/pkey.py | 24 | ||||
-rw-r--r-- | paramiko/transport.py | 3 | ||||
-rw-r--r-- | tests/test_client.py | 3 |
4 files changed, 47 insertions, 10 deletions
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 9b74d938..fd876298 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -119,12 +119,29 @@ class ECDSAKey(PKey): c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: - if msg is None: - raise SSHException('Key object may not be empty') + # Must set ecdsa_curve first; subroutines called herein may need to + # spit out our get_name(), which relies on this. + key_type = msg.get_text() + # But this also means we need to hand it a real key/curve + # identifier, so strip out any cert business. (NOTE: could push + # that into _ECDSACurveSet.get_by_key_format_identifier(), but it + # feels more correct to do it here?) + suffix = '-cert-v01@openssh.com' + if key_type.endswith(suffix): + key_type = key_type[:-len(suffix)] self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( - msg.get_text()) - if self.ecdsa_curve is None: - raise SSHException('Invalid key') + key_type + ) + key_types = self._ECDSA_CURVES.get_key_format_identifier_list() + cert_types = [ + '{}-cert-v01@openssh.com'.format(x) + for x in key_types + ] + self._check_type_and_load_cert( + msg=msg, + key_type=key_types, + cert_type=cert_types, + ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException("Can't handle curve of type %s" % curvename) diff --git a/paramiko/pkey.py b/paramiko/pkey.py index a135bed2..50a99bfa 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -31,7 +31,7 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher from paramiko import util from paramiko.common import o600 -from paramiko.py3compat import u, encodebytes, decodebytes, b +from paramiko.py3compat import u, encodebytes, decodebytes, b, string_types from paramiko.ssh_exception import SSHException, PasswordRequiredException from paramiko.message import Message @@ -372,19 +372,35 @@ class PKey(object): This includes fast-forwarding cert ``msg`` objects past the nonce, so that the subsequent fields are the key numbers; thus the caller may expect to treat the message as key material afterwards either way. - """ + + The obtained key type is returned for classes which need to know what + it was (e.g. ECDSA.) + """ + # Normalization; most classes have a single key type and give a string, + # but eg ECDSA is a 1:N mapping. + key_types = key_type + cert_types = cert_type + if isinstance(key_type, string_types): + key_types = [key_types] + if isinstance(cert_types, string_types): + cert_types = [cert_types] + # Can't do much with no message, that should've been handled elsewhere if msg is None: raise SSHException('Key object may not be empty') + # First field is always key type, in either kind of object. (make sure + # we rewind before grabbing it - sometimes caller had to do their own + # introspection first!) + msg.rewind() type_ = msg.get_text() nonce = None # Regular public key - nothing special to do besides the implicit # type check. - if type_ == key_type: + if type_ in key_types: pass # OpenSSH-compatible certificate - store full copy as .public_blob # (so signing works correctly) and then fast-forward past the # nonce. - elif type_ == cert_type: + elif type_ in cert_types: # This seems the cleanest way to 'clone' an already-being-read # message; they're *IO objects at heart and their .getvalue() # always returns the full value regardless of pointer position. diff --git a/paramiko/transport.py b/paramiko/transport.py index d97bee80..1a95f990 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -208,8 +208,11 @@ class Transport(threading.Thread, ClosingContextManager): 'ssh-dss': DSSKey, 'ssh-dss-cert-v01@openssh.com': DSSKey, 'ecdsa-sha2-nistp256': ECDSAKey, + 'ecdsa-sha2-nistp256-cert-v01@openssh.com': ECDSAKey, 'ecdsa-sha2-nistp384': ECDSAKey, + 'ecdsa-sha2-nistp384-cert-v01@openssh.com': ECDSAKey, 'ecdsa-sha2-nistp521': ECDSAKey, + 'ecdsa-sha2-nistp521-cert-v01@openssh.com': ECDSAKey, 'ssh-ed25519': Ed25519Key, } diff --git a/tests/test_client.py b/tests/test_client.py index 40e2fb12..f0808c4b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -263,6 +263,7 @@ class SSHClientTest (unittest.TestCase): # NOTE: giving cert path here, not key path. (Key path test is below. # They're similar except for which path is given; the expected auth and # server-side behavior is 100% identical.) + # NOTE: only bothered whipping up one cert per overall class/family. for type_ in ('rsa', 'dss', 'ecdsa_256', 'ed25519'): cert_path = test_path('test_{}.key-cert.pub'.format(type_)) self._test_connection( @@ -277,7 +278,7 @@ class SSHClientTest (unittest.TestCase): # about the server-side key object's public blob. Thus, we can prove # that a specific cert was found, along with regular authorization # succeeding proving that the overall flow works. - for type_ in ('rsa', 'dss', 'ecdsa', 'ed25519'): + for type_ in ('rsa', 'dss', 'ecdsa_256', 'ed25519'): key_path = test_path('test_{}.key'.format(type_)) self._test_connection( key_filename=key_path, |