Now generating certificates on the fly for https
authorJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Sun, 10 Jan 2010 00:41:22 +0000 (00:41 +0000)
committerJean-Michel Nirgal Vourgère <jmv@nirgal.com>
Sun, 10 Jan 2010 00:41:22 +0000 (00:41 +0000)
sproxy

diff --git a/sproxy b/sproxy
index aba7cde2597d2ffaad7cc37668d1df7776c7e2f5..489e491bc4754c52ef76c2b740c53330e2cb0ed2 100755 (executable)
--- a/sproxy
+++ b/sproxy
@@ -1,19 +1,13 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# To generate a certificate:
-# openssl req -nodes -new -x509 -keyout proxy.key -out proxy.crt -days 10000
-# openssl req -nodes -new -x509 -keyout proxy.key -out proxy.crt -days 10000 -subj "/O=Spy Proxy/CN=*" -newkey rsa:2048
-#
-# openssl req -nodes -new -subj "/CN=proxy" -days 10000 -keyout proxy.key -out proxy.csr
-# openssl ca -in proxy.csr -out proxy.crt -keyfile ca.key 
-
-
 import sys
+import os
 import logging
 from time import ctime
 import socket
 import threading
+import subprocess
 from gzip import GzipFile
 from StringIO import StringIO
 from OpenSSL import SSL
@@ -242,7 +236,17 @@ class HttpBase:
             logging.debug('-'*80)
         else:
             self.debug_dump_line1()
-
+    
+    def clean_hop_headers(self):
+        #remove any Proxy-* header, and hop by hop headers
+        i = 0
+        while i < len(self.headers):
+            key = self.headers[i][0].lower()
+            if key.startswith('proxy-') or key in ('connection', 'keep-alive', 'te', 'trailers', 'transfer-encoding', 'upgrade'):
+                del self.headers[i]
+            else:
+                i += 1
+    
 
 class HttpRequest(HttpBase):
     # default is no data for requests
@@ -298,6 +302,10 @@ class HttpRequest(HttpBase):
                 result += ':' + str(port)
             return result
 
+        if self.parsed_url.scheme and not self.parsed_url.netloc:
+            self.parsed_url = urlparse.ParseResult('', self.parsed_url.scheme, *self.parsed_url[2:])
+            logging.debug('emptying scheme for netloc')
+
         request_hostname = self.parsed_url.hostname
         request_port = self.parsed_url.port
         request_netloc = join_it(request_hostname, request_port)
@@ -321,16 +329,6 @@ class HttpRequest(HttpBase):
         if not self.parsed_url.scheme:
             self.parsed_url = urlparse.ParseResult(scheme, *self.parsed_url[1:])
 
-    def clean_hop_headers(self):
-        #remove any Proxy-* header, and hop by hop headers
-        i = 0
-        while i < len(self.headers):
-            key = self.headers[i][0].lower()
-            if key.startswith('proxy-') or key in ('connection', 'keep-alive', 'te', 'trailers', 'transfer-encoding', 'upgrade'):
-                del self.headers[i]
-            else:
-                i += 1
-    
     def check_headers_valid(self):
         if not self.http_method:
             raise HttpErrorResponse(400, 'Bad Request', 'Http method is required')
@@ -385,7 +383,11 @@ class HttpErrorResponse(HttpResponse):
         self.set_line1('HTTP/1.1 %s %s' % (errcode, errtitle))
         self.add_header_line('Server: Spy proxy')
         self.add_header_line('Date: %s' % ctime())
-        self.data = errmsg or errtitle
+        if errmsg is None:
+            self.data = errtitle
+        else:
+            self.data = errmsg
+        self.headers.append(('Content-Length', str(len(self.data))))
 
 
 def get_connected_sock(hostname, port):
@@ -446,8 +448,6 @@ def run_request_http(request):
 def run_request_https(request):
     sock = get_connected_sock(request.parsed_url.hostname, request.parsed_url.port or 443)
     ssl_context = SSL.Context(SSL.SSLv23_METHOD)
-    #ssl_context.use_privatekey_file ('certs/proxy.key')
-    #ssl_context.use_certificate_file('certs/proxy.crt')
     ssl_sock = SSL.Connection(ssl_context, sock)
     ssl_sock.set_connect_state()
     request.send_to(ssl_sock, abs_path=True)
@@ -458,6 +458,28 @@ def run_request_https(request):
     response.decompress_data()
     return response
 
