00001 """
00002 RPKI "left-right" protocol.
00003
00004 $Id: left_right.py 3449 2010-09-16 21:30:30Z sra $
00005
00006 Copyright (C) 2009--2010 Internet Systems Consortium ("ISC")
00007
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019
00020 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00021
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034
00035 import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00036 import rpki.http, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
00037 import rpki.publication, rpki.async
00038
00039
00040 enforce_strict_up_down_xml_sender = False
00041
00042 class left_right_namespace(object):
00043 """
00044 XML namespace parameters for left-right protocol.
00045 """
00046
00047 xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/"
00048 nsmap = { None : xmlns }
00049
00050 class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_namespace):
00051 """
00052 Virtual class for top-level left-right protocol data elements.
00053 """
00054
00055 handles = ()
00056
00057 self_id = None
00058 self_handle = None
00059
00060 def self(self):
00061 """Fetch self object to which this object links."""
00062 return self_elt.sql_fetch(self.gctx, self.self_id)
00063
00064 def bsc(self):
00065 """Return BSC object to which this object links."""
00066 return bsc_elt.sql_fetch(self.gctx, self.bsc_id)
00067
00068 def make_reply_clone_hook(self, r_pdu):
00069 """
00070 Set handles when cloning, including _id -> _handle translation.
00071 """
00072 if r_pdu.self_handle is None:
00073 r_pdu.self_handle = self.self_handle
00074 for tag, elt in self.handles:
00075 id_name = tag + "_id"
00076 handle_name = tag + "_handle"
00077 if getattr(r_pdu, handle_name, None) is None:
00078 try:
00079 setattr(r_pdu, handle_name, getattr(elt.sql_fetch(self.gctx, getattr(r_pdu, id_name)), handle_name))
00080 except AttributeError:
00081 continue
00082
00083 @classmethod
00084 def serve_fetch_handle(cls, gctx, self_id, handle):
00085 """
00086 Find an object based on its handle.
00087 """
00088 return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id))
00089
00090 def serve_fetch_one_maybe(self):
00091 """
00092 Find the object on which a get, set, or destroy method should
00093 operate, or which would conflict with a create method.
00094 """
00095 where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3)
00096 args = (getattr(self, self.element_name + "_handle"), self.self_handle)
00097 return self.sql_fetch_where1(self.gctx, where, args, "self")
00098
00099 def serve_fetch_all(self):
00100 """
00101 Find the objects on which a list method should operate.
00102 """
00103 where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name
00104 return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self")
00105
00106 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00107 """
00108 Hook to do _handle => _id translation before saving.
00109
00110 self is always the object to be saved to SQL. For create
00111 operations, self and q_pdu are be the same object; for set
00112 operations, self is the pre-existing object from SQL and q_pdu is
00113 the set request received from the the IRBE.
00114 """
00115 for tag, elt in self.handles:
00116 id_name = tag + "_id"
00117 if getattr(self, id_name, None) is None:
00118 x = elt.serve_fetch_handle(self.gctx, self.self_id, getattr(q_pdu, tag + "_handle"))
00119 if x is None:
00120 raise rpki.exceptions.HandleTranslationError, "Could not translate %r %s_handle" % (self, tag)
00121 setattr(self, id_name, getattr(x, id_name))
00122 cb()
00123
00124 class self_elt(data_elt):
00125 """
00126 <self/> element.
00127 """
00128
00129 element_name = "self"
00130 attributes = ("action", "tag", "self_handle", "crl_interval", "regen_margin")
00131 elements = ("bpki_cert", "bpki_glue")
00132 booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now", "revoke_forgotten")
00133
00134 sql_template = rpki.sql.template("self", "self_id", "self_handle",
00135 "use_hsm", "crl_interval", "regen_margin",
00136 ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00137 handles = ()
00138
00139 use_hsm = False
00140 crl_interval = None
00141 regen_margin = None
00142 bpki_cert = None
00143 bpki_glue = None
00144
00145 def bscs(self):
00146 """Fetch all BSC objects that link to this self object."""
00147 return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00148
00149 def repositories(self):
00150 """Fetch all repository objects that link to this self object."""
00151 return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00152
00153 def parents(self):
00154 """Fetch all parent objects that link to this self object."""
00155 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00156
00157 def children(self):
00158 """Fetch all child objects that link to this self object."""
00159 return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00160
00161 def roas(self):
00162 """Fetch all ROA objects that link to this self object."""
00163 return rpki.rpki_engine.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00164
00165 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00166 """
00167 Extra server actions for self_elt.
00168 """
00169 rpki.log.trace()
00170 self.unimplemented_control("reissue")
00171 actions = []
00172 if q_pdu.rekey:
00173 actions.append(self.serve_rekey)
00174 if q_pdu.revoke:
00175 actions.append(self.serve_revoke)
00176 if q_pdu.revoke_forgotten:
00177 actions.append(self.serve_revoke_forgotten)
00178 if q_pdu.publish_world_now:
00179 actions.append(self.serve_publish_world_now)
00180 if q_pdu.run_now:
00181 actions.append(self.serve_run_now)
00182 def loop(iterator, action):
00183 action(iterator, eb)
00184 rpki.async.iterator(actions, loop, cb)
00185
00186 def serve_rekey(self, cb, eb):
00187 """
00188 Handle a left-right rekey action for this self.
00189 """
00190 rpki.log.trace()
00191 def loop(iterator, parent):
00192 parent.serve_rekey(iterator, eb)
00193 rpki.async.iterator(self.parents(), loop, cb)
00194
00195 def serve_revoke(self, cb, eb):
00196 """
00197 Handle a left-right revoke action for this self.
00198 """
00199 rpki.log.trace()
00200 def loop(iterator, parent):
00201 parent.serve_revoke(iterator, eb)
00202 rpki.async.iterator(self.parents(), loop, cb)
00203
00204 def serve_revoke_forgotten(self, cb, eb):
00205 """
00206 Handle a left-right revoke_forgotten action for this self.
00207 """
00208 rpki.log.trace()
00209 def loop(iterator, parent):
00210 parent.serve_revoke_forgotten(iterator, eb)
00211 rpki.async.iterator(self.parents(), loop, cb)
00212
00213 def serve_publish_world_now(self, cb, eb):
00214 """
00215 Handle a left-right publish_world_now action for this self.
00216
00217 The publication stuff needs refactoring, right now publication is
00218 interleaved with local operations in a way that forces far too
00219 many bounces through the task system for any complex update. The
00220 whole thing ought to be rewritten to queue up outgoing publication
00221 PDUs and only send them when we're all done or when we need to
00222 force publication at a particular point in a multi-phase operation.
00223
00224 Once that reorganization has been done, this method should be
00225 rewritten to reuse the low-level publish() methods that each
00226 object will have...but we're not there yet. So, for now, we just
00227 do this via brute force. Think of it as a trial version to see
00228 whether we've identified everything that needs to be republished
00229 for this operation.
00230 """
00231
00232 def loop(iterator, parent):
00233 q_msg = rpki.publication.msg.query()
00234 for ca in parent.cas():
00235 ca_detail = ca.fetch_active()
00236 if ca_detail is not None:
00237 q_msg.append(rpki.publication.crl_elt.make_publish(ca_detail.crl_uri(ca), ca_detail.latest_crl))
00238 q_msg.append(rpki.publication.manifest_elt.make_publish(ca_detail.manifest_uri(ca), ca_detail.latest_manifest))
00239 q_msg.extend(rpki.publication.certificate_elt.make_publish(c.uri(ca), c.cert) for c in ca_detail.child_certs())
00240 q_msg.extend(rpki.publication.roa_elt.make_publish(r.uri(), r.roa) for r in ca_detail.roas() if r.roa is not None)
00241 parent.repository().call_pubd(iterator, eb, q_msg)
00242
00243 rpki.async.iterator(self.parents(), loop, cb)
00244
00245 def serve_run_now(self, cb, eb):
00246 """
00247 Handle a left-right run_now action for this self.
00248 """
00249 rpki.log.debug("Forced immediate run of periodic actions for self %s[%d]" % (self.self_handle, self.self_id))
00250 self.cron(cb)
00251
00252 def serve_fetch_one_maybe(self):
00253 """
00254 Find the self object upon which a get, set, or destroy action
00255 should operate, or which would conflict with a create method.
00256 """
00257 return self.serve_fetch_handle(self.gctx, None, self.self_handle)
00258
00259 @classmethod
00260 def serve_fetch_handle(cls, gctx, self_id, self_handle):
00261 """
00262 Find a self object based on its self_handle.
00263 """
00264 return cls.sql_fetch_where1(gctx, "self_handle = %s", self_handle)
00265
00266 def serve_fetch_all(self):
00267 """
00268 Find the self objects upon which a list action should operate.
00269 This is different from the list action for all other objects,
00270 where list only works within a given self_id context.
00271 """
00272 return self.sql_fetch_all(self.gctx)
00273
00274 def cron(self, cb):
00275 """
00276 Periodic tasks.
00277 """
00278
00279 def one():
00280 self.gctx.checkpoint()
00281 rpki.log.debug("Self %s[%d] polling parents" % (self.self_handle, self.self_id))
00282 self.client_poll(two)
00283
00284 def two():
00285 self.gctx.checkpoint()
00286 rpki.log.debug("Self %s[%d] updating children" % (self.self_handle, self.self_id))
00287 self.update_children(three)
00288
00289 def three():
00290 self.gctx.checkpoint()
00291 rpki.log.debug("Self %s[%d] updating ROAs" % (self.self_handle, self.self_id))
00292 self.update_roas(four)
00293
00294 def four():
00295 self.gctx.checkpoint()
00296 rpki.log.debug("Self %s[%d] regenerating CRLs and manifests" % (self.self_handle, self.self_id))
00297 self.regenerate_crls_and_manifests(cb)
00298
00299 one()
00300
00301
00302 def client_poll(self, callback):
00303 """
00304 Run the regular client poll cycle with each of this self's parents
00305 in turn.
00306 """
00307
00308 rpki.log.trace()
00309
00310 def parent_loop(parent_iterator, parent):
00311
00312 def got_list(r_msg):
00313 ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas())
00314 self.gctx.checkpoint()
00315
00316 def class_loop(class_iterator, rc):
00317
00318 def class_update_failed(e):
00319 rpki.log.traceback()
00320 rpki.log.warn("Couldn't update class, skipping: %s" % e)
00321 class_iterator()
00322
00323 def class_create_failed(e):
00324 rpki.log.traceback()
00325 rpki.log.warn("Couldn't create class, skipping: %s" % e)
00326 class_iterator()
00327
00328 self.gctx.checkpoint()
00329 if rc.class_name in ca_map:
00330 ca = ca_map[rc.class_name]
00331 del ca_map[rc.class_name]
00332 ca.check_for_updates(parent, rc, class_iterator, class_update_failed)
00333 else:
00334 rpki.rpki_engine.ca_obj.create(parent, rc, class_iterator, class_create_failed)
00335
00336 def class_done():
00337
00338 def ca_loop(iterator, ca):
00339 self.gctx.checkpoint()
00340 ca.delete(parent, iterator)
00341
00342 def ca_done():
00343 self.gctx.checkpoint()
00344 self.gctx.sql.sweep()
00345 parent_iterator()
00346
00347 rpki.async.iterator(ca_map.values(), ca_loop, ca_done)
00348
00349 rpki.async.iterator(r_msg.payload.classes, class_loop, class_done)
00350
00351 def list_failed(e):
00352 rpki.log.traceback()
00353 rpki.log.warn("Couldn't get resource class list from parent %r, skipping: %s" % (parent, e))
00354 parent_iterator()
00355
00356 rpki.up_down.list_pdu.query(parent, got_list, list_failed)
00357
00358 rpki.async.iterator(self.parents(), parent_loop, callback)
00359
00360
00361 def update_children(self, cb):
00362 """
00363 Check for updated IRDB data for all of this self's children and
00364 issue new certs as necessary. Must handle changes both in
00365 resources and in expiration date.
00366 """
00367
00368 rpki.log.trace()
00369 now = rpki.sundial.now()
00370 rsn = now + rpki.sundial.timedelta(seconds = self.regen_margin)
00371 publisher = rpki.rpki_engine.publication_queue()
00372
00373 def loop(iterator, child):
00374
00375 def lose(e):
00376 rpki.log.traceback()
00377 rpki.log.warn("Couldn't update child %r, skipping: %s" % (child, e))
00378 iterator()
00379
00380 def got_resources(irdb_resources):
00381 try:
00382 for child_cert in child_certs:
00383 ca_detail = child_cert.ca_detail()
00384 ca = ca_detail.ca()
00385 if ca_detail.state == "active":
00386 old_resources = child_cert.cert.get_3779resources()
00387 new_resources = irdb_resources.intersection(old_resources).intersection(ca_detail.latest_ca_cert.get_3779resources())
00388
00389 if new_resources.empty():
00390 rpki.log.debug("Resources shrank to the null set, revoking and withdrawing child certificate SKI %s" % child_cert.cert.gSKI())
00391 child_cert.revoke(publisher = publisher)
00392 ca_detail.generate_crl(publisher = publisher)
00393 ca_detail.generate_manifest(publisher = publisher)
00394
00395 elif old_resources != new_resources or (old_resources.valid_until < rsn and irdb_resources.valid_until > now):
00396 rpki.log.debug("Need to reissue child certificate SKI %s" % child_cert.cert.gSKI())
00397 child_cert.reissue(
00398 ca_detail = ca_detail,
00399 resources = new_resources,
00400 publisher = publisher)
00401
00402 elif old_resources.valid_until < now:
00403 rpki.log.debug("Child certificate SKI %s has expired: cert.valid_until %s, irdb.valid_until %s"
00404 % (child_cert.cert.gSKI(), old_resources.valid_until, irdb_resources.valid_until))
00405 child_cert.sql_delete()
00406 publisher.withdraw(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = ca.parent().repository())
00407 ca_detail.generate_manifest(publisher = publisher)
00408
00409 except (SystemExit, rpki.async.ExitNow):
00410 raise
00411 except Exception, e:
00412 self.gctx.checkpoint()
00413 lose(e)
00414 else:
00415 self.gctx.checkpoint()
00416 iterator()
00417
00418 self.gctx.checkpoint()
00419 child_certs = child.child_certs()
00420 if child_certs:
00421 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, lose)
00422 else:
00423 iterator()
00424
00425 def done():
00426 def lose(e):
00427 rpki.log.traceback()
00428 rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e))
00429 self.gctx.checkpoint()
00430 cb()
00431 self.gctx.checkpoint()
00432 publisher.call_pubd(cb, lose)
00433
00434 rpki.async.iterator(self.children(), loop, done)
00435
00436
00437 def regenerate_crls_and_manifests(self, cb):
00438 """
00439 Generate new CRLs and manifests as necessary for all of this
00440 self's CAs. Extracting nextUpdate from a manifest is hard at the
00441 moment due to implementation silliness, so for now we generate a
00442 new manifest whenever we generate a new CRL
00443
00444 This method also cleans up tombstones left behind by revoked
00445 ca_detail objects, since we're walking through the relevant
00446 portions of the database anyway.
00447 """
00448
00449 rpki.log.trace()
00450 now = rpki.sundial.now()
00451 regen_margin = rpki.sundial.timedelta(seconds = self.regen_margin)
00452 publisher = rpki.rpki_engine.publication_queue()
00453
00454 for parent in self.parents():
00455 for ca in parent.cas():
00456 try:
00457 for ca_detail in ca.fetch_revoked():
00458 if now > ca_detail.latest_crl.getNextUpdate():
00459 ca_detail.delete(ca = ca, publisher = publisher)
00460 ca_detail = ca.fetch_active()
00461 if ca_detail is not None and now + regen_margin> ca_detail.latest_crl.getNextUpdate():
00462 ca_detail.generate_crl(publisher = publisher)
00463 ca_detail.generate_manifest(publisher = publisher)
00464 except (SystemExit, rpki.async.ExitNow):
00465 raise
00466 except Exception, e:
00467 rpki.log.traceback()
00468 rpki.log.warn("Couldn't regenerate CRLs and manifests for CA %r, skipping: %s" % (ca, e))
00469
00470 def lose(e):
00471 rpki.log.traceback()
00472 rpki.log.warn("Couldn't publish updated CRLs and manifests for self %r, skipping: %s" % (self.self_handle, e))
00473 self.gctx.checkpoint()
00474 cb()
00475
00476 self.gctx.checkpoint()
00477 publisher.call_pubd(cb, lose)
00478
00479
00480 def update_roas(self, cb):
00481 """
00482 Generate or update ROAs for this self.
00483 """
00484
00485 def got_roa_requests(roa_requests):
00486
00487 self.gctx.checkpoint()
00488
00489 if self.gctx.sql.dirty:
00490 rpki.log.warn("Unexpected dirty SQL cache, flushing")
00491 self.gctx.sql.sweep()
00492
00493 roas = {}
00494 orphans = []
00495 for roa in self.roas():
00496 k = (roa.asn, str(roa.ipv4), str(roa.ipv6))
00497 if k not in roas:
00498 roas[k] = roa
00499 elif (roa.roa is not None and roa.cert is not None and roa.ca_detail() is not None and roa.ca_detail().state == "active" and
00500 (roas[k].roa is None or roas[k].cert is None or roas[k].ca_detail() is None or roas[k].ca_detail().state != "active")):
00501 orphans.append(roas[k])
00502 roas[k] = roa
00503 else:
00504 orphans.append(roa)
00505
00506 publisher = rpki.rpki_engine.publication_queue()
00507 ca_details = set()
00508
00509 seen = set()
00510 for roa_request in roa_requests:
00511 try:
00512 k = (roa_request.asn, str(roa_request.ipv4), str(roa_request.ipv6))
00513 if k in seen:
00514 rpki.log.warn("Skipping duplicate ROA request %r for %r" % (k, roa_request))
00515 continue
00516 seen.add(k)
00517 roa = roas.pop(k, None)
00518 if roa is None:
00519 roa = rpki.rpki_engine.roa_obj(self.gctx, self.self_id, roa_request.asn, roa_request.ipv4, roa_request.ipv6)
00520 rpki.log.debug("Couldn't find existing ROA matching %r, created %r" % (k, roa))
00521 else:
00522 rpki.log.debug("Found existing ROA %r matching %r" % (roa, k))
00523 roa.update(publisher = publisher, fast = True)
00524 ca_details.add(roa.ca_detail())
00525 except (SystemExit, rpki.async.ExitNow):
00526 raise
00527 except Exception, e:
00528 if not isinstance(e, rpki.exceptions.NoCoveringCertForROA):
00529 rpki.log.traceback()
00530 rpki.log.warn("Could not update ROA %r, %r, skipping: %s" % (roa_request, roa, e))
00531
00532 orphans.extend(roas.itervalues())
00533 for roa in orphans:
00534 try:
00535 ca_details.add(roa.ca_detail())
00536 roa.revoke(publisher = publisher, fast = True)
00537 except (SystemExit, rpki.async.ExitNow):
00538 raise
00539 except Exception, e:
00540 rpki.log.traceback()
00541 rpki.log.warn("Could not revoke ROA %r: %s" % (roa, e))
00542
00543 for ca_detail in ca_details:
00544 ca_detail.generate_crl(publisher = publisher)
00545 ca_detail.generate_manifest(publisher = publisher)
00546
00547 self.gctx.sql.sweep()
00548
00549 def publication_failed(e):
00550 rpki.log.traceback()
00551 rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e))
00552 self.gctx.checkpoint()
00553 cb()
00554
00555 self.gctx.checkpoint()
00556 publisher.call_pubd(cb, publication_failed)
00557
00558 def roa_requests_failed(e):
00559 rpki.log.traceback()
00560 rpki.log.warn("Could not fetch ROA requests for %s, skipping: %s" % (self.self_handle, e))
00561 cb()
00562
00563 self.gctx.checkpoint()
00564 self.gctx.irdb_query_roa_requests(self.self_handle, got_roa_requests, roa_requests_failed)
00565
00566 class bsc_elt(data_elt):
00567 """
00568 <bsc/> (Business Signing Context) element.
00569 """
00570
00571 element_name = "bsc"
00572 attributes = ("action", "tag", "self_handle", "bsc_handle", "key_type", "hash_alg", "key_length")
00573 elements = ("signing_cert", "signing_cert_crl", "pkcs10_request")
00574 booleans = ("generate_keypair",)
00575
00576 sql_template = rpki.sql.template("bsc", "bsc_id", "bsc_handle",
00577 "self_id", "hash_alg",
00578 ("private_key_id", rpki.x509.RSA),
00579 ("pkcs10_request", rpki.x509.PKCS10),
00580 ("signing_cert", rpki.x509.X509),
00581 ("signing_cert_crl", rpki.x509.CRL))
00582 handles = (("self", self_elt),)
00583
00584 private_key_id = None
00585 pkcs10_request = None
00586 signing_cert = None
00587 signing_cert_crl = None
00588
00589 def repositories(self):
00590 """Fetch all repository objects that link to this BSC object."""
00591 return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00592
00593 def parents(self):
00594 """Fetch all parent objects that link to this BSC object."""
00595 return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00596
00597 def children(self):
00598 """Fetch all child objects that link to this BSC object."""
00599 return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00600
00601 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00602 """
00603 Extra server actions for bsc_elt -- handle key generation. For
00604 now this only allows RSA with SHA-256.
00605 """
00606 if q_pdu.generate_keypair:
00607 assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
00608 self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048)
00609 self.pkcs10_request = rpki.x509.PKCS10.create(self.private_key_id)
00610 r_pdu.pkcs10_request = self.pkcs10_request
00611 data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb)
00612
00613 class repository_elt(data_elt):
00614 """
00615 <repository/> element.
00616 """
00617
00618 element_name = "repository"
00619 attributes = ("action", "tag", "self_handle", "repository_handle", "bsc_handle", "peer_contact_uri")
00620 elements = ("bpki_cert", "bpki_glue")
00621
00622 sql_template = rpki.sql.template("repository", "repository_id", "repository_handle",
00623 "self_id", "bsc_id", "peer_contact_uri",
00624 ("bpki_cert", rpki.x509.X509),
00625 ("bpki_glue", rpki.x509.X509),
00626 ("last_cms_timestamp", rpki.sundial.datetime))
00627
00628 handles = (("self", self_elt), ("bsc", bsc_elt))
00629
00630 bpki_cert = None
00631 bpki_glue = None
00632
00633 def parents(self):
00634 """Fetch all parent objects that link to this repository object."""
00635 return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
00636
00637 @staticmethod
00638 def default_pubd_handler(pdu):
00639 """
00640 Default handler for publication response PDUs.
00641 """
00642 pdu.raise_if_error()
00643
00644 def call_pubd(self, callback, errback, q_msg, handlers = None):
00645 """
00646 Send a message to publication daemon and return the response.
00647
00648 As a convenience, attempting to send an empty message returns
00649 immediate success without sending anything.
00650
00651 Handlers is a dict of handler functions to process the response
00652 PDUs. If the tag value in the response PDU appears in the dict,
00653 the associated handler is called to process the PDU. If no tag
00654 matches, default_pubd_handler() is called. A handler value of
00655 False suppresses calling of the default handler.
00656 """
00657
00658 try:
00659 rpki.log.trace()
00660
00661 self.gctx.sql.sweep()
00662
00663 if not q_msg:
00664 return callback()
00665
00666 if handlers is None:
00667 handlers = {}
00668
00669 for q_pdu in q_msg:
00670 rpki.log.info("Sending <%s %r %r> to pubd" % (q_pdu.action, q_pdu.uri, q_pdu.payload))
00671
00672 bsc = self.bsc()
00673 q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
00674 bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_cert, self.bpki_glue)
00675
00676 def done(r_der):
00677 try:
00678 r_msg = rpki.publication.cms_msg(DER = r_der).unwrap(bpki_ta_path)
00679 for r_pdu in r_msg:
00680 handler = handlers.get(r_pdu.tag, self.default_pubd_handler)
00681 if handler:
00682 handler(r_pdu)
00683 if len(q_msg) != len(r_msg):
00684 raise rpki.exceptions.BadPublicationReply, "Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg)
00685 callback()
00686 except (rpki.async.ExitNow, SystemExit):
00687 raise
00688 except Exception, e:
00689 errback(e)
00690
00691 rpki.http.client(
00692 url = self.peer_contact_uri,
00693 msg = q_der,
00694 callback = done,
00695 errback = errback)
00696
00697 except (rpki.async.ExitNow, SystemExit):
00698 raise
00699 except Exception, e:
00700 errback(e)
00701
00702 class parent_elt(data_elt):
00703 """
00704 <parent/> element.
00705 """
00706
00707 element_name = "parent"
00708 attributes = ("action", "tag", "self_handle", "parent_handle", "bsc_handle", "repository_handle",
00709 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00710 elements = ("bpki_cms_cert", "bpki_cms_glue")
00711 booleans = ("rekey", "reissue", "revoke", "revoke_forgotten")
00712
00713 sql_template = rpki.sql.template("parent", "parent_id", "parent_handle",
00714 "self_id", "bsc_id", "repository_id",
00715 "peer_contact_uri", "sia_base",
00716 "sender_name", "recipient_name",
00717 ("bpki_cms_cert", rpki.x509.X509),
00718 ("bpki_cms_glue", rpki.x509.X509),
00719 ("last_cms_timestamp", rpki.sundial.datetime))
00720
00721 handles = (("self", self_elt), ("bsc", bsc_elt), ("repository", repository_elt))
00722
00723 bpki_cms_cert = None
00724 bpki_cms_glue = None
00725
00726 def repository(self):
00727 """Fetch repository object to which this parent object links."""
00728 return repository_elt.sql_fetch(self.gctx, self.repository_id)
00729
00730 def cas(self):
00731 """Fetch all CA objects that link to this parent object."""
00732 return rpki.rpki_engine.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
00733
00734 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00735 """
00736 Extra server actions for parent_elt.
00737 """
00738 self.unimplemented_control("reissue")
00739 actions = []
00740 if q_pdu.rekey:
00741 actions.append(self.serve_rekey)
00742 if q_pdu.revoke:
00743 actions.append(self.serve_revoke)
00744 if q_pdu.revoke_forgotten:
00745 actions.append(self.serve_revoke_forgotten)
00746 def loop(iterator, action):
00747 action(iterator, eb)
00748 rpki.async.iterator(actions, loop, cb)
00749
00750 def serve_rekey(self, cb, eb):
00751 """
00752 Handle a left-right rekey action for this parent.
00753 """
00754 def loop(iterator, ca):
00755 ca.rekey(iterator, eb)
00756 rpki.async.iterator(self.cas(), loop, cb)
00757
00758 def serve_revoke(self, cb, eb):
00759 """
00760 Handle a left-right revoke action for this parent.
00761 """
00762 def loop(iterator, ca):
00763 ca.revoke(cb = iterator, eb = eb)
00764 rpki.async.iterator(self.cas(), loop, cb)
00765
00766 def serve_revoke_forgotten(self, cb, eb):
00767 """
00768 Handle a left-right revoke_forgotten action for this parent.
00769
00770 This is a bit fiddly: we have to compare the result of an up-down
00771 list query with what we have locally and identify the SKIs of any
00772 certificates that have gone missing. This should never happen in
00773 ordinary operation, but can arise if we have somehow lost a
00774 private key, in which case there is nothing more we can do with
00775 the issued cert, so we have to clear it. As this really is not
00776 supposed to happen, we don't clear it automatically, instead we
00777 require an explicit trigger.
00778 """
00779
00780 def got_list(r_msg):
00781
00782 ca_map = dict((ca.parent_resource_class, ca) for ca in self.cas())
00783
00784 def rc_loop(rc_iterator, rc):
00785
00786 if rc.class_name in ca_map:
00787
00788 def ski_loop(ski_iterator, ski):
00789 rpki.log.warn("Revoking certificates missing from our database, class %r, SKI %s" % (rc.class_name, ski))
00790 rpki.up_down.revoke_pdu.query(ca, ski, lambda x: ski_iterator(), eb)
00791
00792 ca = ca_map[rc.class_name]
00793 skis_parent_knows_about = set(c.cert.gSKI() for c in rc.certs)
00794 skis_ca_knows_about = set(ca_detail.latest_ca_cert.gSKI() for ca_detail in ca.fetch_issue_response_candidates())
00795 skis_only_parent_knows_about = skis_parent_knows_about - skis_ca_knows_about
00796 rpki.async.iterator(skis_only_parent_knows_about, ski_loop, rc_iterator)
00797
00798 else:
00799 rc_iterator()
00800
00801 rpki.async.iterator(r_msg.payload.classes, rc_loop, cb)
00802
00803 rpki.up_down.list_pdu.query(self, got_list, eb)
00804
00805
00806 def query_up_down(self, q_pdu, cb, eb):
00807 """
00808 Client code for sending one up-down query PDU to this parent.
00809 """
00810
00811 rpki.log.trace()
00812
00813 bsc = self.bsc()
00814 if bsc is None:
00815 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00816
00817 if bsc.signing_cert is None:
00818 raise rpki.exceptions.BSCNotReady, "BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id)
00819
00820 q_msg = rpki.up_down.message_pdu.make_query(
00821 payload = q_pdu,
00822 sender = self.sender_name,
00823 recipient = self.recipient_name)
00824
00825 q_der = rpki.up_down.cms_msg().wrap(q_msg, bsc.private_key_id,
00826 bsc.signing_cert,
00827 bsc.signing_cert_crl)
00828
00829 def unwrap(r_der):
00830 try:
00831 r_msg = rpki.up_down.cms_msg(DER = r_der).unwrap((self.gctx.bpki_ta,
00832 self.self().bpki_cert,
00833 self.self().bpki_glue,
00834 self.bpki_cms_cert,
00835 self.bpki_cms_glue))
00836 r_msg.payload.check_response()
00837 except (SystemExit, rpki.async.ExitNow):
00838 raise
00839 except Exception, e:
00840 eb(e)
00841 else:
00842 cb(r_msg)
00843
00844 rpki.http.client(
00845 msg = q_der,
00846 url = self.peer_contact_uri,
00847 callback = unwrap,
00848 errback = eb)
00849
00850 class child_elt(data_elt):
00851 """
00852 <child/> element.
00853 """
00854
00855 element_name = "child"
00856 attributes = ("action", "tag", "self_handle", "child_handle", "bsc_handle")
00857 elements = ("bpki_cert", "bpki_glue")
00858 booleans = ("reissue", )
00859
00860 sql_template = rpki.sql.template("child", "child_id", "child_handle",
00861 "self_id", "bsc_id",
00862 ("bpki_cert", rpki.x509.X509),
00863 ("bpki_glue", rpki.x509.X509),
00864 ("last_cms_timestamp", rpki.sundial.datetime))
00865
00866 handles = (("self", self_elt), ("bsc", bsc_elt))
00867
00868 bpki_cert = None
00869 bpki_glue = None
00870
00871 def child_certs(self, ca_detail = None, ski = None, unique = False):
00872 """Fetch all child_cert objects that link to this child object."""
00873 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
00874
00875 def parents(self):
00876 """Fetch all parent objects that link to self object to which this child object links."""
00877 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00878
00879 def ca_from_class_name(self, class_name):
00880 """
00881 Fetch the CA corresponding to an up-down class_name.
00882 """
00883 if not class_name.isdigit():
00884 raise rpki.exceptions.BadClassNameSyntax, "Bad class name %s" % class_name
00885 ca = rpki.rpki_engine.ca_obj.sql_fetch(self.gctx, long(class_name))
00886 if ca is None:
00887 raise rpki.exceptions.ClassNameUnknown, "Unknown class name %s" % class_name
00888 parent = ca.parent()
00889 if self.self_id != parent.self_id:
00890 raise rpki.exceptions.ClassNameMismatch, "Class name mismatch: child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id)
00891 return ca
00892
00893 def serve_destroy_hook(self, cb, eb):
00894 """
00895 Extra server actions when destroying a child_elt.
00896 """
00897 publisher = rpki.rpki_engine.publication_queue()
00898 for child_cert in self.child_certs():
00899 child_cert.revoke(publisher = publisher,
00900 generate_crl_and_manifest = True)
00901 publisher.call_pubd(cb, eb)
00902
00903 def serve_up_down(self, query, callback):
00904 """
00905 Outer layer of server handling for one up-down PDU from this child.
00906 """
00907
00908 rpki.log.trace()
00909
00910 bsc = self.bsc()
00911 if bsc is None:
00912 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00913 q_msg = rpki.up_down.cms_msg(DER = query).unwrap((self.gctx.bpki_ta,
00914 self.self().bpki_cert,
00915 self.self().bpki_glue,
00916 self.bpki_cert,
00917 self.bpki_glue))
00918 q_msg.payload.gctx = self.gctx
00919 if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id):
00920 raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender
00921
00922 def done(r_msg):
00923
00924
00925
00926
00927
00928 reply = rpki.up_down.cms_msg().wrap(r_msg, bsc.private_key_id,
00929 bsc.signing_cert, bsc.signing_cert_crl)
00930 callback(reply)
00931
00932 try:
00933 q_msg.serve_top_level(self, done)
00934 except (rpki.async.ExitNow, SystemExit):
00935 raise
00936 except rpki.exceptions.NoActiveCA, data:
00937 done(q_msg.serve_error(data))
00938 except Exception, data:
00939 rpki.log.traceback()
00940 done(q_msg.serve_error(data))
00941
00942 class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
00943 """
00944 <list_resources/> element.
00945 """
00946
00947 element_name = "list_resources"
00948 attributes = ("self_handle", "tag", "child_handle", "valid_until", "asn", "ipv4", "ipv6")
00949 valid_until = None
00950
00951 def startElement(self, stack, name, attrs):
00952 """
00953 Handle <list_resources/> element. This requires special handling
00954 due to the data types of some of the attributes.
00955 """
00956 assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
00957 self.read_attrs(attrs)
00958 if isinstance(self.valid_until, str):
00959 self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until)
00960 if self.asn is not None:
00961 self.asn = rpki.resource_set.resource_set_as(self.asn)
00962 if self.ipv4 is not None:
00963 self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
00964 if self.ipv6 is not None:
00965 self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
00966
00967 def toXML(self):
00968 """
00969 Generate <list_resources/> element. This requires special
00970 handling due to the data types of some of the attributes.
00971 """
00972 elt = self.make_elt()
00973 if isinstance(self.valid_until, int):
00974 elt.set("valid_until", self.valid_until.toXMLtime())
00975 return elt
00976
00977 class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace):
00978 """
00979 <list_roa_requests/> element.
00980 """
00981
00982 element_name = "list_roa_requests"
00983 attributes = ("self_handle", "tag", "asn", "ipv4", "ipv6")
00984
00985 def startElement(self, stack, name, attrs):
00986 """
00987 Handle <list_roa_requests/> element. This requires special handling
00988 due to the data types of some of the attributes.
00989 """
00990 assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack)
00991 self.read_attrs(attrs)
00992 if self.ipv4 is not None:
00993 self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4)
00994 if self.ipv6 is not None:
00995 self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6)
00996
00997 class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace):
00998 """
00999 <list_published_objects/> element.
01000 """
01001
01002 element_name = "list_published_objects"
01003 attributes = ("self_handle", "tag", "uri")
01004 text_attribute = "obj"
01005
01006 obj = None
01007
01008 def serve_dispatch(self, r_msg, cb, eb):
01009 """
01010 Handle a <list_published_objects/> query. The method name is a
01011 misnomer here, there's no action attribute and no dispatch, we
01012 just dump every published object for the specified <self/> and return.
01013 """
01014 for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents():
01015 for ca in parent.cas():
01016 ca_detail = ca.fetch_active()
01017 if ca_detail is not None:
01018 r_msg.append(self.make_reply(ca_detail.crl_uri(ca), ca_detail.latest_crl))
01019 r_msg.append(self.make_reply(ca_detail.manifest_uri(ca), ca_detail.latest_manifest))
01020 r_msg.extend(self.make_reply(c.uri(ca), c.cert) for c in ca_detail.child_certs())
01021 r_msg.extend(self.make_reply(r.uri(), r.roa) for r in ca_detail.roas() if r.roa is not None)
01022 cb()
01023
01024 def make_reply(self, uri, obj):
01025 """
01026 Generate one reply PDU.
01027 """
01028 r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle, uri = uri)
01029 r_pdu.obj = obj.get_Base64()
01030 return r_pdu
01031
01032 class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
01033 """
01034 <list_received_resources/> element.
01035 """
01036
01037 element_name = "list_received_resources"
01038 attributes = ("self_handle", "tag", "parent_handle",
01039 "notBefore", "notAfter", "uri", "sia_uri", "aia_uri", "asn", "ipv4", "ipv6")
01040
01041 def serve_dispatch(self, r_msg, cb, eb):
01042 """
01043 Handle a <list_received_resources/> query. The method name is a
01044 misnomer here, there's no action attribute and no dispatch, we
01045 just dump a bunch of data about every certificate issued to us by
01046 one of our parents, then return.
01047 """
01048 for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents():
01049 for ca in parent.cas():
01050 ca_detail = ca.fetch_active()
01051 if ca_detail is not None and ca_detail.latest_ca_cert is not None:
01052 r_msg.append(self.make_reply(parent.parent_handle, ca_detail.ca_cert_uri, ca_detail.latest_ca_cert))
01053 cb()
01054
01055 def make_reply(self, parent_handle, uri, cert):
01056 """
01057 Generate one reply PDU.
01058 """
01059 resources = cert.get_3779resources()
01060 return self.make_pdu(
01061 tag = self.tag,
01062 self_handle = self.self_handle,
01063 parent_handle = parent_handle,
01064 notBefore = str(cert.getNotBefore()),
01065 notAfter = str(cert.getNotAfter()),
01066 uri = uri,
01067 sia_uri = cert.get_sia_directory_uri(),
01068 aia_uri = cert.get_aia_uri(),
01069 asn = resources.asn,
01070 ipv4 = resources.v4,
01071 ipv6 = resources.v6)
01072
01073 class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace):
01074 """
01075 <report_error/> element.
01076 """
01077
01078 element_name = "report_error"
01079 attributes = ("tag", "self_handle", "error_code")
01080 text_attribute = "error_text"
01081
01082 error_text = None
01083
01084 @classmethod
01085 def from_exception(cls, e, self_handle = None, tag = None):
01086 """
01087 Generate a <report_error/> element from an exception.
01088 """
01089 self = cls()
01090 self.self_handle = self_handle
01091 self.tag = tag
01092 self.error_code = e.__class__.__name__
01093 self.error_text = str(e)
01094 return self
01095
01096 class msg(rpki.xml_utils.msg, left_right_namespace):
01097 """
01098 Left-right PDU.
01099 """
01100
01101
01102
01103 version = 1
01104
01105
01106
01107 pdus = dict((x.element_name, x)
01108 for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt,
01109 list_resources_elt, list_roa_requests_elt,
01110 list_published_objects_elt, list_received_resources_elt,
01111 report_error_elt))
01112
01113 def serve_top_level(self, gctx, cb):
01114 """
01115 Serve one msg PDU.
01116 """
01117
01118 r_msg = self.__class__.reply()
01119
01120 def loop(iterator, q_pdu):
01121
01122 def fail(e):
01123 if not isinstance(e, rpki.exceptions.NotFound):
01124 rpki.log.traceback()
01125 r_msg.append(report_error_elt.from_exception(e, self_handle = q_pdu.self_handle, tag = q_pdu.tag))
01126 cb(r_msg)
01127
01128 try:
01129 q_pdu.gctx = gctx
01130 q_pdu.serve_dispatch(r_msg, iterator, fail)
01131 except (rpki.async.ExitNow, SystemExit):
01132 raise
01133 except Exception, e:
01134 fail(e)
01135
01136 def done():
01137 cb(r_msg)
01138
01139 rpki.async.iterator(self, loop, done)
01140
01141 class sax_handler(rpki.xml_utils.sax_handler):
01142 """
01143 SAX handler for Left-Right protocol.
01144 """
01145
01146 pdu = msg
01147 name = "msg"
01148 version = "1"
01149
01150 class cms_msg(rpki.x509.XML_CMS_object):
01151 """
01152 Class to hold a CMS-signed left-right PDU.
01153 """
01154
01155 encoding = "us-ascii"
01156 schema = rpki.relaxng.left_right
01157 saxify = sax_handler.saxify