diff options
Diffstat (limited to 'rpkid/rpki/https.py')
-rw-r--r-- | rpkid/rpki/https.py | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/rpkid/rpki/https.py b/rpkid/rpki/https.py new file mode 100644 index 00000000..bca5a8b1 --- /dev/null +++ b/rpkid/rpki/https.py @@ -0,0 +1,146 @@ +# $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() |