URI: 
       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'))