summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2017-08-28 20:56:50 -0700
committerJeff Forcier <jeff@bitprophet.org>2017-08-28 20:56:50 -0700
commite0babd7a2da93501fed8a83da0cfb70ce6a90bbd (patch)
tree3c24fca4c541d7f83908a49f7d2e85b0c5cc4903
parent696c6ff18ff539a407fef9b93c3255309e4f7aee (diff)
Implement ECDSA certs.
So mad at that frickin typo'd specification...
-rw-r--r--paramiko/ecdsakey.py27
-rw-r--r--paramiko/pkey.py24
-rw-r--r--paramiko/transport.py3
-rw-r--r--tests/test_client.py3
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,