thttp server: add ssl and bip70 signed requests - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 128285a0501d45cb15fd44163d16946d31bf7e6e DIR parent 9d65120e596afd0e5805ce420b098e8b656e9505 HTML Author: ThomasV <thomasv@electrum.org> Date: Thu, 5 Sep 2019 10:57:50 +0200 http server: add ssl and bip70 signed requests Diffstat: M electrum/commands.py | 4 ++-- M electrum/daemon.py | 31 ++++++++++++++++++++++++------- M electrum/gui/kivy/uix/screens.py | 2 +- M electrum/gui/qt/request_list.py | 7 ++----- M electrum/paymentrequest.py | 4 ++-- M electrum/wallet.py | 19 +++++++++++++------ 6 files changed, 44 insertions(+), 23 deletions(-) --- DIR diff --git a/electrum/commands.py b/electrum/commands.py t@@ -702,7 +702,7 @@ class Commands: @command('w') async def getrequest(self, key): """Return a payment request""" - r = self.wallet.get_payment_request(key, self.config) + r = self.wallet.get_request(key) if not r: raise Exception("Request not found") return self._format_request(r) t@@ -754,7 +754,7 @@ class Commands: expiration = int(expiration) if expiration else None req = self.wallet.make_payment_request(addr, amount, memo, expiration) self.wallet.add_payment_request(req, self.config) - out = self.wallet.get_payment_request(addr, self.config) + out = self.wallet.get_request(addr) return self._format_request(out) @command('w') DIR diff --git a/electrum/daemon.py b/electrum/daemon.py t@@ -34,6 +34,7 @@ import aiohttp from aiohttp import web from base64 import b64decode from collections import defaultdict +import ssl import jsonrpcclient import jsonrpcserver t@@ -184,18 +185,25 @@ class HttpServer(Logger): #await self.pending[key].set() async def run(self): - from aiohttp import helpers + host = self.config.get('http_host', 'localhost') + port = self.config.get('http_port') + root = self.config.get('http_root', '/r') + ssl_keyfile = self.config.get('ssl_keyfile') + ssl_certfile = self.config.get('ssl_certfile') + if ssl_keyfile and ssl_certfile: + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) + else: + ssl_context = None app = web.Application() - #app.on_response_prepare.append(http_server.on_response_prepare) app.add_routes([web.post('/api/create_invoice', self.create_request)]) app.add_routes([web.get('/api/get_invoice', self.get_request)]) app.add_routes([web.get('/api/get_status', self.get_status)]) - app.add_routes([web.static('/electrum', 'electrum/www')]) + app.add_routes([web.get('/bip70/{key}.bip70', self.get_bip70_request)]) + app.add_routes([web.static(root, 'electrum/www')]) runner = web.AppRunner(app) await runner.setup() - host = self.config.get('http_host', 'localhost') - port = int(self.config.get('http_port')) - site = web.TCPSite(runner, port=port, host=host) + site = web.TCPSite(runner, port=port, host=host, ssl_context=ssl_context) await site.start() async def create_request(self, request): t@@ -207,13 +215,22 @@ class HttpServer(Logger): message = params['message'] or "donation" payment_hash = await wallet.lnworker._add_invoice_coro(amount, message, 3600) key = payment_hash.hex() - raise web.HTTPFound('/electrum/index.html?id=' + key) + raise web.HTTPFound(self.root + '/pay?id=' + key) async def get_request(self, r): key = r.query_string request = self.daemon.wallet.get_request(key) return web.json_response(request) + async def get_bip70_request(self, r): + from .paymentrequest import make_request + key = r.match_info['key'] + request = self.daemon.wallet.get_request(key) + if not request: + return web.HTTPNotFound() + pr = make_request(self.config, request) + return web.Response(body=pr.SerializeToString(), content_type='application/bitcoin-paymentrequest') + async def get_status(self, request): ws = web.WebSocketResponse() await ws.prepare(request) DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py t@@ -429,7 +429,7 @@ class ReceiveScreen(CScreen): self.screen.address = addr def on_address(self, addr): - req = self.app.wallet.get_payment_request(addr, self.app.electrum_config) + req = self.app.wallet.get_request(addr) self.screen.status = '' if req: self.screen.message = req.get('memo', '') DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py t@@ -183,11 +183,8 @@ class RequestList(MyTreeView): menu.addAction(_("Copy lightning payment request"), lambda: self.parent.do_copy('Request', req['invoice'])) else: menu.addAction(_("Copy URI"), lambda: self.parent.do_copy('URI', req['URI'])) - if 'http_url' in req: - menu.addAction(_("View in web browser"), lambda: webopen(req['http_url'])) - # do bip70 only for browser access - # so, each request should have an ID, regardless - #menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr)) + if 'view_url' in req: + menu.addAction(_("View in web browser"), lambda: webopen(req['view_url'])) menu.addAction(_("Delete"), lambda: self.parent.delete_request(key)) run_hook('receive_list_menu', menu, key) menu.exec_(self.viewport().mapToGlobal(position)) DIR diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py t@@ -473,8 +473,8 @@ def serialize_request(req): def make_request(config, req): pr = make_unsigned_request(req) - key_path = config.get('ssl_privkey') - cert_path = config.get('ssl_chain') + key_path = config.get('ssl_keyfile') + cert_path = config.get('ssl_certfile') if key_path and cert_path: sign_request_with_x509(pr, key_path, cert_path) return pr DIR diff --git a/electrum/wallet.py b/electrum/wallet.py t@@ -1268,7 +1268,7 @@ class Abstract_Wallet(AddressSynchronizer): return True, conf return False, None - def get_payment_request(self, addr, config): + def get_payment_request(self, addr): r = self.receive_requests.get(addr) if not r: return t@@ -1324,15 +1324,22 @@ class Abstract_Wallet(AddressSynchronizer): from .simple_config import get_config config = get_config() if key in self.receive_requests: - req = self.get_payment_request(key, {}) + req = self.get_payment_request(key) else: req = self.lnworker.get_request(key) if not req: return - if config.get('http_port', 8000): + if config.get('http_port'): host = config.get('http_host', 'localhost') - port = config.get('http_port', 8000) - req['http_url'] = 'http://%s:%d/electrum/index.html?id=%s'%(host, port, key) + port = config.get('http_port') + root = config.get('http_root', '/r') + use_ssl = bool(config.get('ssl_keyfile')) + protocol = 'https' if use_ssl else 'http' + base = '%s://%s:%d'%(protocol, host, port) + req['view_url'] = base + root + '/pay?id=' + key + if use_ssl and 'URI' in req: + request_url = base + '/bip70/' + key + '.bip70' + req['bip70_url'] = request_url return req def receive_tx_callback(self, tx_hash, tx, tx_height): t@@ -1397,7 +1404,7 @@ class Abstract_Wallet(AddressSynchronizer): def get_sorted_requests(self, config): """ sorted by timestamp """ - out = [self.get_payment_request(x, config) for x in self.receive_requests.keys()] + out = [self.get_request(x) for x in self.receive_requests.keys()] if self.lnworker: out += self.lnworker.get_requests() out.sort(key=operator.itemgetter('time'))