 Chromium Code Reviews
 Chromium Code Reviews| Index: net/tools/testserver/testserver.py | 
| diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py | 
| index 28405ffc17e871cabafb5d47ddf1c23c34af7745..3b0a4b0cf463740e1f7717585d079ce830dd5152 100755 | 
| --- a/net/tools/testserver/testserver.py | 
| +++ b/net/tools/testserver/testserver.py | 
| @@ -19,15 +19,17 @@ import BaseHTTPServer | 
| 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 StoppableHTTPServer(BaseHTTPServer.HTTPServer): | 
| 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: | 
| @@ -1876,6 +1888,20 @@ def MakeDataDir(): | 
| 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. | 
| @@ -1956,19 +1982,53 @@ def main(options, args): | 
| 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 on port ' + str(ocsp_server.server_port) | 
| 
Ryan Sleevi
2012/03/13 23:06:39
nit: 
print 'OCSP server started on %s:%d' % (host
 
agl
2012/03/13 23:44:03
Done.
 | 
| + | 
| + 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://127.0.0.1:%d/ocsp" % ocsp_server.server_port, | 
| 
Ryan Sleevi
2012/03/13 23:06:39
bug? 127.0.0.1 -> host
We've avoided hardcoding i
 
agl
2012/03/13 23:44:03
Done.
 | 
| + 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) | 
| @@ -2048,10 +2108,15 @@ def main(options, args): | 
| 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__': | 
| @@ -2082,10 +2147,16 @@ if __name__ == '__main__': | 
| '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 of ' | 
| 
Ryan Sleevi
2012/03/13 23:06:39
nit: 'of of' -> 'of'
 
agl
2012/03/13 23:44:03
Done.
 | 
| + '[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' |