rdobuilder 41a366
From 087df702931f32eeb3a29957a8fe3fa31749d642 Mon Sep 17 00:00:00 2001
rdobuilder 41a366
From: Simo Sorce <simo@redhat.com>
rdobuilder 41a366
Date: Tue, 28 Apr 2020 10:08:02 +0200
rdobuilder 41a366
Subject: [PATCH] Switch to python cryptography
rdobuilder 41a366
rdobuilder 41a366
Original patch: https://github.com/simo5/dnspython/commit/bfe84d523bd4fde7b2655857d78bba85ed05f43c
rdobuilder 41a366
The same change in master: https://github.com/rthalley/dnspython/pull/449
rdobuilder 41a366
---
rdobuilder 41a366
 dns/dnssec.py        | 168 ++++++++++++++++++++-----------------------
rdobuilder 41a366
 setup.py             |   2 +-
rdobuilder 41a366
 tests/test_dnssec.py |  12 +---
rdobuilder 41a366
 3 files changed, 79 insertions(+), 103 deletions(-)
rdobuilder 41a366
rdobuilder 41a366
diff --git a/dns/dnssec.py b/dns/dnssec.py
rdobuilder 41a366
index 35da6b5..73e92da 100644
rdobuilder 41a366
--- a/dns/dnssec.py
rdobuilder 41a366
+++ b/dns/dnssec.py
rdobuilder 41a366
@@ -17,6 +17,7 @@
rdobuilder 41a366
rdobuilder 41a366
 """Common DNSSEC-related functions and constants."""
rdobuilder 41a366
rdobuilder 41a366
+import hashlib  # used in make_ds() to avoid pycrypto dependency
rdobuilder 41a366
 from io import BytesIO
rdobuilder 41a366
 import struct
rdobuilder 41a366
 import time
rdobuilder 41a366
@@ -165,10 +166,10 @@ def make_ds(name, key, algorithm, origin=None):
rdobuilder 41a366
rdobuilder 41a366
     if algorithm.upper() == 'SHA1':
rdobuilder 41a366
         dsalg = 1
rdobuilder 41a366
-        hash = SHA1.new()
rdobuilder 41a366
+        hash = hashlib.sha1()
rdobuilder 41a366
     elif algorithm.upper() == 'SHA256':
rdobuilder 41a366
         dsalg = 2
rdobuilder 41a366
-        hash = SHA256.new()
rdobuilder 41a366
+        hash = hashlib.sha256()
rdobuilder 41a366
     else:
rdobuilder 41a366
         raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
rdobuilder 41a366
rdobuilder 41a366
@@ -214,7 +215,7 @@ def _is_dsa(algorithm):
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
 def _is_ecdsa(algorithm):
