pentoo-overlay/dev-python/dnspython/files/pull290.patch

533 lines
18 KiB
Diff

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 1/2] 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/ChangeLog b/ChangeLog
index e796638..43e5f4b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2017-12-21 Daniel Robbins <drobbins@funtoo.org>
+
+ * dns/dnssec.py: migrated code from pycrypto (apparently no
+ longer maintained) to pycryptodome. All tests passing.
+
2017-01-02 Bob Halley <halley@dnspython.org>
* dns/e164.py: to_e164() was returning binary instead of text,
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/doc/dnssec.rst b/doc/dnssec.rst
index c16a618..64f08b3 100644
--- a/doc/dnssec.rst
+++ b/doc/dnssec.rst
@@ -4,11 +4,10 @@
DNSSEC
======
-Dnspython can do simple DNSSEC signature validation, but
-currently has no facilities for signing. In order to
-use DNSSEC functions, you must have ``pycrypto`` installed.
-If you want to do elliptic curves, you must also have
-``ecdsa`` installed.
+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.
DNSSEC Algorithms
-----------------
diff --git a/doc/installation.rst b/doc/installation.rst
index d5c6634..7854f3d 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 ``pycrypto`` is installed, then dnspython will be able to do
-low-level DNSSEC RSA and DSA signature validation.
+If ``pycryptodome`` 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/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,
From c5534e037b0f3aa552feaab157ff3fb9496ff821 Mon Sep 17 00:00:00 2001
From: Daniel Robbins <drobbins@funtoo.org>
Date: Thu, 21 Dec 2017 12:19:07 -0700
Subject: [PATCH 2/2] update travis to use pycryptodome
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 5bd79bc..b1a5590 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,7 @@ branches:
except:
- python3
install:
- - pip install unittest2 pylint pycrypto ecdsa idna
+ - pip install unittest2 pylint pycryptodome ecdsa idna
script:
- if [[ ($TRAVIS_PYTHON_VERSION != '2.6') ]]; then make lint; fi
- make test