Add client authentification support

BUG=none
TEST=./test/run_all.py

Review URL: https://codereview.appspot.com/6301048
diff --git a/src/mod_pywebsocket/standalone.py b/src/mod_pywebsocket/standalone.py
index e6bc7c7..47a6067 100755
--- a/src/mod_pywebsocket/standalone.py
+++ b/src/mod_pywebsocket/standalone.py
@@ -55,6 +55,20 @@
 handlers under scan_dir are scanned. This is useful in saving scan time.
 
 
+SUPPORTING TLS
+
+To support TLS, run standalone.py with -t, -k, and -c options.
+
+
+SUPPORTING CLIENT AUTHENTICATION
+
+To support client authentication with TLS, run standalone.py with -t, -k, -c,
+and --ca-certificate options.
+
+E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k
+../test/cert/key.pem --ca-certificate=../test/cert/cacert.pem
+
+
 CONFIGURATION FILE
 
 You can also write a configuration file and use it by specifying the path to
@@ -311,10 +325,16 @@
                 continue
             if self.websocket_server_options.use_tls:
                 if _HAS_SSL:
+                    if self.websocket_server_options.ca_certificate:
+                        client_cert_ = ssl.CERT_REQUIRED
+                    else:
+                        client_cert_ = ssl.CERT_NONE
                     socket_ = ssl.wrap_socket(socket_,
                         keyfile=self.websocket_server_options.private_key,
                         certfile=self.websocket_server_options.certificate,
-                        ssl_version=ssl.PROTOCOL_SSLv23)
+                        ssl_version=ssl.PROTOCOL_SSLv23,
+                        ca_certs=self.websocket_server_options.ca_certificate,
+                        cert_reqs=client_cert_)
                 if _HAS_OPEN_SSL:
                     ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
                     ctx.use_privatekey_file(
@@ -715,6 +735,9 @@
                       default='', help='TLS private key file.')
     parser.add_option('-c', '--certificate', dest='certificate',
                       default='', help='TLS certificate file.')
+    parser.add_option('--ca-certificate', dest='ca_certificate', default='',
+                      help=('TLS CA certificate file for client '
+                            'authentication.'))
     parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
                       default='', help='Log file.')
     # Custom log level:
@@ -854,6 +877,13 @@
                     'To use TLS, specify private_key and certificate.')
             sys.exit(1)
 
+    if options.ca_certificate:
+        if not options.use_tls:
+            logging.critical('TLS must be enabled for client authentication.')
+            sys.exit(1)
+        if not _HAS_SSL:
+            logging.critical('Client authentication requires ssl.')
+
     if not options.scan_dir:
         options.scan_dir = options.websock_handlers
 
