Revert "Revert "Revert "Revert "net: add OCSP tests.""""

(First landed in r127486, reverted in r127493 because it broke on
Windows XP, relanded in r127518 and reverted in r127520 because Android got
upset about an unused function.)

I was getting increasingly unhappy altering EV and revocation checking
semantics without any tests. We historically haven't had tests because
online revocation checking is inherently flaky so I amended testserver
with the minimum code to be able to sign and vend OCSP responses.

These tests do not test the final EV/CRLSet/revocation checking
semantics. They are intended to be altered in future CLs.

BUG=none
TEST=net_unittests

https://chromiumcodereview.appspot.com/9663017/

git-svn-id: http://src.chromium.org/svn/trunk/src/net/tools/testserver@127680 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
diff --git a/testserver.py b/testserver.py
index ff20c0f..6461997 100755
--- a/testserver.py
+++ b/testserver.py
@@ -19,15 +19,17 @@
 import cgi
 import errno
 import httplib
+import minica
 import optparse
 import os
 import random
 import re
 import select
-import SocketServer
 import socket
-import sys
+import SocketServer
 import struct
+import sys
+import threading
 import time
 import urllib
 import urlparse
@@ -105,25 +107,35 @@
 
 
 class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
-  """This is a specialization of StoppableHTTPerver that adds client
+  """This is a specialization of StoppableHTTPServer that adds client
   verification."""
 
   pass
 
+class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
+  """This is a specialization of HTTPServer that serves an
+  OCSP response"""
+
+  def serve_forever_on_thread(self):
+    self.thread = threading.Thread(target = self.serve_forever,
+                                   name = "OCSPServerThread")
+    self.thread.start()
+
+  def stop_serving(self):
+    self.shutdown()
+    self.thread.join()
 
 class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
                   ClientRestrictingServerMixIn,
                   StoppableHTTPServer):
-  """This is a specialization of StoppableHTTPerver that add https support and
+  """This is a specialization of StoppableHTTPServer that add https support and
   client verification."""
 
-  def __init__(self, server_address, request_hander_class, cert_path,
+  def __init__(self, server_address, request_hander_class, pem_cert_and_key,
                ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
                record_resume_info):
-    s = open(cert_path).read()
-    self.cert_chain = tlslite.api.X509CertChain().parseChain(s)
-    s = open(cert_path).read()
-    self.private_key = tlslite.api.parsePEMKey(s, private=True)
+    self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
+    self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
     self.ssl_client_auth = ssl_client_auth
     self.ssl_client_cas = []
     for ca_file in ssl_client_cas:
@@ -1889,6 +1901,20 @@
 
   return my_data_dir
 
+class OCSPHandler(BasePageHandler):
+  def __init__(self, request, client_address, socket_server):
+    handlers = [self.OCSPResponse]
+    self.ocsp_response = socket_server.ocsp_response
+    BasePageHandler.__init__(self, request, client_address, socket_server,
+                             [], handlers, [], handlers, [])
+
+  def OCSPResponse(self):
+    self.send_response(200)
+    self.send_header('Content-Type', 'application/ocsp-response')
+    self.send_header('Content-Length', str(len(self.ocsp_response)))
+    self.end_headers()
+
+    self.wfile.write(self.ocsp_response)
 
 class TCPEchoHandler(SocketServer.BaseRequestHandler):
   """The RequestHandler class for TCP echo server.
@@ -1969,19 +1995,55 @@
   server_data = {}
   server_data['host'] = host
 
+  ocsp_server = None
+
   if options.server_type == SERVER_HTTP:
-    if options.cert:
-      # let's make sure the cert file exists.
-      if not os.path.isfile(options.cert):
-        print 'specified server cert file not found: ' + options.cert + \
-              ' exiting...'
-        return
+    if options.https:
+      pem_cert_and_key = None
+      if options.cert_and_key_file:
+        if not os.path.isfile(options.cert_and_key_file):
+          print ('specified server cert file not found: ' +
+                 options.cert_and_key_file + ' exiting...')
+          return
+        pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
+      else:
+        # generate a new certificate and run an OCSP server for it.
+        ocsp_server = OCSPServer((host, 0), OCSPHandler)
+        print ('OCSP server started on %s:%d...' %
+            (host, ocsp_server.server_port))
+
+        ocsp_der = None
+        ocsp_revoked = False
+        ocsp_invalid = False
+
+        if options.ocsp == 'ok':
+          pass
+        elif options.ocsp == 'revoked':
+          ocsp_revoked = True
+        elif options.ocsp == 'invalid':
+          ocsp_invalid = True
+        else:
+          print 'unknown OCSP status: ' + options.ocsp_status
+          return
+
+        (pem_cert_and_key, ocsp_der) = \
+            minica.GenerateCertKeyAndOCSP(
+                subject = "127.0.0.1",
+                ocsp_url = ("http://%s:%d/ocsp" %
+                    (host, ocsp_server.server_port)),
+                ocsp_revoked = ocsp_revoked)
+
+        if ocsp_invalid:
+          ocsp_der = '3'
+
+        ocsp_server.ocsp_response = ocsp_der
+
       for ca_cert in options.ssl_client_ca:
         if not os.path.isfile(ca_cert):
           print 'specified trusted client CA file not found: ' + ca_cert + \
                 ' exiting...'
           return
-      server = HTTPSServer((host, port), TestPageHandler, options.cert,
+      server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
                            options.ssl_client_auth, options.ssl_client_ca,
                            options.ssl_bulk_cipher, options.record_resume)
       print 'HTTPS server started on %s:%d...' % (host, server.server_port)
@@ -2061,10 +2123,15 @@
     startup_pipe.write(server_data_json)
     startup_pipe.close()
 
+  if ocsp_server is not None:
+    ocsp_server.serve_forever_on_thread()
+
   try:
     server.serve_forever()
   except KeyboardInterrupt:
     print 'shutting down server'
+    if ocsp_server is not None:
+      ocsp_server.stop_serving()
     server.stop = True
 
 if __name__ == '__main__':
@@ -2095,10 +2162,16 @@
                            'server will listen on an ephemeral port.')
   option_parser.add_option('', '--data-dir', dest='data_dir',
                            help='Directory from which to read the files.')
-  option_parser.add_option('', '--https', dest='cert',
-                           help='Specify that https should be used, specify '
-                           'the path to the cert containing the private key '
-                           'the server should use.')
+  option_parser.add_option('', '--https', action='store_true', dest='https',
+                           help='Specify that https should be used.')
+  option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
+                           help='specify the path to the file containing the '
+                           'certificate and private key for the server in PEM '
+                           'format')
+  option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
+                           help='The type of OCSP response generated for the '
+                           'automatically generated certificate. One of '
+                           '[ok,revoked,invalid]')
   option_parser.add_option('', '--https-record-resume', dest='record_resume',
                            const=True, default=False, action='store_const',
                            help='Record resumption cache events rather than'