From: Brian Wellington Date: Tue, 10 Mar 2020 17:58:56 +0000 (-0700) Subject: Use PyCryptodome for ECDSA. X-Git-Tag: v2.0.0rc1~334^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=be4671ca4b72d72798b1958da7041596f199d436;p=thirdparty%2Fdnspython.git Use PyCryptodome for ECDSA. --- diff --git a/.travis.yml b/.travis.yml index 608d02d7..61c95220 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,6 @@ branches: except: - python3 install: - - pip install typing pylint pycryptodome ecdsa idna requests requests-toolbelt + - pip install typing pylint pycryptodome idna requests requests-toolbelt script: - make test diff --git a/dns/dnssec.py b/dns/dnssec.py index 7ce11244..f2b453c7 100644 --- a/dns/dnssec.py +++ b/dns/dnssec.py @@ -362,31 +362,20 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): number.bytes_to_long(dsa_q))) sig = rrsig.signature[1:] elif _is_ecdsa(rrsig.algorithm): - # use ecdsa for NIST-384p -- not currently supported by pycryptodome - if not _have_ecdsa: - raise ImportError('DNSSEC validation for algorithm %u requires edcsa library' % rrsig.algorithm) - keyptr = candidate_key.key - if rrsig.algorithm == ECDSAP256SHA256: - curve = ecdsa.curves.NIST256p - key_len = 32 - elif rrsig.algorithm == ECDSAP384SHA384: - curve = ecdsa.curves.NIST384p - key_len = 48 - - x = number.bytes_to_long(keyptr[0:key_len]) - y = number.bytes_to_long(keyptr[key_len:key_len * 2]) - if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y): - raise ValidationFailure('invalid ECDSA key') - point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order) - verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, - curve) - pubkey = ECKeyWrapper(verifying_key, key_len) - r = rrsig.signature[:key_len] - s = rrsig.signature[key_len:] - sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r), - number.bytes_to_long(s)) + curve = 'secp256r1' + octets = 32 + else: + curve = 'secp384r1' + octets = 48 + ecdsa_x = keyptr[0:octets] + ecdsa_y = keyptr[octets:octets * 2] + pubkey = CryptoECC.construct( + curve = curve, + point_x = number.bytes_to_long(ecdsa_x), + point_y = number.bytes_to_long(ecdsa_y)) + sig = rrsig.signature elif _is_eddsa(rrsig.algorithm) or _is_gost(rrsig.algorithm): raise UnsupportedAlgorithm( @@ -418,13 +407,9 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): verifier = pkcs1_15.new(pubkey) # will raise ValueError if verify fails: verifier.verify(hash, sig) - elif _is_dsa(rrsig.algorithm): + elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm): verifier = DSS.new(pubkey, 'fips-186-3') verifier.verify(hash, sig) - elif _is_ecdsa(rrsig.algorithm): - digest = hash.digest() - if not pubkey.verify(digest, sig): - raise ValueError else: # Raise here for code clarity; this won't actually ever happen # since if the algorithm is really unknown we'd already have @@ -495,39 +480,20 @@ try: # test we're using pycryptodome, not pycrypto (which misses SHA1 for example) from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512 from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA + from Crypto.PublicKey import ECC as CryptoECC from Crypto.Signature import pkcs1_15, DSS from Crypto.Util import number except ImportError: from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512 from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA + from Cryptodome.PublicKey import ECC as CryptoECC from Cryptodome.Signature import pkcs1_15, DSS from Cryptodome.Util import number except ImportError: validate = _need_pycrypto validate_rrsig = _need_pycrypto _have_pycrypto = False - _have_ecdsa = False else: validate = _validate validate_rrsig = _validate_rrsig _have_pycrypto = True - - try: - import ecdsa - import ecdsa.ecdsa - import ecdsa.ellipticcurve - import ecdsa.keys - except ImportError: - _have_ecdsa = False - else: - _have_ecdsa = True - - class ECKeyWrapper(object): - - def __init__(self, key, key_len): - self.key = key - self.key_len = key_len - - def verify(self, digest, sig): - diglong = number.bytes_to_long(digest) - return self.key.pubkey.verifies(diglong, sig) diff --git a/dns/dnssec.pyi b/dns/dnssec.pyi index 5699b3e1..da02c151 100644 --- a/dns/dnssec.pyi +++ b/dns/dnssec.pyi @@ -3,7 +3,6 @@ from . import rdataset, rrset, exception, name, rdtypes, rdata, node import dns.rdtypes.ANY.DS as DS import dns.rdtypes.ANY.DNSKEY as DNSKEY -_have_ecdsa : bool _have_pycrypto : bool def validate_rrsig(rrset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsig : rdata.Rdata, keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin : Optional[name.Name] = None, now : Optional[int] = None) -> None: diff --git a/doc/dnssec.rst b/doc/dnssec.rst index eee8ea74..079ba231 100644 --- a/doc/dnssec.rst +++ b/doc/dnssec.rst @@ -6,8 +6,7 @@ DNSSEC Dnspython can do simple DNSSEC signature validation, but currently has no facilities for signing. In order to use DNSSEC functions, you must have -``pycryptodome`` or ``pycryptodomex`` installed. If you want to do elliptic -curves, you must also have ``ecdsa`` installed. +``pycryptodome`` or ``pycryptodomex`` installed. DNSSEC Functions ---------------- @@ -33,4 +32,4 @@ DNSSEC Algorithms .. autodata:: dns.dnssec.ECDSAP384SHA384 .. autodata:: dns.dnssec.INDIRECT .. autodata:: dns.dnssec.PRIVATEDNS -.. autodata:: dns.dnssec.PRIVATEOID \ No newline at end of file +.. autodata:: dns.dnssec.PRIVATEOID diff --git a/doc/installation.rst b/doc/installation.rst index d9d7339e..9c619e90 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -46,9 +46,6 @@ Optional Modules The following modules are optional, but recommended for full functionality. If ``pycryptodome`` / ``pycryptodomex`` is installed, then dnspython will be -able to do low-level DNSSEC RSA and DSA signature validation. - -If ``ecdsa`` is installed, then Elliptic Curve signature algorithms will -be available for low-level DNSSEC signature validation. +able to do low-level DNSSEC RSA, DSA, and ECDSA signature validation. If ``idna`` is installed, then IDNA 2008 will be available. diff --git a/setup.py b/setup.py index 8279a890..2dac565e 100755 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ direct manipulation of DNS zones, messages, names, and records.""", 'tests_require': ['typing ; python_version<"3.5"'], 'extras_require': { 'IDNA': ['idna>=2.1'], - 'DNSSEC': ['pycryptodome', 'ecdsa>=0.13'], + 'DNSSEC': ['pycryptodome>=3.4'], }, 'ext_modules': ext_modules if compile_cython else None, 'zip_safe': False if compile_cython else None, diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py index 24492103..31078c7b 100644 --- a/tests/test_dnssec.py +++ b/tests/test_dnssec.py @@ -186,28 +186,20 @@ class DNSSECValidatorTestCase(unittest.TestCase): abs_dsa_keys, None, when2) self.assertRaises(dns.dnssec.ValidationFailure, bad) - @unittest.skipUnless(dns.dnssec._have_ecdsa, - "python ECDSA cannot be imported") def testAbsoluteECDSA256Good(self): # type: () -> None dns.dnssec.validate(abs_ecdsa256_soa, abs_ecdsa256_soa_rrsig, abs_ecdsa256_keys, None, when3) - @unittest.skipUnless(dns.dnssec._have_ecdsa, - "python ECDSA cannot be imported") def testAbsoluteECDSA256Bad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate(abs_other_ecdsa256_soa, abs_ecdsa256_soa_rrsig, abs_ecdsa256_keys, None, when3) self.assertRaises(dns.dnssec.ValidationFailure, bad) - @unittest.skipUnless(dns.dnssec._have_ecdsa, - "python ECDSA cannot be imported") def testAbsoluteECDSA384Good(self): # type: () -> None dns.dnssec.validate(abs_ecdsa384_soa, abs_ecdsa384_soa_rrsig, abs_ecdsa384_keys, None, when4) - @unittest.skipUnless(dns.dnssec._have_ecdsa, - "python ECDSA cannot be imported") def testAbsoluteECDSA384Bad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate(abs_other_ecdsa384_soa, abs_ecdsa384_soa_rrsig,