summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Orange <crazycasta@gmail.com>2016-04-25 13:53:06 -0600
committerAlex Orange <crazycasta@gmail.com>2016-04-25 14:56:51 -0600
commit39244216e4b8b1e0ef684473b9387dca7256bc37 (patch)
tree8abe3ac609c8ba18c61ff2743dd9df9423ddd596
parent86645149c9d066d5fe9222525c8bdf91df7f7de9 (diff)
downloadparamiko-39244216e4b8b1e0ef684473b9387dca7256bc37.tar.gz
Add support for ECDSA key sizes 384 and 521 alongside the existing 256.
Previously only 256-bit was handled and in certain cases (private key reading) 384- and 521-bit keys were treated as 256-bit keys causing silent errors. Tests have been added to specifically test the 384 and 521 keysizes. As RFC 5656 defines 256, 384, and 521 as the required keysizes this seems a good set to test. Also, this will cover the branches at ecdsakey.py:55. Test keys were renamed and test_client.py was modified as a result. This also fixes two bugs in ecdsakey.py. First, when calculating bytes needed to store a key, the assumption was made that the key size (in bits) was divisible by 8 (see line 137). This has been fixed by rounding up (wasn't an issue as only 256-bit keys were used before). Another bug was that the key padding in asbytes was being done backwards (was padding on current_length - needed_length bytes).
-rw-r--r--paramiko/ecdsakey.py117
-rw-r--r--paramiko/hostkeys.py2
-rw-r--r--paramiko/transport.py3
-rw-r--r--tests/test_client.py6
-rw-r--r--tests/test_ecdsa_256.key (renamed from tests/test_ecdsa.key)0
-rw-r--r--tests/test_ecdsa_384.key6
-rw-r--r--tests/test_ecdsa_521.key7
-rw-r--r--tests/test_ecdsa_password_256.key (renamed from tests/test_ecdsa_password.key)0
-rw-r--r--tests/test_ecdsa_password_384.key9
-rw-r--r--tests/test_ecdsa_password_521.key10
-rw-r--r--tests/test_pkey.py190
11 files changed, 308 insertions, 42 deletions
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
index c69bef73..6663baa2 100644
--- a/paramiko/ecdsakey.py
+++ b/paramiko/ecdsakey.py
@@ -37,12 +37,71 @@ from paramiko.ssh_exception import SSHException
from paramiko.util import deflate_long, inflate_long
+class _ECDSACurve(object):
+ """
+ Object for representing a specific ECDSA Curve (i.e. nistp256, nistp384,
+ etc.). Handles the generation of the key format identifier and the
+ selection of the proper hash function. Also grabs the proper curve from the
+ ecdsa package.
+ """
+ def __init__(self, curve_class, nist_name):
+ self.nist_name = nist_name
+ self.key_length = curve_class.key_size
+
+ # Defined in RFC 5656 6.2
+ self.key_format_identifier = "ecdsa-sha2-" + self.nist_name
+
+ # Defined in RFC 5656 6.2.1
+ if self.key_length <= 256:
+ self.hash_object = hashes.SHA256
+ elif self.key_length <= 384:
+ self.hash_object = hashes.SHA384
+ else:
+ self.hash_object = hashes.SHA512
+
+ self.curve_class = curve_class
+
+
+class _ECDSACurveSet(object):
+ """
+ A collection to hold the ECDSA curves. Allows querying by oid and by key
+ format identifier. The two ways in which ECDSAKey needs to be able to look
+ up curves.
+ """
+ def __init__(self, ecdsa_curves):
+ self.ecdsa_curves = ecdsa_curves
+
+ def get_key_format_identifier_list(self):
+ return [curve.key_format_identifier for curve in self.ecdsa_curves]
+
+ def get_by_curve_class(self, curve_class):
+ for curve in self.ecdsa_curves:
+ if curve.curve_class == curve_class:
+ return curve
+
+ def get_by_key_format_identifier(self, key_format_identifier):
+ for curve in self.ecdsa_curves:
+ if curve.key_format_identifier == key_format_identifier:
+ return curve
+
+ def get_by_key_length(self, key_length):
+ for curve in self.ecdsa_curves:
+ if curve.key_length == key_length:
+ return curve
+
+
class ECDSAKey(PKey):
"""
Representation of an ECDSA key which can be used to sign and verify SSH2
data.
"""
+ _ECDSA_CURVES = _ECDSACurveSet([
+ _ECDSACurve(ec.SECP256R1, 'nistp256'),
+ _ECDSACurve(ec.SECP384R1, 'nistp384'),
+ _ECDSACurve(ec.SECP521R1, 'nistp521'),
+ ])
+
def __init__(self, msg=None, data=None, filename=None, password=None,
vals=None, file_obj=None, validate_point=True):
self.verifying_key = None
@@ -57,41 +116,53 @@ class ECDSAKey(PKey):
msg = Message(data)
if vals is not None:
self.signing_key, self.verifying_key = vals
+ 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')
- if msg.get_text() != 'ecdsa-sha2-nistp256':
+ self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier(
+ msg.get_text())
+ if self.ecdsa_curve is None:
raise SSHException('Invalid key')
curvename = msg.get_text()
- if curvename != 'nistp256':
+ if curvename != self.ecdsa_curve.nist_name:
raise SSHException("Can't handle curve of type %s" % curvename)
pointinfo = msg.get_binary()
if pointinfo[0:1] != four_byte:
raise SSHException('Point compression is being used: %s' %
binascii.hexlify(pointinfo))
- curve = ec.SECP256R1()
+ curve = self.ecdsa_curve.curve_class()
+ key_bytes = (curve.key_size + 7) // 8
numbers = ec.EllipticCurvePublicNumbers(
- x=inflate_long(pointinfo[1:1 + curve.key_size // 8], always_positive=True),
- y=inflate_long(pointinfo[1 + curve.key_size // 8:], always_positive=True),
+ x=inflate_long(pointinfo[1:1 + key_bytes],
+ always_positive=True),
+ y=inflate_long(pointinfo[1 + key_bytes:],
+ always_positive=True),
curve=curve
)
self.verifying_key = numbers.public_key(backend=default_backend())
- self.size = 256
+
+ @classmethod
+ def supported_key_format_identifiers(cls):
+ return cls._ECDSA_CURVES.get_key_format_identifier_list()
def asbytes(self):
key = self.verifying_key
m = Message()
- m.add_string('ecdsa-sha2-nistp256')
- m.add_string('nistp256')
+ m.add_string(self.ecdsa_curve.key_format_identifier)
+ m.add_string(self.ecdsa_curve.nist_name)
numbers = key.public_numbers()
+ key_size_bytes = (key.curve.key_size + 7) // 8
+
x_bytes = deflate_long(numbers.x, add_sign_padding=False)
- x_bytes = b'\x00' * (len(x_bytes) - key.curve.key_size // 8) + x_bytes
+ x_bytes = b'\x00' * (key_size_bytes - len(x_bytes)) + x_bytes
y_bytes = deflate_long(numbers.y, add_sign_padding=False)
- y_bytes = b'\x00' * (len(y_bytes) - key.curve.key_size // 8) + y_bytes
+ y_bytes = b'\x00' * (key_size_bytes - len(y_bytes)) + y_bytes
point_str = four_byte + x_bytes + y_bytes
m.add_string(point_str)
@@ -107,34 +178,35 @@ class ECDSAKey(PKey):
return hash(h)
def get_name(self):
- return 'ecdsa-sha2-nistp256'
+ return self.ecdsa_curve.key_format_identifier
def get_bits(self):
- return self.size
+ return self.ecdsa_curve.key_length
def can_sign(self):
return self.signing_key is not None
def sign_ssh_data(self, data):
- signer = self.signing_key.signer(ec.ECDSA(hashes.SHA256()))
+ ecdsa = ec.ECDSA(self.ecdsa_curve.hash_object())
+ signer = self.signing_key.signer(ecdsa)
signer.update(data)
sig = signer.finalize()
r, s = decode_rfc6979_signature(sig)
m = Message()
- m.add_string('ecdsa-sha2-nistp256')
+ m.add_string(self.ecdsa_curve.key_format_identifier)
m.add_string(self._sigencode(r, s))
return m
def verify_ssh_sig(self, data, msg):
- if msg.get_text() != 'ecdsa-sha2-nistp256':
+ if msg.get_text() != self.ecdsa_curve.key_format_identifier:
return False
sig = msg.get_binary()
sigR, sigS = self._sigdecode(sig)
signature = encode_rfc6979_signature(sigR, sigS)
verifier = self.verifying_key.verifier(
- signature, ec.ECDSA(hashes.SHA256())
+ signature, ec.ECDSA(self.ecdsa_curve.hash_object())
)
verifier.update(data)
try:
@@ -160,8 +232,8 @@ class ECDSAKey(PKey):
password=password
)
- @staticmethod
- def generate(curve=ec.SECP256R1(), progress_func=None):
+ @classmethod
+ def generate(cls, curve=ec.SECP256R1(), progress_func=None, bits=None):
"""
Generate a new private ECDSA key. This factory function can be used to
generate a new host key or authentication key.
@@ -169,6 +241,12 @@ class ECDSAKey(PKey):
:param function progress_func: Not used for this type of key.
:returns: A new private key (`.ECDSAKey`) object
"""
+ if bits is not None:
+ curve = cls._ECDSA_CURVES.get_by_key_length(bits)
+ if curve is None:
+ raise ValueError("Unsupported key length: %d"%(bits))
+ curve = curve.curve_class()
+
private_key = ec.generate_private_key(curve, backend=default_backend())
return ECDSAKey(vals=(private_key, private_key.public_key()))
@@ -192,7 +270,8 @@ class ECDSAKey(PKey):
self.signing_key = key
self.verifying_key = key.public_key()
- self.size = key.curve.key_size
+ curve_class = key.curve.__class__
+ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(curve_class)
def _sigencode(self, r, s):
msg = Message()
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index 38ac866b..2ee3d27f 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -331,7 +331,7 @@ class HostKeyEntry:
key = RSAKey(data=decodebytes(key))
elif keytype == 'ssh-dss':
key = DSSKey(data=decodebytes(key))
- elif keytype == 'ecdsa-sha2-nistp256':
+ elif keytype in ECDSAKey.supported_key_format_identifiers():
key = ECDSAKey(data=decodebytes(key), validate_point=False)
else:
log.info("Unable to handle key of type %s" % (keytype,))
diff --git a/paramiko/transport.py b/paramiko/transport.py
index a521ef07..a314847e 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -121,8 +121,7 @@ class Transport (threading.Thread, ClosingContextManager):
_preferred_keys = (
'ssh-rsa',
'ssh-dss',
- 'ecdsa-sha2-nistp256',
- )
+ )+tuple(ECDSAKey.supported_key_format_identifiers())
_preferred_kex = (
'diffie-hellman-group1-sha1',
'diffie-hellman-group14-sha1',
diff --git a/tests/test_client.py b/tests/test_client.py
index f42d79d9..63ff9297 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -182,7 +182,7 @@ class SSHClientTest (unittest.TestCase):
"""
verify that SSHClient works with an ECDSA key.
"""
- self._test_connection(key_filename=test_path('test_ecdsa.key'))
+ self._test_connection(key_filename=test_path('test_ecdsa_256.key'))
def test_3_multiple_key_files(self):
"""
@@ -199,8 +199,8 @@ class SSHClientTest (unittest.TestCase):
for attempt, accept in (
(['rsa', 'dss'], ['dss']), # Original test #3
(['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly
- (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail
- (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success
+ (['dss', 'rsa', 'ecdsa_256'], ['dss']), # Try ECDSA but fail
+ (['rsa', 'ecdsa_256'], ['ecdsa']), # ECDSA success
):
try:
self._test_connection(
diff --git a/tests/test_ecdsa.key b/tests/test_ecdsa_256.key
index 42d44734..42d44734 100644
--- a/tests/test_ecdsa.key
+++ b/tests/test_ecdsa_256.key
diff --git a/tests/test_ecdsa_384.key b/tests/test_ecdsa_384.key
new file mode 100644
index 00000000..796bf417
--- /dev/null
+++ b/tests/test_ecdsa_384.key
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDBDdO8IXvlLJgM7+sNtPl7tI7FM5kzuEUEEPRjXIPQM7mISciwJPBt+
+y43EuG8nL4mgBwYFK4EEACKhZANiAAQWxom0C1vQAGYhjdoREMVmGKBWlisDdzyk
+mgyUjKpiJ9WfbIEVLsPGP8OdNjhr1y/8BZNIts+dJd6VmYw+4HzB+4F+U1Igs8K0
+JEvh59VNkvWheViadDXCM2MV8Nq+DNg=
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_ecdsa_521.key b/tests/test_ecdsa_521.key
new file mode 100644
index 00000000..b87dc90f
--- /dev/null
+++ b/tests/test_ecdsa_521.key
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIAprQtAS3OF6iVUkT8IowTHWicHzShGgk86EtuEXvfQnhZFKsWm6Jo
+iqAr1yEaiuI9LfB3Xs8cjuhgEEfbduYr/f6gBwYFK4EEACOhgYkDgYYABACaOaFL
+ZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj
+4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRA
+L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA==
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_ecdsa_password.key b/tests/test_ecdsa_password_256.key
index eb7910ed..eb7910ed 100644
--- a/tests/test_ecdsa_password.key
+++ b/tests/test_ecdsa_password_256.key
diff --git a/tests/test_ecdsa_password_384.key b/tests/test_ecdsa_password_384.key
new file mode 100644
index 00000000..eba33c14
--- /dev/null
+++ b/tests/test_ecdsa_password_384.key
@@ -0,0 +1,9 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,7F7B5DBE4CE040D822441AFE7A023A1D
+
+y/d6tGonAXYgJniQoFCdto+CuT1y1s41qzwNLN9YdNq/+R/dtQvZAaOuGtHJRFE6
+wWabhY1bSjavVPT2z1Zw1jhDJX5HGrf9LDoyORKtUWtUJoUvGdYLHbcg8Q+//WRf
+R0A01YuSw1SJX0a225S1aRcsDAk1k5F8EMb8QzSSDgjAOI8ldQF35JI+ofNSGjgS
+BPOlorQXTJxDOGmokw/Wql6MbhajXKPO39H2Z53W88U=
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_ecdsa_password_521.key b/tests/test_ecdsa_password_521.key
new file mode 100644
index 00000000..5986b930
--- /dev/null
+++ b/tests/test_ecdsa_password_521.key
@@ -0,0 +1,10 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,AEB2DE62C65D1A88C4940A3476B2F10A
+
+5kNk/FFPbHa0402QTrgpIT28uirJ4Amvb2/ryOEyOCe0NPbTLCqlQekj2RFYH2Un
+pgCLUDkelKQv4pyuK8qWS7R+cFjE/gHHCPUWkK3djZUC8DKuA9lUKeQIE+V1vBHc
+L5G+MpoYrPgaydcGx/Uqnc/kVuZx1DXLwrGGtgwNROVBtmjXC9EdfeXHLL1y0wvH
+paNgacJpUtgqJEmiehf7eL/eiReegG553rZK3jjfboGkREUaKR5XOgamiKUtgKoc
+sMpImVYCsRKd/9RI+VOqErZaEvy/9j0Ye3iH32wGOaA=
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_pkey.py b/tests/test_pkey.py
index ec128140..59b3bb43 100644
--- a/tests/test_pkey.py
+++ b/tests/test_pkey.py
@@ -24,6 +24,7 @@ import unittest
import os
from binascii import hexlify
from hashlib import md5
+import base64
from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util
from paramiko.py3compat import StringIO, byte_chr, b, bytes
@@ -33,11 +34,15 @@ from tests.util import test_path
# from openssh's ssh-keygen
PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c='
PUB_DSS = 'ssh-dss AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF608EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIEAkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgE='
-PUB_ECDSA = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo='
+PUB_ECDSA_256 = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo='
+PUB_ECDSA_384 = 'ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBbGibQLW9AAZiGN2hEQxWYYoFaWKwN3PKSaDJSMqmIn1Z9sgRUuw8Y/w502OGvXL/wFk0i2z50l3pWZjD7gfMH7gX5TUiCzwrQkS+Hn1U2S9aF5WJp0NcIzYxXw2r4M2A=='
+PUB_ECDSA_521 = 'ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACaOaFLZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRAL4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA=='
FINGER_RSA = '1024 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5'
FINGER_DSS = '1024 44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c'
-FINGER_ECDSA = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60'
+FINGER_ECDSA_256 = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60'
+FINGER_ECDSA_384 = '384 c1:8d:a0:59:09:47:41:8e:a8:a6:07:01:29:23:b4:65'
+FINGER_ECDSA_521 = '521 44:58:22:52:12:33:16:0e:ce:0e:be:2c:7c:7e:cc:1e'
SIGNED_RSA = '20:d7:8a:31:21:cb:f7:92:12:f2:a4:89:37:f5:78:af:e6:16:b6:25:b9:97:3d:a2:cd:5f:ca:20:21:73:4c:ad:34:73:8f:20:77:28:e2:94:15:08:d8:91:40:7a:85:83:bf:18:37:95:dc:54:1a:9b:88:29:6c:73:ca:38:b4:04:f1:56:b9:f2:42:9d:52:1b:29:29:b4:4f:fd:c9:2d:af:47:d2:40:76:30:f3:63:45:0c:d9:1d:43:86:0f:1c:70:e2:93:12:34:f3:ac:c5:0a:2f:14:50:66:59:f1:88:ee:c1:4a:e9:d1:9c:4e:46:f0:0e:47:6f:38:74:f1:44:a8'
RSA_PRIVATE_OUT = """\
@@ -73,7 +78,7 @@ h9pT9XHqn+1rZ4bK+QGA
-----END DSA PRIVATE KEY-----
"""
-ECDSA_PRIVATE_OUT = """\
+ECDSA_PRIVATE_OUT_256 = """\
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
@@ -81,6 +86,25 @@ ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
-----END EC PRIVATE KEY-----
"""
+ECDSA_PRIVATE_OUT_384 = """\
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDBDdO8IXvlLJgM7+sNtPl7tI7FM5kzuEUEEPRjXIPQM7mISciwJPBt+
+y43EuG8nL4mgBwYFK4EEACKhZANiAAQWxom0C1vQAGYhjdoREMVmGKBWlisDdzyk
+mgyUjKpiJ9WfbIEVLsPGP8OdNjhr1y/8BZNIts+dJd6VmYw+4HzB+4F+U1Igs8K0
+JEvh59VNkvWheViadDXCM2MV8Nq+DNg=
+-----END EC PRIVATE KEY-----
+"""
+
+ECDSA_PRIVATE_OUT_521 = """\
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIAprQtAS3OF6iVUkT8IowTHWicHzShGgk86EtuEXvfQnhZFKsWm6Jo
+iqAr1yEaiuI9LfB3Xs8cjuhgEEfbduYr/f6gBwYFK4EEACOhgYkDgYYABACaOaFL
+ZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj
+4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRA
+L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA==
+-----END EC PRIVATE KEY-----
+"""
+
x1234 = b'\x01\x02\x03\x04'
@@ -121,7 +145,7 @@ class KeyTest (unittest.TestCase):
self.assertEqual(exp_rsa, my_rsa)
self.assertEqual(PUB_RSA.split()[1], key.get_base64())
self.assertEqual(1024, key.get_bits())
-
+
def test_4_load_dss(self):
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
self.assertEqual('ssh-dss', key.get_name())
@@ -205,43 +229,72 @@ class KeyTest (unittest.TestCase):
msg.rewind()
self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
- def test_10_load_ecdsa(self):
- key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
+ def test_C_generate_ecdsa(self):
+ key = ECDSAKey.generate()
+ msg = key.sign_ssh_data(b'jerri blank')
+ msg.rewind()
+ self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
+ self.assertEqual(key.get_bits(), 256)
+ self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp256')
+
+ key = ECDSAKey.generate(bits=256)
+ msg = key.sign_ssh_data(b'jerri blank')
+ msg.rewind()
+ self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
+ self.assertEqual(key.get_bits(), 256)
+ self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp256')
+
+ key = ECDSAKey.generate(bits=384)
+ msg = key.sign_ssh_data(b'jerri blank')
+ msg.rewind()
+ self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
+ self.assertEqual(key.get_bits(), 384)
+ self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp384')
+
+ key = ECDSAKey.generate(bits=521)
+ msg = key.sign_ssh_data(b'jerri blank')
+ msg.rewind()
+ self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
+ self.assertEqual(key.get_bits(), 521)
+ self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp521')
+
+ def test_10_load_ecdsa_256(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key'))
self.assertEqual('ecdsa-sha2-nistp256', key.get_name())
- exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', ''))
+ exp_ecdsa = b(FINGER_ECDSA_256.split()[1].replace(':', ''))
my_ecdsa = hexlify(key.get_fingerprint())
self.assertEqual(exp_ecdsa, my_ecdsa)
- self.assertEqual(PUB_ECDSA.split()[1], key.get_base64())
+ self.assertEqual(PUB_ECDSA_256.split()[1], key.get_base64())
self.assertEqual(256, key.get_bits())
s = StringIO()
key.write_private_key(s)
- self.assertEqual(ECDSA_PRIVATE_OUT, s.getvalue())
+ self.assertEqual(ECDSA_PRIVATE_OUT_256, s.getvalue())
s.seek(0)
key2 = ECDSAKey.from_private_key(s)
self.assertEqual(key, key2)
- def test_11_load_ecdsa_password(self):
- key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password.key'), b'television')
+ def test_11_load_ecdsa_password_256(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_256.key'), b'television')
self.assertEqual('ecdsa-sha2-nistp256', key.get_name())
- exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', ''))
+ exp_ecdsa = b(FINGER_ECDSA_256.split()[1].replace(':', ''))
my_ecdsa = hexlify(key.get_fingerprint())
self.assertEqual(exp_ecdsa, my_ecdsa)
- self.assertEqual(PUB_ECDSA.split()[1], key.get_base64())
+ self.assertEqual(PUB_ECDSA_256.split()[1], key.get_base64())
self.assertEqual(256, key.get_bits())
- def test_12_compare_ecdsa(self):
+ def test_12_compare_ecdsa_256(self):
# verify that the private & public keys compare equal
- key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key'))
self.assertEqual(key, key)
pub = ECDSAKey(data=key.asbytes())
self.assertTrue(key.can_sign())
self.assertTrue(not pub.can_sign())
self.assertEqual(key, pub)
- def test_13_sign_ecdsa(self):
+ def test_13_sign_ecdsa_256(self):
# verify that the rsa private key can sign and verify
- key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key'))
msg = key.sign_ssh_data(b'ice weasels')
self.assertTrue(type(msg) is Message)
msg.rewind()
@@ -255,6 +308,109 @@ class KeyTest (unittest.TestCase):
pub = ECDSAKey(data=key.asbytes())
self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
+ def test_14_load_ecdsa_384(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key'))
+ self.assertEqual('ecdsa-sha2-nistp384', key.get_name())
+ exp_ecdsa = b(FINGER_ECDSA_384.split()[1].replace(':', ''))
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEqual(exp_ecdsa, my_ecdsa)
+ self.assertEqual(PUB_ECDSA_384.split()[1], key.get_base64())
+ self.assertEqual(384, key.get_bits())
+
+ s = StringIO()
+ key.write_private_key(s)
+ self.assertEqual(ECDSA_PRIVATE_OUT_384, s.getvalue())
+ s.seek(0)
+ key2 = ECDSAKey.from_private_key(s)
+ self.assertEqual(key, key2)
+
+ def test_15_load_ecdsa_password_384(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_384.key'), b'television')
+ self.assertEqual('ecdsa-sha2-nistp384', key.get_name())
+ exp_ecdsa = b(FINGER_ECDSA_384.split()[1].replace(':', ''))
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEqual(exp_ecdsa, my_ecdsa)
+ self.assertEqual(PUB_ECDSA_384.split()[1], key.get_base64())
+ self.assertEqual(384, key.get_bits())
+
+ def test_16_compare_ecdsa_384(self):
+ # verify that the private & public keys compare equal
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key'))
+ self.assertEqual(key, key)
+ pub = ECDSAKey(data=key.asbytes())
+ self.assertTrue(key.can_sign())
+ self.assertTrue(not pub.can_sign())
+ self.assertEqual(key, pub)
+
+ def test_17_sign_ecdsa_384(self):
+ # verify that the rsa private key can sign and verify
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key'))
+ msg = key.sign_ssh_data(b'ice weasels')
+ self.assertTrue(type(msg) is Message)
+ msg.rewind()
+ self.assertEqual('ecdsa-sha2-nistp384', msg.get_text())
+ # ECDSA signatures, like DSS signatures, tend to be different
+ # each time, so we can't compare against a "known correct"
+ # signature.
+ # Even the length of the signature can change.
+
+ msg.rewind()
+ pub = ECDSAKey(data=key.asbytes())
+ self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
+
+ def test_18_load_ecdsa_521(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key'))
+ self.assertEqual('ecdsa-sha2-nistp521', key.get_name())
+ exp_ecdsa = b(FINGER_ECDSA_521.split()[1].replace(':', ''))
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEqual(exp_ecdsa, my_ecdsa)
+ self.assertEqual(PUB_ECDSA_521.split()[1], key.get_base64())
+ self.assertEqual(521, key.get_bits())
+
+ s = StringIO()
+ key.write_private_key(s)
+ # Different versions of OpenSSL (SSLeay versions 0x1000100f and
+ # 0x1000207f for instance) use different apparently valid (as far as
+ # ssh-keygen is concerned) padding. So we can't check the actual value
+ # of the pem encoded key.
+ s.seek(0)
+ key2 = ECDSAKey.from_private_key(s)
+ self.assertEqual(key, key2)
+
+ def test_19_load_ecdsa_password_521(self):
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_521.key'), b'television')
+ self.assertEqual('ecdsa-sha2-nistp521', key.get_name())
+ exp_ecdsa = b(FINGER_ECDSA_521.split()[1].replace(':', ''))
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEqual(exp_ecdsa, my_ecdsa)
+ self.assertEqual(PUB_ECDSA_521.split()[1], key.get_base64())
+ self.assertEqual(521, key.get_bits())
+
+ def test_20_compare_ecdsa_521(self):
+ # verify that the private & public keys compare equal
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key'))
+ self.assertEqual(key, key)
+ pub = ECDSAKey(data=key.asbytes())
+ self.assertTrue(key.can_sign())
+ self.assertTrue(not pub.can_sign())
+ self.assertEqual(key, pub)
+
+ def test_21_sign_ecdsa_521(self):
+ # verify that the rsa private key can sign and verify
+ key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key'))
+ msg = key.sign_ssh_data(b'ice weasels')
+ self.assertTrue(type(msg) is Message)
+ msg.rewind()
+ self.assertEqual('ecdsa-sha2-nistp521', msg.get_text())
+ # ECDSA signatures, like DSS signatures, tend to be different
+ # each time, so we can't compare against a "known correct"
+ # signature.
+ # Even the length of the signature can change.
+
+ msg.rewind()
+ pub = ECDSAKey(data=key.asbytes())
+ self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg))
+
def test_salt_size(self):
# Read an existing encrypted private key
file_ = test_path('test_rsa_password.key')