From fe0bf509f528dbdc50c7182f81057c6a4e15e4bd Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Sat, 5 Apr 2014 22:42:12 +0000 Subject: Source tree reorg, phase 1. Almost everything moved, no file contents changed. svn path=/branches/tk685/; revision=5757 --- rpki/rootd.py | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 rpki/rootd.py (limited to 'rpki/rootd.py') diff --git a/rpki/rootd.py b/rpki/rootd.py new file mode 100644 index 00000000..43e84873 --- /dev/null +++ b/rpki/rootd.py @@ -0,0 +1,385 @@ +# $Id$ +# +# Copyright (C) 2013--2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC") +# Portions 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 notices and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +# ISC, OR 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. + +""" +Trivial RPKI up-down protocol root server. Not recommended for +production use. Overrides a bunch of method definitions from the +rpki.* classes in order to reuse as much code as possible. +""" + +import os +import time +import argparse +import sys +import rpki.resource_set +import rpki.up_down +import rpki.left_right +import rpki.x509 +import rpki.http +import rpki.config +import rpki.exceptions +import rpki.relaxng +import rpki.sundial +import rpki.log +import rpki.daemonize + +rootd = None + +class list_pdu(rpki.up_down.list_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + r_msg.payload = rpki.up_down.list_response_pdu() + rootd.compose_response(r_msg) + callback() + +class issue_pdu(rpki.up_down.issue_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + self.pkcs10.check_valid_request_ca() + r_msg.payload = rpki.up_down.issue_response_pdu() + rootd.compose_response(r_msg, self.pkcs10) + callback() + +class revoke_pdu(rpki.up_down.revoke_pdu): + def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): + rpki.log.debug("Revocation requested for SKI %s" % self.ski) + subject_cert = rootd.get_subject_cert() + if subject_cert is None: + rpki.log.debug("No subject certificate, nothing to revoke") + raise rpki.exceptions.NotInDatabase + if subject_cert.gSKI() != self.ski: + rpki.log.debug("Subject certificate has different SKI %s, not revoking" % subject_cert.gSKI()) + raise rpki.exceptions.NotInDatabase + rpki.log.debug("Revoking certificate %s" % self.ski) + now = rpki.sundial.now() + rootd.revoke_subject_cert(now) + rootd.del_subject_cert() + rootd.del_subject_pkcs10() + rootd.generate_crl_and_manifest(now) + r_msg.payload = rpki.up_down.revoke_response_pdu() + r_msg.payload.class_name = self.class_name + r_msg.payload.ski = self.ski + callback() + +class error_response_pdu(rpki.up_down.error_response_pdu): + exceptions = rpki.up_down.error_response_pdu.exceptions.copy() + exceptions[rpki.exceptions.ClassNameUnknown, revoke_pdu] = 1301 + exceptions[rpki.exceptions.NotInDatabase, revoke_pdu] = 1302 + +class message_pdu(rpki.up_down.message_pdu): + + name2type = { + "list" : list_pdu, + "list_response" : rpki.up_down.list_response_pdu, + "issue" : issue_pdu, + "issue_response" : rpki.up_down.issue_response_pdu, + "revoke" : revoke_pdu, + "revoke_response" : rpki.up_down.revoke_response_pdu, + "error_response" : error_response_pdu } + + type2name = dict((v, k) for k, v in name2type.items()) + + error_pdu_type = error_response_pdu + + def log_query(self, child): + """ + Log query we're handling. + """ + rpki.log.info("Serving %s query" % self.type) + +class sax_handler(rpki.up_down.sax_handler): + pdu = message_pdu + +class cms_msg(rpki.up_down.cms_msg): + saxify = sax_handler.saxify + +class main(object): + + def get_root_cert(self): + rpki.log.debug("Read root cert %s" % self.rpki_root_cert_file) + self.rpki_root_cert = rpki.x509.X509(Auto_file = self.rpki_root_cert_file) + + def root_newer_than_subject(self): + return os.stat(self.rpki_root_cert_file).st_mtime > \ + os.stat(os.path.join(self.rpki_root_dir, self.rpki_subject_cert)).st_mtime + + def get_subject_cert(self): + filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert) + try: + x = rpki.x509.X509(Auto_file = filename) + rpki.log.debug("Read subject cert %s" % filename) + return x + except IOError: + return None + + def set_subject_cert(self, cert): + filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert) + rpki.log.debug("Writing subject cert %s, SKI %s" % (filename, cert.hSKI())) + f = open(filename, "wb") + f.write(cert.get_DER()) + f.close() + + def del_subject_cert(self): + filename = os.path.join(self.rpki_root_dir, self.rpki_subject_cert) + rpki.log.debug("Deleting subject cert %s" % filename) + os.remove(filename) + + def get_subject_pkcs10(self): + try: + x = rpki.x509.PKCS10(Auto_file = self.rpki_subject_pkcs10) + rpki.log.debug("Read subject PKCS #10 %s" % self.rpki_subject_pkcs10) + return x + except IOError: + return None + + def set_subject_pkcs10(self, pkcs10): + rpki.log.debug("Writing subject PKCS #10 %s" % self.rpki_subject_pkcs10) + f = open(self.rpki_subject_pkcs10, "wb") + f.write(pkcs10.get_DER()) + f.close() + + def del_subject_pkcs10(self): + rpki.log.debug("Deleting subject PKCS #10 %s" % self.rpki_subject_pkcs10) + try: + os.remove(self.rpki_subject_pkcs10) + except OSError: + pass + + def issue_subject_cert_maybe(self, new_pkcs10): + now = rpki.sundial.now() + subject_cert = self.get_subject_cert() + old_pkcs10 = self.get_subject_pkcs10() + if new_pkcs10 is not None and new_pkcs10 != old_pkcs10: + self.set_subject_pkcs10(new_pkcs10) + if subject_cert is not None: + rpki.log.debug("PKCS #10 changed, regenerating subject certificate") + self.revoke_subject_cert(now) + subject_cert = None + if subject_cert is not None and subject_cert.getNotAfter() <= now + self.rpki_subject_regen: + rpki.log.debug("Subject certificate has reached expiration threshold, regenerating") + self.revoke_subject_cert(now) + subject_cert = None + if subject_cert is not None and self.root_newer_than_subject(): + rpki.log.debug("Root certificate has changed, regenerating subject") + self.revoke_subject_cert(now) + subject_cert = None + self.get_root_cert() + if subject_cert is not None: + return subject_cert + pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10 + if pkcs10 is None: + rpki.log.debug("No PKCS #10 request, can't generate subject certificate yet") + return None + resources = self.rpki_root_cert.get_3779resources() + notAfter = now + self.rpki_subject_lifetime + rpki.log.info("Generating subject cert %s with resources %s, expires %s" % ( + self.rpki_base_uri + self.rpki_subject_cert, resources, notAfter)) + req_key = pkcs10.getPublicKey() + req_sia = pkcs10.get_SIA() + self.next_serial_number() + subject_cert = self.rpki_root_cert.issue( + keypair = self.rpki_root_key, + subject_key = req_key, + serial = self.serial_number, + sia = req_sia, + aia = self.rpki_root_cert_uri, + crldp = self.rpki_base_uri + self.rpki_root_crl, + resources = resources, + notBefore = now, + notAfter = notAfter) + self.set_subject_cert(subject_cert) + self.generate_crl_and_manifest(now) + return subject_cert + + def generate_crl_and_manifest(self, now): + subject_cert = self.get_subject_cert() + self.next_serial_number() + self.next_crl_number() + while self.revoked and self.revoked[0][1] + 2 * self.rpki_subject_regen < now: + del self.revoked[0] + crl = rpki.x509.CRL.generate( + keypair = self.rpki_root_key, + issuer = self.rpki_root_cert, + serial = self.crl_number, + thisUpdate = now, + nextUpdate = now + self.rpki_subject_regen, + revokedCertificates = self.revoked) + rpki.log.debug("Writing CRL %s" % os.path.join(self.rpki_root_dir, self.rpki_root_crl)) + f = open(os.path.join(self.rpki_root_dir, self.rpki_root_crl), "wb") + f.write(crl.get_DER()) + f.close() + manifest_content = [(self.rpki_root_crl, crl)] + if subject_cert is not None: + manifest_content.append((self.rpki_subject_cert, subject_cert)) + manifest_resources = rpki.resource_set.resource_bag.from_inheritance() + manifest_keypair = rpki.x509.RSA.generate() + manifest_cert = self.rpki_root_cert.issue( + keypair = self.rpki_root_key, + subject_key = manifest_keypair.get_public(), + serial = self.serial_number, + sia = (None, None, self.rpki_base_uri + self.rpki_root_manifest), + aia = self.rpki_root_cert_uri, + crldp = self.rpki_base_uri + self.rpki_root_crl, + resources = manifest_resources, + notBefore = now, + notAfter = now + self.rpki_subject_lifetime, + is_ca = False) + manifest = rpki.x509.SignedManifest.build( + serial = self.crl_number, + thisUpdate = now, + nextUpdate = now + self.rpki_subject_regen, + names_and_objs = manifest_content, + keypair = manifest_keypair, + certs = manifest_cert) + rpki.log.debug("Writing manifest %s" % os.path.join(self.rpki_root_dir, self.rpki_root_manifest)) + f = open(os.path.join(self.rpki_root_dir, self.rpki_root_manifest), "wb") + f.write(manifest.get_DER()) + f.close() + + def revoke_subject_cert(self, now): + self.revoked.append((self.get_subject_cert().getSerial(), now)) + + def compose_response(self, r_msg, pkcs10 = None): + subject_cert = self.issue_subject_cert_maybe(pkcs10) + rc = rpki.up_down.class_elt() + rc.class_name = self.rpki_class_name + rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri) + rc.from_resource_bag(self.rpki_root_cert.get_3779resources()) + rc.issuer = self.rpki_root_cert + r_msg.payload.classes.append(rc) + if subject_cert is not None: + rc.certs.append(rpki.up_down.certificate_elt()) + rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_base_uri + self.rpki_subject_cert) + rc.certs[0].cert = subject_cert + + def up_down_handler(self, query, path, cb): + try: + q_cms = cms_msg(DER = query) + q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert)) + self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + return cb(400, reason = "Could not process PDU: %s" % e) + + def done(r_msg): + cb(200, body = cms_msg().wrap( + r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, + self.rootd_bpki_crl if self.include_bpki_crl else None)) + + try: + q_msg.serve_top_level(None, done) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + try: + done(q_msg.serve_error(e)) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + cb(500, reason = "Could not process PDU: %s" % e) + + + def next_crl_number(self): + if self.crl_number is None: + try: + crl = rpki.x509.CRL(DER_file = os.path.join(self.rpki_root_dir, self.rpki_root_crl)) + self.crl_number = crl.getCRLNumber() + except: # pylint: disable=W0702 + self.crl_number = 0 + self.crl_number += 1 + return self.crl_number + + + def next_serial_number(self): + if self.serial_number is None: + subject_cert = self.get_subject_cert() + if subject_cert is not None: + self.serial_number = subject_cert.getSerial() + 1 + else: + self.serial_number = 0 + self.serial_number += 1 + return self.serial_number + + + def __init__(self): + + global rootd + rootd = self # Gross, but simpler than what we'd have to do otherwise + + self.rpki_root_cert = None + self.serial_number = None + self.crl_number = None + self.revoked = [] + self.cms_timestamp = None + + os.environ["TZ"] = "UTC" + time.tzset() + + parser = argparse.ArgumentParser(description = __doc__) + parser.add_argument("-c", "--config", + help = "override default location of configuration file") + parser.add_argument("-d", "--debug", action = "store_true", + help = "enable debugging mode") + parser.add_argument("-f", "--foreground", action = "store_true", + help = "do not daemonize") + parser.add_argument("--pidfile", + help = "override default location of pid file") + args = parser.parse_args() + + rpki.log.init("rootd", use_syslog = not args.debug) + + self.cfg = rpki.config.parser(args.config, "rootd") + self.cfg.set_global_flags() + + if not args.foreground and not args.debug: + rpki.daemonize.daemon(pidfile = args.pidfile) + + self.bpki_ta = rpki.x509.X509(Auto_update = self.cfg.get("bpki-ta")) + self.rootd_bpki_key = rpki.x509.RSA( Auto_update = self.cfg.get("rootd-bpki-key")) + self.rootd_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("rootd-bpki-cert")) + self.rootd_bpki_crl = rpki.x509.CRL( Auto_update = self.cfg.get("rootd-bpki-crl")) + self.child_bpki_cert = rpki.x509.X509(Auto_update = self.cfg.get("child-bpki-cert")) + + self.http_server_host = self.cfg.get("server-host", "") + self.http_server_port = self.cfg.getint("server-port") + + self.rpki_class_name = self.cfg.get("rpki-class-name", "wombat") + + self.rpki_root_dir = self.cfg.get("rpki-root-dir") + self.rpki_base_uri = self.cfg.get("rpki-base-uri", "rsync://" + self.rpki_class_name + ".invalid/") + + self.rpki_root_key = rpki.x509.RSA(Auto_update = self.cfg.get("rpki-root-key")) + self.rpki_root_cert_file = self.cfg.get("rpki-root-cert") + self.rpki_root_cert_uri = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "root.cer") + + self.rpki_root_manifest = self.cfg.get("rpki-root-manifest", "root.mft") + self.rpki_root_crl = self.cfg.get("rpki-root-crl", "root.crl") + self.rpki_subject_cert = self.cfg.get("rpki-subject-cert", "child.cer") + self.rpki_subject_pkcs10 = self.cfg.get("rpki-subject-pkcs10", "child.pkcs10") + + self.rpki_subject_lifetime = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-lifetime", "8w")) + self.rpki_subject_regen = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen", self.rpki_subject_lifetime.convert_to_seconds() / 2)) + + self.include_bpki_crl = self.cfg.getboolean("include-bpki-crl", False) + + rpki.http.server(host = self.http_server_host, + port = self.http_server_port, + handlers = self.up_down_handler) -- cgit v1.2.3