Browse Source

remote watchtower: enforce that SSL is used, on the client-side

bip39-recovery
SomberNight 5 years ago
parent
commit
662d0d92bd
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 10
      electrum/lnworker.py
  2. 15
      electrum/tests/test_util.py
  3. 14
      electrum/util.py

10
electrum/lnworker.py

@ -17,6 +17,7 @@ from functools import partial
from collections import defaultdict from collections import defaultdict
import concurrent import concurrent
from concurrent import futures from concurrent import futures
import urllib.parse
import dns.resolver import dns.resolver
import dns.exception import dns.exception
@ -36,7 +37,7 @@ from .bip32 import BIP32Node
from .util import bh2u, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions from .util import bh2u, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions
from .util import ignore_exceptions, make_aiohttp_session, SilentTaskGroup from .util import ignore_exceptions, make_aiohttp_session, SilentTaskGroup
from .util import timestamp_to_datetime, random_shuffled_copy from .util import timestamp_to_datetime, random_shuffled_copy
from .util import MyEncoder from .util import MyEncoder, is_private_netaddress
from .logging import Logger from .logging import Logger
from .lntransport import LNTransport, LNResponderTransport from .lntransport import LNTransport, LNResponderTransport
from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
@ -531,10 +532,17 @@ class LNWallet(LNWorker):
@log_exceptions @log_exceptions
async def sync_with_remote_watchtower(self): async def sync_with_remote_watchtower(self):
while True: while True:
# periodically poll if the user updated 'watchtower_url'
await asyncio.sleep(5) await asyncio.sleep(5)
watchtower_url = self.config.get('watchtower_url') watchtower_url = self.config.get('watchtower_url')
if not watchtower_url: if not watchtower_url:
continue continue
parsed_url = urllib.parse.urlparse(watchtower_url)
if not (parsed_url.scheme == 'https' or is_private_netaddress(parsed_url.hostname)):
self.logger.warning(f"got watchtower URL for remote tower but we won't use it! "
f"can only use HTTPS (except if private IP): not using {watchtower_url!r}")
continue
# try to sync with the remote watchtower
try: try:
async with make_aiohttp_session(proxy=self.network.proxy) as session: async with make_aiohttp_session(proxy=self.network.proxy) as session:
watchtower = JsonRPCClient(session, watchtower_url) watchtower = JsonRPCClient(session, watchtower_url)

15
electrum/tests/test_util.py

@ -2,7 +2,7 @@ from decimal import Decimal
from electrum.util import (format_satoshis, format_fee_satoshis, parse_URI, from electrum.util import (format_satoshis, format_fee_satoshis, parse_URI,
is_hash256_str, chunks, is_ip_address, list_enabled_bits, is_hash256_str, chunks, is_ip_address, list_enabled_bits,
format_satoshis_plain) format_satoshis_plain, is_private_netaddress)
from . import ElectrumTestCase from . import ElectrumTestCase
@ -148,3 +148,16 @@ class TestUtil(ElectrumTestCase):
self.assertFalse(is_ip_address("2001:db8:0:0:g:ff00:42:8329")) self.assertFalse(is_ip_address("2001:db8:0:0:g:ff00:42:8329"))
self.assertFalse(is_ip_address("lol")) self.assertFalse(is_ip_address("lol"))
self.assertFalse(is_ip_address(":@ASD:@AS\x77\x22\xff¬!")) self.assertFalse(is_ip_address(":@ASD:@AS\x77\x22\xff¬!"))
def test_is_private_netaddress(self):
self.assertTrue(is_private_netaddress("127.0.0.1"))
self.assertTrue(is_private_netaddress("127.5.6.7"))
self.assertTrue(is_private_netaddress("::1"))
self.assertTrue(is_private_netaddress("[::1]"))
self.assertTrue(is_private_netaddress("localhost"))
self.assertTrue(is_private_netaddress("localhost."))
self.assertFalse(is_private_netaddress("[::2]"))
self.assertFalse(is_private_netaddress("2a00:1450:400e:80d::200e"))
self.assertFalse(is_private_netaddress("[2a00:1450:400e:80d::200e]"))
self.assertFalse(is_private_netaddress("8.8.8.8"))
self.assertFalse(is_private_netaddress("example.com"))

14
electrum/util.py

@ -42,6 +42,7 @@ import time
from typing import NamedTuple, Optional from typing import NamedTuple, Optional
import ssl import ssl
import ipaddress import ipaddress
from ipaddress import IPv4Address, IPv6Address
import random import random
import attr import attr
@ -1238,6 +1239,19 @@ def is_ip_address(x: Union[str, bytes]) -> bool:
return False return False
def is_private_netaddress(host: str) -> bool:
if str(host) in ('localhost', 'localhost.',):
return True
if host[0] == '[' and host[-1] == ']': # IPv6
host = host[1:-1]
try:
ip_addr = ipaddress.ip_address(host) # type: Union[IPv4Address, IPv6Address]
return ip_addr.is_private
except ValueError:
pass # not an IP
return False
def list_enabled_bits(x: int) -> Sequence[int]: def list_enabled_bits(x: int) -> Sequence[int]:
"""e.g. 77 (0b1001101) --> (0, 2, 3, 6)""" """e.g. 77 (0b1001101) --> (0, 2, 3, 6)"""
binary = bin(x)[2:] binary = bin(x)[2:]

Loading…
Cancel
Save