rdobuilder 41a366
-    return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))
rdobuilder 41a366
+    return (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
 def _is_md5(algorithm):
rdobuilder 41a366
@@ -240,18 +241,26 @@ def _is_sha512(algorithm):
rdobuilder 41a366
rdobuilder 41a366
 def _make_hash(algorithm):
rdobuilder 41a366
     if _is_md5(algorithm):
rdobuilder 41a366
-        return MD5.new()
rdobuilder 41a366
+        return hashes.MD5()
rdobuilder 41a366
     if _is_sha1(algorithm):
rdobuilder 41a366
-        return SHA1.new()
rdobuilder 41a366
+        return hashes.SHA1()
rdobuilder 41a366
     if _is_sha256(algorithm):
rdobuilder 41a366
-        return SHA256.new()
rdobuilder 41a366
+        return hashes.SHA256()
rdobuilder 41a366
     if _is_sha384(algorithm):
rdobuilder 41a366
-        return SHA384.new()
rdobuilder 41a366
+        return hashes.SHA384()
rdobuilder 41a366
     if _is_sha512(algorithm):
rdobuilder 41a366
-        return SHA512.new()
rdobuilder 41a366
+        return hashes.SHA512()
rdobuilder 41a366
+    if algorithm == ED25519:
rdobuilder 41a366
+        return hashes.SHA512()
rdobuilder 41a366
+    if algorithm == ED448:
rdobuilder 41a366
+        return hashes.SHAKE256(114)
rdobuilder 41a366
     raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
+def _bytes_to_long(b):
rdobuilder 41a366
+    return int.from_bytes(b, 'big')
rdobuilder 41a366
+
rdobuilder 41a366
+
rdobuilder 41a366
 def _make_algorithm_id(algorithm):
rdobuilder 41a366
     if _is_md5(algorithm):
rdobuilder 41a366
         oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
rdobuilder 41a366
@@ -316,8 +325,6 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rdobuilder 41a366
         if rrsig.inception > now:
rdobuilder 41a366
             raise ValidationFailure('not yet valid')
rdobuilder 41a366
rdobuilder 41a366
-        hash = _make_hash(rrsig.algorithm)
rdobuilder 41a366
-
rdobuilder 41a366
         if _is_rsa(rrsig.algorithm):
rdobuilder 41a366
             keyptr = candidate_key.key
rdobuilder 41a366
             (bytes_,) = struct.unpack('!B', keyptr[0:1])
rdobuilder 41a366
@@ -328,9 +335,9 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rdobuilder 41a366
             rsa_e = keyptr[0:bytes_]
rdobuilder 41a366
             rsa_n = keyptr[bytes_:]
rdobuilder 41a366
             try:
rdobuilder 41a366
-                pubkey = CryptoRSA.construct(
rdobuilder 41a366
-                    (number.bytes_to_long(rsa_n),
rdobuilder 41a366
-                     number.bytes_to_long(rsa_e)))
rdobuilder 41a366
+                public_key = rsa.RSAPublicNumbers(
rdobuilder 41a366
+                    _bytes_to_long(rsa_e),
rdobuilder 41a366
+                    _bytes_to_long(rsa_n)).public_key(default_backend())
rdobuilder 41a366
             except ValueError:
rdobuilder 41a366
                 raise ValidationFailure('invalid public key')
rdobuilder 41a366
             sig = rrsig.signature
rdobuilder 41a366
@@ -346,42 +353,47 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rdobuilder 41a366
             dsa_g = keyptr[0:octets]
rdobuilder 41a366
             keyptr = keyptr[octets:]
rdobuilder 41a366
             dsa_y = keyptr[0:octets]
rdobuilder 41a366
-            pubkey = CryptoDSA.construct(
rdobuilder 41a366
-                (number.bytes_to_long(dsa_y),
rdobuilder 41a366
-                 number.bytes_to_long(dsa_g),
rdobuilder 41a366
-                 number.bytes_to_long(dsa_p),
rdobuilder 41a366
-                 number.bytes_to_long(dsa_q)))
rdobuilder 41a366
-            sig = rrsig.signature[1:]
rdobuilder 41a366
+            try:
rdobuilder 41a366
+                public_key = dsa.DSAPublicNumbers(
rdobuilder 41a366
+                    _bytes_to_long(dsa_y),
rdobuilder 41a366
+                    dsa.DSAParameterNumbers(
rdobuilder 41a366
+                        _bytes_to_long(dsa_p),
rdobuilder 41a366
+                        _bytes_to_long(dsa_q),
rdobuilder 41a366
+                        _bytes_to_long(dsa_g))).public_key(default_backend())
rdobuilder 41a366
+            except ValueError:
rdobuilder 41a366
+                raise ValidationFailure('invalid public key')
rdobuilder 41a366
+            sig_r = rrsig.signature[1:21]
rdobuilder 41a366
+            sig_s = rrsig.signature[21:]
rdobuilder 41a366
+            sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
rdobuilder 41a366
+                                             _bytes_to_long(sig_s))
rdobuilder 41a366
         elif _is_ecdsa(rrsig.algorithm):
rdobuilder 41a366
-            # use ecdsa for NIST-384p -- not currently supported by pycryptodome
rdobuilder 41a366
-
rdobuilder 41a366
             keyptr = candidate_key.key
rdobuilder 41a366
-
rdobuilder 41a366
             if rrsig.algorithm == ECDSAP256SHA256:
rdobuilder 41a366
-                curve = ecdsa.curves.NIST256p
rdobuilder 41a366
-                key_len = 32
rdobuilder 41a366
+                curve = ec.SECP256R1()
rdobuilder 41a366
+                octets = 32
rdobuilder 41a366
             elif rrsig.algorithm == ECDSAP384SHA384:
