# $Id$ # Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. """HTTPS utilities, both client and server. At the moment this only knows how to use the PEM certs in my subversion repository; generalizing it would not be hard, but the more general version should use SQL anyway. """ import httplib, BaseHTTPServer, tlslite.api, glob, traceback, urlparse, socket import rpki.x509, rpki.exceptions, rpki.log rpki_content_type = "application/x-rpki" def client(msg, privateKey, certChain, x509TrustList, url, timeout = 300): """Open client HTTPS connection, send a message, wait for response. This function wraps most of what one needs to do to send a message over HTTPS and get a response. The certificate checking isn't quite up to snuff; it's better than with the other packages I've found, but doesn't appear to handle subjectAltName extensions (sigh). """ u = urlparse.urlparse(url) assert u.scheme in ("", "https") and \ u.username is None and \ u.password is None and \ u.params == "" and \ u.query == "" and \ u.fragment == "" # We could add a "settings = foo" argument to the following call to # pass in a tlslite.HandshakeSettings object that would let us # insist on, eg, particular SSL/TLS versions. httpc = tlslite.api.HTTPTLSConnection(host = u.hostname or "localhost", port = u.port or 443, privateKey = privateKey.get_tlslite(), certChain = certChain.tlslite_certChain(), x509TrustList = x509TrustList.tlslite_trustList()) httpc.connect() httpc.sock.settimeout(timeout) httpc.request("POST", u.path, msg, {"Content-Type" : rpki_content_type}) response = httpc.getresponse() if response.status == httplib.OK: return response.read() else: r = response.read() raise rpki.exceptions.HTTPRequestFailed, \ "HTTP request failed with status %s, response %s" % (response.status, r) class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """Derived type to supply POST handler and override logging.""" rpki_handlers = None # Subclass must bind def rpki_find_handler(self): """Helper method to search self.rpki_handlers.""" for s,h in self.rpki_handlers: if self.path.startswith(s): return h return None def do_POST(self): """POST handler.""" try: handler = self.rpki_find_handler() if self.headers["Content-Type"] != rpki_content_type: rcode, rtext = 415, "Received Content-Type %s, expected %s" \ % (self.headers["Content-Type"], rpki_content_type) elif handler is None: rcode, rtext = 404, "No handler found for URL " + self.path else: rcode, rtext = handler(query = self.rfile.read(int(self.headers["Content-Length"])), path = self.path) except Exception, edata: rpki.log.error(traceback.format_exc()) rcode, rtext = 500, "Unhandled exception %s" % edata self.send_response(rcode) self.send_header("Content-Type", rpki_content_type) self.end_headers() self.wfile.write(rtext) def log_message(self, format, *args): """Redirect HTTP server logging into our own logging system.""" if args: rpki.log.info(format % args) else: rpki.log.info(format) class httpServer(tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): """Derived type to handle TLS aspects of HTTPS.""" rpki_certChain = None rpki_privateKey = None rpki_sessionCache = None def handshake(self, tlsConnection): """TLS handshake handler.""" assert self.rpki_certChain is not None assert self.rpki_privateKey is not None assert self.rpki_sessionCache is not None try: # We could add a "settings = foo" argument to the following call # to pass in a tlslite.HandshakeSettings object that would let # us insist on, eg, particular SSL/TLS versions. tlsConnection.handshakeServer(certChain = self.rpki_certChain, privateKey = self.rpki_privateKey, sessionCache = self.rpki_sessionCache) tlsConnection.ignoreAbruptClose = True return True except tlslite.api.TLSError, error: rpki.log.warn("TLS handshake failure: " + str(error)) return False def server(handlers, privateKey, certChain, port = 4433, host = ""): """Run an HTTPS server and wait (forever) for connections.""" if not isinstance(handlers, (tuple, list)): handlers = (("/", handlers),) class boundRequestHandler(requestHandler): rpki_handlers = handlers httpd = httpServer((host, port), boundRequestHandler) httpd.rpki_privateKey = privateKey.get_tlslite() httpd.rpki_certChain = certChain.tlslite_certChain() httpd.rpki_sessionCache = tlslite.api.SessionCache() httpd.serve_forever()