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