rdobuilder 41a366
-                curve = ecdsa.curves.NIST384p
rdobuilder 41a366
-                key_len = 48
rdobuilder 41a366
-
rdobuilder 41a366
-            x = number.bytes_to_long(keyptr[0:key_len])
rdobuilder 41a366
-            y = number.bytes_to_long(keyptr[key_len:key_len * 2])
rdobuilder 41a366
-            if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):
rdobuilder 41a366
-                raise ValidationFailure('invalid ECDSA key')
rdobuilder 41a366
-            point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
rdobuilder 41a366
-            verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
rdobuilder 41a366
-                                                                      curve)
rdobuilder 41a366
-            pubkey = ECKeyWrapper(verifying_key, key_len)
rdobuilder 41a366
-            r = rrsig.signature[:key_len]
rdobuilder 41a366
-            s = rrsig.signature[key_len:]
rdobuilder 41a366
-            sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r),
rdobuilder 41a366
-                                        number.bytes_to_long(s))
rdobuilder 41a366
+                curve = ec.SECP384R1()
rdobuilder 41a366
+                octets = 48
rdobuilder 41a366
+            ecdsa_x = keyptr[0:octets]
rdobuilder 41a366
+            ecdsa_y = keyptr[octets:octets * 2]
rdobuilder 41a366
+            try:
rdobuilder 41a366
+                public_key = ec.EllipticCurvePublicNumbers(
rdobuilder 41a366
+                    curve=curve,
rdobuilder 41a366
+                    x=_bytes_to_long(ecdsa_x),
rdobuilder 41a366
+                    y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
rdobuilder 41a366
+            except ValueError:
rdobuilder 41a366
+                raise ValidationFailure('invalid public key')
rdobuilder 41a366
+            sig_r = rrsig.signature[0:octets]
rdobuilder 41a366
+            sig_s = rrsig.signature[octets:]
rdobuilder 41a366
+            sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
rdobuilder 41a366
+                                             _bytes_to_long(sig_s))
rdobuilder 41a366
rdobuilder 41a366
         else:
rdobuilder 41a366
             raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
rdobuilder 41a366
rdobuilder 41a366
-        hash.update(_to_rdata(rrsig, origin)[:18])
rdobuilder 41a366
-        hash.update(rrsig.signer.to_digestable(origin))
rdobuilder 41a366
+        data = b''
rdobuilder 41a366
+        data += _to_rdata(rrsig, origin)[:18]
rdobuilder 41a366
+        data += rrsig.signer.to_digestable(origin)
rdobuilder 41a366
rdobuilder 41a366
         if rrsig.labels < len(rrname) - 1:
rdobuilder 41a366
             suffix = rrname.split(rrsig.labels + 1)[1]
rdobuilder 41a366
@@ -391,25 +403,21 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rdobuilder 41a366
                               rrsig.original_ttl)
rdobuilder 41a366
         rrlist = sorted(rdataset)
rdobuilder 41a366
         for rr in rrlist:
rdobuilder 41a366
-            hash.update(rrnamebuf)
rdobuilder 41a366
-            hash.update(rrfixed)
rdobuilder 41a366
+            data += rrnamebuf
rdobuilder 41a366
+            data += rrfixed
rdobuilder 41a366
             rrdata = rr.to_digestable(origin)
rdobuilder 41a366
             rrlen = struct.pack('!H', len(rrdata))
rdobuilder 41a366
-            hash.update(rrlen)
rdobuilder 41a366
-            hash.update(rrdata)
rdobuilder 41a366
+            data += rrlen
rdobuilder 41a366
+            data += rrdata
rdobuilder 41a366
rdobuilder 41a366
+        chosen_hash = _make_hash(rrsig.algorithm)
rdobuilder 41a366
         try:
rdobuilder 41a366
             if _is_rsa(rrsig.algorithm):
rdobuilder 41a366
-                verifier = pkcs1_15.new(pubkey)
rdobuilder 41a366
-                # will raise ValueError if verify fails:
rdobuilder 41a366
-                verifier.verify(hash, sig)
rdobuilder 41a366
+                public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
rdobuilder 41a366
             elif _is_dsa(rrsig.algorithm):
rdobuilder 41a366
-                verifier = DSS.new(pubkey, 'fips-186-3')
rdobuilder 41a366
-                verifier.verify(hash, sig)
rdobuilder 41a366
+                public_key.verify(sig, data, chosen_hash)
rdobuilder 41a366
             elif _is_ecdsa(rrsig.algorithm):
rdobuilder 41a366
-                digest = hash.digest()
rdobuilder 41a366
-                if not pubkey.verify(digest, sig):
rdobuilder 41a366
-                    raise ValueError
rdobuilder 41a366
+                public_key.verify(sig, data, ec.ECDSA(chosen_hash))
rdobuilder 41a366
             else:
