diff options
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | paramiko/dsskey.py | 2 | ||||
-rw-r--r-- | paramiko/ecdsakey.py | 87 | ||||
-rw-r--r-- | paramiko/util.py | 3 | ||||
-rw-r--r-- | setup.py | 1 |
5 files changed, 60 insertions, 34 deletions
@@ -37,7 +37,6 @@ Requirements - Python 2.6 or better <http://www.python.org/> - this includes Python 3.2 and higher as well. - Cryptography 0.6 or better <https://cryptography.io> - - ecdsa 0.9 or better <https://pypi.python.org/pypi/ecdsa> - pyasn1 0.1.7 or better <https://pypi.python.org/pypi/pyasn1> If you have setuptools, you can build and install paramiko and all its diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py index 87293e3c..496e8527 100644 --- a/paramiko/dsskey.py +++ b/paramiko/dsskey.py @@ -20,8 +20,6 @@ DSS keys. """ -import os - from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index fefd5eb5..831fa003 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -20,19 +20,28 @@ ECDSA keys """ +import base64 import binascii +import textwrap from hashlib import sha256 -from ecdsa import SigningKey, VerifyingKey, der, curves +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec + +from pyasn1.codec.der import decoder, encoder from paramiko.common import four_byte, one_byte +from paramiko.dsskey import _DSSSigValue from paramiko.message import Message from paramiko.pkey import PKey from paramiko.py3compat import byte_chr, u from paramiko.ssh_exception import SSHException +from paramiko.util import deflate_long, inflate_long -class ECDSAKey (PKey): +class ECDSAKey(PKey): """ Representation of an ECDSA key which can be used to sign and verify SSH2 data. @@ -65,9 +74,13 @@ class ECDSAKey (PKey): if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) - self.verifying_key = VerifyingKey.from_string(pointinfo[1:], - curve=curves.NIST256p, - validate_point=validate_point) + curve = ec.SECP256R1() + 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), + curve=curve + ) + self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256 def asbytes(self): @@ -76,8 +89,15 @@ class ECDSAKey (PKey): m.add_string('ecdsa-sha2-nistp256') m.add_string('nistp256') - point_str = four_byte + key.to_string() + numbers = key.public_numbers() + + x_bytes = deflate_long(numbers.x, add_sign_padding=False) + x_bytes = b'\x00' * (len(x_bytes) - key.curve.key_size // 8) + 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 + + point_str = four_byte + x_bytes + y_bytes m.add_string(point_str) return m.asbytes() @@ -86,8 +106,8 @@ class ECDSAKey (PKey): def __hash__(self): h = hash(self.get_name()) - h = h * 37 + hash(self.verifying_key.pubkey.point.x()) - h = h * 37 + hash(self.verifying_key.pubkey.point.y()) + h = h * 37 + hash(self.verifying_key.public_numbers().x) + h = h * 37 + hash(self.verifying_key.public_numbers().y) return hash(h) def get_name(self): @@ -100,23 +120,34 @@ class ECDSAKey (PKey): return self.signing_key is not None def sign_ssh_data(self, data): - sig = self.signing_key.sign_deterministic( - data, sigencode=self._sigencode, hashfunc=sha256) + signer = self.signing_key.signer(ec.ECDSA(hashes.SHA256())) + signer.update(data) + sig = signer.finalize() + (r, s), _ = decoder.decode(sig) + m = Message() m.add_string('ecdsa-sha2-nistp256') - m.add_string(sig) + m.add_string(self._sigencode(r, s)) return m def verify_ssh_sig(self, data, msg): if msg.get_text() != 'ecdsa-sha2-nistp256': return False sig = msg.get_binary() - - # verify the signature by SHA'ing the data and encrypting it - # using the public key. - hash_obj = sha256(data).digest() - return self.verifying_key.verify_digest(sig, hash_obj, - sigdecode=self._sigdecode) + sigR, sigS = self._sigdecode(sig) + sig_asn1 = _DSSSigValue() + sig_asn1.setComponentByName('r', sigR) + sig_asn1.setComponentByName('s', sigS) + signature = encoder.encode(sig_asn1) + + verifier = self.verifying_key.verifier(signature, ec.ECDSA(hashes.SHA256())) + verifier.update(data) + try: + verifier.verify() + except InvalidSignature: + return False + else: + return True def write_private_key_file(self, filename, password=None): key = self.signing_key or self.verifying_key @@ -126,7 +157,7 @@ class ECDSAKey (PKey): key = self.signing_key or self.verifying_key self._write_private_key('EC', file_obj, key.to_der(), password) - def generate(curve=curves.NIST256p, progress_func=None): + def generate(curve=ec.SECP256R1(), progress_func=None): """ Generate a new private RSA key. This factory function can be used to generate a new host key or authentication key. @@ -153,23 +184,23 @@ class ECDSAKey (PKey): byte_chr(5) * 5, byte_chr(6) * 6, byte_chr(7) * 7] def _decode_key(self, data): - s, padding = der.remove_sequence(data) - if padding: - if padding not in self.ALLOWED_PADDINGS: - raise ValueError("weird padding: %s" % u(binascii.hexlify(data))) - data = data[:-len(padding)] - key = SigningKey.from_der(data) + s = """ +-----BEGIN EC PRIVATE KEY----- +%s +-----END EC PRIVATE KEY----- +""" % "\n".join(textwrap.wrap(base64.b64encode(data), 64)) + key = serialization.load_pem_private_key(s, password=None, backend=default_backend()) self.signing_key = key - self.verifying_key = key.get_verifying_key() - self.size = 256 + self.verifying_key = key.public_key() + self.size = key.curve.key_size - def _sigencode(self, r, s, order): + def _sigencode(self, r, s): msg = Message() msg.add_mpint(r) msg.add_mpint(s) return msg.asbytes() - def _sigdecode(self, sig, order): + def _sigdecode(self, sig): msg = Message(sig) r = msg.get_mpint() s = msg.get_mpint() diff --git a/paramiko/util.py b/paramiko/util.py index 25062d00..d90fd981 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -22,7 +22,6 @@ Useful functions used by the rest of paramiko. from __future__ import generators -import array from binascii import hexlify, unhexlify import errno import sys @@ -32,7 +31,7 @@ import threading import logging from paramiko.common import DEBUG, zero_byte, xffffffff, max_byte -from paramiko.py3compat import PY2, long, byte_ord, b, byte_chr +from paramiko.py3compat import PY2, long, byte_ord, b from paramiko.config import SSHConfig @@ -42,7 +42,6 @@ try: kw = { 'install_requires': [ 'cryptography >= 0.6', - 'ecdsa >= 0.11', 'pyasn1 >= 0.1.7', ], } |