RPKI Engine 1.0

rootd.py (3793)

Go to the documentation of this file.
00001 """
00002 Trivial RPKI up-down protocol root server, for testing.  Not suitable
00003 for production use.  Overrides a bunch of method definitions from the
00004 rpki.* classes in order to reuse as much code as possible.
00005 
00006 Usage: python rootd.py [ { -c | --config } configfile ] [ { -h | --help } ]
00007 
00008 $Id: rootd.py 3793 2011-04-27 04:34:52Z sra $
00009 
00010 Copyright (C) 2009--2010  Internet Systems Consortium ("ISC")
00011 
00012 Permission to use, copy, modify, and distribute this software for any
00013 purpose with or without fee is hereby granted, provided that the above
00014 copyright notice and this permission notice appear in all copies.
00015 
00016 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00017 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00018 AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00019 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00020 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00021 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00022 PERFORMANCE OF THIS SOFTWARE.
00023 
00024 Portions copyright (C) 2007--2008  American Registry for Internet Numbers ("ARIN")
00025 
00026 Permission to use, copy, modify, and distribute this software for any
00027 purpose with or without fee is hereby granted, provided that the above
00028 copyright notice and this permission notice appear in all copies.
00029 
00030 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00031 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00032 AND FITNESS.  IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00033 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00034 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00035 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00036 PERFORMANCE OF THIS SOFTWARE.
00037 """
00038 
00039 import os, time, getopt, sys
00040 import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509
00041 import rpki.http, rpki.config, rpki.exceptions, rpki.relaxng
00042 import rpki.sundial, rpki.log
00043 
00044 rootd = None
00045 
00046 class list_pdu(rpki.up_down.list_pdu):
00047   def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
00048     r_msg.payload = rpki.up_down.list_response_pdu()
00049     rootd.compose_response(r_msg)
00050     callback()
00051 
00052 class issue_pdu(rpki.up_down.issue_pdu):
00053   def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
00054     self.pkcs10.check_valid_rpki()
00055     r_msg.payload = rpki.up_down.issue_response_pdu()
00056     rootd.compose_response(r_msg, self.pkcs10)
00057     callback()
00058 
00059 class revoke_pdu(rpki.up_down.revoke_pdu):
00060   def serve_pdu(self, q_msg, r_msg, ignored, callback, errback):
00061     rootd.subject_cert = get_subject_cert()
00062     if subject_cert is None or subject_cert.gSKI() != self.ski:
00063       raise rpki.exceptions.NotInDatabase
00064     rootd.del_subject_cert()
00065     rootd.del_subject_pkcs10()
00066     r_msg.payload = rpki.up_down.revoke_response_pdu()
00067     r_msg.payload.class_name = self.class_name
00068     r_msg.payload.ski = self.ski
00069     callback()
00070 
00071 class message_pdu(rpki.up_down.message_pdu):
00072 
00073   name2type = {
00074     "list"            : list_pdu,
00075     "list_response"   : rpki.up_down.list_response_pdu,
00076     "issue"           : issue_pdu,
00077     "issue_response"  : rpki.up_down.issue_response_pdu,
00078     "revoke"          : revoke_pdu,
00079     "revoke_response" : rpki.up_down.revoke_response_pdu,
00080     "error_response"  : rpki.up_down.error_response_pdu }
00081 
00082   type2name = dict((v, k) for k, v in name2type.items())
00083 
00084   def log_query(self, child):
00085     """
00086     Log query we're handling.
00087     """
00088     rpki.log.info("Serving %s query" % self.type)
00089 
00090 class sax_handler(rpki.up_down.sax_handler):
00091   pdu = message_pdu
00092 
00093 class cms_msg(rpki.up_down.cms_msg):
00094   saxify = sax_handler.saxify
00095 
00096 class main(object):
00097 
00098   rpki_root_cert = None
00099 
00100   def get_root_cert(self):
00101     rpki.log.debug("Read root cert %s" % self.rpki_root_cert_file)
00102     self.rpki_root_cert = rpki.x509.X509(Auto_file = self.rpki_root_cert_file)
00103 
00104   def root_newer_than_subject(self):
00105     return os.stat(self.rpki_root_cert_file).st_mtime > os.stat(self.rpki_root_dir + self.rpki_subject_cert).st_mtime
00106 
00107   def get_subject_cert(self):
00108     filename = self.rpki_root_dir + self.rpki_subject_cert
00109     try:
00110       x = rpki.x509.X509(Auto_file = filename)
00111       rpki.log.debug("Read subject cert %s" % filename)
00112       return x
00113     except IOError:
00114       return None
00115 
00116   def set_subject_cert(self, cert):
00117     filename = self.rpki_root_dir + self.rpki_subject_cert
00118     rpki.log.debug("Writing subject cert %s, SKI %s" % (filename, cert.hSKI()))
00119     f = open(filename, "wb")
00120     f.write(cert.get_DER())
00121     f.close()
00122 
00123   def del_subject_cert(self):
00124     filename = self.rpki_root_dir + self.rpki_subject_cert
00125     rpki.log.debug("Deleting subject cert %s" % filename)
00126     os.remove(filename)
00127 
00128   def get_subject_pkcs10(self):
00129     try:
00130       x = rpki.x509.PKCS10(Auto_file = self.rpki_subject_pkcs10)
00131       rpki.log.debug("Read subject PKCS #10 %s" % self.rpki_subject_pkcs10)
00132       return x
00133     except IOError:
00134       return None
00135 
00136   def set_subject_pkcs10(self, pkcs10):
00137     rpki.log.debug("Writing subject PKCS #10 %s" % self.rpki_subject_pkcs10)
00138     f = open(self.rpki_subject_pkcs10, "wb")
00139     f.write(pkcs10.get_DER())
00140     f.close()
00141 
00142   def del_subject_pkcs10(self):
00143     rpki.log.debug("Deleting subject PKCS #10 %s" % self.rpki_subject_pkcs10)
00144     try:
00145       os.remove(self.rpki_subject_pkcs10)
00146     except OSError:
00147       pass
00148 
00149   def issue_subject_cert_maybe(self, new_pkcs10):
00150     now = rpki.sundial.now()
00151     subject_cert = self.get_subject_cert()
00152     old_pkcs10 = self.get_subject_pkcs10()
00153     if new_pkcs10 is not None and new_pkcs10 != old_pkcs10:
00154       self.set_subject_pkcs10(new_pkcs10)
00155       if subject_cert is not None:
00156         rpki.log.debug("PKCS #10 changed, regenerating subject certificate")
00157         subject_cert = None
00158     if subject_cert is not None and subject_cert.getNotAfter() <= now + self.rpki_subject_regen:
00159       rpki.log.debug("Subject certificate has reached expiration threshold, regenerating")
00160       subject_cert = None
00161     if subject_cert is not None and self.root_newer_than_subject():
00162       rpki.log.debug("Root certificate has changed, regenerating subject")
00163       subject_cert = None
00164     self.get_root_cert()
00165     if subject_cert is not None:
00166       return subject_cert
00167     pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10
00168     if pkcs10 is None:
00169       rpki.log.debug("No PKCS #10 request, can't generate subject certificate yet")
00170       return None
00171     resources = self.rpki_root_cert.get_3779resources()
00172     rpki.log.info("Generating subject cert with resources " + str(resources))
00173     req_key = pkcs10.getPublicKey()
00174     req_sia = pkcs10.get_SIA()
00175     crldp = self.rpki_base_uri + self.rpki_root_crl
00176     serial = now.totimestamp()
00177     subject_cert = self.rpki_root_cert.issue(
00178       keypair     = self.rpki_root_key,
00179       subject_key = req_key,
00180       serial      = serial,
00181       sia         = req_sia,
00182       aia         = self.rpki_root_cert_uri,
00183       crldp       = crldp,
00184       resources   = resources,
00185       notAfter    = now + self.rpki_subject_lifetime)
00186     crl = rpki.x509.CRL.generate(
00187       keypair             = self.rpki_root_key,
00188       issuer              = self.rpki_root_cert,
00189       serial              = serial,
00190       thisUpdate          = now,
00191       nextUpdate          = now + self.rpki_subject_lifetime,
00192       revokedCertificates = ())
00193     rpki.log.debug("Writing CRL %s" % (self.rpki_root_dir + self.rpki_root_crl))
00194     f = open(self.rpki_root_dir + self.rpki_root_crl, "wb")
00195     f.write(crl.get_DER())
00196     f.close()
00197     manifest_resources = rpki.resource_set.resource_bag.from_inheritance()
00198     manifest_keypair = rpki.x509.RSA.generate()
00199     manifest_cert = self.rpki_root_cert.issue(
00200       keypair     = self.rpki_root_key,
00201       subject_key = manifest_keypair.get_RSApublic(),
00202       serial      = serial + 1,
00203       sia         = None,
00204       aia         = self.rpki_root_cert_uri,
00205       crldp       = crldp,
00206       resources   = manifest_resources,
00207       notAfter    = now + self.rpki_subject_lifetime,
00208       is_ca       = False)
00209     manifest = rpki.x509.SignedManifest.build(
00210       serial         = serial,
00211       thisUpdate     = now,
00212       nextUpdate     = now + self.rpki_subject_lifetime,
00213       names_and_objs = [(self.rpki_subject_cert, subject_cert), (self.rpki_root_crl, crl)],
00214       keypair        = manifest_keypair,
00215       certs          = manifest_cert)
00216     rpki.log.debug("Writing manifest %s" % (self.rpki_root_dir + self.rpki_root_manifest))
00217     f = open(self.rpki_root_dir + self.rpki_root_manifest, "wb")
00218     f.write(manifest.get_DER())
00219     f.close()
00220     self.set_subject_cert(subject_cert)
00221     return subject_cert
00222 
00223   def compose_response(self, r_msg, pkcs10 = None):
00224     subject_cert = self.issue_subject_cert_maybe(pkcs10)
00225     rc = rpki.up_down.class_elt()
00226     rc.class_name = self.rpki_class_name
00227     rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri)
00228     rc.from_resource_bag(self.rpki_root_cert.get_3779resources())
00229     rc.issuer = self.rpki_root_cert
00230     r_msg.payload.classes.append(rc)
00231     if subject_cert is not None:
00232       rc.certs.append(rpki.up_down.certificate_elt())
00233       rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_base_uri + self.rpki_subject_cert)
00234       rc.certs[0].cert = subject_cert
00235 
00236   def up_down_handler(self, query, path, cb):
00237     try:
00238       q_msg = cms_msg(DER = query).unwrap((self.bpki_ta, self.child_bpki_cert))
00239     except (rpki.async.ExitNow, SystemExit):
00240       raise
00241     except Exception, e:
00242       rpki.log.traceback()
00243       return cb(400, reason = "Could not process PDU: %s" % e)
00244 
00245     def done(r_msg):
00246       cb(200, body = cms_msg().wrap(r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl))
00247 
00248     try:
00249       q_msg.serve_top_level(None, done)
00250     except (rpki.async.ExitNow, SystemExit):
00251       raise
00252     except Exception, e:
00253       rpki.log.traceback()
00254       try:
00255         done(q_msg.serve_error(e))
00256       except (rpki.async.ExitNow, SystemExit):
00257         raise
00258       except Exception, e:
00259         rpki.log.traceback()
00260         cb(500, reason = "Could not process PDU: %s" % e)
00261 
00262   def __init__(self):
00263 
00264     global rootd
00265     rootd = self                        # Gross, but simpler than what we'd have to do otherwise
00266 
00267     os.environ["TZ"] = "UTC"
00268     time.tzset()
00269 
00270     self.cfg_file = None
00271 
00272     opts, argv = getopt.getopt(sys.argv[1:], "c:dh?", ["config=", "debug", "help"])
00273     for o, a in opts:
00274       if o in ("-h", "--help", "-?"):
00275         print __doc__
00276         sys.exit(0)
00277       elif o in ("-c", "--config"):
00278         self.cfg_file = a
00279       elif o in ("-d", "--debug"):
00280         rpki.log.use_syslog = False
00281     if argv:
00282       raise rpki.exceptions.CommandParseFailure, "Unexpected arguments %s" % argv
00283 
00284     rpki.log.init("rootd")
00285 
00286     self.cfg = rpki.config.parser(self.cfg_file, "rootd")
00287 
00288     self.cfg.set_global_flags()
00289 
00290     self.bpki_ta                 = rpki.x509.X509(Auto_file = self.cfg.get("bpki-ta"))
00291     self.rootd_bpki_key          = rpki.x509.RSA( Auto_file = self.cfg.get("rootd-bpki-key"))
00292     self.rootd_bpki_cert         = rpki.x509.X509(Auto_file = self.cfg.get("rootd-bpki-cert"))
00293     self.rootd_bpki_crl          = rpki.x509.CRL( Auto_file = self.cfg.get("rootd-bpki-crl"))
00294     self.child_bpki_cert         = rpki.x509.X509(Auto_file = self.cfg.get("child-bpki-cert"))
00295 
00296     self.http_server_host        = self.cfg.get("server-host", "")
00297     self.http_server_port        = int(self.cfg.get("server-port"))
00298 
00299     self.rpki_class_name         = self.cfg.get("rpki-class-name", "wombat")
00300 
00301     self.rpki_root_dir           = self.cfg.get("rpki-root-dir")
00302     self.rpki_base_uri           = self.cfg.get("rpki-base-uri", "rsync://" + self.rpki_class_name + ".invalid/")
00303 
00304     self.rpki_root_key           = rpki.x509.RSA( Auto_file = self.cfg.get("rpki-root-key"))
00305     self.rpki_root_cert_file     = self.cfg.get("rpki-root-cert")
00306     self.rpki_root_cert_uri      = self.cfg.get("rpki-root-cert-uri", self.rpki_base_uri + "Root.cer")
00307 
00308     self.rpki_root_manifest      = self.cfg.get("rpki-root-manifest", "Root.mnf")
00309     self.rpki_root_crl           = self.cfg.get("rpki-root-crl",      "Root.crl")
00310     self.rpki_subject_cert       = self.cfg.get("rpki-subject-cert",  "Child.cer")
00311     self.rpki_subject_pkcs10     = self.cfg.get("rpki-subject-pkcs10", "Child.pkcs10")
00312 
00313     self.rpki_subject_lifetime   = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-lifetime", "30d"))
00314     self.rpki_subject_regen      = rpki.sundial.timedelta.parse(self.cfg.get("rpki-subject-regen", self.rpki_subject_lifetime.convert_to_seconds() / 2))
00315 
00316     rpki.http.server(host     = self.http_server_host,
00317                      port     = self.http_server_port,
00318                      handlers = self.up_down_handler)
 All Classes Namespaces Files Functions Variables