rdobuilder 41a366
                 # Raise here for code clarity; this won't actually ever happen
rdobuilder 41a366
                 # since if the algorithm is really unknown we'd already have
rdobuilder 41a366
@@ -417,7 +425,7 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rdobuilder 41a366
                 raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
rdobuilder 41a366
             # If we got here, we successfully verified so we can return without error
rdobuilder 41a366
             return
rdobuilder 41a366
-        except ValueError:
rdobuilder 41a366
+        except InvalidSignature:
rdobuilder 41a366
             # this happens on an individual validation failure
rdobuilder 41a366
             continue
rdobuilder 41a366
     # nothing verified -- raise failure:
rdobuilder 41a366
@@ -472,48 +480,24 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None):
rdobuilder 41a366
     raise ValidationFailure("no RRSIGs validated")
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
-def _need_pycrypto(*args, **kwargs):
rdobuilder 41a366
-    raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex")
rdobuilder 41a366
+def _need_pyca(*args, **kwargs):
rdobuilder 41a366
+    raise NotImplementedError("DNSSEC validation requires python cryptography")
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
 try:
rdobuilder 41a366
-    try:
rdobuilder 41a366
-        # test we're using pycryptodome, not pycrypto (which misses SHA1 for example)
rdobuilder 41a366
-        from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512
rdobuilder 41a366
-        from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
rdobuilder 41a366
-        from Crypto.Signature import pkcs1_15, DSS
rdobuilder 41a366
-        from Crypto.Util import number
rdobuilder 41a366
-    except ImportError:
rdobuilder 41a366
-        from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512
rdobuilder 41a366
-        from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
rdobuilder 41a366
-        from Cryptodome.Signature import pkcs1_15, DSS
rdobuilder 41a366
-        from Cryptodome.Util import number
rdobuilder 41a366
+    from cryptography.exceptions import InvalidSignature
rdobuilder 41a366
+    from cryptography.hazmat.backends import default_backend
rdobuilder 41a366
+    from cryptography.hazmat.primitives import hashes
rdobuilder 41a366
+    from cryptography.hazmat.primitives.asymmetric import padding
rdobuilder 41a366
+    from cryptography.hazmat.primitives.asymmetric import utils
rdobuilder 41a366
+    from cryptography.hazmat.primitives.asymmetric import dsa
rdobuilder 41a366
+    from cryptography.hazmat.primitives.asymmetric import ec
rdobuilder 41a366
+    from cryptography.hazmat.primitives.asymmetric import rsa
rdobuilder 41a366
 except ImportError:
rdobuilder 41a366
-    validate = _need_pycrypto
rdobuilder 41a366
-    validate_rrsig = _need_pycrypto
rdobuilder 41a366
-    _have_pycrypto = False
rdobuilder 41a366
-    _have_ecdsa = False
rdobuilder 41a366
+    validate = _need_pyca
rdobuilder 41a366
+    validate_rrsig = _need_pyca
rdobuilder 41a366
+    _have_pyca = False
rdobuilder 41a366
 else:
rdobuilder 41a366
     validate = _validate
rdobuilder 41a366
     validate_rrsig = _validate_rrsig
