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