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 2913 2009-12-28 20:55:38Z 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 rpki_root_cert = None 00047 00048 def get_root_cert(): 00049 global rpki_root_cert 00050 rpki.log.debug("Read root cert %s" % rpki_root_cert_file) 00051 rpki_root_cert = rpki.x509.X509(Auto_file = rpki_root_cert_file) 00052 00053 def root_newer_than_subject(): 00054 return os.stat(rpki_root_cert_file).st_mtime > os.stat(rpki_root_dir + rpki_subject_cert).st_mtime 00055 00056 def get_subject_cert(): 00057 filename = rpki_root_dir + rpki_subject_cert 00058 try: 00059 x = rpki.x509.X509(Auto_file = filename) 00060 rpki.log.debug("Read subject cert %s" % filename) 00061 return x 00062 except IOError: 00063 return None 00064 00065 def set_subject_cert(cert): 00066 filename = rpki_root_dir + rpki_subject_cert 00067 rpki.log.debug("Writing subject cert %s, SKI %s" % (filename, cert.hSKI())) 00068 f = open(filename, "wb") 00069 f.write(cert.get_DER()) 00070 f.close() 00071 00072 def del_subject_cert(): 00073 filename = rpki_root_dir + rpki_subject_cert 00074 rpki.log.debug("Deleting subject cert %s" % filename) 00075 os.remove(filename) 00076 00077 def get_subject_pkcs10(): 00078 try: 00079 x = rpki.x509.PKCS10(Auto_file = rpki_subject_pkcs10) 00080 rpki.log.debug("Read subject PKCS #10 %s" % rpki_subject_pkcs10) 00081 return x 00082 except IOError: 00083 return None 00084 00085 def set_subject_pkcs10(pkcs10): 00086 rpki.log.debug("Writing subject PKCS #10 %s" % rpki_subject_pkcs10) 00087 f = open(rpki_subject_pkcs10, "wb") 00088 f.write(pkcs10.get_DER()) 00089 f.close() 00090 00091 def del_subject_pkcs10(): 00092 rpki.log.debug("Deleting subject PKCS #10 %s" % rpki_subject_pkcs10) 00093 try: 00094 os.remove(rpki_subject_pkcs10) 00095 except OSError: 00096 pass 00097 00098 def issue_subject_cert_maybe(new_pkcs10): 00099 now = rpki.sundial.now() 00100 subject_cert = get_subject_cert() 00101 old_pkcs10 = get_subject_pkcs10() 00102 if new_pkcs10 is not None and new_pkcs10 != old_pkcs10: 00103 set_subject_pkcs10(new_pkcs10) 00104 if subject_cert is not None: 00105 rpki.log.debug("PKCS #10 changed, regenerating subject certificate") 00106 subject_cert = None 00107 if subject_cert is not None and subject_cert.getNotAfter() <= now + rpki_subject_regen: 00108 rpki.log.debug("Subject certificate has reached expiration threshold, regenerating") 00109 subject_cert = None 00110 if subject_cert is not None and root_newer_than_subject(): 00111 rpki.log.debug("Root certificate has changed, regenerating subject") 00112 subject_cert = None 00113 get_root_cert() 00114 if subject_cert is not None: 00115 return subject_cert 00116 pkcs10 = old_pkcs10 if new_pkcs10 is None else new_pkcs10 00117 if pkcs10 is None: 00118 rpki.log.debug("No PKCS #10 request, can't generate subject certificate yet") 00119 return None 00120 resources = rpki_root_cert.get_3779resources() 00121 rpki.log.info("Generating subject cert with resources " + str(resources)) 00122 req_key = pkcs10.getPublicKey() 00123 req_sia = pkcs10.get_SIA() 00124 crldp = rpki_base_uri + rpki_root_crl 00125 serial = now.totimestamp() 00126 subject_cert = rpki_root_cert.issue( 00127 keypair = rpki_root_key, 00128 subject_key = req_key, 00129 serial = serial, 00130 sia = req_sia, 00131 aia = rpki_root_cert_uri, 00132 crldp = crldp, 00133 resources = resources, 00134 notAfter = now + rpki_subject_lifetime) 00135 crl = rpki.x509.CRL.generate( 00136 keypair = rpki_root_key, 00137 issuer = rpki_root_cert, 00138 serial = serial, 00139 thisUpdate = now, 00140 nextUpdate = now + rpki_subject_lifetime, 00141 revokedCertificates = ()) 00142 rpki.log.debug("Writing CRL %s" % (rpki_root_dir + rpki_root_crl)) 00143 f = open(rpki_root_dir + rpki_root_crl, "wb") 00144 f.write(crl.get_DER()) 00145 f.close() 00146 manifest_resources = rpki.resource_set.resource_bag( 00147 asn = rpki.resource_set.resource_set_as(rpki.resource_set.inherit_token), 00148 v4 = rpki.resource_set.resource_set_ipv4(rpki.resource_set.inherit_token), 00149 v6 = rpki.resource_set.resource_set_ipv6(rpki.resource_set.inherit_token)) 00150 manifest_keypair = rpki.x509.RSA.generate() 00151 manifest_cert = rpki_root_cert.issue( 00152 keypair = rpki_root_key, 00153 subject_key = manifest_keypair.get_RSApublic(), 00154 serial = serial + 1, 00155 sia = None, 00156 aia = rpki_root_cert_uri, 00157 crldp = crldp, 00158 resources = manifest_resources, 00159 notAfter = now + rpki_subject_lifetime, 00160 is_ca = False) 00161 manifest = rpki.x509.SignedManifest.build( 00162 serial = serial, 00163 thisUpdate = now, 00164 nextUpdate = now + rpki_subject_lifetime, 00165 names_and_objs = [(rpki_subject_cert, subject_cert), (rpki_root_crl, crl)], 00166 keypair = manifest_keypair, 00167 certs = manifest_cert) 00168 rpki.log.debug("Writing manifest %s" % (rpki_root_dir + rpki_root_manifest)) 00169 f = open(rpki_root_dir + rpki_root_manifest, "wb") 00170 f.write(manifest.get_DER()) 00171 f.close() 00172 set_subject_cert(subject_cert) 00173 return subject_cert 00174 00175 def compose_response(r_msg, pkcs10 = None): 00176 subject_cert = issue_subject_cert_maybe(pkcs10) 00177 rc = rpki.up_down.class_elt() 00178 rc.class_name = rpki_class_name 00179 rc.cert_url = rpki.up_down.multi_uri(rpki_root_cert_uri) 00180 rc.from_resource_bag(rpki_root_cert.get_3779resources()) 00181 rc.issuer = rpki_root_cert 00182 r_msg.payload.classes.append(rc) 00183 if subject_cert is not None: 00184 rc.certs.append(rpki.up_down.certificate_elt()) 00185 rc.certs[0].cert_url = rpki.up_down.multi_uri(rpki_base_uri + rpki_subject_cert) 00186 rc.certs[0].cert = subject_cert 00187 00188 class list_pdu(rpki.up_down.list_pdu): 00189 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00190 r_msg.payload = rpki.up_down.list_response_pdu() 00191 compose_response(r_msg) 00192 callback() 00193 00194 class issue_pdu(rpki.up_down.issue_pdu): 00195 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00196 self.pkcs10.check_valid_rpki() 00197 r_msg.payload = rpki.up_down.issue_response_pdu() 00198 compose_response(r_msg, self.pkcs10) 00199 callback() 00200 00201 class revoke_pdu(rpki.up_down.revoke_pdu): 00202 def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): 00203 subject_cert = get_subject_cert() 00204 if subject_cert is None or subject_cert.gSKI() != self.ski: 00205 raise rpki.exceptions.NotInDatabase 00206 del_subject_cert() 00207 del_subject_pkcs10() 00208 r_msg.payload = rpki.up_down.revoke_response_pdu() 00209 r_msg.payload.class_name = self.class_name 00210 r_msg.payload.ski = self.ski 00211 callback() 00212 00213 class message_pdu(rpki.up_down.message_pdu): 00214 00215 name2type = { 00216 "list" : list_pdu, 00217 "list_response" : rpki.up_down.list_response_pdu, 00218 "issue" : issue_pdu, 00219 "issue_response" : rpki.up_down.issue_response_pdu, 00220 "revoke" : revoke_pdu, 00221 "revoke_response" : rpki.up_down.revoke_response_pdu, 00222 "error_response" : rpki.up_down.error_response_pdu } 00223 00224 type2name = dict((v, k) for k, v in name2type.items()) 00225 00226 def log_query(self, child): 00227 """ 00228 Log query we're handling. 00229 """ 00230 rpki.log.info("Serving %s query" % self.type) 00231 00232 class sax_handler(rpki.up_down.sax_handler): 00233 pdu = message_pdu 00234 00235 class cms_msg(rpki.up_down.cms_msg): 00236 saxify = sax_handler.saxify 00237 00238 def up_down_handler(query, path, cb): 00239 try: 00240 q_msg = cms_msg.unwrap(query, (bpki_ta, child_bpki_cert)) 00241 except (rpki.async.ExitNow, SystemExit): 00242 raise 00243 except Exception, data: 00244 rpki.log.traceback() 00245 return cb(400, "Could not process PDU: %s" % data) 00246 00247 def done(r_msg): 00248 r_cms = cms_msg.wrap(r_msg, rootd_bpki_key, rootd_bpki_cert, rootd_bpki_crl) 00249 cb(200, r_cms) 00250 00251 try: 00252 q_msg.serve_top_level(None, done) 00253 except (rpki.async.ExitNow, SystemExit): 00254 raise 00255 except Exception, data: 00256 rpki.log.traceback() 00257 try: 00258 done(q_msg.serve_error(data)) 00259 except (rpki.async.ExitNow, SystemExit): 00260 raise 00261 except Exception, data: 00262 rpki.log.traceback() 00263 cb(500, "Could not process PDU: %s" % data) 00264 00265 os.environ["TZ"] = "UTC" 00266 time.tzset() 00267 00268 cfg_file = "rootd.conf" 00269 00270 opts, argv = getopt.getopt(sys.argv[1:], "c:dh?", ["config=", "debug", "help"]) 00271 for o, a in opts: 00272 if o in ("-h", "--help", "-?"): 00273 print __doc__ 00274 sys.exit(0) 00275 elif o in ("-c", "--config"): 00276 cfg_file = a 00277 elif o in ("-d", "--debug"): 00278 rpki.log.use_syslog = False 00279 if argv: 00280 raise RuntimeError, "Unexpected arguments %s" % argv 00281 00282 rpki.log.init("rootd") 00283 00284 cfg = rpki.config.parser(cfg_file, "rootd") 00285 00286 cfg.set_global_flags() 00287 00288 bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta")) 00289 rootd_bpki_key = rpki.x509.RSA( Auto_file = cfg.get("rootd-bpki-key")) 00290 rootd_bpki_cert = rpki.x509.X509(Auto_file = cfg.get("rootd-bpki-cert")) 00291 rootd_bpki_crl = rpki.x509.CRL( Auto_file = cfg.get("rootd-bpki-crl")) 00292 child_bpki_cert = rpki.x509.X509(Auto_file = cfg.get("child-bpki-cert")) 00293 00294 https_server_host = cfg.get("server-host", "") 00295 https_server_port = int(cfg.get("server-port")) 00296 00297 rpki_class_name = cfg.get("rpki-class-name", "wombat") 00298 00299 rpki_root_dir = cfg.get("rpki-root-dir") 00300 rpki_base_uri = cfg.get("rpki-base-uri", "rsync://" + rpki_class_name + ".invalid/") 00301 00302 rpki_root_key = rpki.x509.RSA( Auto_file = cfg.get("rpki-root-key")) 00303 rpki_root_cert_file = cfg.get("rpki-root-cert") 00304 rpki_root_cert_uri = cfg.get("rpki-root-cert-uri", rpki_base_uri + "Root.cer") 00305 00306 rpki_root_manifest = cfg.get("rpki-root-manifest", "Root.mnf") 00307 rpki_root_crl = cfg.get("rpki-root-crl", "Root.crl") 00308 rpki_subject_cert = cfg.get("rpki-subject-cert", "Child.cer") 00309 rpki_subject_pkcs10 = cfg.get("rpki-subject-pkcs10", "Child.pkcs10") 00310 00311 rpki_subject_lifetime = rpki.sundial.timedelta.parse(cfg.get("rpki-subject-lifetime", "30d")) 00312 rpki_subject_regen = rpki.sundial.timedelta.parse(cfg.get("rpki-subject-regen", rpki_subject_lifetime.convert_to_seconds() / 2)) 00313 00314 rpki.https.server(server_key = rootd_bpki_key, 00315 server_cert = rootd_bpki_cert, 00316 client_ta = (bpki_ta, child_bpki_cert), 00317 host = https_server_host, 00318 port = https_server_port, 00319 handlers = up_down_handler)