From c15a34bc0ee818620fdb23a2a2bc0366f23feab0 Mon Sep 17 00:00:00 2001 From: Tomas Krizek Date: Wed, 18 Jul 2018 14:56:31 +0200 Subject: [PATCH] dns/dnssec: support both pycryptodome and pycryptodomex --- ChangeLog | 3 +- dns/dnssec.py | 84 +++++++++++++++++++++++++------------------- doc/dnssec.rst | 4 +-- doc/installation.rst | 4 +-- setup.py | 2 +- tests/test_dnssec.py | 26 +++----------- 6 files changed, 58 insertions(+), 65 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1e15697b..0c76f969 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,8 @@ 2017-12-21 Daniel Robbins * dns/dnssec.py: migrated code from pycrypto (apparently no - longer maintained) to pycryptodome. All tests passing. + longer maintained) to pycryptodome/pycryptodomex. + All tests passing. 2018-07-18 Tomas Krizek diff --git a/dns/dnssec.py b/dns/dnssec.py index b154b10b..634c934b 100644 --- a/dns/dnssec.py +++ b/dns/dnssec.py @@ -27,8 +27,7 @@ import dns.rdata import dns.rdatatype import dns.rdataclass from ._compat import string_types -from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512 -from Crypto.Signature import pkcs1_15, DSS + class UnsupportedAlgorithm(dns.exception.DNSException): """The DNSSEC algorithm is not supported.""" @@ -327,9 +326,9 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): rsa_e = keyptr[0:bytes_] rsa_n = keyptr[bytes_:] try: - pubkey = Crypto.PublicKey.RSA.construct( - (Crypto.Util.number.bytes_to_long(rsa_n), - Crypto.Util.number.bytes_to_long(rsa_e))) + pubkey = CryptoRSA.construct( + (number.bytes_to_long(rsa_n), + number.bytes_to_long(rsa_e))) except ValueError: raise ValidationFailure('invalid public key') sig = rrsig.signature @@ -345,11 +344,11 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): dsa_g = keyptr[0:octets] keyptr = keyptr[octets:] dsa_y = keyptr[0:octets] - pubkey = Crypto.PublicKey.DSA.construct( - (Crypto.Util.number.bytes_to_long(dsa_y), - Crypto.Util.number.bytes_to_long(dsa_g), - Crypto.Util.number.bytes_to_long(dsa_p), - Crypto.Util.number.bytes_to_long(dsa_q))) + pubkey = CryptoDSA.construct( + (number.bytes_to_long(dsa_y), + number.bytes_to_long(dsa_g), + number.bytes_to_long(dsa_p), + 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 @@ -363,8 +362,8 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): curve = ecdsa.curves.NIST384p key_len = 48 - x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len]) - y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2]) + 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) @@ -373,8 +372,8 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): pubkey = ECKeyWrapper(verifying_key, key_len) r = rrsig.signature[:key_len] s = rrsig.signature[key_len:] - sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r), - Crypto.Util.number.bytes_to_long(s)) + sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r), + number.bytes_to_long(s)) else: raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) @@ -474,36 +473,47 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): def _need_pycrypto(*args, **kwargs): - raise NotImplementedError("DNSSEC validation requires pycryptodome") + raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex") + try: - import Crypto.PublicKey.RSA - import Crypto.PublicKey.DSA - import Crypto.Util.number - validate = _validate - validate_rrsig = _validate_rrsig - _have_pycrypto = True + 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.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.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 - _have_ecdsa = True - - class ECKeyWrapper(object): + try: + import ecdsa + import ecdsa.ecdsa + import ecdsa.ellipticcurve + import ecdsa.keys + except ImportError: + _have_ecdsa = False + else: + _have_ecdsa = True - def __init__(self, key, key_len): - self.key = key - self.key_len = key_len + class ECKeyWrapper(object): - def verify(self, digest, sig): - diglong = Crypto.Util.number.bytes_to_long(digest) - return self.key.pubkey.verifies(diglong, sig) + def __init__(self, key, key_len): + self.key = key + self.key_len = key_len -except ImportError: - _have_ecdsa = False + def verify(self, digest, sig): + diglong = number.bytes_to_long(digest) + return self.key.pubkey.verifies(diglong, sig) diff --git a/doc/dnssec.rst b/doc/dnssec.rst index 64f08b3c..115f9734 100644 --- a/doc/dnssec.rst +++ b/doc/dnssec.rst @@ -6,8 +6,8 @@ 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`` installed. If you want to do elliptic curves, you must also -have ``ecdsa`` installed. +``pycryptodome`` or ``pycryptodomex`` installed. If you want to do elliptic +curves, you must also have ``ecdsa`` installed. DNSSEC Algorithms ----------------- diff --git a/doc/installation.rst b/doc/installation.rst index 7854f3da..e480655a 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -45,8 +45,8 @@ Optional Modules The following modules are optional, but recommended for full functionality. -If ``pycryptodome`` is installed, then dnspython will be able to do low-level -DNSSEC RSA and DSA signature validation. +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. diff --git a/setup.py b/setup.py index b4511fa3..d190eb62 100755 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ direct manipulation of DNS zones, messages, names, and records.""", 'provides': ['dns'], 'extras_require': { 'IDNA': ['idna>=2.1'], - 'DNSSEC': ['pycrypto>=2.6.1', 'ecdsa>=0.13'], + 'DNSSEC': ['pycryptodome', 'ecdsa>=0.13'], }, } diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py index 9fb037e1..78b1cdc9 100644 --- a/tests/test_dnssec.py +++ b/tests/test_dnssec.py @@ -20,12 +20,6 @@ try: except ImportError: import unittest -try: - import Crypto.Util.number # pylint: disable=unused-import - import_ok = True -except ImportError: - import_ok = False - import dns.dnssec import dns.name import dns.rdata @@ -156,36 +150,28 @@ abs_other_ecdsa384_soa = dns.rrset.from_text('example.', 86400, 'IN', 'SOA', abs_ecdsa384_soa_rrsig = dns.rrset.from_text('example.', 86400, 'IN', 'RRSIG', "SOA 14 1 86400 20130929021229 20130921230729 63571 example. CrnCu34EeeRz0fEhL9PLlwjpBKGYW8QjBjFQTwd+ViVLRAS8tNkcDwQE NhSV89NEjj7ze1a/JcCfcJ+/mZgnvH4NHLNg3Tf6KuLZsgs2I4kKQXEk 37oIHravPEOlGYNI") -@unittest.skipUnless(import_ok, "skipping DNSSEC tests because pycryptodome is not" - " installed") + + +@unittest.skipUnless(dns.dnssec._have_pycrypto, + "Pycryptodome cannot be imported") class DNSSECValidatorTestCase(unittest.TestCase): - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testAbsoluteRSAGood(self): dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testDuplicateKeytag(self): dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys_duplicate_keytag, None, when) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testAbsoluteRSABad(self): def bad(): dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None, when) self.failUnlessRaises(dns.dnssec.ValidationFailure, bad) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testRelativeRSAGood(self): dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, abs_dnspython_org, when) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testRelativeRSABad(self): def bad(): dns.dnssec.validate(rel_other_soa, rel_soa_rrsig, rel_keys, @@ -196,14 +182,10 @@ class DNSSECValidatorTestCase(unittest.TestCase): ds = dns.dnssec.make_ds(abs_dnspython_org, sep_key, 'SHA256') self.failUnless(ds == good_ds) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testAbsoluteDSAGood(self): dns.dnssec.validate(abs_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None, when2) - @unittest.skipUnless(dns.dnssec._have_pycrypto, - "Pycryptodome cannot be imported") def testAbsoluteDSABad(self): def bad(): dns.dnssec.validate(abs_other_dsa_soa, abs_dsa_soa_rrsig, -- 2.47.3