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 Default configuration file is rootd.conf, override with --config option. 00009 00010 $Id: rootd.py 2571 2009-07-04 20:13:22Z sra $ 00011 00012 Copyright (C) 2009 Internet Systems Consortium ("ISC") 00013 00014 Permission to use, copy, modify, and distribute this software for any 00015 purpose with or without fee is hereby granted, provided that the above 00016 copyright notice and this permission notice appear in all copies. 00017 00018 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 00019 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 00020 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 00021 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 00022 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 00023 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 00024 PERFORMANCE OF THIS SOFTWARE. 00025 00026 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") 00027 00028 Permission to use, copy, modify, and distribute this software for any 00029 purpose with or without fee is hereby granted, provided that the above 00030 copyright notice and this permission notice appear in all copies. 00031 00032 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH 00033 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 00034 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, 00035 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 00036 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 00037 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 00038 PERFORMANCE OF THIS SOFTWARE. 00039 """ 00040 00041 import os, time, getopt, sys 00042 import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509 00043 import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng 00044 import rpki.sundial, rpki.log 00045 00046 def get_subject_cert(): 00047 filename = rpki_root_dir + rpki_subject_cert 00048 try: 00049 x = rpki.x509.X509(Auto_file = filename) 00050 rpki.log.debug("Read subject cert %s" % filename) 00051 return x 00052 except IOError: 00053 return None 00054 00055 def set_subject_cert(cert): 00056 filename = rpki_root_dir + rpki_subject_cert 00057 rpki.log.debug("Writing subject cert %s" % filename) 00058 f = open(filename, "wb") 00059 f.write(cert.get_DER()) 00060 f.close() 00061 00062 def del_subject_cert(): 00063 filename = rpki_root_dir + rpki_subject_cert 00064 rpki.log.debug("Deleting subject cert %s" % filename) 00065 os.remove(filename) 00066 00067 def get_subject_pkcs10(): 00068 filename = rpki_subject_pkcs10 00069 try: 00070 x = rpki.x509.PKCS10(Auto_file = filename) 00071 rpki.log.debug("Read subject PKCS #10 %s" % filename) 00072 return x 00073 except IOError: 00074 return None 00075 00076 def set_subject_pkcs10(pkcs10): 00077 rpki.log.debug("Writing subject PKCS #10 %s" % rpki_subject_pkcs10) 00078 f = open(rpki_subject_pkcs10, "wb") 00079 f.write(pkcs10.get_DER()) 00080 f.close() 00081 00082 def issue_subject_cert_maybe(): 00083 now = rpki.sundial.now() 00084 subject_cert = get_subject_cert() 00085 if subject_cert is not None: 00086 if subject_cert.getNotAfter() > now + rpki_subject_regen: 00087 return subject_cert 00088 rpki.log.debug("Subject certificate has reached expiration threshold, regenerating") 00089 pkcs10 = get_subject_pkcs10() 00090 if pkcs10 is None: 00091 rpki.log.debug("No saved PKCS #10 request") 00092 return None 00093 resources = rpki_root_cert.get_3779resources() 00094 rpki.log.info("Generating subject cert with resources " + str(resources)) 00095 req_key = pkcs10.getPublicKey() 00096 req_sia = pkcs10.get_SIA() 00097 crldp = rpki_base_uri + rpki_root_crl 00098 subject_cert = rpki_root_cert.issue( 00099 keypair = rpki_root_key, 00100 subject_key = req_key, 00101 serial = int(time.time()), 00102 sia = req_sia, 00103 aia = rpki_root_cert_uri, 00104 crldp = crldp, 00105 resources = resources, 00106 notAfter = now + rpki_subject_lifetime) 00107 crl = rpki.x509.CRL.generate( 00108 keypair = rpki_root_key, 00109 issuer = rpki_root_cert, 00110 serial = 1, 00111 thisUpdate = now, 00112 nextUpdate = now + rpki_subject_lifetime, 00113 revokedCertificates = ()) 00114 rpki.log.debug("Writing CRL %s" % (rpki_root_dir + rpki_root_crl)) 00115 f = open(rpki_root_dir + rpki_root_crl, "wb") 00116 f.write(crl.get_DER()) 00117 f.close() 00118 manifest_resources = rpki.resource_set.resource_bag( 00119 asn = rpki.resource_set.resource_set_as("<inherit>"), 00120 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"), 00121 v6 = rpki.resource_set.resource_set_ipv6("<inherit>")) 00122 manifest_keypair = rpki.x509.RSA.generate() 00123 manifest_cert = rpki_root_cert.issue( 00124 keypair = rpki_root_key, 00125 subject_key = manifest_keypair.get_RSApublic(), 00126 serial = int(time.time()) + 1, 00127 sia = None, 00128 aia = rpki_root_cert_uri, 00129 crldp = crldp, 00130 resources = manifest_resources, 00131 notAfter = now + rpki_subject_lifetime, 00132 is_ca = False) 00133 manifest = rpki.x509.SignedManifest.build( 00134 serial = int(time.time()), 00135 thisUpdate = now, 00136 nextUpdate = now + rpki_subject_lifetime, 00137 names_and_objs = [(rpki_subject_cert, subject_cert), (rpki_root_crl, crl)], 00138 keypair = manifest_keypair, 00139 certs = manifest_cert) 00140 rpki.log.debug("Writing manifest %s" % (rpki_root_dir + rpki_root_manifest)) 00141 f = open(rpki_root_dir + rpki_root_manifest, "wb") 00142 f.write(manifest.get_DER()) 00143 f.close() 00144 set_subject_cert(subject_cert) 00145 return subject_cert 00146 00147 def compose_response(r_msg): 00148 rc = rpki.up_down.class_elt() 00149 rc.class_name = rpki_class_name 00150 rc.cert_url = rpki.up_down.multi_uri(rpki_root_cert_uri) 00151 rc.from_resource_bag(rpki_root_cert.get_3779resources()) 00152 rc.issuer = rpki_root_cert 00153 r_msg.payload.classes.append(rc) 00154 subject_cert = issue_subject_cert_maybe() 00155 if subject_cert is not None: 00156 rc.certs.append(rpki.up_down.certificate_elt()) 00157 rc.certs[0].cert_url = rpki.up_down.multi_uri(rpki_base_uri + rpki_subject_cert) 00158 rc.certs[0].cert = subject_cert 00159 00160 class list_pdu(rpki.up_down.list_pdu): 00161 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00162 r_msg.payload = rpki.up_down.list_response_pdu() 00163 compose_response(r_msg) 00164 callback() 00165 00166 class issue_pdu(rpki.up_down.issue_pdu): 00167 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00168 self.pkcs10.check_valid_rpki() 00169 set_subject_pkcs10(self.pkcs10) 00170 r_msg.payload = rpki.up_down.issue_response_pdu() 00171 compose_response(r_msg) 00172 callback() 00173 00174 class revoke_pdu(rpki.up_down.revoke_pdu): 00175 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00176 subject_cert = get_subject_cert() 00177 if subject_cert is None or subject_cert.gSKI() != self.ski: 00178 raise rpki.exceptions.NotInDatabase 00179 del_subject_cert() 00180 r_msg.payload = rpki.up_down.revoke_response_pdu() 00181 r_msg.payload.class_name = self.class_name 00182 r_msg.payload.ski = self.ski 00183 callback() 00184 00185 class message_pdu(rpki.up_down.message_pdu): 00186 name2type = { 00187 "list" : list_pdu, 00188 "list_response" : rpki.up_down.list_response_pdu, 00189 "issue" : issue_pdu, 00190 "issue_response" : rpki.up_down.issue_response_pdu, 00191 "revoke" : revoke_pdu, 00192 "revoke_response" : rpki.up_down.revoke_response_pdu, 00193 "error_response" : rpki.up_down.error_response_pdu } 00194 type2name = dict((v, k) for k, v in name2type.items()) 00195 00196 class sax_handler(rpki.up_down.sax_handler): 00197 pdu = message_pdu 00198 00199 class cms_msg(rpki.up_down.cms_msg): 00200 saxify = sax_handler.saxify 00201 00202 def up_down_handler(query, path, cb): 00203 try: 00204 q_msg = cms_msg.unwrap(query, (bpki_ta, child_bpki_cert)) 00205 except (rpki.async.ExitNow, SystemExit): 00206 raise 00207 except Exception, data: 00208 rpki.log.traceback() 00209 return cb(400, "Could not process PDU: %s" % data) 00210 00211 def done(r_msg): 00212 r_cms = cms_msg.wrap(r_msg, rootd_bpki_key, rootd_bpki_cert, rootd_bpki_crl) 00213 cb(200, r_cms) 00214 00215 try: 00216 q_msg.serve_top_level(None, done) 00217 except (rpki.async.ExitNow, SystemExit): 00218 raise 00219 except Exception, data: 00220 rpki.log.traceback() 00221 try: 00222 done(q_msg.serve_error(data)) 00223 except (rpki.async.ExitNow, SystemExit): 00224 raise 00225 except Exception, data: 00226 rpki.log.traceback() 00227 cb(500, "Could not process PDU: %s" % data) 00228 00229 os.environ["TZ"] = "UTC" 00230 time.tzset() 00231 00232 rpki.log.init("rootd") 00233 00234 cfg_file = "rootd.conf" 00235 00236 opts, argv = getopt.getopt(sys.argv[1:], "c:h?", ["config=", "help"]) 00237 for o, a in opts: 00238 if o in ("-h", "--help", "-?"): 00239 print __doc__ 00240 sys.exit(0) 00241 if o in ("-c", "--config"): 00242 cfg_file = a 00243 if argv: 00244 raise RuntimeError, "Unexpected arguments %s" % argv 00245 00246 cfg = rpki.config.parser(cfg_file, "rootd") 00247 00248 bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta")) 00249 rootd_bpki_key = rpki.x509.RSA( Auto_file = cfg.get("rootd-bpki-key")) 00250 rootd_bpki_cert = rpki.x509.X509(Auto_file = cfg.get("rootd-bpki-cert")) 00251 rootd_bpki_crl = rpki.x509.CRL( Auto_file = cfg.get("rootd-bpki-crl")) 00252 child_bpki_cert = rpki.x509.X509(Auto_file = cfg.get("child-bpki-cert")) 00253 00254 https_server_host = cfg.get("server-host", "") 00255 https_server_port = int(cfg.get("server-port")) 00256 00257 rpki_class_name = cfg.get("rpki-class-name", "wombat") 00258 00259 rpki_root_dir = cfg.get("rpki-root-dir") 00260 rpki_base_uri = cfg.get("rpki-base-uri", "rsync://" + rpki_class_name + ".invalid/") 00261 00262 rpki_root_key = rpki.x509.RSA( Auto_file = cfg.get("rpki-root-key")) 00263 rpki_root_cert = rpki.x509.X509(Auto_file = cfg.get("rpki-root-cert")) 00264 rpki_root_cert_uri = cfg.get("rpki-root-cert-uri", rpki_base_uri + "Root.cer") 00265 00266 rpki_root_manifest = cfg.get("rpki-root-manifest", "Root.mnf") 00267 rpki_root_crl = cfg.get("rpki-root-crl", "Root.crl") 00268 rpki_subject_cert = cfg.get("rpki-subject-cert", "Subroot.cer") 00269 rpki_subject_pkcs10 = cfg.get("rpki-subject-pkcs10", "Subroot.pkcs10") 00270 00271 rpki_subject_lifetime = rpki.sundial.timedelta.parse(cfg.get("rpki-subject-lifetime", "30d")) 00272 rpki_subject_regen = rpki.sundial.timedelta.parse(cfg.get("rpki-subject-regen", rpki_subject_lifetime.convert_to_seconds() / 2)) 00273 00274 rpki.https.server(server_key = rootd_bpki_key, 00275 server_cert = rootd_bpki_cert, 00276 client_ta = (bpki_ta, child_bpki_cert), 00277 host = https_server_host, 00278 port = https_server_port, 00279 handlers = up_down_handler)