aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/https.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki/https.py')
-rw-r--r--rpkid/rpki/https.py146
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()