From ad27d482f033fef9f3693f3bcdff2b84d9b5bffa51fbe897d1367a40ccdece48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Wed, 9 May 2018 13:46:46 +0000 Subject: [PATCH] - Add patch pycryptodome.patch to work with pycryptodome: * pycryptodome.patch OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-dnspython?expand=0&rev=40 --- pycryptodome.patch | 461 +++++++++++++++++++++++++++++++++++++++ python-dnspython.changes | 6 + python-dnspython.spec | 15 +- 3 files changed, 476 insertions(+), 6 deletions(-) create mode 100644 pycryptodome.patch diff --git a/pycryptodome.patch b/pycryptodome.patch new file mode 100644 index 0000000..a0b378f --- /dev/null +++ b/pycryptodome.patch @@ -0,0 +1,461 @@ +From 10b8a42c90d037880fbfc81fb71adb27251252e3 Mon Sep 17 00:00:00 2001 +From: Daniel Robbins +Date: Thu, 21 Dec 2017 09:24:40 -0700 +Subject: [PATCH] Update DNSSEC code to use pycryptodome instead of pycrypto. + These changes make dnspython *incompatible* with pycrypto -- pycryptodome + must be used. The ecdsa module continues to be used for ECDSA support. + +--- + ChangeLog | 5 ++ + dns/__init__.py | 1 - + dns/dnssec.py | 161 +++++++++++++++++++++++++++------------------------ + dns/hash.py | 31 ---------- + dns/tsig.py | 4 +- + doc/dnssec.rst | 9 ++- + doc/installation.rst | 4 +- + tests/test_dnssec.py | 16 ++--- + 8 files changed, 105 insertions(+), 126 deletions(-) + delete mode 100644 dns/hash.py + +diff --git a/dns/__init__.py b/dns/__init__.py +index c848e48..3852729 100644 +--- a/dns/__init__.py ++++ b/dns/__init__.py +@@ -22,7 +22,6 @@ + 'entropy', + 'exception', + 'flags', +- 'hash', + 'inet', + 'ipv4', + 'ipv6', +diff --git a/dns/dnssec.py b/dns/dnssec.py +index b91a64f..2b5d5b2 100644 +--- a/dns/dnssec.py ++++ b/dns/dnssec.py +@@ -20,7 +20,6 @@ + import time + + import dns.exception +-import dns.hash + import dns.name + import dns.node + import dns.rdataset +@@ -28,7 +27,8 @@ + 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.""" +@@ -39,27 +39,27 @@ class ValidationFailure(dns.exception.DNSException): + + + #: RSAMD5 +-RSAMD5 = 1 ++ALGO_RSAMD5 = 1 + #: DH +-DH = 2 ++ALGO_DH = 2 + #: DSA +-DSA = 3 ++ALGO_DSA = 3 + #: ECC +-ECC = 4 ++ALGO_ECC = 4 + #: RSASHA1 +-RSASHA1 = 5 ++ALGO_RSASHA1 = 5 + #: DSANSEC3SHA1 +-DSANSEC3SHA1 = 6 ++ALGO_DSANSEC3SHA1 = 6 + #: RSASHA1NSEC3SHA1 +-RSASHA1NSEC3SHA1 = 7 ++ALGO_RSASHA1NSEC3SHA1 = 7 + #: RSASHA256 +-RSASHA256 = 8 ++ALGO_RSASHA256 = 8 + #: RSASHA512 +-RSASHA512 = 10 ++ALGO_RSASHA512 = 10 + #: ECDSAP256SHA256 +-ECDSAP256SHA256 = 13 ++ALGO_ECDSAP256SHA256 = 13 + #: ECDSAP384SHA384 +-ECDSAP384SHA384 = 14 ++ALGO_ECDSAP384SHA384 = 14 + #: INDIRECT + INDIRECT = 252 + #: PRIVATEDNS +@@ -68,18 +68,18 @@ class ValidationFailure(dns.exception.DNSException): + PRIVATEOID = 254 + + _algorithm_by_text = { +- 'RSAMD5': RSAMD5, +- 'DH': DH, +- 'DSA': DSA, +- 'ECC': ECC, +- 'RSASHA1': RSASHA1, +- 'DSANSEC3SHA1': DSANSEC3SHA1, +- 'RSASHA1NSEC3SHA1': RSASHA1NSEC3SHA1, +- 'RSASHA256': RSASHA256, +- 'RSASHA512': RSASHA512, ++ 'RSAMD5': ALGO_RSAMD5, ++ 'DH': ALGO_DH, ++ 'DSA': ALGO_DSA, ++ 'ECC': ALGO_ECC, ++ 'RSASHA1': ALGO_RSASHA1, ++ 'DSANSEC3SHA1': ALGO_DSANSEC3SHA1, ++ 'RSASHA1NSEC3SHA1': ALGO_RSASHA1NSEC3SHA1, ++ 'RSASHA256': ALGO_RSASHA256, ++ 'RSASHA512': ALGO_RSASHA512, + 'INDIRECT': INDIRECT, +- 'ECDSAP256SHA256': ECDSAP256SHA256, +- 'ECDSAP384SHA384': ECDSAP384SHA384, ++ 'ECDSAP256SHA256': ALGO_ECDSAP256SHA256, ++ 'ECDSAP384SHA384': ALGO_ECDSAP384SHA384, + 'PRIVATEDNS': PRIVATEDNS, + 'PRIVATEOID': PRIVATEOID, + } +@@ -132,7 +132,7 @@ def key_id(key, origin=None): + + rdata = _to_rdata(key, origin) + rdata = bytearray(rdata) +- if key.algorithm == RSAMD5: ++ if key.algorithm == ALGO_RSAMD5: + return (rdata[-3] << 8) + rdata[-2] + else: + total = 0 +@@ -164,10 +164,10 @@ def make_ds(name, key, algorithm, origin=None): + + if algorithm.upper() == 'SHA1': + dsalg = 1 +- hash = dns.hash.hashes['SHA1']() ++ hash = SHA1.new() + elif algorithm.upper() == 'SHA256': + dsalg = 2 +- hash = dns.hash.hashes['SHA256']() ++ hash = SHA256.new() + else: + raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm) + +@@ -203,51 +203,51 @@ def _find_candidate_keys(keys, rrsig): + + + def _is_rsa(algorithm): +- return algorithm in (RSAMD5, RSASHA1, +- RSASHA1NSEC3SHA1, RSASHA256, +- RSASHA512) ++ return algorithm in (ALGO_RSAMD5, ALGO_RSASHA1, ++ ALGO_RSASHA1NSEC3SHA1, ALGO_RSASHA256, ++ ALGO_RSASHA512) + + + def _is_dsa(algorithm): +- return algorithm in (DSA, DSANSEC3SHA1) ++ return algorithm in (ALGO_DSA, ALGO_DSANSEC3SHA1) + + + def _is_ecdsa(algorithm): +- return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384)) ++ return _have_ecdsa and (algorithm in (ALGO_ECDSAP256SHA256, ALGO_ECDSAP384SHA384)) + + + def _is_md5(algorithm): +- return algorithm == RSAMD5 ++ return algorithm == ALGO_RSAMD5 + + + def _is_sha1(algorithm): +- return algorithm in (DSA, RSASHA1, +- DSANSEC3SHA1, RSASHA1NSEC3SHA1) ++ return algorithm in (ALGO_DSA, ALGO_RSASHA1, ++ ALGO_DSANSEC3SHA1, ALGO_RSASHA1NSEC3SHA1) + + + def _is_sha256(algorithm): +- return algorithm in (RSASHA256, ECDSAP256SHA256) ++ return algorithm in (ALGO_RSASHA256, ALGO_ECDSAP256SHA256) + + + def _is_sha384(algorithm): +- return algorithm == ECDSAP384SHA384 ++ return algorithm == ALGO_ECDSAP384SHA384 + + + def _is_sha512(algorithm): +- return algorithm == RSASHA512 ++ return algorithm == ALGO_RSASHA512 + + + def _make_hash(algorithm): + if _is_md5(algorithm): +- return dns.hash.hashes['MD5']() ++ return MD5.new() + if _is_sha1(algorithm): +- return dns.hash.hashes['SHA1']() ++ return SHA1.new() + if _is_sha256(algorithm): +- return dns.hash.hashes['SHA256']() ++ return SHA256.new() + if _is_sha384(algorithm): +- return dns.hash.hashes['SHA384']() ++ return SHA384.new() + if _is_sha512(algorithm): +- return dns.hash.hashes['SHA512']() ++ return SHA512.new() + raise ValidationFailure('unknown hash for algorithm %u' % algorithm) + + +@@ -326,11 +326,13 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): + keyptr = keyptr[2:] + rsa_e = keyptr[0:bytes_] + rsa_n = keyptr[bytes_:] +- keylen = len(rsa_n) * 8 +- pubkey = Crypto.PublicKey.RSA.construct( +- (Crypto.Util.number.bytes_to_long(rsa_n), +- Crypto.Util.number.bytes_to_long(rsa_e))) +- sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),) ++ try: ++ pubkey = Crypto.PublicKey.RSA.construct( ++ (Crypto.Util.number.bytes_to_long(rsa_n), ++ Crypto.Util.number.bytes_to_long(rsa_e))) ++ except ValueError: ++ raise ValidationFailure('invalid public key') ++ sig = rrsig.signature + elif _is_dsa(rrsig.algorithm): + keyptr = candidate_key.key + (t,) = struct.unpack('!B', keyptr[0:1]) +@@ -348,20 +350,19 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): + Crypto.Util.number.bytes_to_long(dsa_g), + Crypto.Util.number.bytes_to_long(dsa_p), + Crypto.Util.number.bytes_to_long(dsa_q))) +- (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:]) +- sig = (Crypto.Util.number.bytes_to_long(dsa_r), +- Crypto.Util.number.bytes_to_long(dsa_s)) ++ sig = rrsig.signature[1:] + elif _is_ecdsa(rrsig.algorithm): +- if rrsig.algorithm == ECDSAP256SHA256: ++ # use ecdsa for NIST-384p -- not currently supported by pycryptodome ++ ++ keyptr = candidate_key.key ++ ++ if rrsig.algorithm == ALGO_ECDSAP256SHA256: + curve = ecdsa.curves.NIST256p + key_len = 32 +- elif rrsig.algorithm == ECDSAP384SHA384: ++ elif rrsig.algorithm == ALGO_ECDSAP384SHA384: + curve = ecdsa.curves.NIST384p + key_len = 48 +- else: +- # shouldn't happen +- raise ValidationFailure('unknown ECDSA curve') +- keyptr = candidate_key.key ++ + x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len]) + y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2]) + if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y): +@@ -374,6 +375,7 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): + s = rrsig.signature[key_len:] + sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r), + Crypto.Util.number.bytes_to_long(s)) ++ + else: + raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) + +@@ -395,24 +397,31 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): + hash.update(rrlen) + hash.update(rrdata) + +- digest = hash.digest() +- +- if _is_rsa(rrsig.algorithm): +- # PKCS1 algorithm identifier goop +- digest = _make_algorithm_id(rrsig.algorithm) + digest +- padlen = keylen // 8 - len(digest) - 3 +- digest = struct.pack('!%dB' % (2 + padlen + 1), +- *([0, 1] + [0xFF] * padlen + [0])) + digest +- elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm): +- pass +- else: +- # Raise here for code clarity; this won't actually ever happen +- # since if the algorithm is really unknown we'd already have +- # raised an exception above +- raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) +- +- if pubkey.verify(digest, sig): ++ try: ++ if _is_rsa(rrsig.algorithm): ++ verifier = pkcs1_15.new(pubkey) ++ # will raise ValueError if verify fails: ++ verifier.verify(hash, sig) ++ elif _is_dsa(rrsig.algorithm): ++ verifier = DSS.new(pubkey, 'fips-186-3') ++ verifier.verify(hash, sig) ++ elif _is_ecdsa(rrsig.algorithm): ++ digest = hash.digest() ++ if pubkey.verify(digest, sig): ++ return ++ else: ++ 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 ++ # raised an exception above ++ raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) ++ # If we got here, we successfully verified so we can return without error + return ++ except ValueError: ++ # this happens on an individual validation failure ++ continue ++ # nothing verified -- raise failure: + raise ValidationFailure('verify failure') + + +@@ -444,10 +453,8 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): + rrname = rrset.name + + if isinstance(rrsigset, tuple): +- rrsigname = rrsigset[0] + rrsigrdataset = rrsigset[1] + else: +- rrsigname = rrsigset.name + rrsigrdataset = rrsigset + + rrname = rrname.choose_relativity(origin) +@@ -465,7 +472,7 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None): + + + def _need_pycrypto(*args, **kwargs): +- raise NotImplementedError("DNSSEC validation requires pycrypto") ++ raise NotImplementedError("DNSSEC validation requires pycryptodome") + + try: + import Crypto.PublicKey.RSA +diff --git a/dns/hash.py b/dns/hash.py +deleted file mode 100644 +index 966838a..0000000 +--- a/dns/hash.py ++++ /dev/null +@@ -1,31 +0,0 @@ +-# Copyright (C) 2011 Nominum, Inc. +-# +-# Permission to use, copy, modify, and distribute this software and its +-# documentation for any purpose with or without fee is hereby granted, +-# provided that the above copyright notice and this permission notice +-# appear in all copies. +-# +-# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +- +-"""Hashing backwards compatibility wrapper""" +- +-import hashlib +- +- +-hashes = {} +-hashes['MD5'] = hashlib.md5 +-hashes['SHA1'] = hashlib.sha1 +-hashes['SHA224'] = hashlib.sha224 +-hashes['SHA256'] = hashlib.sha256 +-hashes['SHA384'] = hashlib.sha384 +-hashes['SHA512'] = hashlib.sha512 +- +- +-def get(algorithm): +- return hashes[algorithm.upper()] +diff --git a/dns/tsig.py b/dns/tsig.py +index c57d879..fd9d56a 100644 +--- a/dns/tsig.py ++++ b/dns/tsig.py +@@ -19,9 +19,9 @@ + import struct + + import dns.exception +-import dns.hash + import dns.rdataclass + import dns.name ++import dns.dnssec + from ._compat import long, string_types, text_type + + class BadTime(dns.exception.DNSException): +@@ -211,7 +211,7 @@ def get_algorithm(algorithm): + algorithm = dns.name.from_text(algorithm) + + try: +- return (algorithm.to_digestable(), dns.hash.hashes[_hashes[algorithm]]) ++ return (algorithm.to_digestable(), dns.dnssec._make_hash(algorithm)) + except KeyError: + raise NotImplementedError("TSIG algorithm " + str(algorithm) + + " is not supported") +diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py +index 80bd626..9fb037e 100644 +--- a/tests/test_dnssec.py ++++ b/tests/test_dnssec.py +@@ -156,22 +156,22 @@ + 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 pycrypto is not" ++@unittest.skipUnless(import_ok, "skipping DNSSEC tests because pycryptodome is not" + " installed") + class DNSSECValidatorTestCase(unittest.TestCase): + + @unittest.skipUnless(dns.dnssec._have_pycrypto, +- "PyCrypto cannot be imported") ++ "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, +- "PyCrypto cannot be imported") ++ "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, +- "PyCrypto cannot be imported") ++ "Pycryptodome cannot be imported") + def testAbsoluteRSABad(self): + def bad(): + dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None, +@@ -179,13 +179,13 @@ def bad(): + self.failUnlessRaises(dns.dnssec.ValidationFailure, bad) + + @unittest.skipUnless(dns.dnssec._have_pycrypto, +- "PyCrypto cannot be imported") ++ "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, +- "PyCrypto cannot be imported") ++ "Pycryptodome cannot be imported") + def testRelativeRSABad(self): + def bad(): + dns.dnssec.validate(rel_other_soa, rel_soa_rrsig, rel_keys, +@@ -197,13 +197,13 @@ def testMakeSHA256DS(self): + self.failUnless(ds == good_ds) + + @unittest.skipUnless(dns.dnssec._have_pycrypto, +- "PyCrypto cannot be imported") ++ "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, +- "PyCrypto cannot be imported") ++ "Pycryptodome cannot be imported") + def testAbsoluteDSABad(self): + def bad(): + dns.dnssec.validate(abs_other_dsa_soa, abs_dsa_soa_rrsig, diff --git a/python-dnspython.changes b/python-dnspython.changes index 249e689..547d085 100644 --- a/python-dnspython.changes +++ b/python-dnspython.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed May 9 13:42:40 UTC 2018 - tchvatal@suse.com + +- Add patch pycryptodome.patch to work with pycryptodome: + * pycryptodome.patch + ------------------------------------------------------------------- Tue May 2 21:42:12 UTC 2017 - sor.alexei@meowr.ru diff --git a/python-dnspython.spec b/python-dnspython.spec index 1504ec9..bd906fa 100644 --- a/python-dnspython.spec +++ b/python-dnspython.spec @@ -1,7 +1,7 @@ # # spec file for package python-dnspython # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -23,7 +23,7 @@ Release: 0 Summary: A DNS toolkit for Python License: ISC Group: Development/Languages/Python -Url: http://dnspython.org/ +URL: http://dnspython.org/ Source: http://dnspython.org/kits/%{version}/dnspython-%{version}.tar.gz Source2: http://dnspython.org/kits/%{version}/dnspython-%{version}.tar.gz.asc Source3: python-dnspython.keyring @@ -31,17 +31,19 @@ Source3: python-dnspython.keyring Patch0: 210.patch # PATCH-FEATURE-OPENSUSE readme.patch -- Add the readme as patch as not included in the tarball. Patch1: readme.patch +# PATCH-FIX-UPSTREAM pycryptodome.patch tchvatal@suse.com -- use pycryptodome https://github.com/rthalley/dnspython/pull/290 +Patch2: pycryptodome.patch BuildRequires: %{python_module devel} BuildRequires: %{python_module ecdsa} BuildRequires: %{python_module idna} -BuildRequires: %{python_module pycrypto} +BuildRequires: %{python_module pycryptodome} BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: netcfg BuildRequires: python-rpm-macros Recommends: python-ecdsa Recommends: python-idna -Recommends: python-pycrypto +Recommends: python-pycryptodome BuildArch: noarch %description @@ -65,6 +67,7 @@ allowed it to be opened under a BSD-style licence. chmod -x examples/* %patch0 %patch1 +%patch2 -p1 %build %python_build @@ -79,8 +82,8 @@ test -f tests/test_resolver.py && rm tests/test_resolver.py %python_exec setup.py test %files %{python_files} -%defattr(-,root,root) -%doc ChangeLog LICENSE README.md examples/ +%license LICENSE +%doc ChangeLog README.md examples/ %{python_sitelib}/dns/ %{python_sitelib}/dnspython-%{version}-py%{python_version}.egg-info