add dnssec verification to payment requests
This commit is contained in:
@@ -720,8 +720,18 @@ class ElectrumWindow(QMainWindow):
|
|||||||
self.save_request_button.setEnabled(False)
|
self.save_request_button.setEnabled(False)
|
||||||
|
|
||||||
def export_payment_request(self, addr):
|
def export_payment_request(self, addr):
|
||||||
|
alias = str(self.config.get('alias'))
|
||||||
|
alias_privkey = None
|
||||||
|
if alias:
|
||||||
|
alias_info = self.contacts.resolve_openalias(alias)
|
||||||
|
if alias_info:
|
||||||
|
alias_addr, alias_name = alias_info
|
||||||
|
if alias_addr and self.wallet.is_mine(alias_addr):
|
||||||
|
password = self.password_dialog()
|
||||||
|
alias_privkey = self.wallet.get_private_key(alias_addr, password)[0]
|
||||||
|
|
||||||
r = self.wallet.get_payment_request(addr, self.config)
|
r = self.wallet.get_payment_request(addr, self.config)
|
||||||
pr = paymentrequest.make_request(self.config, r)
|
pr = paymentrequest.make_request(self.config, r, alias, alias_privkey)
|
||||||
name = r['id'] + '.bip70'
|
name = r['id'] + '.bip70'
|
||||||
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
||||||
if fileName:
|
if fileName:
|
||||||
@@ -1272,7 +1282,7 @@ class ElectrumWindow(QMainWindow):
|
|||||||
|
|
||||||
def get_payment_request_thread():
|
def get_payment_request_thread():
|
||||||
self.payment_request = get_payment_request(request_url)
|
self.payment_request = get_payment_request(request_url)
|
||||||
if self.payment_request.verify():
|
if self.payment_request.verify(self.contacts):
|
||||||
self.emit(SIGNAL('payment_request_ok'))
|
self.emit(SIGNAL('payment_request_ok'))
|
||||||
else:
|
else:
|
||||||
self.emit(SIGNAL('payment_request_error'))
|
self.emit(SIGNAL('payment_request_error'))
|
||||||
@@ -1485,7 +1495,7 @@ class ElectrumWindow(QMainWindow):
|
|||||||
|
|
||||||
def show_invoice(self, key):
|
def show_invoice(self, key):
|
||||||
pr = self.invoices.get(key)
|
pr = self.invoices.get(key)
|
||||||
pr.verify()
|
pr.verify(self.contacts)
|
||||||
self.show_pr_details(pr)
|
self.show_pr_details(pr)
|
||||||
|
|
||||||
def show_pr_details(self, pr):
|
def show_pr_details(self, pr):
|
||||||
@@ -1521,7 +1531,7 @@ class ElectrumWindow(QMainWindow):
|
|||||||
pr = self.invoices.get(key)
|
pr = self.invoices.get(key)
|
||||||
self.payment_request = pr
|
self.payment_request = pr
|
||||||
self.prepare_for_payment_request()
|
self.prepare_for_payment_request()
|
||||||
if pr.verify():
|
if pr.verify(self.contacts):
|
||||||
self.payment_request_ok()
|
self.payment_request_ok()
|
||||||
else:
|
else:
|
||||||
self.payment_request_error()
|
self.payment_request_error()
|
||||||
|
|||||||
@@ -478,11 +478,15 @@ class EC_KEY(object):
|
|||||||
def get_public_key(self, compressed=True):
|
def get_public_key(self, compressed=True):
|
||||||
return point_to_ser(self.pubkey.point, compressed).encode('hex')
|
return point_to_ser(self.pubkey.point, compressed).encode('hex')
|
||||||
|
|
||||||
def sign_message(self, message, compressed, address):
|
def sign(self, msg_hash):
|
||||||
private_key = ecdsa.SigningKey.from_secret_exponent( self.secret, curve = SECP256k1 )
|
private_key = ecdsa.SigningKey.from_secret_exponent(self.secret, curve = SECP256k1)
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
signature = private_key.sign_digest_deterministic( Hash( msg_magic(message) ), hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string )
|
signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string)
|
||||||
assert public_key.verify_digest( signature, Hash( msg_magic(message) ), sigdecode = ecdsa.util.sigdecode_string)
|
assert public_key.verify_digest(signature, msg_hash, sigdecode = ecdsa.util.sigdecode_string)
|
||||||
|
return signature
|
||||||
|
|
||||||
|
def sign_message(self, message, compressed, address):
|
||||||
|
signature = self.sign(Hash(msg_magic(message)))
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
sig = base64.b64encode(chr(27 + i + (4 if compressed else 0)) + signature)
|
sig = base64.b64encode(chr(27 + i + (4 if compressed else 0)) + signature)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -98,19 +98,29 @@ class PaymentRequest:
|
|||||||
self.memo = self.details.memo
|
self.memo = self.details.memo
|
||||||
self.payment_url = self.details.payment_url
|
self.payment_url = self.details.payment_url
|
||||||
|
|
||||||
def verify(self):
|
def verify(self, contacts):
|
||||||
|
if not self.raw:
|
||||||
|
self.error = "Empty request"
|
||||||
|
return
|
||||||
|
pr = pb2.PaymentRequest()
|
||||||
|
pr.ParseFromString(self.raw)
|
||||||
|
if not pr.signature:
|
||||||
|
self.error = "No signature"
|
||||||
|
return
|
||||||
|
|
||||||
|
if pr.pki_type in ["x509+sha256", "x509+sha1"]:
|
||||||
|
return self.verify_x509(pr)
|
||||||
|
elif pr.pki_type in ["dnssec+btc", "dnssec+ecdsa"]:
|
||||||
|
return self.verify_dnssec(pr, contacts)
|
||||||
|
else:
|
||||||
|
self.error = "ERROR: Unsupported PKI Type for Message Signature"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def verify_x509(self, paymntreq):
|
||||||
""" verify chain of certificates. The last certificate is the CA"""
|
""" verify chain of certificates. The last certificate is the CA"""
|
||||||
if not ca_list:
|
if not ca_list:
|
||||||
self.error = "Trusted certificate authorities list not found"
|
self.error = "Trusted certificate authorities list not found"
|
||||||
return False
|
return False
|
||||||
if not self.raw:
|
|
||||||
self.error = "Empty request"
|
|
||||||
return
|
|
||||||
paymntreq = pb2.PaymentRequest()
|
|
||||||
paymntreq.ParseFromString(self.raw)
|
|
||||||
if not paymntreq.signature:
|
|
||||||
self.error = "No signature"
|
|
||||||
return
|
|
||||||
cert = pb2.X509Certificates()
|
cert = pb2.X509Certificates()
|
||||||
cert.ParseFromString(paymntreq.pki_data)
|
cert.ParseFromString(paymntreq.pki_data)
|
||||||
cert_num = len(cert.certificate)
|
cert_num = len(cert.certificate)
|
||||||
@@ -184,9 +194,6 @@ class PaymentRequest:
|
|||||||
verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
|
verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)
|
||||||
elif paymntreq.pki_type == "x509+sha1":
|
elif paymntreq.pki_type == "x509+sha1":
|
||||||
verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
|
verify = pubkey0.hashAndVerify(sigBytes, msgBytes)
|
||||||
else:
|
|
||||||
self.error = "ERROR: Unsupported PKI Type for Message Signature"
|
|
||||||
return False
|
|
||||||
if not verify:
|
if not verify:
|
||||||
self.error = "ERROR: Invalid Signature for Payment Request Data"
|
self.error = "ERROR: Invalid Signature for Payment Request Data"
|
||||||
return False
|
return False
|
||||||
@@ -194,6 +201,28 @@ class PaymentRequest:
|
|||||||
self.error = 'Signed by Trusted CA: ' + ca.get_common_name()
|
self.error = 'Signed by Trusted CA: ' + ca.get_common_name()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def verify_dnssec(self, pr, contacts):
|
||||||
|
sig = pr.signature
|
||||||
|
alias = pr.pki_data
|
||||||
|
info = contacts.resolve(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')
|
||||||
|
pr.signature = ''
|
||||||
|
message = pr.SerializeToString()
|
||||||
|
if bitcoin.verify_message(address, sig, message):
|
||||||
|
self.error = 'Verified with DNSSEC'
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.error = "verify failed"
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.error = "unknown algo"
|
||||||
|
return False
|
||||||
|
|
||||||
def has_expired(self):
|
def has_expired(self):
|
||||||
return self.details.expires and self.details.expires < int(time.time())
|
return self.details.expires and self.details.expires < int(time.time())
|
||||||
|
|
||||||
@@ -258,7 +287,7 @@ class PaymentRequest:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
def make_payment_request(outputs, memo, time, expires, key_path, cert_path, alias, alias_privkey):
|
||||||
pd = pb2.PaymentDetails()
|
pd = pb2.PaymentDetails()
|
||||||
for script, amount in outputs:
|
for script, amount in outputs:
|
||||||
pd.outputs.add(amount=amount, script=script)
|
pd.outputs.add(amount=amount, script=script)
|
||||||
@@ -268,9 +297,16 @@ def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
|||||||
pr = pb2.PaymentRequest()
|
pr = pb2.PaymentRequest()
|
||||||
pr.serialized_payment_details = pd.SerializeToString()
|
pr.serialized_payment_details = pd.SerializeToString()
|
||||||
pr.signature = ''
|
pr.signature = ''
|
||||||
pr = pb2.PaymentRequest()
|
|
||||||
pr.serialized_payment_details = pd.SerializeToString()
|
if alias and alias_privkey:
|
||||||
pr.signature = ''
|
pr.pki_type = 'dnssec+btc'
|
||||||
|
pr.pki_data = str(alias)
|
||||||
|
message = pr.SerializeToString()
|
||||||
|
ec_key = bitcoin.regenerate_key(alias_privkey)
|
||||||
|
address = bitcoin.address_from_private_key(alias_privkey)
|
||||||
|
compressed = bitcoin.is_compressed(alias_privkey)
|
||||||
|
pr.signature = ec_key.sign_message(message, compressed, address)
|
||||||
|
|
||||||
if key_path and cert_path:
|
if key_path and cert_path:
|
||||||
import tlslite
|
import tlslite
|
||||||
with open(key_path, 'r') as f:
|
with open(key_path, 'r') as f:
|
||||||
@@ -289,7 +325,7 @@ def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
|||||||
return pr.SerializeToString()
|
return pr.SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
def make_request(config, req):
|
def make_request(config, req, alias=None, alias_privkey=None):
|
||||||
from transaction import Transaction
|
from transaction import Transaction
|
||||||
addr = req['address']
|
addr = req['address']
|
||||||
time = req['timestamp']
|
time = req['timestamp']
|
||||||
@@ -300,7 +336,7 @@ def make_request(config, req):
|
|||||||
outputs = [(script, amount)]
|
outputs = [(script, amount)]
|
||||||
key_path = config.get('ssl_privkey')
|
key_path = config.get('ssl_privkey')
|
||||||
cert_path = config.get('ssl_chain')
|
cert_path = config.get('ssl_chain')
|
||||||
return make_payment_request(outputs, message, time, time + expiration if expiration else None, key_path, cert_path)
|
return make_payment_request(outputs, message, time, time + expiration if expiration else None, key_path, cert_path, alias, alias_privkey)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user