tadd dnssec verification to payment requests - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit f3c4a55e77ef648310b835f95197776cb2584661 DIR parent 652f0d0b7fc33f53679628ac73e9e43062e50319 HTML Author: ThomasV <thomasv@gitorious> Date: Tue, 7 Jul 2015 08:59:03 +0200 add dnssec verification to payment requests Diffstat: M gui/qt/main_window.py | 18 ++++++++++++++---- M lib/bitcoin.py | 12 ++++++++---- M lib/paymentrequest.py | 70 +++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 25 deletions(-) --- DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py t@@ -720,8 +720,18 @@ class ElectrumWindow(QMainWindow): self.save_request_button.setEnabled(False) 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) - pr = paymentrequest.make_request(self.config, r) + pr = paymentrequest.make_request(self.config, r, alias, alias_privkey) name = r['id'] + '.bip70' fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70") if fileName: t@@ -1272,7 +1282,7 @@ class ElectrumWindow(QMainWindow): def get_payment_request_thread(): 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')) else: self.emit(SIGNAL('payment_request_error')) t@@ -1485,7 +1495,7 @@ class ElectrumWindow(QMainWindow): def show_invoice(self, key): pr = self.invoices.get(key) - pr.verify() + pr.verify(self.contacts) self.show_pr_details(pr) def show_pr_details(self, pr): t@@ -1521,7 +1531,7 @@ class ElectrumWindow(QMainWindow): pr = self.invoices.get(key) self.payment_request = pr self.prepare_for_payment_request() - if pr.verify(): + if pr.verify(self.contacts): self.payment_request_ok() else: self.payment_request_error() DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py t@@ -478,11 +478,15 @@ class EC_KEY(object): def get_public_key(self, compressed=True): return point_to_ser(self.pubkey.point, compressed).encode('hex') - def sign_message(self, message, compressed, address): - private_key = ecdsa.SigningKey.from_secret_exponent( self.secret, curve = SECP256k1 ) + def sign(self, msg_hash): + private_key = ecdsa.SigningKey.from_secret_exponent(self.secret, curve = SECP256k1) 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 ) - assert public_key.verify_digest( signature, Hash( msg_magic(message) ), sigdecode = ecdsa.util.sigdecode_string) + signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_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): sig = base64.b64encode(chr(27 + i + (4 if compressed else 0)) + signature) try: DIR diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py t@@ -98,19 +98,29 @@ class PaymentRequest: self.memo = self.details.memo self.payment_url = self.details.payment_url - def verify(self): - """ verify chain of certificates. The last certificate is the CA""" - if not ca_list: - self.error = "Trusted certificate authorities list not found" - return False + def verify(self, contacts): if not self.raw: self.error = "Empty request" return - paymntreq = pb2.PaymentRequest() - paymntreq.ParseFromString(self.raw) - if not paymntreq.signature: + 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""" + if not ca_list: + self.error = "Trusted certificate authorities list not found" + return False cert = pb2.X509Certificates() cert.ParseFromString(paymntreq.pki_data) cert_num = len(cert.certificate) t@@ -184,9 +194,6 @@ class PaymentRequest: verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes) elif paymntreq.pki_type == "x509+sha1": verify = pubkey0.hashAndVerify(sigBytes, msgBytes) - else: - self.error = "ERROR: Unsupported PKI Type for Message Signature" - return False if not verify: self.error = "ERROR: Invalid Signature for Payment Request Data" return False t@@ -194,6 +201,28 @@ class PaymentRequest: self.error = 'Signed by Trusted CA: ' + ca.get_common_name() 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): return self.details.expires and self.details.expires < int(time.time()) t@@ -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() for script, amount in outputs: pd.outputs.add(amount=amount, script=script) t@@ -268,9 +297,16 @@ def make_payment_request(outputs, memo, time, expires, key_path, cert_path): pr = pb2.PaymentRequest() pr.serialized_payment_details = pd.SerializeToString() pr.signature = '' - pr = pb2.PaymentRequest() - pr.serialized_payment_details = pd.SerializeToString() - pr.signature = '' + + if alias and alias_privkey: + 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: import tlslite with open(key_path, 'r') as f: t@@ -289,7 +325,7 @@ def make_payment_request(outputs, memo, time, expires, key_path, cert_path): return pr.SerializeToString() -def make_request(config, req): +def make_request(config, req, alias=None, alias_privkey=None): from transaction import Transaction addr = req['address'] time = req['timestamp'] t@@ -300,7 +336,7 @@ def make_request(config, req): outputs = [(script, amount)] key_path = config.get('ssl_privkey') 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)