diff --git a/src/test/cert/cacert.pem b/src/test/cert/cacert.pem
new file mode 100644
index 0000000..4dadae1
--- /dev/null
+++ b/src/test/cert/cacert.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvDCCAiWgAwIBAgIJAKqVghkGF1rSMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
+BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQxFDAS
+BgNVBAMTC3B5d2Vic29ja2V0MB4XDTEyMDYwNjA3MjQzM1oXDTM5MTAyMzA3MjQz
+M1owSTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMRQwEgYDVQQKEwtweXdl
+YnNvY2tldDEUMBIGA1UEAxMLcHl3ZWJzb2NrZXQwgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBAKoSEW2biQxVrMMKdn/8PJzDYiSXDPR9WQbLRRQ1Gm5jkCYiahXW
+u2CbTThfPPfi2NHA3I+HlT7gO9yR7RVUvN6ISUzGwXDEq4f4UNqtQOhQaqqK+CZ9
+LO/BhO/YYfNrbSPlYzHUKaT9ese7xO9VzVKLW+qUf2Mjh4/+SzxBDNP7AgMBAAGj
+gaswgagwHQYDVR0OBBYEFOsWdxCSuyhwaZeab6BoTho3++bzMHkGA1UdIwRyMHCA
+FOsWdxCSuyhwaZeab6BoTho3++bzoU2kSzBJMQswCQYDVQQGEwJKUDEOMAwGA1UE
+CBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtweXdlYnNv
+Y2tldIIJAKqVghkGF1rSMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
+gsMI1WEYqNw/jhUIdrTBcCxJ0X6hJvA9ziKANVm1Rs+4P3YDArkQ8bCr6xY+Kw7s
+Zp0yE7dM8GMdi+DU6hL3t3E5eMkTS1yZr9WCK4f2RLo+et98selZydpHemF3DJJ3
+gAj8Sx4LBaG8Cb/WnEMPv3MxG3fBE5favF6V4jU07hQ=
+-----END CERTIFICATE-----
diff --git a/src/test/cert/cert.pem b/src/test/cert/cert.pem
new file mode 100644
index 0000000..25379a7
--- /dev/null
+++ b/src/test/cert/cert.pem
@@ -0,0 +1,61 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket
+        Validity
+            Not Before: Jun  6 07:25:08 2012 GMT
+            Not After : Oct 23 07:25:08 2039 GMT
+        Subject: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:de:10:ce:3a:5a:04:a4:1c:29:93:5c:23:82:1a:
+                    f2:06:01:e6:2b:a4:0f:dd:77:49:76:89:03:a2:21:
+                    de:04:75:c6:e2:dd:fb:35:27:3a:a2:92:8e:12:62:
+                    2b:3e:1f:f4:78:df:b6:94:cb:27:d6:cb:d6:37:d7:
+                    5c:08:f0:09:3e:c9:ce:24:2d:00:c9:df:4a:e0:99:
+                    e5:fb:23:a9:e2:d6:c9:3d:96:fa:01:88:de:5a:89:
+                    b0:cf:03:67:6f:04:86:1d:ef:62:1c:55:a9:07:9a:
+                    2e:66:2a:73:5b:4c:62:03:f9:82:83:db:68:bf:b8:
+                    4b:0b:8b:93:11:b8:54:73:7b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Cert Type: 
+                SSL Server
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                82:A1:73:8B:16:0C:7C:E4:D3:46:95:13:95:1A:32:C1:84:E9:06:00
+            X509v3 Authority Key Identifier: 
+                keyid:EB:16:77:10:92:BB:28:70:69:97:9A:6F:A0:68:4E:1A:37:FB:E6:F3
+
+    Signature Algorithm: sha1WithRSAEncryption
+        6b:b3:46:29:02:df:b0:c8:8e:c4:d7:7f:a0:1e:0d:1a:eb:2f:
+        df:d1:48:57:36:5f:95:8c:1b:f0:51:d6:52:e7:8d:84:3b:9f:
+        d8:ed:22:9c:aa:bd:ee:9b:90:1d:84:a3:4c:0b:cb:eb:64:73:
+        ba:f7:15:ce:da:5f:db:8b:15:07:a6:28:7f:b9:8c:11:9b:64:
+        d3:f1:be:52:4f:c3:d8:58:fe:de:56:63:63:3b:51:ed:a7:81:
+        f9:05:51:70:63:32:09:0e:94:7e:05:fe:a1:56:18:34:98:d5:
+        99:1e:4e:27:38:89:90:6a:e5:ce:60:35:01:f5:de:34:60:b1:
+        cb:ae
+-----BEGIN CERTIFICATE-----
+MIICmDCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJKUDEO
+MAwGA1UECBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtw
+eXdlYnNvY2tldDAeFw0xMjA2MDYwNzI1MDhaFw0zOTEwMjMwNzI1MDhaMEkxCzAJ
+BgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQx
+FDASBgNVBAMTC3B5d2Vic29ja2V0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1Jzqiko4SYis+
+H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoBiN5aibDPA2dv
+BIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQABo4GPMIGMMAkG
+A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMCwGCWCGSAGG+EIBDQQfFh1PcGVu
+U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUgqFzixYMfOTTRpUT
+lRoywYTpBgAwHwYDVR0jBBgwFoAU6xZ3EJK7KHBpl5pvoGhOGjf75vMwDQYJKoZI
+hvcNAQEFBQADgYEAa7NGKQLfsMiOxNd/oB4NGusv39FIVzZflYwb8FHWUueNhDuf
+2O0inKq97puQHYSjTAvL62RzuvcVztpf24sVB6Yof7mMEZtk0/G+Uk/D2Fj+3lZj
+YztR7aeB+QVRcGMyCQ6UfgX+oVYYNJjVmR5OJziJkGrlzmA1AfXeNGCxy64=
+-----END CERTIFICATE-----
diff --git a/src/test/cert/client_cert.p12 b/src/test/cert/client_cert.p12
new file mode 100644
index 0000000..14e1399
--- /dev/null
+++ b/src/test/cert/client_cert.p12
Binary files differ
diff --git a/src/test/cert/key.pem b/src/test/cert/key.pem
new file mode 100644
index 0000000..fae8583
--- /dev/null
+++ b/src/test/cert/key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1
+Jzqiko4SYis+H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoB
+iN5aibDPA2dvBIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQAB
+AoGBAIuCuV1Vcnb7rm8CwtgZP5XgmY8vSjxTldafa6XvawEYUTP0S77v/1llg1Yv
+UIV+I+PQgG9oVoYOl22LoimHS/Z3e1fsot5tDYszGe8/Gkst4oaReSoxvBUa6WXp
+QSo7YFCajuHtE+W/gzF+UHbdzzXIDjQZ314LNF5t+4UnsEPBAkEA+girImqWoM2t
+3UR8f8oekERwsmEMf9DH5YpH4cvUnvI+kwesC/r2U8Sho++fyEMUNm7aIXGqNLga
+ogAM+4NX4QJBAONdSxSay22egTGNoIhLndljWkuOt/9FWj2klf/4QxD4blMJQ5Oq
+QdOGAh7nVQjpPLQ5D7CBVAKpGM2CD+QJBtsCQEP2kz35pxPylG3urcC2mfQxBkkW
+ZCViBNP58GwJ0bOauTOSBEwFXWuLqTw8aDwxL49UNmqc0N0fpe2fAehj3UECQQCm
+FH/DjU8Lw7ybddjNtm6XXPuYNagxz3cbkB4B3FchDleIUDwMoVF0MW9bI5/54mV1
+QDk1tUKortxvQZJaAD4BAkEAhGOHQqPd6bBBoFBvpaLzPJMxwLKrB+Wtkq/QlC72
+ClRiMn2g8SALiIL3BDgGXKcKE/Wy7jo/af/JCzQ/cPqt/A==
+-----END RSA PRIVATE KEY-----