openalias: always enforce DNSSEC validation succeeds
This commit is contained in:
@@ -870,12 +870,10 @@ class Commands(Logger):
|
||||
if x is None:
|
||||
return None
|
||||
out = await wallet.contacts.resolve(x)
|
||||
if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
|
||||
raise UserFacingException(f"cannot verify alias: {x}")
|
||||
return out['address']
|
||||
|
||||
@command('n')
|
||||
async def sweep(self, privkey, destination, fee=None, feerate=None, nocheck=False, imax=100):
|
||||
async def sweep(self, privkey, destination, fee=None, feerate=None, imax=100):
|
||||
"""
|
||||
Sweep private keys. Returns a transaction that spends UTXOs from
|
||||
privkey to a destination address. The transaction will not be broadcast.
|
||||
@@ -885,12 +883,10 @@ class Commands(Logger):
|
||||
arg:decimal:fee:Transaction fee (absolute, in BTC)
|
||||
arg:decimal:feerate:Transaction fee rate (in sat/vbyte)
|
||||
arg:int:imax:Maximum number of inputs
|
||||
arg:bool:nocheck:Do not verify aliases
|
||||
"""
|
||||
from .wallet import sweep
|
||||
fee_policy = self._get_fee_policy(fee, feerate)
|
||||
privkeys = privkey.split()
|
||||
self.nocheck = nocheck
|
||||
#dest = self._resolver(destination)
|
||||
tx = await sweep(
|
||||
privkeys,
|
||||
@@ -942,7 +938,7 @@ class Commands(Logger):
|
||||
|
||||
@command('wp')
|
||||
async def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None,
|
||||
nocheck=False, unsigned=False, rbf=True, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
|
||||
unsigned=False, rbf=True, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
|
||||
"""Create an on-chain transaction.
|
||||
|
||||
arg:str:destination:Bitcoin address, contact or alias
|
||||
@@ -955,7 +951,6 @@ class Commands(Logger):
|
||||
arg:bool:addtransaction:Whether transaction is to be used for broadcasting afterwards. Adds transaction to the wallet
|
||||
arg:int:locktime:Set locktime block number
|
||||
arg:bool:unsigned:Do not sign transaction
|
||||
arg:bool:nocheck:Do not verify aliases
|
||||
arg:json:from_coins:Source coins (must be in wallet; use sweep to spend from non-wallet address)
|
||||
"""
|
||||
return await self.paytomany(
|
||||
@@ -965,7 +960,6 @@ class Commands(Logger):
|
||||
from_addr=from_addr,
|
||||
from_coins=from_coins,
|
||||
change_addr=change_addr,
|
||||
nocheck=nocheck,
|
||||
unsigned=unsigned,
|
||||
rbf=rbf,
|
||||
password=password,
|
||||
@@ -976,7 +970,7 @@ class Commands(Logger):
|
||||
|
||||
@command('wp')
|
||||
async def paytomany(self, outputs, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None,
|
||||
nocheck=False, unsigned=False, rbf=True, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
|
||||
unsigned=False, rbf=True, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
|
||||
"""Create a multi-output transaction.
|
||||
|
||||
arg:json:outputs:json list of ["address", "amount in BTC"]
|
||||
@@ -988,10 +982,8 @@ class Commands(Logger):
|
||||
arg:bool:addtransaction:Whether transaction is to be used for broadcasting afterwards. Adds transaction to the wallet
|
||||
arg:int:locktime:Set locktime block number
|
||||
arg:bool:unsigned:Do not sign transaction
|
||||
arg:bool:nocheck:Do not verify aliases
|
||||
arg:json:from_coins:Source coins (must be in wallet; use sweep to spend from non-wallet address)
|
||||
"""
|
||||
self.nocheck = nocheck
|
||||
fee_policy = self._get_fee_policy(fee, feerate)
|
||||
domain_addr = from_addr.split(',') if from_addr else None
|
||||
domain_coins = from_coins.split(',') if from_coins else None
|
||||
@@ -1150,7 +1142,11 @@ class Commands(Logger):
|
||||
|
||||
arg:str:key:the alias to be retrieved
|
||||
"""
|
||||
return await wallet.contacts.resolve(key)
|
||||
d = await wallet.contacts.resolve(key)
|
||||
if d.get("type") == "openalias":
|
||||
# we always validate DNSSEC now
|
||||
d["validated"] = True
|
||||
return d
|
||||
|
||||
@command('w')
|
||||
async def searchcontacts(self, query, wallet: Abstract_Wallet = None):
|
||||
|
||||
@@ -107,12 +107,11 @@ class Contacts(dict, Logger):
|
||||
async def resolve_openalias(cls, url: str) -> Dict[str, Any]:
|
||||
out = await cls._resolve_openalias(url)
|
||||
if out:
|
||||
address, name, validated = out
|
||||
address, name = out
|
||||
return {
|
||||
'address': address,
|
||||
'name': name,
|
||||
'type': 'openalias',
|
||||
'validated': validated
|
||||
}
|
||||
return {}
|
||||
|
||||
@@ -138,7 +137,7 @@ class Contacts(dict, Logger):
|
||||
asyncio.run_coroutine_threadsafe(f(), get_asyncio_loop())
|
||||
|
||||
@classmethod
|
||||
async def _resolve_openalias(cls, url: str) -> Optional[Tuple[str, str, bool]]:
|
||||
async def _resolve_openalias(cls, url: str) -> Optional[Tuple[str, str]]:
|
||||
# support email-style addresses, per the OA standard
|
||||
url = url.replace('@', '.')
|
||||
try:
|
||||
@@ -146,6 +145,9 @@ class Contacts(dict, Logger):
|
||||
except DNSException as e:
|
||||
_logger.info(f'Error resolving openalias: {repr(e)}')
|
||||
return None
|
||||
if not validated: # enforce DNSSEC validation. without it, DNS is completely insecure
|
||||
_logger.info(f"DNSSEC validation failed for {url=!r}, or maybe dependencies are missing and could not even try.")
|
||||
return None
|
||||
prefix = 'btc'
|
||||
for record in records:
|
||||
if record.rdtype != dns.rdatatype.TXT:
|
||||
@@ -158,7 +160,7 @@ class Contacts(dict, Logger):
|
||||
name = address
|
||||
if not address:
|
||||
continue
|
||||
return address, name, validated
|
||||
return address, name
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -137,7 +137,11 @@ async def _get_and_validate(ns, url, _type) -> dns.rrset.RRset:
|
||||
return rrset
|
||||
|
||||
|
||||
async def query(url, rtype) -> Tuple[dns.rrset.RRset, bool]:
|
||||
async def query(url: str, rtype: dns.rdatatype.RdataType) -> Tuple[dns.rrset.RRset, bool]:
|
||||
"""Try to do DNS resolution, including DNSSEC.
|
||||
'validated' shows whether the DNSSEC checks passed. DNS is completely INSECURE without DNSSEC,
|
||||
so the caller must carefully consider whether the response can be used for anything if validated=False.
|
||||
"""
|
||||
# FIXME this method is not using the network proxy. (although the proxy might not support UDP?)
|
||||
# 8.8.8.8 is Google's public DNS server
|
||||
nameservers = ['8.8.8.8']
|
||||
|
||||
@@ -437,8 +437,7 @@ class SettingsDialog(QDialog, QtEventListener):
|
||||
self.alias_e.setStyleSheet("")
|
||||
return
|
||||
if self.wallet.contacts.alias_info:
|
||||
alias_addr, alias_name, validated = self.wallet.contacts.alias_info
|
||||
self.alias_e.setStyleSheet((ColorScheme.GREEN if validated else ColorScheme.RED).as_stylesheet(True))
|
||||
self.alias_e.setStyleSheet(ColorScheme.GREEN.as_stylesheet(True))
|
||||
else:
|
||||
self.alias_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
|
||||
|
||||
|
||||
@@ -330,10 +330,6 @@ class PaymentIdentifier(Logger):
|
||||
elif openalias_result := await openalias_task:
|
||||
self.openalias_data = openalias_result
|
||||
address = openalias_result.get('address')
|
||||
if not openalias_result.get('validated'):
|
||||
self.warning = _(
|
||||
'WARNING: the alias "{}" could not be validated via an additional '
|
||||
'security check, DNSSEC, and thus may not be correct.').format(openalias_key)
|
||||
try:
|
||||
# this assertion error message is shown in the GUI
|
||||
assert bitcoin.is_address(address), f"{_('Openalias address invalid')}: {address[:100]}"
|
||||
@@ -594,10 +590,6 @@ class PaymentIdentifier(Logger):
|
||||
name = self.openalias_data.get('name')
|
||||
description = name
|
||||
recipient = key + ' <' + address + '>'
|
||||
validated = self.openalias_data.get('validated')
|
||||
if not validated:
|
||||
self.warning = _('WARNING: the alias "{}" could not be validated via an additional '
|
||||
'security check, DNSSEC, and thus may not be correct.').format(key)
|
||||
|
||||
elif self.bolt11:
|
||||
recipient, amount, description = self._get_bolt11_fields()
|
||||
|
||||
@@ -225,9 +225,6 @@ class PaymentRequest:
|
||||
sig = pr.signature
|
||||
alias = pr.pki_data
|
||||
info: dict = await Contacts.resolve_openalias(alias)
|
||||
if info.get('validated') is not True:
|
||||
self.error = "Alias verification failed (DNSSEC)"
|
||||
return False
|
||||
if pr.pki_type == "dnssec+btc":
|
||||
self.requestor = alias
|
||||
address = info.get('address')
|
||||
|
||||
Reference in New Issue
Block a user