15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# found in the LICENSE file.
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""An https server that forwards requests to another server. This allows a
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)server that supports http only to be accessed over https.
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import BaseHTTPServer
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import os
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import SocketServer
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import sys
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urllib2
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urlparse
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import testserver_base
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import tlslite.api
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class RedirectSuppressor(urllib2.HTTPErrorProcessor):
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Prevents urllib2 from following http redirects.
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  If this class is placed in an urllib2.OpenerDirector's handler chain before
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  the default urllib2.HTTPRedirectHandler, it will terminate the processing of
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  responses containing redirect codes (301, 302, 303, 307) before they reach the
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  default redirect handler.
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def http_response(self, req, response):
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return response
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def https_response(self, req, response):
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return response
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class RequestForwarder(BaseHTTPServer.BaseHTTPRequestHandler):
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Handles requests received by forwarding them to the another server."""
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def do_GET(self):
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards GET requests."""
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._forward(None)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def do_POST(self):
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards POST requests."""
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._forward(self.rfile.read(int(self.headers['Content-Length'])))
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def _forward(self, body):
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Forwards a GET or POST request to another server.
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      body: The request body. This should be |None| for GET requests.
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    request_url = urlparse.urlparse(self.path)
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    url = urlparse.urlunparse((self.server.forward_scheme,
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               self.server.forward_netloc,
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               self.server.forward_path + request_url[2],
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[3],
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[4],
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               request_url[5]))
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    headers = dict((key, value) for key, value in dict(self.headers).iteritems()
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                   if key.lower() != 'host')
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    opener = urllib2.build_opener(RedirectSuppressor)
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    forward = opener.open(urllib2.Request(url, body, headers))
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.send_response(forward.getcode())
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for key, value in dict(forward.info()).iteritems():
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # RFC 6265 states in section 3:
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      #
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # Origin servers SHOULD NOT fold multiple Set-Cookie header fields into
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # a single header field.
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      #
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # Python 2 does not obey this requirement and folds multiple Set-Cookie
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # header fields into one. The following code undoes this folding by
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # splitting the Set-Cookie header field at each comma. Note that this is a
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # hack because the code does not (and cannot reliably) distinguish between
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # commas inserted by Python while folding multiple headers and commas that
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # were part of the original Set-Cookie headers.
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if key == 'set-cookie':
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for cookie in value.split(','):
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          self.send_header(key, cookie)
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      else:
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.send_header(key, value)
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.end_headers()
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.wfile.write(forward.read())
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class MultiThreadedHTTPSServer(SocketServer.ThreadingMixIn,
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               tlslite.api.TLSSocketServerMixIn,
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.ClientRestrictingServerMixIn,
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.BrokenPipeHandlerMixIn,
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                               testserver_base.StoppableHTTPServer):
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """A multi-threaded version of testserver.HTTPSServer."""
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self, server_address, request_hander_class, pem_cert_and_key):
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Initializes the server.
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server_address: Server host and port.
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      request_hander_class: The class that will handle requests to the server.
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      pem_cert_and_key: Path to file containing the https cert and private key.
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
102a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    self.cert_chain = tlslite.api.X509CertChain()
103a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    self.cert_chain.parsePemList(pem_cert_and_key)
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Force using only python implementation - otherwise behavior is different
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # depending on whether m2crypto Python module is present (error is thrown
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # when it is). m2crypto uses a C (based on OpenSSL) implementation under
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # the hood.
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                               private=True,
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                               implementations=['python'])
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    testserver_base.StoppableHTTPServer.__init__(self,
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                                 server_address,
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                                 request_hander_class)
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def handshake(self, tlsConnection):
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Performs the SSL handshake for an https connection.
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection: The https connection.
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Returns:
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      Whether the SSL handshake succeeded.
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    try:
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self.tlsConnection = tlsConnection
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection.handshakeServer(certChain=self.cert_chain,
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                    privateKey=self.private_key)
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      tlsConnection.ignoreAbruptClose = True
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return True
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    except:
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return False
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class ServerRunner(testserver_base.TestServerRunner):
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Runner that starts an https server which forwards requests to another
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  server.
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def create_server(self, server_data):
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Performs the SSL handshake for an https connection.
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server_data: Dictionary that holds information about the server.
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Returns:
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      The started server.
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    port = self.options.port
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    host = self.options.host
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not os.path.isfile(self.options.cert_and_key_file):
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise testserver_base.OptionError(
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          'Specified server cert file not found: ' +
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          self.options.cert_and_key_file)
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    pem_cert_and_key = open(self.options.cert_and_key_file).read()
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server = MultiThreadedHTTPSServer((host, port),
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                      RequestForwarder,
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                      pem_cert_and_key)
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    forward_target = urlparse.urlparse(self.options.forward_target)
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_scheme = forward_target[0]
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_netloc = forward_target[1]
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_path = forward_target[2].rstrip('/')
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server.forward_host = forward_target.hostname
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if forward_target.port:
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      server.forward_host += ':' + str(forward_target.port)
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server_data['port'] = server.server_port
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return server
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def add_options(self):
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Specifies the command-line options understood by the server."""
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    testserver_base.TestServerRunner.add_options(self)
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--https', action='store_true',
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  help='Ignored (provided for compatibility '
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'only).')
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--cert-and-key-file', help='The path to the '
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'file containing the certificate and private '
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'key for the server in PEM format.')
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self.option_parser.add_option('--forward-target', help='The URL prefix to '
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  'which requests will be forwarded.')
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if __name__ == '__main__':
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  sys.exit(ServerRunner().main())
186