+def make_https_sslcontext(hostname):
+    # To generate a certificate:
+    # openssl req -nodes -new -x509 -keyout certs/proxy.key -out certs/proxy.crt -days 10000
+    #
+    # openssl req -nodes -new -x509 -keyout certs/ca.key -out certs/ca.crt -days 10000 -subj "/O=Spy Proxy/CN=*" -newkey rsa:2048
+    #
+    # openssl req -nodes -new -subj "/CN=*.nirgal.com" -days 10000 -keyout certs/nirgal.com.key -out certs/nirgal.com.csr
+    # openssl x509 -req -in certs/nirgal.com.csr -out certs/nirgal.com.crt -CA certs/ca.crt -CAkey certs/ca.key [-CAcreateserial]
+    # openssl req -batch -nodes -new -subj "/CN=*.nirgal.com" -days 10000 -keyout certs/nirgal.com.key | openssl x509 -req -out certs/nirgal.com.crt -CA certs/ca.crt -CAkey certs/ca.key 
+
+    keyfile = os.path.join('certs', hostname+'.key')
+    crtfile = os.path.join('certs', hostname+'.crt')
+    csrfile = os.path.join('certs', hostname+'.csr')
+
+    ssl_context = SSL.Context(SSL.SSLv23_METHOD)
+    if not os.path.exists(keyfile) or not os.path.exists(crtfile):
+        logging.debug('Generating custom SSL certificates for %s', hostname)
+        subprocess.call(['openssl', 'req', '-nodes', '-new', '-subj', '/CN='+hostname, '-days', '10000', '-keyout', keyfile, '-out', csrfile])
+        subprocess.call(['openssl', 'x509', '-req', '-in', csrfile, '-out', crtfile, '-CA', 'certs/ca.crt', '-CAkey', 'certs/ca.key'])
+    ssl_context.use_privatekey_file (keyfile)
+    ssl_context.use_certificate_file(crtfile)
+    return ssl_context
 
 class ProxyConnectionIn(threading.Thread):
     def __init__(self, clientsocket):
@@ -500,6 +522,8 @@ class ProxyConnectionIn(threading.Thread):
                     request_in.debug_dump('REQUEST')
 
                     response = run_request_http(request_in)
+                    response.clean_hop_headers()
+                    response.headers.append(('Connection', 'close'))
 
                     response.debug_dump('RESPONSE')
 
@@ -509,12 +533,10 @@ class ProxyConnectionIn(threading.Thread):
                 logging.info("%s %s %s %s %s", request_in.http_method, request_in.parsed_url.geturl(), request_in.http_version, response.line1[9:12], len(response.data) or '-')
 
             elif request_in.http_method == 'CONNECT':
+                request_in.clean_host_request()
                 HttpErrorResponse(200, 'Proceed', '').send_to(self.clientsocket)
-                #self.clientsocket.send('HTTP/1.1 200 Proceed\r\n\r\n\r\n')
 
-                ssl_context = SSL.Context(SSL.SSLv23_METHOD)
-                ssl_context.use_privatekey_file ('certs/proxy.key')
-                ssl_context.use_certificate_file('certs/proxy.crt')
+                ssl_context = make_https_sslcontext(request_in.parsed_url.hostname)
                 ssl_sock = SSL.Connection(ssl_context, self.clientsocket)
                 ssl_sock.set_accept_state()
 
@@ -530,6 +552,8 @@ class ProxyConnectionIn(threading.Thread):
                 request_in_ssl.set_default_scheme('https')
 
                 response = run_request_https(request_in_ssl)
+                response.clean_hop_headers()
+                response.headers.append(('Connection', 'close'))
 
                 response.send_to(ssl_sock)
                 
@@ -626,6 +650,10 @@ if __name__ == '__main__':
         action='store_true', dest='log_full_transfers', default=False,
         help="log full requests and responses")
     
+    parser.add_option('--dump-length',
+        action='store', type='int', dest='dump_length', default=160,
+        help="length of data dump")
+    
     parser.add_option('-d', '--debug',
         action='store_true', dest='debug', default=False,
         help="debug mode")
@@ -642,10 +670,6 @@ if __name__ == '__main__':
         action='store_true', dest='debug_length', default=False,
         help="dump lengthes information")
     
-    parser.add_option('--dump-length',
-        action='store', type='int', dest='dump_length', default=160,
-        help="length of data dump")
-    
 
     options, args = parser.parse_args()
     main(*args)