00001 """
00002 Global context for rpkid.
00003
00004 $Id: rpki_engine.py 3449 2010-09-16 21:30:30Z sra $
00005
00006 Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
00007
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019
00020 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00021
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034
00035 import lxml.etree, re, random
00036 import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql
00037 import rpki.http, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log, rpki.async
00038
00039 class rpkid_context(object):
00040 """
00041 A container for various global rpkid parameters.
00042 """
00043
00044 def __init__(self, cfg):
00045
00046 self.sql = rpki.sql.session(cfg)
00047
00048 self.bpki_ta = rpki.x509.X509(Auto_update = cfg.get("bpki-ta"))
00049 self.irdb_cert = rpki.x509.X509(Auto_update = cfg.get("irdb-cert"))
00050 self.irbe_cert = rpki.x509.X509(Auto_update = cfg.get("irbe-cert"))
00051 self.rpkid_cert = rpki.x509.X509(Auto_update = cfg.get("rpkid-cert"))
00052 self.rpkid_key = rpki.x509.RSA( Auto_update = cfg.get("rpkid-key"))
00053
00054 self.irdb_url = cfg.get("irdb-url")
00055
00056 self.http_server_host = cfg.get("server-host", "")
00057 self.http_server_port = cfg.getint("server-port", 4433)
00058
00059 self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/")
00060
00061 self.use_internal_cron = cfg.getboolean("use-internal-cron", True)
00062
00063 self.initial_delay = random.randint(cfg.getint("initial-delay-min", 10),
00064 cfg.getint("initial-delay-max", 120))
00065
00066
00067 self.cron_period = rpki.sundial.timedelta(seconds = cfg.getint("cron-period", 120))
00068 self.cron_keepalive = rpki.sundial.timedelta(seconds = cfg.getint("cron-keepalive", 0))
00069 if not self.cron_keepalive:
00070 self.cron_keepalive = self.cron_period * 4
00071 self.cron_timeout = None
00072
00073 def start_cron(self):
00074 """
00075 Start clock for rpkid's internal cron process.
00076 """
00077
00078 if self.use_internal_cron:
00079 self.cron_timer = rpki.async.timer(handler = self.cron)
00080 when = rpki.sundial.now() + rpki.sundial.timedelta(seconds = self.initial_delay)
00081 rpki.log.debug("Scheduling initial cron pass at %s" % when)
00082 self.cron_timer.set(when)
00083 else:
00084 rpki.log.debug("Not using internal clock, start_cron() call ignored")
00085
00086 def irdb_query(self, q_pdu, callback, errback, expected_pdu_count = None):
00087 """
00088 Perform an IRDB callback query.
00089 """
00090
00091 rpki.log.trace()
00092
00093 q_msg = rpki.left_right.msg.query()
00094 q_msg.append(q_pdu)
00095 q_der = rpki.left_right.cms_msg().wrap(q_msg, self.rpkid_key, self.rpkid_cert)
00096
00097 def unwrap(r_der):
00098 r_cms = rpki.left_right.cms_msg(DER = r_der)
00099 r_msg = r_cms.unwrap((self.bpki_ta, self.irdb_cert))
00100 if not r_msg.is_reply() or not all(type(r_pdu) is type(q_pdu) for r_pdu in r_msg):
00101 raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % r_cms.pretty_print_content()
00102 if expected_pdu_count is not None and len(r_msg) != expected_pdu_count:
00103 assert isinstance(expected_pdu_count, (int, long))
00104 raise rpki.exceptions.BadIRDBReply, "Expected exactly %d PDU%s from IRDB: %s" (
00105 expected_pdu_count, "" if expected_pdu_count == 1 else "s", r_cms.pretty_print_content())
00106 callback(r_msg)
00107
00108 rpki.http.client(
00109 url = self.irdb_url,
00110 msg = q_der,
00111 callback = unwrap,
00112 errback = errback)
00113
00114 def irdb_query_child_resources(self, self_handle, child_handle, callback, errback):
00115 """
00116 Ask IRDB about a child's resources.
00117 """
00118
00119 rpki.log.trace()
00120
00121 q_pdu = rpki.left_right.list_resources_elt()
00122 q_pdu.self_handle = self_handle
00123 q_pdu.child_handle = child_handle
00124
00125 def done(r_msg):
00126 callback(rpki.resource_set.resource_bag(
00127 asn = r_msg[0].asn,
00128 v4 = r_msg[0].ipv4,
00129 v6 = r_msg[0].ipv6,
00130 valid_until = r_msg[0].valid_until))
00131
00132 self.irdb_query(q_pdu, done, errback, expected_pdu_count = 1)
00133
00134 def irdb_query_roa_requests(self, self_handle, callback, errback):
00135 """
00136 Ask IRDB about self's ROA requests.
00137 """
00138
00139 rpki.log.trace()
00140
00141 q_pdu = rpki.left_right.list_roa_requests_elt()
00142 q_pdu.self_handle = self_handle
00143
00144 self.irdb_query(q_pdu, callback, errback)
00145
00146 def left_right_handler(self, query, path, cb):
00147 """
00148 Process one left-right PDU.
00149 """
00150
00151 rpki.log.trace()
00152
00153 def done(r_msg):
00154 reply = rpki.left_right.cms_msg().wrap(r_msg, self.rpkid_key, self.rpkid_cert)
00155 self.sql.sweep()
00156 cb(200, reply)
00157
00158 try:
00159 self.sql.ping()
00160 q_msg = rpki.left_right.cms_msg(DER = query).unwrap((self.bpki_ta, self.irbe_cert))
00161 if not q_msg.is_query():
00162 raise rpki.exceptions.BadQuery, "Message type is not query"
00163 q_msg.serve_top_level(self, done)
00164 except (rpki.async.ExitNow, SystemExit):
00165 raise
00166 except Exception, data:
00167 rpki.log.traceback()
00168 cb(500, "Unhandled exception %s" % data)
00169
00170 up_down_url_regexp = re.compile("/up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I)
00171
00172 def up_down_handler(self, query, path, cb):
00173 """
00174 Process one up-down PDU.
00175 """
00176
00177 rpki.log.trace()
00178
00179 def done(reply):
00180 self.sql.sweep()
00181 cb(200, reply)
00182
00183 try:
00184 self.sql.ping()
00185 match = self.up_down_url_regexp.search(path)
00186 if match is None:
00187 raise rpki.exceptions.BadContactURL, "Bad path: %s" % path
00188 self_handle, child_handle = match.groups()
00189 child = rpki.left_right.child_elt.sql_fetch_where1(self, "self.self_handle = %s AND child.child_handle = %s AND child.self_id = self.self_id",
00190 (self_handle, child_handle), "self")
00191 if child is None:
00192 raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_handle
00193 child.serve_up_down(query, done)
00194 except (rpki.async.ExitNow, SystemExit):
00195 raise
00196 except Exception, data:
00197 rpki.log.traceback()
00198 cb(400, "Could not process PDU: %s" % data)
00199
00200 def checkpoint(self):
00201 """
00202 Record that we were still alive when we got here, by resetting
00203 keepalive timer.
00204 """
00205 self.cron_timeout = rpki.sundial.now() + self.cron_keepalive
00206
00207 def cron(self, cb = None):
00208 """
00209 Periodic tasks.
00210 """
00211
00212 rpki.log.trace()
00213 self.sql.ping()
00214
00215 now = rpki.sundial.now()
00216
00217 assert self.use_internal_cron or self.cron_timeout is None
00218
00219 if self.use_internal_cron:
00220
00221 if self.cron_timeout and self.cron_timeout < now:
00222 rpki.log.warn("cron keepalive threshold %s has expired, breaking lock" % self.cron_timeout)
00223 self.cron_timeout = None
00224
00225 when = now + self.cron_period
00226 rpki.log.debug("Scheduling next cron run at %s" % when)
00227 self.cron_timer.set(when)
00228
00229 if self.cron_timeout:
00230 rpki.log.warn("cron already running, keepalive will expire at %s" % self.cron_timeout)
00231 return
00232
00233 self.checkpoint()
00234
00235 def loop(iterator, s):
00236 self.checkpoint()
00237 s.cron(iterator)
00238
00239 def done():
00240 self.sql.sweep()
00241 self.cron_timeout = None
00242 rpki.log.info("Finished cron run started at %s" % now)
00243 if not self.use_internal_cron:
00244 cb()
00245
00246 def lose(e):
00247 self.cron_timeout = None
00248 if self.use_internal_cron:
00249 rpki.log.traceback()
00250 else:
00251 raise
00252
00253 try:
00254 rpki.async.iterator(rpki.left_right.self_elt.sql_fetch_all(self), loop, done)
00255
00256 except (rpki.async.ExitNow, SystemExit):
00257 self.cron_timeout = None
00258 raise
00259
00260 except Exception, e:
00261 lose(e)
00262
00263 def cronjob_handler(self, query, path, cb):
00264 """
00265 External trigger for periodic tasks. This is somewhat obsolete
00266 now that we have internal timers, but the test framework still
00267 uses it.
00268 """
00269
00270 if self.use_internal_cron:
00271 cb(500, "Running cron internally")
00272 else:
00273 self.cron(lambda: cb(200, "OK"))
00274
00275 class ca_obj(rpki.sql.sql_persistent):
00276 """
00277 Internal CA object.
00278 """
00279
00280 sql_template = rpki.sql.template(
00281 "ca",
00282 "ca_id",
00283 "last_crl_sn",
00284 ("next_crl_update", rpki.sundial.datetime),
00285 "last_issued_sn", "last_manifest_sn",
00286 ("next_manifest_update", rpki.sundial.datetime),
00287 "sia_uri", "parent_id", "parent_resource_class")
00288
00289 last_crl_sn = 0
00290 last_issued_sn = 0
00291 last_manifest_sn = 0
00292
00293 def parent(self):
00294 """Fetch parent object to which this CA object links."""
00295 return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
00296
00297 def ca_details(self):
00298 """Fetch all ca_detail objects that link to this CA object."""
00299 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
00300
00301 def fetch_pending(self):
00302 """Fetch the pending ca_details for this CA, if any."""
00303 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,))
00304
00305 def fetch_active(self):
00306 """Fetch the active ca_detail for this CA, if any."""
00307 return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,))
00308
00309 def fetch_deprecated(self):
00310 """Fetch deprecated ca_details for this CA, if any."""
00311 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,))
00312
00313 def fetch_revoked(self):
00314 """Fetch revoked ca_details for this CA, if any."""
00315 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,))
00316
00317 def fetch_issue_response_candidates(self):
00318 """
00319 Fetch ca_details which are candidates for consideration when
00320 processing an up-down issue_response PDU.
00321 """
00322
00323 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state != 'revoked'", (self.ca_id,))
00324
00325 def construct_sia_uri(self, parent, rc):
00326 """
00327 Construct the sia_uri value for this CA given configured
00328 information and the parent's up-down protocol list_response PDU.
00329 """
00330
00331 sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync()
00332 if not sia_uri or not sia_uri.startswith(parent.sia_base):
00333 sia_uri = parent.sia_base
00334 if not sia_uri.endswith("/"):
00335 raise rpki.exceptions.BadURISyntax, "SIA URI must end with a slash: %s" % sia_uri
00336 return sia_uri + str(self.ca_id) + "/"
00337
00338 def check_for_updates(self, parent, rc, cb, eb):
00339 """
00340 Parent has signaled continued existance of a resource class we
00341 already knew about, so we need to check for an updated
00342 certificate, changes in resource coverage, revocation and reissue
00343 with the same key, etc.
00344 """
00345
00346 sia_uri = self.construct_sia_uri(parent, rc)
00347 sia_uri_changed = self.sia_uri != sia_uri
00348 if sia_uri_changed:
00349 self.sia_uri = sia_uri
00350 self.sql_mark_dirty()
00351
00352 rc_resources = rc.to_resource_bag()
00353 cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs)
00354
00355 def loop(iterator, ca_detail):
00356
00357 self.gctx.checkpoint()
00358
00359 rc_cert = cert_map.pop(ca_detail.public_key.get_SKI(), None)
00360
00361 if rc_cert is None:
00362
00363 rpki.log.warn("Certificate in database missing from list_response, class %r, SKI %s, maybe parent certificate went away?"
00364 % (rc.class_name, ca_detail.public_key.gSKI()))
00365 publisher = publication_queue()
00366 ca_detail.delete(ca = ca_detail.ca(), publisher = publisher)
00367 return publisher.call_pubd(iterator, eb)
00368
00369 else:
00370
00371 if ca_detail.state in ("pending", "active"):
00372
00373 if ca_detail.state == "pending":
00374 current_resources = rpki.resource_set.resource_bag()
00375 else:
00376 current_resources = ca_detail.latest_ca_cert.get_3779resources()
00377
00378 if (ca_detail.state == "pending" or
00379 sia_uri_changed or
00380 ca_detail.latest_ca_cert != rc_cert.cert or
00381 current_resources.undersized(rc_resources) or
00382 current_resources.oversized(rc_resources)):
00383 return ca_detail.update(
00384 parent = parent,
00385 ca = self,
00386 rc = rc,
00387 sia_uri_changed = sia_uri_changed,
00388 old_resources = current_resources,
00389 callback = iterator,
00390 errback = eb)
00391
00392 iterator()
00393
00394 def done():
00395 if cert_map:
00396 rpki.log.warn("Certificates in list_response missing from our database, class %r, SKIs %s"
00397 % (rc.class_name, ", ".join(c.cert.gSKI() for c in cert_map.values())))
00398 self.gctx.checkpoint()
00399 cb()
00400
00401 ca_details = self.fetch_issue_response_candidates()
00402
00403 if True:
00404 for x in cert_map.itervalues():
00405 rpki.log.debug("Parent thinks I have %r %s" % (x, x.cert.gSKI()))
00406 for x in ca_details:
00407 if x.latest_ca_cert is not None:
00408 rpki.log.debug("I think I have %r %s" % (x, x.latest_ca_cert.gSKI()))
00409
00410 if ca_details:
00411 rpki.async.iterator(ca_details, loop, done)
00412 else:
00413 rpki.log.warn("Existing certificate class %r with no certificates, rekeying" % rc.class_name)
00414 self.gctx.checkpoint()
00415 self.rekey(cb, eb)
00416
00417 @classmethod
00418 def create(cls, parent, rc, cb, eb):
00419 """
00420 Parent has signaled existance of a new resource class, so we need
00421 to create and set up a corresponding CA object.
00422 """
00423
00424 self = cls()
00425 self.gctx = parent.gctx
00426 self.parent_id = parent.parent_id
00427 self.parent_resource_class = rc.class_name
00428 self.sql_store()
00429 self.sia_uri = self.construct_sia_uri(parent, rc)
00430 ca_detail = ca_detail_obj.create(self)
00431
00432 def done(issue_response):
00433 ca_detail.activate(
00434 ca = self,
00435 cert = issue_response.payload.classes[0].certs[0].cert,
00436 uri = issue_response.payload.classes[0].certs[0].cert_url,
00437 callback = cb,
00438 errback = eb)
00439
00440 rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb)
00441
00442 def delete(self, parent, callback):
00443 """
00444 The list of current resource classes received from parent does not
00445 include the class corresponding to this CA, so we need to delete
00446 it (and its little dog too...).
00447
00448 All certs published by this CA are now invalid, so need to
00449 withdraw them, the CRL, and the manifest from the repository,
00450 delete all child_cert and ca_detail records associated with this
00451 CA, then finally delete this CA itself.
00452 """
00453
00454 def lose(e):
00455 rpki.log.traceback()
00456 rpki.log.warn("Could not delete CA %r, skipping: %s" % (self, e))
00457 callback()
00458
00459 def done():
00460 self.sql_delete()
00461 callback()
00462
00463 publisher = publication_queue()
00464 for ca_detail in self.ca_details():
00465 ca_detail.delete(ca = self, publisher = publisher)
00466 publisher.call_pubd(done, lose)
00467
00468 def next_serial_number(self):
00469 """
00470 Allocate a certificate serial number.
00471 """
00472 self.last_issued_sn += 1
00473 self.sql_mark_dirty()
00474 return self.last_issued_sn
00475
00476 def next_manifest_number(self):
00477 """
00478 Allocate a manifest serial number.
00479 """
00480 self.last_manifest_sn += 1
00481 self.sql_mark_dirty()
00482 return self.last_manifest_sn
00483
00484 def next_crl_number(self):
00485 """
00486 Allocate a CRL serial number.
00487 """
00488 self.last_crl_sn += 1
00489 self.sql_mark_dirty()
00490 return self.last_crl_sn
00491
00492 def rekey(self, cb, eb):
00493 """
00494 Initiate a rekey operation for this ca. Generate a new keypair.
00495 Request cert from parent using new keypair. Mark result as our
00496 active ca_detail. Reissue all child certs issued by this ca using
00497 the new ca_detail.
00498 """
00499
00500 rpki.log.trace()
00501
00502 parent = self.parent()
00503 old_detail = self.fetch_active()
00504 new_detail = ca_detail_obj.create(self)
00505
00506 def done(issue_response):
00507 new_detail.activate(
00508 ca = self,
00509 cert = issue_response.payload.classes[0].certs[0].cert,
00510 uri = issue_response.payload.classes[0].certs[0].cert_url,
00511 predecessor = old_detail,
00512 callback = cb,
00513 errback = eb)
00514
00515 rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb)
00516
00517 def revoke(self, cb, eb):
00518 """
00519 Revoke deprecated ca_detail objects associated with this ca.
00520 """
00521
00522 rpki.log.trace()
00523
00524 def loop(iterator, ca_detail):
00525 ca_detail.revoke(cb = iterator, eb = eb)
00526
00527 rpki.async.iterator(self.fetch_deprecated(), loop, cb)
00528
00529 class ca_detail_obj(rpki.sql.sql_persistent):
00530 """
00531 Internal CA detail object.
00532 """
00533
00534 sql_template = rpki.sql.template(
00535 "ca_detail",
00536 "ca_detail_id",
00537 ("private_key_id", rpki.x509.RSA),
00538 ("public_key", rpki.x509.RSApublic),
00539 ("latest_ca_cert", rpki.x509.X509),
00540 ("manifest_private_key_id", rpki.x509.RSA),
00541 ("manifest_public_key", rpki.x509.RSApublic),
00542 ("latest_manifest_cert", rpki.x509.X509),
00543 ("latest_manifest", rpki.x509.SignedManifest),
00544 ("latest_crl", rpki.x509.CRL),
00545 ("crl_published", rpki.sundial.datetime),
00546 ("manifest_published", rpki.sundial.datetime),
00547 "state",
00548 "ca_cert_uri",
00549 "ca_id")
00550
00551 crl_published = None
00552 manifest_published = None
00553 latest_ca_cert = None
00554
00555 def sql_decode(self, vals):
00556 """
00557 Extra assertions for SQL decode of a ca_detail_obj.
00558 """
00559 rpki.sql.sql_persistent.sql_decode(self, vals)
00560 assert self.public_key is None or self.private_key_id is None or self.public_key.get_DER() == self.private_key_id.get_public_DER()
00561 assert self.manifest_public_key is None or self.manifest_private_key_id is None or self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
00562
00563 def ca(self):
00564 """Fetch CA object to which this ca_detail links."""
00565 return ca_obj.sql_fetch(self.gctx, self.ca_id)
00566
00567 def child_certs(self, child = None, ski = None, unique = False):
00568 """Fetch all child_cert objects that link to this ca_detail."""
00569 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique)
00570
00571 def revoked_certs(self):
00572 """Fetch all revoked_cert objects that link to this ca_detail."""
00573 return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00574
00575 def roas(self):
00576 """Fetch all ROA objects that link to this ca_detail."""
00577 return rpki.rpki_engine.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00578
00579 def crl_uri(self, ca):
00580 """Return publication URI for this ca_detail's CRL."""
00581 return ca.sia_uri + self.crl_uri_tail()
00582
00583 def crl_uri_tail(self):
00584 """Return tail (filename portion) of publication URI for this ca_detail's CRL."""
00585 return self.public_key.gSKI() + ".crl"
00586
00587 def manifest_uri(self, ca):
00588 """Return publication URI for this ca_detail's manifest."""
00589 return ca.sia_uri + self.public_key.gSKI() + ".mnf"
00590
00591 def activate(self, ca, cert, uri, callback, errback, predecessor = None):
00592 """
00593 Activate this ca_detail.
00594 """
00595
00596 publisher = publication_queue()
00597
00598 self.latest_ca_cert = cert
00599 self.ca_cert_uri = uri.rsync()
00600 self.generate_manifest_cert(ca)
00601 self.state = "active"
00602 self.generate_crl(publisher = publisher)
00603 self.generate_manifest(publisher = publisher)
00604 self.sql_mark_dirty()
00605
00606 if predecessor is not None:
00607 predecessor.state = "deprecated"
00608 predecessor.sql_mark_dirty()
00609 for child_cert in predecessor.child_certs():
00610 child_cert.reissue(ca_detail = self, publisher = publisher)
00611 for roa in predecessor.roas():
00612 roa.regenerate(publisher = publisher)
00613
00614 publisher.call_pubd(callback, errback)
00615
00616 def delete(self, ca, publisher, allow_failure = False):
00617 """
00618 Delete this ca_detail and all of the certs it issued.
00619
00620 If allow_failure is true, we clean up as much as we can but don't
00621 raise an exception.
00622 """
00623
00624 repository = ca.parent().repository()
00625 for child_cert in self.child_certs():
00626 publisher.withdraw(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = repository,
00627 handler = False if allow_failure else None)
00628 for roa in self.roas():
00629 roa.revoke(publisher = publisher, allow_failure = allow_failure)
00630 try:
00631 latest_manifest = self.latest_manifest
00632 except AttributeError:
00633 latest_manifest = None
00634 if latest_manifest is not None:
00635 publisher.withdraw(cls = rpki.publication.manifest_elt, uri = self.manifest_uri(ca), obj = self.latest_manifest, repository = repository,
00636 handler = False if allow_failure else None)
00637 try:
00638 latest_crl = self.latest_crl
00639 except AttributeError:
00640 latest_crl = None
00641 if latest_crl is not None:
00642 publisher.withdraw(cls = rpki.publication.crl_elt, uri = self.crl_uri(ca), obj = self.latest_crl, repository = repository,
00643 handler = False if allow_failure else None)
00644 for cert in self.child_certs() + self.revoked_certs():
00645 cert.sql_delete()
00646 self.sql_delete()
00647
00648 def revoke(self, cb, eb):
00649 """
00650 Request revocation of all certificates whose SKI matches the key
00651 for this ca_detail.
00652
00653 Tasks:
00654
00655 - Request revocation of old keypair by parent.
00656
00657 - Revoke all child certs issued by the old keypair.
00658
00659 - Generate a final CRL, signed with the old keypair, listing all
00660 the revoked certs, with a next CRL time after the last cert or
00661 CRL signed by the old keypair will have expired.
00662
00663 - Generate a corresponding final manifest.
00664
00665 - Destroy old keypairs.
00666
00667 - Leave final CRL and manifest in place until their nextupdate
00668 time has passed.
00669 """
00670
00671 ca = self.ca()
00672 parent = ca.parent()
00673
00674 def parent_revoked(r_msg):
00675
00676 if r_msg.payload.ski != self.latest_ca_cert.gSKI():
00677 raise rpki.exceptions.SKIMismatch
00678
00679 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00680
00681 nextUpdate = rpki.sundial.now()
00682
00683 if self.latest_manifest is not None:
00684 try:
00685 self.latest_manifest.get_content()
00686 except rpki.exceptions.CMSContentNotSet:
00687 self.latest_manifest.extract()
00688 nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate())
00689
00690 if self.latest_crl is not None:
00691 nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate())
00692
00693 publisher = publication_queue()
00694
00695 for child_cert in self.child_certs():
00696 nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter())
00697 child_cert.revoke(publisher = publisher)
00698
00699 nextUpdate += crl_interval
00700 self.generate_crl(publisher = publisher, nextUpdate = nextUpdate)
00701 self.generate_manifest(publisher = publisher, nextUpdate = nextUpdate)
00702 self.private_key_id = None
00703 self.manifest_private_key_id = None
00704 self.manifest_public_key = None
00705 self.latest_manifest_cert = None
00706 self.state = "revoked"
00707 self.sql_mark_dirty()
00708 publisher.call_pubd(cb, eb)
00709
00710 rpki.up_down.revoke_pdu.query(ca, self.latest_ca_cert.gSKI(), parent_revoked, eb)
00711
00712 def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback):
00713 """
00714 Need to get a new certificate for this ca_detail and perhaps frob
00715 children of this ca_detail.
00716 """
00717
00718 def issued(issue_response):
00719 self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert
00720 new_resources = self.latest_ca_cert.get_3779resources()
00721 publisher = publication_queue()
00722
00723 if sia_uri_changed or old_resources.oversized(new_resources):
00724 for child_cert in self.child_certs():
00725 child_resources = child_cert.cert.get_3779resources()
00726 if sia_uri_changed or child_resources.oversized(new_resources):
00727 child_cert.reissue(
00728 ca_detail = self,
00729 resources = child_resources.intersection(new_resources),
00730 publisher = publisher)
00731
00732 publisher.call_pubd(callback, errback)
00733
00734 rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback)
00735
00736 @classmethod
00737 def create(cls, ca):
00738 """
00739 Create a new ca_detail object for a specified CA.
00740 """
00741 self = cls()
00742 self.gctx = ca.gctx
00743 self.ca_id = ca.ca_id
00744 self.state = "pending"
00745
00746 self.private_key_id = rpki.x509.RSA.generate()
00747 self.public_key = self.private_key_id.get_RSApublic()
00748
00749 self.manifest_private_key_id = rpki.x509.RSA.generate()
00750 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00751
00752 self.sql_store()
00753 return self
00754
00755 def issue_ee(self, ca, resources, subject_key, sia = None):
00756 """
00757 Issue a new EE certificate.
00758 """
00759
00760 return self.latest_ca_cert.issue(
00761 keypair = self.private_key_id,
00762 subject_key = subject_key,
00763 serial = ca.next_serial_number(),
00764 sia = sia,
00765 aia = self.ca_cert_uri,
00766 crldp = self.crl_uri(ca),
00767 resources = resources,
00768 notAfter = self.latest_ca_cert.getNotAfter(),
00769 is_ca = False)
00770
00771
00772 def generate_manifest_cert(self, ca):
00773 """
00774 Generate a new manifest certificate for this ca_detail.
00775 """
00776
00777 resources = rpki.resource_set.resource_bag(
00778 asn = rpki.resource_set.resource_set_as(rpki.resource_set.inherit_token),
00779 v4 = rpki.resource_set.resource_set_ipv4(rpki.resource_set.inherit_token),
00780 v6 = rpki.resource_set.resource_set_ipv6(rpki.resource_set.inherit_token))
00781
00782 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00783
00784 def issue(self, ca, child, subject_key, sia, resources, publisher, child_cert = None):
00785 """
00786 Issue a new certificate to a child. Optional child_cert argument
00787 specifies an existing child_cert object to update in place; if not
00788 specified, we create a new one. Returns the child_cert object
00789 containing the newly issued cert.
00790 """
00791
00792 assert child_cert is None or (child_cert.child_id == child.child_id and
00793 child_cert.ca_detail_id == self.ca_detail_id)
00794
00795 cert = self.latest_ca_cert.issue(
00796 keypair = self.private_key_id,
00797 subject_key = subject_key,
00798 serial = ca.next_serial_number(),
00799 aia = self.ca_cert_uri,
00800 crldp = self.crl_uri(ca),
00801 sia = sia,
00802 resources = resources,
00803 notAfter = resources.valid_until)
00804
00805 if child_cert is None:
00806 child_cert = rpki.rpki_engine.child_cert_obj(
00807 gctx = child.gctx,
00808 child_id = child.child_id,
00809 ca_detail_id = self.ca_detail_id,
00810 cert = cert)
00811 rpki.log.debug("Created new child_cert %r" % child_cert)
00812 else:
00813 child_cert.cert = cert
00814 rpki.log.debug("Reusing existing child_cert %r" % child_cert)
00815
00816 child_cert.ski = cert.get_SKI()
00817 child_cert.published = rpki.sundial.now()
00818 child_cert.sql_store()
00819 publisher.publish(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = ca.parent().repository(),
00820 handler = child_cert.published_callback)
00821 self.generate_manifest(publisher = publisher)
00822 return child_cert
00823
00824 def generate_crl(self, publisher, nextUpdate = None):
00825 """
00826 Generate a new CRL for this ca_detail. At the moment this is
00827 unconditional, that is, it is up to the caller to decide whether a
00828 new CRL is needed.
00829 """
00830
00831 ca = self.ca()
00832 parent = ca.parent()
00833 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00834 now = rpki.sundial.now()
00835
00836 if nextUpdate is None:
00837 nextUpdate = now + crl_interval
00838
00839 certlist = []
00840 for revoked_cert in self.revoked_certs():
00841 if now > revoked_cert.expires + crl_interval:
00842 revoked_cert.sql_delete()
00843 else:
00844 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00845 certlist.sort()
00846
00847 self.latest_crl = rpki.x509.CRL.generate(
00848 keypair = self.private_key_id,
00849 issuer = self.latest_ca_cert,
00850 serial = ca.next_crl_number(),
00851 thisUpdate = now,
00852 nextUpdate = nextUpdate,
00853 revokedCertificates = certlist)
00854
00855 self.crl_published = rpki.sundial.now()
00856 self.sql_mark_dirty()
00857 publisher.publish(cls = rpki.publication.crl_elt, uri = self.crl_uri(ca), obj = self.latest_crl, repository = parent.repository(),
00858 handler = self.crl_published_callback)
00859
00860 def crl_published_callback(self, pdu):
00861 """
00862 Check result of CRL publication.
00863 """
00864 pdu.raise_if_error()
00865 self.crl_published = None
00866 self.sql_mark_dirty()
00867
00868 def generate_manifest(self, publisher, nextUpdate = None):
00869 """
00870 Generate a new manifest for this ca_detail.
00871 """
00872
00873 ca = self.ca()
00874 parent = ca.parent()
00875 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00876 now = rpki.sundial.now()
00877
00878 if nextUpdate is None:
00879 nextUpdate = now + crl_interval
00880
00881 if self.latest_manifest_cert is None or self.latest_manifest_cert.getNotAfter() < nextUpdate:
00882 self.generate_manifest_cert(ca)
00883
00884 objs = [(self.crl_uri_tail(), self.latest_crl)]
00885 objs.extend((c.uri_tail(), c.cert) for c in self.child_certs())
00886 objs.extend((r.uri_tail(), r.roa) for r in self.roas() if r.roa is not None)
00887
00888 self.latest_manifest = rpki.x509.SignedManifest.build(
00889 serial = ca.next_manifest_number(),
00890 thisUpdate = now,
00891 nextUpdate = nextUpdate,
00892 names_and_objs = objs,
00893 keypair = self.manifest_private_key_id,
00894 certs = self.latest_manifest_cert)
00895
00896
00897 self.manifest_published = rpki.sundial.now()
00898 self.sql_mark_dirty()
00899 publisher.publish(cls = rpki.publication.manifest_elt, uri = self.manifest_uri(ca), obj = self.latest_manifest, repository = parent.repository(),
00900 handler = self.manifest_published_callback)
00901
00902 def manifest_published_callback(self, pdu):
00903 """
00904 Check result of manifest publication.
00905 """
00906 pdu.raise_if_error()
00907 self.manifest_published = None
00908 self.sql_mark_dirty()
00909
00910
00911 class child_cert_obj(rpki.sql.sql_persistent):
00912 """
00913 Certificate that has been issued to a child.
00914 """
00915
00916 sql_template = rpki.sql.template(
00917 "child_cert",
00918 "child_cert_id",
00919 ("cert", rpki.x509.X509),
00920 "child_id",
00921 "ca_detail_id",
00922 "ski",
00923 ("published", rpki.sundial.datetime))
00924
00925 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00926 """
00927 Initialize a child_cert_obj.
00928 """
00929 rpki.sql.sql_persistent.__init__(self)
00930 self.gctx = gctx
00931 self.child_id = child_id
00932 self.ca_detail_id = ca_detail_id
00933 self.cert = cert
00934 self.published = None
00935 if child_id or ca_detail_id or cert:
00936 self.sql_mark_dirty()
00937
00938 def child(self):
00939 """Fetch child object to which this child_cert object links."""
00940 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00941
00942 def ca_detail(self):
00943 """Fetch ca_detail object to which this child_cert object links."""
00944 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00945
00946 def uri_tail(self):
00947 """Return the tail (filename) portion of the URI for this child_cert."""
00948 return self.cert.gSKI() + ".cer"
00949
00950 def uri(self, ca):
00951 """Return the publication URI for this child_cert."""
00952 return ca.sia_uri + self.uri_tail()
00953
00954 def revoke(self, publisher, generate_crl_and_manifest = False):
00955 """
00956 Revoke a child cert.
00957 """
00958
00959 ca_detail = self.ca_detail()
00960 ca = ca_detail.ca()
00961 rpki.log.debug("Revoking %r %r" % (self, self.uri(ca)))
00962 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00963 publisher.withdraw(cls = rpki.publication.certificate_elt, uri = self.uri(ca), obj = self.cert, repository = ca.parent().repository())
00964 self.gctx.sql.sweep()
00965 self.sql_delete()
00966 if generate_crl_and_manifest:
00967 ca_detail.generate_crl(publisher = publisher)
00968 ca_detail.generate_manifest(publisher = publisher)
00969
00970 def reissue(self, ca_detail, publisher, resources = None, sia = None):
00971 """
00972 Reissue an existing child cert, reusing the public key. If the
00973 child cert we would generate is identical to the one we already
00974 have, we just return the one we already have. If we have to
00975 revoke the old child cert when generating the new one, we have to
00976 generate a new child_cert_obj, so calling code that needs the
00977 updated child_cert_obj must use the return value from this method.
00978 """
00979
00980 ca = ca_detail.ca()
00981 child = self.child()
00982
00983 old_resources = self.cert.get_3779resources()
00984 old_sia = self.cert.get_SIA()
00985 old_ca_detail = self.ca_detail()
00986
00987 if resources is None:
00988 resources = old_resources
00989
00990 if sia is None:
00991 sia = old_sia
00992
00993 assert resources.valid_until is not None and old_resources.valid_until is not None
00994
00995 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00996 rpki.log.debug("No change to %r" % self)
00997 return self
00998
00999 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
01000 new_issuer = ca_detail != old_ca_detail
01001
01002 rpki.log.debug("Reissuing %r, must_revoke %s, new_issuer %s" % (self, must_revoke, new_issuer))
01003
01004 if resources.valid_until != old_resources.valid_until:
01005 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
01006
01007 if must_revoke:
01008 for x in child.child_certs(ca_detail = ca_detail, ski = self.ski):
01009 rpki.log.debug("Revoking child_cert %r" % x)
01010 x.revoke(publisher = publisher)
01011 ca_detail.generate_crl(publisher = publisher)
01012 ca_detail.generate_manifest(publisher = publisher)
01013
01014 child_cert = ca_detail.issue(
01015 ca = ca,
01016 child = child,
01017 subject_key = self.cert.getPublicKey(),
01018 sia = sia,
01019 resources = resources,
01020 child_cert = None if must_revoke or new_issuer else self,
01021 publisher = publisher)
01022
01023 rpki.log.debug("New child_cert %r uri %s" % (child_cert, child_cert.uri(ca)))
01024
01025 return child_cert
01026
01027 @classmethod
01028 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
01029 """
01030 Fetch all child_cert objects matching a particular set of
01031 parameters. This is a wrapper to consolidate various queries that
01032 would otherwise be inline SQL WHERE expressions. In most cases
01033 code calls this indirectly, through methods in other classes.
01034 """
01035
01036 args = []
01037 where = []
01038
01039 if child:
01040 where.append("child_id = %s")
01041 args.append(child.child_id)
01042
01043 if ca_detail:
01044 where.append("ca_detail_id = %s")
01045 args.append(ca_detail.ca_detail_id)
01046
01047 if ski:
01048 where.append("ski = %s")
01049 args.append(ski)
01050
01051 where = " AND ".join(where)
01052
01053 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
01054
01055 if unique:
01056 return cls.sql_fetch_where1(gctx, where, args)
01057 else:
01058 return cls.sql_fetch_where(gctx, where, args)
01059
01060 def published_callback(self, pdu):
01061 """
01062 Publication callback: check result and mark published.
01063 """
01064 pdu.raise_if_error()
01065 self.published = None
01066 self.sql_mark_dirty()
01067
01068 class revoked_cert_obj(rpki.sql.sql_persistent):
01069 """
01070 Tombstone for a revoked certificate.
01071 """
01072
01073 sql_template = rpki.sql.template(
01074 "revoked_cert",
01075 "revoked_cert_id",
01076 "serial",
01077 "ca_detail_id",
01078 ("revoked", rpki.sundial.datetime),
01079 ("expires", rpki.sundial.datetime))
01080
01081 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
01082 """Initialize a revoked_cert_obj."""
01083 rpki.sql.sql_persistent.__init__(self)
01084 self.gctx = gctx
01085 self.serial = serial
01086 self.revoked = revoked
01087 self.expires = expires
01088 self.ca_detail_id = ca_detail_id
01089 if serial or revoked or expires or ca_detail_id:
01090 self.sql_mark_dirty()
01091
01092 def ca_detail(self):
01093 """Fetch ca_detail object to which this revoked_cert_obj links."""
01094 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
01095
01096 @classmethod
01097 def revoke(cls, cert, ca_detail):
01098 """
01099 Revoke a certificate.
01100 """
01101 return cls(
01102 serial = cert.getSerial(),
01103 expires = cert.getNotAfter(),
01104 revoked = rpki.sundial.now(),
01105 gctx = ca_detail.gctx,
01106 ca_detail_id = ca_detail.ca_detail_id)
01107
01108 class roa_obj(rpki.sql.sql_persistent):
01109 """
01110 Route Origin Authorization.
01111 """
01112
01113 sql_template = rpki.sql.template(
01114 "roa",
01115 "roa_id",
01116 "ca_detail_id",
01117 "self_id",
01118 "asn",
01119 ("roa", rpki.x509.ROA),
01120 ("cert", rpki.x509.X509),
01121 ("published", rpki.sundial.datetime))
01122
01123 ca_detail_id = None
01124 cert = None
01125 roa = None
01126 published = None
01127
01128 def self(self):
01129 """
01130 Fetch self object to which this roa_obj links.
01131 """
01132 return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
01133
01134 def ca_detail(self):
01135 """
01136 Fetch ca_detail object to which this roa_obj links.
01137 """
01138 return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
01139
01140 def sql_fetch_hook(self):
01141 """
01142 Extra SQL fetch actions for roa_obj -- handle prefix lists.
01143 """
01144 for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"),
01145 (6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")):
01146 setattr(self, attribute, datatype.from_sql(
01147 self.gctx.sql,
01148 """
01149 SELECT prefix, prefixlen, max_prefixlen FROM roa_prefix
01150 WHERE roa_id = %s AND version = %s
01151 """,
01152 (self.roa_id, version)))
01153
01154 def sql_insert_hook(self):
01155 """
01156 Extra SQL insert actions for roa_obj -- handle prefix lists.
01157 """
01158 for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)):
01159 if prefix_set:
01160 self.gctx.sql.executemany(
01161 """
01162 INSERT roa_prefix (roa_id, prefix, prefixlen, max_prefixlen, version)
01163 VALUES (%s, %s, %s, %s, %s)
01164 """,
01165 ((self.roa_id, x.prefix, x.prefixlen, x.max_prefixlen, version)
01166 for x in prefix_set))
01167
01168 def sql_delete_hook(self):
01169 """
01170 Extra SQL delete actions for roa_obj -- handle prefix lists.
01171 """
01172 self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,))
01173
01174 def __init__(self, gctx = None, self_id = None, asn = None, ipv4 = None, ipv6 = None):
01175 rpki.sql.sql_persistent.__init__(self)
01176 self.gctx = gctx
01177 self.self_id = self_id
01178 self.asn = asn
01179 self.ipv4 = ipv4
01180 self.ipv6 = ipv6
01181
01182
01183
01184
01185
01186
01187 def update(self, publisher, fast = False):
01188 """
01189 Bring this roa_obj's ROA up to date if necesssary.
01190 """
01191
01192 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
01193 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
01194
01195 me = "<%s %s>" % (self.asn, ("%s,%s" % (v4, v6)).strip(","))
01196
01197 if self.roa is None:
01198 rpki.log.debug("ROA doesn't exist, generating %s" % me)
01199 return self.generate(publisher = publisher, fast = fast)
01200
01201 ca_detail = self.ca_detail()
01202
01203 if ca_detail is None:
01204 rpki.log.debug("ROA has no associated ca_detail, generating %s" % me)
01205 return self.generate(publisher = publisher, fast = fast)
01206
01207 if ca_detail.state != "active":
01208 rpki.log.debug("ROA's associated ca_detail not active (state %r), regenerating %s" % (ca_detail.state, me))
01209 return self.regenerate(publisher = publisher, fast = fast)
01210
01211 regen_time = self.cert.getNotAfter() - rpki.sundial.timedelta(seconds = self.self().regen_margin)
01212
01213 if rpki.sundial.now() > regen_time:
01214 rpki.log.debug("ROA past threshold %s, regenerating %s" % (regen_time, me))
01215 return self.regenerate(publisher = publisher, fast = fast)
01216
01217 ca_resources = ca_detail.latest_ca_cert.get_3779resources()
01218 ee_resources = self.cert.get_3779resources()
01219
01220 if ee_resources.oversized(ca_resources):
01221 rpki.log.debug("ROA oversized with respect to CA, regenerating %s" % me)
01222 return self.regenerate(publisher = publisher, fast = fast)
01223
01224 if ee_resources.v4 != v4 or ee_resources.v6 != v6:
01225 rpki.log.debug("ROA resources do not match EE, regenerating %s" % me)
01226 return self.regenerate(publisher = publisher, fast = fast)
01227
01228 def generate(self, publisher, fast = False):
01229 """
01230 Generate a ROA.
01231
01232 At present this does not support ROAs with multiple signatures
01233 (neither does the current CMS code).
01234
01235 At present we have no way of performing a direct lookup from a
01236 desired set of resources to a covering certificate, so we have to
01237 search. This could be quite slow if we have a lot of active
01238 ca_detail objects. Punt on the issue for now, revisit if
01239 profiling shows this as a hotspot.
01240
01241 Once we have the right covering certificate, we generate the ROA
01242 payload, generate a new EE certificate, use the EE certificate to
01243 sign the ROA payload, publish the result, then throw away the
01244 private key for the EE cert, all per the ROA specification. This
01245 implies that generating a lot of ROAs will tend to thrash
01246 /dev/random, but there is not much we can do about that.
01247
01248 If fast is set, we leave generating the new manifest for our
01249 caller to handle, presumably at the end of a bulk operation.
01250 """
01251
01252 if self.ipv4 is None and self.ipv6 is None:
01253 raise rpki.exceptions.EmptyROAPrefixList
01254
01255
01256
01257
01258
01259 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
01260 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
01261
01262 ca_detail = self.ca_detail()
01263 if ca_detail is None or ca_detail.state != "active":
01264 ca_detail = None
01265 for parent in self.self().parents():
01266 for ca in parent.cas():
01267 ca_detail = ca.fetch_active()
01268 if ca_detail is not None:
01269 resources = ca_detail.latest_ca_cert.get_3779resources()
01270 if v4.issubset(resources.v4) and v6.issubset(resources.v6):
01271 break
01272 ca_detail = None
01273 if ca_detail is not None:
01274 break
01275
01276 if ca_detail is None:
01277 raise rpki.exceptions.NoCoveringCertForROA, "generate() could not find a certificate covering %s %s" % (v4, v6)
01278
01279 ca = ca_detail.ca()
01280 resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6)
01281 keypair = rpki.x509.RSA.generate()
01282
01283 self.ca_detail_id = ca_detail.ca_detail_id
01284 self.cert = ca_detail.issue_ee(
01285 ca = ca,
01286 resources = resources,
01287 subject_key = keypair.get_RSApublic(),
01288 sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.uri(keypair))),))
01289 self.roa = rpki.x509.ROA.build(self.asn, self.ipv4, self.ipv6, keypair, (self.cert,))
01290 self.published = rpki.sundial.now()
01291 self.sql_store()
01292
01293 rpki.log.debug("Generating ROA %r" % self.uri())
01294 publisher.publish(cls = rpki.publication.roa_elt, uri = self.uri(), obj = self.roa, repository = ca.parent().repository(), handler = self.published_callback)
01295 if not fast:
01296 ca_detail.generate_manifest(publisher = publisher)
01297
01298 def published_callback(self, pdu):
01299 """
01300 Check publication result.
01301 """
01302 pdu.raise_if_error()
01303 self.published = None
01304 self.sql_mark_dirty()
01305
01306 def revoke(self, publisher, regenerate = False, allow_failure = False, fast = False):
01307 """
01308 Withdraw ROA associated with this roa_obj.
01309
01310 In order to preserve make-before-break properties without
01311 duplicating code, this method also handles generating a
01312 replacement ROA when requested.
01313
01314 If allow_failure is set, failing to withdraw the ROA will not be
01315 considered an error.
01316
01317 If fast is set, SQL actions will be deferred, on the assumption
01318 that our caller will handle regenerating CRL and manifest and
01319 flushing the SQL cache.
01320 """
01321
01322 ca_detail = self.ca_detail()
01323 cert = self.cert
01324 roa = self.roa
01325 uri = self.uri()
01326
01327 if ca_detail.state != 'active':
01328 self.ca_detail_id = None
01329
01330 if regenerate:
01331 self.generate(publisher = publisher, fast = fast)
01332
01333 rpki.log.debug("Withdrawing ROA %r and revoking its EE cert" % uri)
01334 rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
01335 publisher.withdraw(cls = rpki.publication.roa_elt, uri = uri, obj = roa, repository = ca_detail.ca().parent().repository(),
01336 handler = False if allow_failure else None)
01337 self.sql_mark_deleted()
01338 if not fast:
01339 ca_detail.generate_crl(publisher = publisher)
01340 ca_detail.generate_manifest(publisher = publisher)
01341 self.gctx.sql.sweep()
01342
01343 def regenerate(self, publisher, fast = False):
01344 """
01345 Reissue ROA associated with this roa_obj.
01346 """
01347 if self.ca_detail() is None:
01348 self.generate(publisher = publisher, fast = fast)
01349 else:
01350 self.revoke(publisher = publisher, regenerate = True, fast = fast)
01351
01352 def uri(self, key = None):
01353 """
01354 Return the publication URI for this roa_obj's ROA.
01355 """
01356 return self.ca_detail().ca().sia_uri + self.uri_tail(key)
01357
01358 def uri_tail(self, key = None):
01359 """
01360 Return the tail (filename portion) of the publication URI for this
01361 roa_obj's ROA.
01362 """
01363 return (key or self.cert).gSKI() + ".roa"
01364
01365
01366 class publication_queue(object):
01367 """
01368 Utility to simplify publication from within rpkid.
01369
01370 General idea here is to accumulate a collection of objects to be
01371 published, in one or more repositories, each potentially with its
01372 own completion callback. Eventually we want to publish everything
01373 we've accumulated, at which point we need to iterate over the
01374 collection and do repository.call_pubd() for each repository.
01375 """
01376
01377 replace = True
01378
01379 def __init__(self):
01380 self.repositories = {}
01381 self.msgs = {}
01382 self.handlers = {}
01383 if self.replace:
01384 self.uris = {}
01385
01386 def _add(self, uri, obj, repository, handler, make_pdu):
01387 rid = id(repository)
01388 if rid not in self.repositories:
01389 self.repositories[rid] = repository
01390 self.msgs[rid] = rpki.publication.msg.query()
01391 if self.replace and uri in self.uris:
01392 rpki.log.debug("Removing publication duplicate <%s %r %r>" % (self.uris[uri].action, self.uris[uri].uri, self.uris[uri].payload))
01393 self.msgs[rid].remove(self.uris.pop(uri))
01394 pdu = make_pdu(uri = uri, obj = obj)
01395 if handler is not None:
01396 self.handlers[id(pdu)] = handler
01397 pdu.tag = id(pdu)
01398 self.msgs[rid].append(pdu)
01399 if self.replace:
01400 self.uris[uri] = pdu
01401
01402 def publish(self, cls, uri, obj, repository, handler = None):
01403 return self._add( uri, obj, repository, handler, cls.make_publish)
01404
01405 def withdraw(self, cls, uri, obj, repository, handler = None):
01406 return self._add( uri, obj, repository, handler, cls.make_withdraw)
01407
01408 def call_pubd(self, cb, eb):
01409 def loop(iterator, rid):
01410 self.repositories[rid].call_pubd(iterator, eb, self.msgs[rid], self.handlers)
01411 rpki.async.iterator(self.repositories, loop, cb)