remote pool_handle_t
[tlspool] / tool / https_proxy.py
1 #!/usr/bin/python
2 #
3 # https_proxy -- Pass on HTTPS connections but sit in between with the TLS Pool
4 #
5 # This program accepts the CONNECT request from RFC 2817.  The argument to this
6 # method is server:port, which will be used to connect, and set the server-side
7 # identity.  The TLS Pool is used twice; once for the connection to the server
8 # and once for the connection to the client after having reported 200 OK.  The
9 # latter connection will be signed on-the-fly.
10 #
11 # The intention of this program is to run for the single user of a desktop
12 # machine.  It is a proxy to support the client, not the server.  Specifically,
13 # it enables the user to replace local-software SSL/TLS stacks, and replace
14 # it with the implementation of the TLS Pool, including the extra security
15 # mechanisms, identity selection and centralised control that it offers.
16 #
17 # Regarding efficiency, the connections are established from Python, calling
18 # the TLS Pool for all the hard work.  Once the unwrapping and rewrapping of
19 # the connection has been setup, it is entirely left to the TLS Pool.  The
20 # price for the proxy then is an additional encryption and decryption, after
21 # the extra time to exchange two handshakes in sequence (as a result of how
22 # a HTTPS_PROXY has been specified to work in RFC2817).
23 # TODO: At present, the Python module tlspool.py is unsuitable for threading,
24 # and so this implementation takes one request at a time.  This can lead to
25 # additional sequencing of TLS handshakes, especially when multiple resources
26 # are accessed at the same time.  This is a matter of implementation of the
27 # Python module, which should probably be improved at some point.  The code
28 # below already holds a commented-out threading variation.
29 #
30 # From: Rick van Rein <rick@openfortress.nl>
31
32
33 import sys
34 import socket
35 import threading
36
37 import tlspool
38
39 if len (sys.argv) == 2:
40         tlspool.open_poolhandle (sys.argv [1])
41
42 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
43 from SocketServer import ThreadingMixIn
44
45
46 class Handler (BaseHTTPRequestHandler):
47
48         def do_GET (self):
49                 self.send_response (400, 'Bad Request')
50                 self.end_headers ()
51                 self.wfile.write ('Only use me to proxy HTTPS please!\r\n')
52                 return
53
54         do_POST = do_GET
55
56         def do_CONNECT (self):
57                 #
58                 # Parse the request line, CONNECT servername:port HTTP/1.1
59                 #
60                 try:
61                         servername, port = self.path.split (':')
62                         port = int (port)
63                 except:
64                         self.send_response (400, 'Bad Request')
65                         return
66                 #
67                 # Connect to the server
68                 #
69                 # srvtls = socket.socket (socket.AF_INET6, socket.SOCK_STREAM, 0)
70                 # if srvtls.connect_ex ( (servername, port) ) != 0:
71                 #       srvtls.close ()
72                 if True:
73                         srvtls = socket.socket (socket.AF_INET, socket.SOCK_STREAM, 0)
74                         if srvtls.connect_ex ( (servername, port) ) != 0:
75                                 srvtls.close ()
76                                 srvtls = -1
77                 if srvtls == -1:
78                         self.send_response (408, 'Request Timeout')
79                         return
80                 #
81                 # Start TLS on the server connection through the TLS Pool.
82                 # This is done without indicating or limiting the client
83                 # identity, and anything the TLS Pool wants to do to set
84                 # that is permitted.  This enables the user to benefit from
85                 # the TLS Pool's plethora of connection options.
86                 #
87                 #OLD# # Since the socket library implements these differently from
88                 #OLD# # common sockets (?!?) we shall apply Python's usual wrapper.
89                 #OLD# #
90                 #OLD# _srvtxt, _clitxt = socket.socketpair ()
91                 #OLD# srvtxt = socket._socketobject (_sock=_srvtxt)
92                 #OLD# clitxt = socket._socketobject (_sock=_clitxt)
93                 #OLD# print 'server TLS socket ::', type (srvtls)
94                 #OLD# print 'server TXT socket ::', type (srvtxt)
95                 # print 'server TXT plainfd =', srvtxt.fromfd
96                 #OLD# srvcnx = tlspool.Connection (cryptsocket=srvtls, plainsocket=srvtxt)
97                 srvcnx = tlspool.Connection (cryptsocket=srvtls)
98                 srvcnx.tlsdata.flags = ( tlspool.PIOF_STARTTLS_LOCALROLE_CLIENT |
99                                          tlspool.PIOF_STARTTLS_REMOTEROLE_SERVER |
100                                          tlspool.PIOF_STARTTLS_FORK |
101                                          tlspool.PIOF_STARTTLS_DETACH )
102                 srvcnx.tlsdata.remoteid = servername
103                 srvcnx.tlsdata.ipproto = socket.IPPROTO_TCP
104                 srvcnx.tlsdata.service = 'http'
105                 try:
106                         clitxt = srvcnx.starttls ()
107                 except:
108                         self.send_response (403, 'Forbidden')
109                         return
110                 #
111                 # Report the success of setting up the backend connection
112                 #
113                 self.send_response (200, 'Connection Established')
114                 self.end_headers ()
115                 #
116                 # Now pass the client connection through the TLS Pool too.
117                 # The plaintext connections from the client and server will
118                 # be connected.
119                 #
120                 clitls = self.wfile
121                 #OLD# print 'client TLS socket ::', type (clitls)
122                 #OLD# print 'client TXT socket ::', type (clitxt)
123                 clicnx = tlspool.Connection (cryptsocket=clitls, plainsocket=clitxt)
124                 clicnx.tlsdata.flags = ( tlspool.PIOF_STARTTLS_LOCALROLE_SERVER |
125                                          tlspool.PIOF_STARTTLS_REMOTEROLE_CLIENT |
126                                          tlspool.PIOF_STARTTLS_FORK |
127                                          tlspool.PIOF_STARTTLS_DETACH |
128                                          tlspool.PIOF_STARTTLS_IGNORE_REMOTEID |
129                                          tlspool.PIOF_STARTTLS_LOCALID_ONTHEFLY )
130                 clicnx.tlsdata.localid = servername
131                 clicnx.tlsdata.ipproto = socket.IPPROTO_TCP
132                 clicnx.tlsdata.service = 'http'
133                 try:
134                         clicnx.starttls ()
135                 finally:
136                         srvcnx.close ()
137
138
139 class ThreadedHTTPServer (ThreadingMixIn, HTTPServer):
140         """Handle requests in a separate thread."""
141         pass
142
143
144 #TODO# Use a plain server for now!
145 if __name__ == '__main__':
146         sockaddr = ('0.0.0.0', 8080)
147         server = ThreadedHTTPServer ( sockaddr, Handler)
148         print 'HTTPS proxy started on %s:%d -- stoppable with Ctrl-C' % sockaddr
149         server.serve_forever()
150