diff --git a/README.rst b/README.rst index 9540f2612..7c3351451 100644 --- a/README.rst +++ b/README.rst @@ -55,9 +55,8 @@ libsecp256k1 yourself:: sudo apt-get install automake libtool ./contrib/make_libsecp256k1.sh -Due to the need for fast symmetric ciphers, either one of `pycryptodomex`_ -or `cryptography`_ is required. Install from your package manager -(or from pip):: +Due to the need for fast symmetric ciphers, `cryptography`_ is required. +Install from your package manager (or from pip):: sudo apt-get install python3-cryptography diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt index e6f64e585..bce25465a 100644 --- a/contrib/requirements/requirements-binaries.txt +++ b/contrib/requirements/requirements-binaries.txt @@ -1,2 +1,5 @@ PyQt5<5.15 -cryptography>=2.1 + +# we need at least cryptography>=2.1 for electrum.crypto, +# and at least cryptography>=2.6 for dnspython[DNSSEC] +cryptography>=2.6 diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 312d955a1..4096ac920 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -1,7 +1,6 @@ ecdsa>=0.14 qrcode protobuf>=3.12 -dnspython<2.0 qdarkstyle<2.9 aiorpcx>=0.18,<0.19 aiohttp>=3.3.0,<4.0.0 @@ -9,3 +8,7 @@ aiohttp_socks>=0.3 certifi bitstring attrs>=19.2.0 + +# Note that we also need the dnspython[DNSSEC] extra which pulls in cryptography, +# but as that is not pure-python it cannot be listed in this file! +dnspython>=2.0 diff --git a/electrum/dnssec.py b/electrum/dnssec.py index 425c9bb7f..331cac74d 100644 --- a/electrum/dnssec.py +++ b/electrum/dnssec.py @@ -58,135 +58,6 @@ import dns.rdtypes.ANY.TXT import dns.rdtypes.IN.A import dns.rdtypes.IN.AAAA - -# Pure-Python version of dns.dnssec._validate_rsig -import ecdsa -from . import rsakey - - -def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): - from dns.dnssec import ValidationFailure, ECDSAP256SHA256, ECDSAP384SHA384 - from dns.dnssec import _find_candidate_keys, _make_hash, _is_ecdsa, _is_rsa, _to_rdata, _make_algorithm_id - - if isinstance(origin, str): - origin = dns.name.from_text(origin, dns.name.root) - - for candidate_key in _find_candidate_keys(keys, rrsig): - if not candidate_key: - raise ValidationFailure('unknown key') - - # For convenience, allow the rrset to be specified as a (name, rdataset) - # tuple as well as a proper rrset - if isinstance(rrset, tuple): - rrname = rrset[0] - rdataset = rrset[1] - else: - rrname = rrset.name - rdataset = rrset - - if now is None: - now = time.time() - if rrsig.expiration < now: - raise ValidationFailure('expired') - if rrsig.inception > now: - raise ValidationFailure('not yet valid') - - hash = _make_hash(rrsig.algorithm) - - if _is_rsa(rrsig.algorithm): - keyptr = candidate_key.key - (bytes,) = struct.unpack('!B', keyptr[0:1]) - keyptr = keyptr[1:] - if bytes == 0: - (bytes,) = struct.unpack('!H', keyptr[0:2]) - keyptr = keyptr[2:] - rsa_e = keyptr[0:bytes] - rsa_n = keyptr[bytes:] - n = int.from_bytes(rsa_n, byteorder='big', signed=False) - e = int.from_bytes(rsa_e, byteorder='big', signed=False) - pubkey = rsakey.RSAKey(n, e) - sig = rrsig.signature - - elif _is_ecdsa(rrsig.algorithm): - if rrsig.algorithm == ECDSAP256SHA256: - curve = ecdsa.curves.NIST256p - key_len = 32 - elif rrsig.algorithm == ECDSAP384SHA384: - curve = ecdsa.curves.NIST384p - key_len = 48 - else: - # shouldn't happen - raise ValidationFailure('unknown ECDSA curve') - keyptr = candidate_key.key - x = int.from_bytes(keyptr[0:key_len], byteorder='big', signed=False) - y = int.from_bytes(keyptr[key_len:key_len * 2], byteorder='big', signed=False) - assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y) - point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order) - verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, curve) - r = rrsig.signature[:key_len] - s = rrsig.signature[key_len:] - sig = ecdsa.ecdsa.Signature(int.from_bytes(r, byteorder='big', signed=False), - int.from_bytes(s, byteorder='big', signed=False)) - - else: - raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) - - hash.update(_to_rdata(rrsig, origin)[:18]) - hash.update(rrsig.signer.to_digestable(origin)) - - if rrsig.labels < len(rrname) - 1: - suffix = rrname.split(rrsig.labels + 1)[1] - rrname = dns.name.from_text('*', suffix) - rrnamebuf = rrname.to_digestable(origin) - rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, - rrsig.original_ttl) - rrlist = sorted(rdataset) - for rr in rrlist: - hash.update(rrnamebuf) - hash.update(rrfixed) - rrdata = rr.to_digestable(origin) - rrlen = struct.pack('!H', len(rrdata)) - hash.update(rrlen) - hash.update(rrdata) - - digest = hash.digest() - - if _is_rsa(rrsig.algorithm): - digest = _make_algorithm_id(rrsig.algorithm) + digest - if pubkey.verify(bytearray(sig), bytearray(digest)): - return - - elif _is_ecdsa(rrsig.algorithm): - diglong = int.from_bytes(digest, byteorder='big', signed=False) - if verifying_key.pubkey.verifies(diglong, sig): - return - - else: - raise ValidationFailure('unknown algorithm %s' % rrsig.algorithm) - - raise ValidationFailure('verify failure') - - -class PyCryptodomexHashAlike: - def __init__(self, hashlib_func): - self._hash = hashlib_func - def new(self): - return self._hash() - - -# replace validate_rrsig -dns.dnssec._validate_rrsig = python_validate_rrsig -dns.dnssec.validate_rrsig = python_validate_rrsig -dns.dnssec.validate = dns.dnssec._validate -dns.dnssec._have_ecdsa = True -dns.dnssec.MD5 = PyCryptodomexHashAlike(hashlib.md5) -dns.dnssec.SHA1 = PyCryptodomexHashAlike(hashlib.sha1) -dns.dnssec.SHA256 = PyCryptodomexHashAlike(hashlib.sha256) -dns.dnssec.SHA384 = PyCryptodomexHashAlike(hashlib.sha384) -dns.dnssec.SHA512 = PyCryptodomexHashAlike(hashlib.sha512) - - - from .logging import get_logger diff --git a/electrum/tests/test_dnssec.py b/electrum/tests/test_dnssec.py deleted file mode 100644 index f45e80f41..000000000 --- a/electrum/tests/test_dnssec.py +++ /dev/null @@ -1,39 +0,0 @@ -import dns - -from electrum import dnssec - -from . import ElectrumTestCase - - -class TestDnsSec(ElectrumTestCase): - - def test_python_validate_rrsig_ecdsa(self): - rrset = dns.rrset.from_text("getmonero.org.", 3599, 1, 48, - "257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d xCjjnopKl+GqJxpVXckHAeF+KkxLbxIL fDLUT0rAK9iUzy1L53eKGQ==", - "256 3 13 koPbw9wmYZ7ggcjnQ6ayHyhHaDNMYELK TqT+qRGrZpWSccr/lBcrm10Z1PuQHB3A zhii+sb0PYFkH1ruxLhe5g==") - rrsig = dns.rdtypes.ANY.RRSIG.RRSIG.from_text(1, 46, - dns.tokenizer.Tokenizer("DNSKEY 13 2 3600 20180612115508 20180413115508 2371 getmonero.org. SSjtP2jCtXPukps7E3kum709xq2TH6Lt Ur32UhE7WKwSUfLTZ4EAoD5g22mi1fpB GDGb30kCMndDVjnHAEBDWw==")) - keys = {dns.name.Name([b'getmonero', b'org', b'']): rrset} - origin = None - now = 1527185178.7842247 - - # 'None' means it is valid - self.assertEqual(None, dnssec.python_validate_rrsig(rrset, rrsig, keys, origin, now)) - - def test_python_validate_rrsig_rsa(self): - rrset = dns.rrset.from_text("getmonero.org.", 12698, 1, 43, - "2371 13 2 3b7f818a879ecb9931dae983d4529afedeb53993759d8080735083f954d40bc8") - rrsig = dns.rdtypes.ANY.RRSIG.RRSIG.from_text(1, 46, - dns.tokenizer.Tokenizer("DS 7 2 86400 20180609010045 20180519000045 1862 org. SgdGsY4BAm7c3qpwzVLy3ua4orvrsJQO 0rUQDDrrXR6lElnbF+AS0gEEfdZfDv11 65AuNil/+kT2Qh/ExgstvhWQ88XdDnHB ouvRMf9pg3p/q5Otet/StRzf33SMPgC1 zLzkfkSBCjJkwVmwde8saGnjdcW522ra Ge/6JcsryRw=")) - - rrset2 = dns.rrset.from_text("org.", 866, 1, 48, - "256 3 7 AwEAAXxsMmN/JgpEE9Y4uFNRJm7Q9GBw mEYUCsCxuKlgBU9WrQEFRrvAeMamUBeX 4SE8s3V/TEk/TgGmPPp0pMkKD7mseluK 6Ard2HZ6O3nPAzL4i8py/UDRUmYNSCxw fdfjUWRmcB9H+NKWMsJoDhAkLFqg5HS7 f0j4Vb99Wac24Fk7", - "256 3 7 AwEAAcLdAPt3vn/ND00zZlyTx7OBko+9 YeCrSl2eGuEXjef0Lqf0tKGikoHwnmTH tT8J/aGqkZImLMVByJbknE0wKDnbvbKD oTQxPwUQZLH6k3sTdsPKESKDSBSc6VFM q35gx6CeuRYZ9KkGWiUsKqJhXPo6tyJF CBxfaNQQyrzBnv4/", - "257 3 7 AwEAAZTjbIO5kIpxWUtyXc8avsKyHIIZ +LjC2Dv8naO+Tz6X2fqzDC1bdq7HlZwt kaqTkMVVJ+8gE9FIreGJ4c8G1GdbjQgb P1OyYIG7OHTc4hv5T2NlyWr6k6QFz98Q 4zwFIGTFVvwBhmrMDYsOTtXakK6QwHov A1+83BsUACxlidpwB0hQacbD6x+I2RCD zYuTzj64Jv0/9XsX6AYV3ebcgn4hL1jI R2eJYyXlrAoWxdzxcW//5yeL5RVWuhRx ejmnSVnCuxkfS4AQ485KH2tpdbWcCopL JZs6tw8q3jWcpTGzdh/v3xdYfNpQNcPI mFlxAun3BtORPA2r8ti6MNoJEHU=", - "257 3 7 AwEAAcMnWBKLuvG/LwnPVykcmpvnntwx fshHlHRhlY0F3oz8AMcuF8gw9McCw+Bo C2YxWaiTpNPuxjSNhUlBtcJmcdkz3/r7 PIn0oDf14ept1Y9pdPh8SbIBIWx50ZPf VRlj8oQXv2Y6yKiQik7bi3MT37zMRU2k w2oy3cgrsGAzGN4s/C6SFYon5N1Q2O4h GDbeOq538kATOy0GFELjuauV9guX/431 msYu4Rgb5lLuQ3Mx5FSIxXpI/RaAn2mh M4nEZ/5IeRPKZVGydcuLBS8GZlxW4qbb 8MgRZ8bwMg0pqWRHmhirGmJIt3UuzvN1 pSFBfX7ysI9PPhSnwXCNDXk0kk0=") - keys = {dns.name.Name([b'org', b'']): rrset2} - origin = None - now = 1527191953.6527798 - - # 'None' means it is valid - self.assertEqual(None, dnssec.python_validate_rrsig(rrset, rrsig, keys, origin, now)) diff --git a/setup.py b/setup.py index 68fac9e69..fc5d9c71a 100755 --- a/setup.py +++ b/setup.py @@ -53,8 +53,8 @@ if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']: extras_require = { 'hardware': requirements_hw, 'gui': ['pyqt5'], - 'crypto': ['cryptography>=2.1'], - 'tests': ['pycryptodomex>=3.7', 'cryptography>=2.1', 'pyaes>=0.1a1'], + 'crypto': ['cryptography>=2.6'], + 'tests': ['pycryptodomex>=3.7', 'cryptography>=2.6', 'pyaes>=0.1a1'], } # 'full' extra that tries to grab everything an enduser would need (except for libsecp256k1...) extras_require['full'] = [pkg for sublist in