File pycryptodome.patch of Package python-dnspython

From 10b8a42c90d037880fbfc81fb71adb27251252e3 Mon Sep 17 00:00:00 2001
From: Daniel Robbins <drobbins@funtoo.org>
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

Index: dnspython-1.15.0/dns/__init__.py
===================================================================
--- dnspython-1.15.0.orig/dns/__init__.py
+++ dnspython-1.15.0/dns/__init__.py
@@ -22,7 +22,6 @@ __all__ = [
     'entropy',
     'exception',
     'flags',
-    'hash',
     'inet',
     'ipv4',
     'ipv6',
Index: dnspython-1.15.0/dns/dnssec.py
===================================================================
--- dnspython-1.15.0.orig/dns/dnssec.py
+++ dnspython-1.15.0/dns/dnssec.py
@@ -20,7 +20,6 @@ import struct
 import time
 
 import dns.exception
-import dns.hash
 import dns.name
 import dns.node
 import dns.rdataset
@@ -28,7 +27,8 @@ 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):
 
@@ -39,34 +39,34 @@ class ValidationFailure(dns.exception.DN
 
     """The DNSSEC signature is invalid."""
 
-RSAMD5 = 1
-DH = 2
-DSA = 3
-ECC = 4
-RSASHA1 = 5
-DSANSEC3SHA1 = 6
-RSASHA1NSEC3SHA1 = 7
-RSASHA256 = 8
-RSASHA512 = 10
-ECDSAP256SHA256 = 13
-ECDSAP384SHA384 = 14
+ALGO_RSAMD5 = 1
+ALGO_DH = 2
+ALGO_DSA = 3
+ALGO_ECC = 4
+ALGO_RSASHA1 = 5
+ALGO_DSANSEC3SHA1 = 6
+ALGO_RSASHA1NSEC3SHA1 = 7
+ALGO_RSASHA256 = 8
+ALGO_RSASHA512 = 10
+ALGO_ECDSAP256SHA256 = 13
+ALGO_ECDSAP384SHA384 = 14
 INDIRECT = 252
 PRIVATEDNS = 253
 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,
 }
@@ -107,7 +107,7 @@ def _to_rdata(record, origin):
 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
@@ -123,10 +123,10 @@ def key_id(key, origin=None):
 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)
 
@@ -162,51 +162,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)
 
 
@@ -284,11 +284,13 @@ def _validate_rrsig(rrset, rrsig, keys,
                 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])
@@ -306,20 +308,19 @@ def _validate_rrsig(rrset, rrsig, keys,
                  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])
             assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
@@ -331,6 +332,7 @@ def _validate_rrsig(rrset, rrsig, keys,
             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)
 
@@ -352,24 +354,31 @@ def _validate_rrsig(rrset, rrsig, keys,
             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')
 
 
@@ -401,10 +410,8 @@ def _validate(rrset, rrsigset, keys, ori
         rrname = rrset.name
 
     if isinstance(rrsigset, tuple):
-        rrsigname = rrsigset[0]
         rrsigrdataset = rrsigset[1]
     else:
-        rrsigname = rrsigset.name
         rrsigrdataset = rrsigset
 
     rrname = rrname.choose_relativity(origin)
@@ -422,7 +429,7 @@ def _validate(rrset, rrsigset, keys, ori
 
 
 def _need_pycrypto(*args, **kwargs):
-    raise NotImplementedError("DNSSEC validation requires pycrypto")
+    raise NotImplementedError("DNSSEC validation requires pycryptodome")
 
 try:
     import Crypto.PublicKey.RSA
Index: dnspython-1.15.0/dns/hash.py
===================================================================
--- dnspython-1.15.0.orig/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()]
Index: dnspython-1.15.0/dns/tsig.py
===================================================================
--- dnspython-1.15.0.orig/dns/tsig.py
+++ dnspython-1.15.0/dns/tsig.py
@@ -19,9 +19,9 @@ import hmac
 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")
Index: dnspython-1.15.0/tests/test_dnssec.py
===================================================================
--- dnspython-1.15.0.orig/tests/test_dnssec.py
+++ dnspython-1.15.0/tests/test_dnssec.py
@@ -156,22 +156,22 @@ abs_other_ecdsa384_soa = dns.rrset.from_
 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 @@ class DNSSECValidatorTestCase(unittest.T
         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 @@ class DNSSECValidatorTestCase(unittest.T
         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,
openSUSE Build Service is sponsored by