rdobuilder 41a366
-    _have_pycrypto = True
rdobuilder 41a366
-
rdobuilder 41a366
-    try:
rdobuilder 41a366
-        import ecdsa
rdobuilder 41a366
-        import ecdsa.ecdsa
rdobuilder 41a366
-        import ecdsa.ellipticcurve
rdobuilder 41a366
-        import ecdsa.keys
rdobuilder 41a366
-    except ImportError:
rdobuilder 41a366
-        _have_ecdsa = False
rdobuilder 41a366
-    else:
rdobuilder 41a366
-        _have_ecdsa = True
rdobuilder 41a366
-
rdobuilder 41a366
-        class ECKeyWrapper(object):
rdobuilder 41a366
-
rdobuilder 41a366
-            def __init__(self, key, key_len):
rdobuilder 41a366
-                self.key = key
rdobuilder 41a366
-                self.key_len = key_len
rdobuilder 41a366
-
rdobuilder 41a366
-            def verify(self, digest, sig):
rdobuilder 41a366
-                diglong = number.bytes_to_long(digest)
rdobuilder 41a366
-                return self.key.pubkey.verifies(diglong, sig)
rdobuilder 41a366
+    _have_pyca = True
rdobuilder 41a366
diff --git a/setup.py b/setup.py
rdobuilder 41a366
index 743d43c..2ee38a7 100755
rdobuilder 41a366
--- a/setup.py
rdobuilder 41a366
+++ b/setup.py
rdobuilder 41a366
@@ -75,7 +75,7 @@ direct manipulation of DNS zones, messages, names, and records.""",
rdobuilder 41a366
     'provides': ['dns'],
rdobuilder 41a366
     'extras_require': {
rdobuilder 41a366
         'IDNA': ['idna>=2.1'],
rdobuilder 41a366
-        'DNSSEC': ['pycryptodome', 'ecdsa>=0.13'],
rdobuilder 41a366
+        'DNSSEC': ['cryptography>=2.3'],
rdobuilder 41a366
         },
rdobuilder 41a366
     'ext_modules': ext_modules if compile_cython else None,
rdobuilder 41a366
     'zip_safe': False if compile_cython else None,
rdobuilder 41a366
diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py
rdobuilder 41a366
index c87862a..20b52b2 100644
rdobuilder 41a366
--- a/tests/test_dnssec.py
rdobuilder 41a366
+++ b/tests/test_dnssec.py
rdobuilder 41a366
@@ -151,8 +151,8 @@ abs_ecdsa384_soa_rrsig = dns.rrset.from_text('example.', 86400, 'IN', 'RRSIG',
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
rdobuilder 41a366
-@unittest.skipUnless(dns.dnssec._have_pycrypto,
rdobuilder 41a366
-                     "Pycryptodome cannot be imported")
rdobuilder 41a366
+@unittest.skipUnless(dns.dnssec._have_pyca,
rdobuilder 41a366
+                     "Python Cryptography cannot be imported")
rdobuilder 41a366
 class DNSSECValidatorTestCase(unittest.TestCase):
rdobuilder 41a366
rdobuilder 41a366
     def testAbsoluteRSAGood(self): # type: () -> None
rdobuilder 41a366
@@ -199,28 +199,20 @@ class DNSSECValidatorTestCase(unittest.TestCase):
rdobuilder 41a366
         ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA256')
rdobuilder 41a366
         self.failUnless(ds == example_ds_sha256)
rdobuilder 41a366
rdobuilder 41a366
-    @unittest.skipUnless(dns.dnssec._have_ecdsa,
rdobuilder 41a366
-                         "python ECDSA cannot be imported")
rdobuilder 41a366
     def testAbsoluteECDSA256Good(self): # type: () -> None
rdobuilder 41a366
         dns.dnssec.validate(abs_ecdsa256_soa, abs_ecdsa256_soa_rrsig,
rdobuilder 41a366
                             abs_ecdsa256_keys, None, when3)
rdobuilder 41a366
rdobuilder 41a366
-    @unittest.skipUnless(dns.dnssec._have_ecdsa,
rdobuilder 41a366
-                         "python ECDSA cannot be imported")
rdobuilder 41a366
     def testAbsoluteECDSA256Bad(self): # type: () -> None
rdobuilder 41a366
         def bad(): # type: () -> None
rdobuilder 41a366
             dns.dnssec.validate(abs_other_ecdsa256_soa, abs_ecdsa256_soa_rrsig,
rdobuilder 41a366
                                 abs_ecdsa256_keys, None, when3)
rdobuilder 41a366
         self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
rdobuilder 41a366
rdobuilder 41a366
-    @unittest.skipUnless(dns.dnssec._have_ecdsa,
rdobuilder 41a366
-                         "python ECDSA cannot be imported")
rdobuilder 41a366
     def testAbsoluteECDSA384Good(self): # type: () -> None
rdobuilder 41a366
         dns.dnssec.validate(abs_ecdsa384_soa, abs_ecdsa384_soa_rrsig,
rdobuilder 41a366
                             abs_ecdsa384_keys, None, when4)
rdobuilder 41a366
rdobuilder 41a366
-    @unittest.skipUnless(dns.dnssec._have_ecdsa,
rdobuilder 41a366
-                         "python ECDSA cannot be imported")
rdobuilder 41a366
     def testAbsoluteECDSA384Bad(self): # type: () -> None
rdobuilder 41a366
         def bad(): # type: () -> None
rdobuilder 41a366
             dns.dnssec.validate(abs_other_ecdsa384_soa, abs_ecdsa384_soa_rrsig,
rdobuilder 41a366
--
rdobuilder 41a366
2.26.2
rdobuilder 41a366