diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 15:14:27 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 15:14:27 +0000 |
commit | dffe824dde902d3dc2da49f13bf94cfe6d00dc5f (patch) | |
tree | 64efe94957dc97e2aa2d108ee9a0465c61da3a9a /net/tools | |
parent | 5edc5b36105063b5dc4fcb5ba4769ea25fbb327e (diff) | |
download | chromium_src-dffe824dde902d3dc2da49f13bf94cfe6d00dc5f.zip chromium_src-dffe824dde902d3dc2da49f13bf94cfe6d00dc5f.tar.gz chromium_src-dffe824dde902d3dc2da49f13bf94cfe6d00dc5f.tar.bz2 |
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: svn://svn.chromium.org/chrome/trunk/src@127680 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/tools')
-rw-r--r-- | net/tools/testserver/asn1.py | 165 | ||||
-rw-r--r-- | net/tools/testserver/minica.py | 328 | ||||
-rwxr-xr-x | net/tools/testserver/testserver.py | 113 |
3 files changed, 586 insertions, 20 deletions
diff --git a/net/tools/testserver/asn1.py b/net/tools/testserver/asn1.py new file mode 100644 index 0000000..c0e0398 --- /dev/null +++ b/net/tools/testserver/asn1.py @@ -0,0 +1,165 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file implements very minimal ASN.1, DER serialization. + +import types + + +def ToDER(obj): + '''ToDER converts the given object into DER encoding''' + if type(obj) == types.NoneType: + # None turns into NULL + return TagAndLength(5, 0) + if type(obj) == types.StringType: + # Strings are PRINTABLESTRING + return TagAndLength(19, len(obj)) + obj + if type(obj) == types.BooleanType: + val = "\x00" + if obj: + val = "\xff" + return TagAndLength(1, 1) + val + if type(obj) == types.IntType or type(obj) == types.LongType: + big_endian = [] + val = obj + while val != 0: + big_endian.append(val & 0xff) + val >>= 8 + + if len(big_endian) == 0 or big_endian[-1] >= 128: + big_endian.append(0) + + big_endian.reverse() + return TagAndLength(2, len(big_endian)) + ToBytes(big_endian) + + return obj.ToDER() + + +def ToBytes(array_of_bytes): + '''ToBytes converts the array of byte values into a binary string''' + return ''.join([chr(x) for x in array_of_bytes]) + + +def TagAndLength(tag, length): + der = [tag] + if length < 128: + der.append(length) + elif length < 256: + der.append(0x81) + der.append(length) + elif length < 65535: + der.append(0x82) + der.append(length >> 8) + der.append(length & 0xff) + else: + assert False + + return ToBytes(der) + + +class Raw(object): + '''Raw contains raw DER encoded bytes that are used verbatim''' + def __init__(self, der): + self.der = der + + def ToDER(self): + return self.der + + +class Explicit(object): + '''Explicit prepends an explicit tag''' + def __init__(self, tag, child): + self.tag = tag + self.child = child + + def ToDER(self): + der = ToDER(self.child) + tag = self.tag + tag |= 0x80 # content specific + tag |= 0x20 # complex + return TagAndLength(tag, len(der)) + der + + +class ENUMERATED(object): + def __init__(self, value): + self.value = value + + def ToDER(self): + return TagAndLength(10, 1) + chr(self.value) + + +class SEQUENCE(object): + def __init__(self, children): + self.children = children + + def ToDER(self): + der = ''.join([ToDER(x) for x in self.children]) + return TagAndLength(0x30, len(der)) + der + + +class SET(object): + def __init__(self, children): + self.children = children + + def ToDER(self): + der = ''.join([ToDER(x) for x in self.children]) + return TagAndLength(0x31, len(der)) + der + + +class OCTETSTRING(object): + def __init__(self, val): + self.val = val + + def ToDER(self): + return TagAndLength(4, len(self.val)) + self.val + + +class OID(object): + def __init__(self, parts): + self.parts = parts + + def ToDER(self): + if len(self.parts) < 2 or self.parts[0] > 6 or self.parts[1] >= 40: + assert False + + der = [self.parts[0]*40 + self.parts[1]] + for x in self.parts[2:]: + if x == 0: + der.append(0) + else: + octets = [] + while x != 0: + v = x & 0x7f + if len(octets) > 0: + v |= 0x80 + octets.append(v) + x >>= 7 + octets.reverse() + der = der + octets + + return TagAndLength(6, len(der)) + ToBytes(der) + + +class UTCTime(object): + def __init__(self, time_str): + self.time_str = time_str + + def ToDER(self): + return TagAndLength(23, len(self.time_str)) + self.time_str + + +class GeneralizedTime(object): + def __init__(self, time_str): + self.time_str = time_str + + def ToDER(self): + return TagAndLength(24, len(self.time_str)) + self.time_str + + +class BitString(object): + def __init__(self, bits): + self.bits = bits + + def ToDER(self): + return TagAndLength(3, 1 + len(self.bits)) + "\x00" + self.bits diff --git a/net/tools/testserver/minica.py b/net/tools/testserver/minica.py new file mode 100644 index 0000000..9824fd0 --- /dev/null +++ b/net/tools/testserver/minica.py @@ -0,0 +1,328 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import asn1 +import hashlib +import os + + +# This file implements very minimal certificate and OCSP generation. It's +# designed to test revocation checking. + +def RandomNumber(length_in_bytes): + '''RandomNumber returns a random number of length 8*|length_in_bytes| bits''' + rand = os.urandom(length_in_bytes) + n = 0 + for x in rand: + n <<= 8 + n |= ord(x) + return n + + +def ModExp(n, e, p): + '''ModExp returns n^e mod p''' + r = 1 + while e != 0: + if e & 1: + r = (r*n) % p + e >>= 1 + n = (n*n) % p + return r + +# PKCS1v15_SHA1_PREFIX is the ASN.1 prefix for a SHA1 signature. +PKCS1v15_SHA1_PREFIX = '3021300906052b0e03021a05000414'.decode('hex') + +class RSA(object): + def __init__(self, modulus, e, d): + self.m = modulus + self.e = e + self.d = d + + self.modlen = 0 + m = modulus + while m != 0: + self.modlen += 1 + m >>= 8 + + def Sign(self, message): + digest = hashlib.sha1(message).digest() + prefix = PKCS1v15_SHA1_PREFIX + + em = ['\xff'] * (self.modlen - 1 - len(prefix) - len(digest)) + em[0] = '\x00' + em[1] = '\x01' + em += "\x00" + prefix + digest + + n = 0 + for x in em: + n <<= 8 + n |= ord(x) + + s = ModExp(n, self.d, self.m) + out = [] + while s != 0: + out.append(s & 0xff) + s >>= 8 + out.reverse() + return '\x00' * (self.modlen - len(out)) + asn1.ToBytes(out) + + def ToDER(self): + return asn1.ToDER(asn1.SEQUENCE([self.m, self.e])) + + +def Name(cn = None, c = None, o = None): + names = asn1.SEQUENCE([]) + + if cn is not None: + names.children.append( + asn1.SET([ + asn1.SEQUENCE([ + COMMON_NAME, cn, + ]) + ]) + ) + + if c is not None: + names.children.append( + asn1.SET([ + asn1.SEQUENCE([ + COUNTRY, c, + ]) + ]) + ) + + if o is not None: + names.children.append( + asn1.SET([ + asn1.SEQUENCE([ + ORGANIZATION, o, + ]) + ]) + ) + + return names + + +# The private key and root certificate name are hard coded here: + +# This is the private key +KEY = RSA(0x00a71998f2930bfe73d031a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da999de4ed65832afcb0807f98787506539d258a0ce3c2c77967653099a9034a9b115a876c39a8c4e4ed4acd0c64095946fb39eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1afc45519ab9eca47c30185c771c64aa5ecf07d, + 3, + 0x6f6665f70cb2a9a28acbc5aa0cd374cfb49f49e371a542de0a86aa4a0554cc87f7e71113edf399021ca875aaffbafaf8aee268c3b15ded2c84fb9a4375bbc6011d841e57833bc6f998d25daf6fa7f166b233e3e54a4bae7a5aaaba21431324967d5ff3e1d4f413827994262115ca54396e7068d0afa7af787a5782bc7040e6d3) + +# And the same thing in PEM format +KEY_PEM = '''-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCnGZjykwv+c9AxqH8TPS83ju7u1Sp35E0Pyf9vB/8yy/PamZ3k +7WWDKvywgH+YeHUGU50ligzjwsd5Z2UwmakDSpsRWodsOajE5O1KzQxkCVlG+znu +60egcE27AYrPSMOhxLiV/ECftKNAqYaxr8RVGaueykfDAYXHccZKpezwfQIBAwKB +gG9mZfcMsqmiisvFqgzTdM+0n0njcaVC3gqGqkoFVMyH9+cRE+3zmQIcqHWq/7r6 ++K7iaMOxXe0shPuaQ3W7xgEdhB5XgzvG+ZjSXa9vp/FmsjPj5UpLrnpaqrohQxMk +ln1f8+HU9BOCeZQmIRXKVDlucGjQr6eveHpXgrxwQObTAkEA2wBAfuduw5G0/VfN +Wx66D5fbPccfYFqLM5LuTimLmNqzK2gIKXckB2sm44gJZ6wVlumaB1CSNug2LNYx +3cAjUwJBAMNUo1hbI8ugqqwI9kpxv9+2Heea4BlnXbS6tYF8pvkHMoliuxNbXmmB +u4zNB5iZ6V0ZZ4nvtUNo2cGr/h/Lcu8CQQCSACr/RPSCYSNTj948vya1D+d+hL+V +kbIiYfQ0G7Jl5yIc8AVw+hgE8hntBVuacrkPRmaviwwkms7IjsvpKsI3AkEAgjhs +5ZIX3RXHHVtO3EvVP86+mmdAEO+TzdHOVlMZ+1ohsOx8t5I+8QEnszNaZbvw6Lua +W/UjgkXmgR1UFTJMnwJBAKErmAw21/g3SST0a4wlyaGT/MbXL8Ouwnb5IOKQVe55 +CZdeVeSh6cJ4hAcQKfr2s1JaZTJFIBPGKAif5HqpydA= +-----END RSA PRIVATE KEY----- +''' + +# Root certificate CN +ISSUER_CN = "Testing CA" + +# All certificates are issued under this policy OID, in the Google arc: +CERT_POLICY_OID = asn1.OID([1, 3, 6, 1, 4, 1, 11129, 2, 4, 1]) + +# These result in the following root certificate: +# -----BEGIN CERTIFICATE----- +# MIIB0TCCATqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpUZXN0aW5nIENBMB4X +# DTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2MDAwMFowFTETMBEGA1UEAxMKVGVzdGluZyBDQTCBnTAN +# BgkqhkiG9w0BAQEFAAOBiwAwgYcCgYEApxmY8pML/nPQMah/Ez0vN47u7tUqd+RND8n/bwf/Msvz +# 2pmd5O1lgyr8sIB/mHh1BlOdJYoM48LHeWdlMJmpA0qbEVqHbDmoxOTtSs0MZAlZRvs57utHoHBN +# uwGKz0jDocS4lfxAn7SjQKmGsa/EVRmrnspHwwGFx3HGSqXs8H0CAQOjMzAxMBIGA1UdEwEB/wQI +# MAYBAf8CAQAwGwYDVR0gAQEABBEwDzANBgsrBgEEAdZ5AgHODzANBgkqhkiG9w0BAQUFAAOBgQA/ +# STb40A6D+93jMfLGQzXc997IsaJZdoPt7tYa8PqGJBL62EiTj+erd/H5pDZx/2/bcpOG4m9J56yg +# wOohbllw2TM+oeEd8syzV6X+1SIPnGI56JRrm3UXcHYx1Rq5loM9WKAiz/WmIWmskljsEQ7+542p +# q0pkHjs8nuXovSkUYA== +# -----END CERTIFICATE----- + +# If you update any of the above, you can generate a new root with the +# following line: +# print DERToPEM(MakeCertificate(ISSUER_CN, ISSUER_CN, 1, KEY, KEY, None)) + + +# Various OIDs + +AIA_OCSP = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1]) +AUTHORITY_INFORMATION_ACCESS = asn1.OID([1, 3, 6, 1, 5, 5, 7, 1, 1]) +BASIC_CONSTRAINTS = asn1.OID([2, 5, 29, 19]) +CERT_POLICIES = asn1.OID([2, 5, 29, 32]) +COMMON_NAME = asn1.OID([2, 5, 4, 3]) +COUNTRY = asn1.OID([2, 5, 4, 6]) +HASH_SHA1 = asn1.OID([1, 3, 14, 3, 2, 26]) +OCSP_TYPE_BASIC = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1, 1]) +ORGANIZATION = asn1.OID([2, 5, 4, 10]) +PUBLIC_KEY_RSA = asn1.OID([1, 2, 840, 113549, 1, 1, 1]) +SHA1_WITH_RSA_ENCRYPTION = asn1.OID([1, 2, 840, 113549, 1, 1, 5]) + + +def MakeCertificate( + issuer_cn, subject_cn, serial, pubkey, privkey, ocsp_url = None): + '''MakeCertificate returns a DER encoded certificate, signed by privkey.''' + extensions = asn1.SEQUENCE([]) + + # Default subject name fields + c = "XX" + o = "Testing Org" + + if issuer_cn == subject_cn: + # Root certificate. + c = None + o = None + extensions.children.append( + asn1.SEQUENCE([ + basic_constraints, + True, + asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([ + True, # IsCA + 0, # Path len + ]))), + ])) + + if ocsp_url is not None: + extensions.children.append( + asn1.SEQUENCE([ + AUTHORITY_INFORMATION_ACCESS, + False, + asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([ + asn1.SEQUENCE([ + AIA_OCSP, + asn1.Raw(asn1.TagAndLength(0x86, len(ocsp_url)) + ocsp_url), + ]), + ]))), + ])) + + extensions.children.append( + asn1.SEQUENCE([ + CERT_POLICIES, + False, + asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([ + asn1.SEQUENCE([ # PolicyInformation + CERT_POLICY_OID, + ]), + ]))), + ]) + ) + + tbsCert = asn1.ToDER(asn1.SEQUENCE([ + asn1.Explicit(0, 2), # Version + serial, + asn1.SEQUENCE([SHA1_WITH_RSA_ENCRYPTION, None]), # SignatureAlgorithm + Name(cn = issuer_cn), # Issuer + asn1.SEQUENCE([ # Validity + asn1.UTCTime("100101060000Z"), # NotBefore + asn1.UTCTime("321201060000Z"), # NotAfter + ]), + Name(cn = subject_cn, c = c, o = o), # Subject + asn1.SEQUENCE([ # SubjectPublicKeyInfo + asn1.SEQUENCE([ # Algorithm + PUBLIC_KEY_RSA, + None, + ]), + asn1.BitString(asn1.ToDER(pubkey)), + ]), + asn1.Explicit(3, extensions), + ])) + + return asn1.ToDER(asn1.SEQUENCE([ + asn1.Raw(tbsCert), + asn1.SEQUENCE([ + SHA1_WITH_RSA_ENCRYPTION, + None, + ]), + asn1.BitString(privkey.Sign(tbsCert)), + ])) + + +def MakeOCSPResponse(issuer_cn, issuer_key, serial, revoked): + # https://tools.ietf.org/html/rfc2560 + issuer_name_hash = asn1.OCTETSTRING( + hashlib.sha1(asn1.ToDER(Name(cn = issuer_cn))).digest()) + + issuer_key_hash = asn1.OCTETSTRING( + hashlib.sha1(asn1.ToDER(issuer_key)).digest()) + + cert_status = None + if revoked: + cert_status = asn1.Explicit(1, asn1.GeneralizedTime("20100101060000Z")) + else: + cert_status = asn1.Raw(asn1.TagAndLength(0x80 | 0, 0)) + + basic_resp_data_der = asn1.ToDER(asn1.SEQUENCE([ + asn1.Explicit(2, issuer_key_hash), + asn1.GeneralizedTime("20100101060000Z"), # producedAt + asn1.SEQUENCE([ + asn1.SEQUENCE([ # SingleResponse + asn1.SEQUENCE([ # CertID + asn1.SEQUENCE([ # hashAlgorithm + HASH_SHA1, + None, + ]), + issuer_name_hash, + issuer_key_hash, + serial, + ]), + cert_status, + asn1.GeneralizedTime("20100101060000Z"), # thisUpdate + asn1.Explicit(0, asn1.GeneralizedTime("20300101060000Z")), # nextUpdate + ]), + ]), + ])) + + basic_resp = asn1.SEQUENCE([ + asn1.Raw(basic_resp_data_der), + asn1.SEQUENCE([ + SHA1_WITH_RSA_ENCRYPTION, + None, + ]), + asn1.BitString(issuer_key.Sign(basic_resp_data_der)), + ]) + + resp = asn1.SEQUENCE([ + asn1.ENUMERATED(0), + asn1.Explicit(0, asn1.SEQUENCE([ + OCSP_TYPE_BASIC, + asn1.OCTETSTRING(asn1.ToDER(basic_resp)), + ])) + ]) + + return asn1.ToDER(resp) + + +def DERToPEM(der): + pem = '-----BEGIN CERTIFICATE-----\n' + pem += der.encode('base64') + pem += '-----END CERTIFICATE-----\n' + return pem + + +def GenerateCertKeyAndOCSP(subject = "127.0.0.1", + ocsp_url = "http://127.0.0.1", + ocsp_revoked = False): + '''GenerateCertKeyAndOCSP returns a (cert_and_key_pem, ocsp_der) where: + * cert_and_key_pem contains a certificate and private key in PEM format + with the given subject common name and OCSP URL. + * ocsp_der contains a DER encoded OCSP response or None if ocsp_url is + None''' + + serial = RandomNumber(16) + cert_der = MakeCertificate(ISSUER_CN, subject, serial, KEY, KEY, ocsp_url) + cert_pem = DERToPEM(cert_der) + + ocsp_der = None + if ocsp_url is not None: + ocsp_der = MakeOCSPResponse(ISSUER_CN, KEY, serial, ocsp_revoked) + + return (cert_pem + KEY_PEM, ocsp_der) diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index ff20c0f..6461997 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: @@ -1889,6 +1901,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. @@ -1969,19 +1995,55 @@ 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 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 @@ 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__': @@ -2095,10 +2162,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 ' + '[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' |