00001 """
00002 RPKI "left-right" protocol.
00003
00004 $Id: left_right.py 2931 2010-01-06 21:03:14Z 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 rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00036 import rpki.https, 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)
00388 if old_resources != new_resources or (old_resources.valid_until < rsn and irdb_resources.valid_until > now):
00389 rpki.log.debug("Need to reissue child certificate SKI %s" % child_cert.cert.gSKI())
00390 child_cert.reissue(
00391 ca_detail = ca_detail,
00392 resources = new_resources,
00393 publisher = publisher)
00394 if old_resources.valid_until < now:
00395 rpki.log.debug("Child certificate SKI %s has expired: cert.valid_until %s, irdb.valid_until %s"
00396 % (child_cert.cert.gSKI(), old_resources.valid_until, irdb_resources.valid_until))
00397 child_cert.sql_delete()
00398 ca_detail.generate_manifest(publisher = publisher)
00399 publisher.withdraw(cls = rpki.publication.certificate_elt, uri = child_cert.uri(ca), obj = child_cert.cert, repository = ca.parent().repository())
00400 except (SystemExit, rpki.async.ExitNow):
00401 raise
00402 except Exception, e:
00403 self.gctx.checkpoint()
00404 lose(e)
00405 else:
00406 self.gctx.checkpoint()
00407 iterator()
00408
00409 self.gctx.checkpoint()
00410 child_certs = child.child_certs()
00411 if child_certs:
00412 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, lose)
00413 else:
00414 iterator()
00415
00416 def done():
00417 def lose(e):
00418 rpki.log.traceback()
00419 rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e))
00420 self.gctx.checkpoint()
00421 cb()
00422 self.gctx.checkpoint()
00423 publisher.call_pubd(cb, lose)
00424
00425 rpki.async.iterator(self.children(), loop, done)
00426
00427
00428 def regenerate_crls_and_manifests(self, cb):
00429 """
00430 Generate new CRLs and manifests as necessary for all of this
00431 self's CAs. Extracting nextUpdate from a manifest is hard at the
00432 moment due to implementation silliness, so for now we generate a
00433 new manifest whenever we generate a new CRL
00434
00435 This method also cleans up tombstones left behind by revoked
00436 ca_detail objects, since we're walking through the relevant
00437 portions of the database anyway.
00438 """
00439
00440 rpki.log.trace()
00441 now = rpki.sundial.now()
00442 publisher = rpki.rpki_engine.publication_queue()
00443
00444 for parent in self.parents():
00445 for ca in parent.cas():
00446 try:
00447 for ca_detail in ca.fetch_revoked():
00448 if now > ca_detail.latest_crl.getNextUpdate():
00449 ca_detail.delete(ca = ca, publisher = publisher)
00450 ca_detail = ca.fetch_active()
00451 if ca_detail is not None and now > ca_detail.latest_crl.getNextUpdate():
00452 ca_detail.generate_crl(publisher = publisher)
00453 ca_detail.generate_manifest(publisher = publisher)
00454 except (SystemExit, rpki.async.ExitNow):
00455 raise
00456 except Exception, e:
00457 rpki.log.traceback()
00458 rpki.log.warn("Couldn't regenerate CRLs and manifests for CA %r, skipping: %s" % (ca, e))
00459
00460 def lose(e):
00461 rpki.log.traceback()
00462 rpki.log.warn("Couldn't publish updated CRLs and manifests for self %r, skipping: %s" % (self.self_handle, e))
00463 self.gctx.checkpoint()
00464 cb()
00465
00466 self.gctx.checkpoint()
00467 publisher.call_pubd(cb, lose)
00468
00469
00470 def update_roas(self, cb):
00471 """
00472 Generate or update ROAs for this self.
00473 """
00474
00475 def got_roa_requests(roa_requests):
00476
00477 self.gctx.checkpoint()
00478
00479 roas = dict(((r.asn, str(r.ipv4), str(r.ipv6)), r) for r in self.roas())
00480
00481 publisher = rpki.rpki_engine.publication_queue()
00482
00483 for roa_request in roa_requests:
00484 try:
00485 roa = roas.pop((roa_request.asn, str(roa_request.ipv4), str(roa_request.ipv6)), None)
00486 if roa is None:
00487 roa = rpki.rpki_engine.roa_obj(self.gctx, self.self_id, roa_request.asn, roa_request.ipv4, roa_request.ipv6)
00488 roa.update(publisher = publisher)
00489 except (SystemExit, rpki.async.ExitNow):
00490 raise
00491 except Exception, e:
00492 if not isinstance(e, rpki.exceptions.NoCoveringCertForROA):
00493 rpki.log.traceback()
00494 rpki.log.warn("Could not update ROA %r, %r, skipping: %s" % (roa_request, roa, e))
00495
00496
00497
00498
00499
00500 for roa in roas.values():
00501 try:
00502 roa.revoke(publisher = publisher)
00503 except (SystemExit, rpki.async.ExitNow):
00504 raise
00505 except Exception, e:
00506 rpki.log.traceback()
00507 rpki.log.warn("Could not revoke ROA %r: %s" % (roa, e))
00508
00509 def publication_failed(e):
00510 rpki.log.traceback()
00511 rpki.log.warn("Couldn't publish for %s, skipping: %s" % (self.self_handle, e))
00512 self.gctx.checkpoint()
00513 cb()
00514
00515 self.gctx.checkpoint()
00516 publisher.call_pubd(cb, publication_failed)
00517
00518 def roa_requests_failed(e):
00519 rpki.log.traceback()
00520 rpki.log.warn("Could not fetch ROA requests for %s, skipping: %s" % (self.self_handle, e))
00521 cb()
00522
00523 self.gctx.checkpoint()
00524 self.gctx.irdb_query_roa_requests(self.self_handle, got_roa_requests, roa_requests_failed)
00525
00526 class bsc_elt(data_elt):
00527 """
00528 <bsc/> (Business Signing Context) element.
00529 """
00530
00531 element_name = "bsc"
00532 attributes = ("action", "tag", "self_handle", "bsc_handle", "key_type", "hash_alg", "key_length")
00533 elements = ("signing_cert", "signing_cert_crl", "pkcs10_request")
00534 booleans = ("generate_keypair",)
00535
00536 sql_template = rpki.sql.template("bsc", "bsc_id", "bsc_handle",
00537 "self_id", "hash_alg",
00538 ("private_key_id", rpki.x509.RSA),
00539 ("pkcs10_request", rpki.x509.PKCS10),
00540 ("signing_cert", rpki.x509.X509),
00541 ("signing_cert_crl", rpki.x509.CRL))
00542 handles = (("self", self_elt),)
00543
00544 private_key_id = None
00545 pkcs10_request = None
00546 signing_cert = None
00547 signing_cert_crl = None
00548
00549 def repositories(self):
00550 """Fetch all repository objects that link to this BSC object."""
00551 return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00552
00553 def parents(self):
00554 """Fetch all parent objects that link to this BSC object."""
00555 return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00556
00557 def children(self):
00558 """Fetch all child objects that link to this BSC object."""
00559 return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00560
00561 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00562 """
00563 Extra server actions for bsc_elt -- handle key generation. For
00564 now this only allows RSA with SHA-256.
00565 """
00566 if q_pdu.generate_keypair:
00567 assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
00568 self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048)
00569 self.pkcs10_request = rpki.x509.PKCS10.create(self.private_key_id)
00570 r_pdu.pkcs10_request = self.pkcs10_request
00571 data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb)
00572
00573 class repository_elt(data_elt):
00574 """
00575 <repository/> element.
00576 """
00577
00578 element_name = "repository"
00579 attributes = ("action", "tag", "self_handle", "repository_handle", "bsc_handle", "peer_contact_uri")
00580 elements = ("bpki_cert", "bpki_glue")
00581
00582 sql_template = rpki.sql.template("repository", "repository_id", "repository_handle",
00583 "self_id", "bsc_id", "peer_contact_uri",
00584 ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00585 handles = (("self", self_elt), ("bsc", bsc_elt))
00586
00587 bpki_cert = None
00588 bpki_glue = None
00589
00590 def parents(self):
00591 """Fetch all parent objects that link to this repository object."""
00592 return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
00593
00594 @staticmethod
00595 def default_pubd_handler(pdu):
00596 """
00597 Default handler for publication response PDUs.
00598 """
00599 pdu.raise_if_error()
00600
00601 def call_pubd(self, callback, errback, q_msg, handlers = {}):
00602 """
00603 Send a message to publication daemon and return the response.
00604
00605 As a convenience, attempting to send an empty message returns
00606 immediate success without sending anything.
00607
00608 Handlers is a dict of handler functions to process the response
00609 PDUs. If the tag value in the response PDU appears in the dict,
00610 the associated handler is called to process the PDU. If no tag
00611 matches, default_pubd_handler() is called. A handler value of
00612 False suppresses calling of the default handler.
00613 """
00614
00615 rpki.log.trace()
00616
00617 self.gctx.sql.sweep()
00618
00619 if not q_msg:
00620 return callback()
00621
00622 for q_pdu in q_msg:
00623 rpki.log.info("Sending <%s %r %r> to pubd" % (q_pdu.action, q_pdu.uri, q_pdu.payload))
00624
00625 bsc = self.bsc()
00626 q_cms = rpki.publication.cms_msg.wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
00627 bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_cert, self.bpki_glue)
00628
00629 def done(r_cms):
00630 try:
00631 r_msg = rpki.publication.cms_msg.unwrap(r_cms, bpki_ta_path)
00632 for r_pdu in r_msg:
00633 handler = handlers.get(r_pdu.tag, self.default_pubd_handler)
00634 if handler:
00635 handler(r_pdu)
00636 if len(q_msg) != len(r_msg):
00637 raise rpki.exceptions.BadPublicationReply, "Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg)
00638 callback()
00639 except (rpki.async.ExitNow, SystemExit):
00640 raise
00641 except Exception, e:
00642 errback(e)
00643
00644 rpki.https.client(
00645 client_key = bsc.private_key_id,
00646 client_cert = bsc.signing_cert,
00647 server_ta = bpki_ta_path,
00648 url = self.peer_contact_uri,
00649 msg = q_cms,
00650 callback = done,
00651 errback = errback)
00652
00653 class parent_elt(data_elt):
00654 """
00655 <parent/> element.
00656 """
00657
00658 element_name = "parent"
00659 attributes = ("action", "tag", "self_handle", "parent_handle", "bsc_handle", "repository_handle",
00660 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00661 elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue")
00662 booleans = ("rekey", "reissue", "revoke", "revoke_forgotten")
00663
00664 sql_template = rpki.sql.template("parent", "parent_id", "parent_handle",
00665 "self_id", "bsc_id", "repository_id",
00666 ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509),
00667 ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509),
00668 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00669 handles = (("self", self_elt), ("bsc", bsc_elt), ("repository", repository_elt))
00670
00671 bpki_cms_cert = None
00672 bpki_cms_glue = None
00673 bpki_https_cert = None
00674 bpki_https_glue = None
00675
00676 def repository(self):
00677 """Fetch repository object to which this parent object links."""
00678 return repository_elt.sql_fetch(self.gctx, self.repository_id)
00679
00680 def cas(self):
00681 """Fetch all CA objects that link to this parent object."""
00682 return rpki.rpki_engine.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
00683
00684 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00685 """
00686 Extra server actions for parent_elt.
00687 """
00688 self.unimplemented_control("reissue")
00689 actions = []
00690 if q_pdu.rekey:
00691 actions.append(self.serve_rekey)
00692 if q_pdu.revoke:
00693 actions.append(self.serve_revoke)
00694 if q_pdu.revoke_forgotten:
00695 actions.append(self.serve_revoke_forgotten)
00696 def loop(iterator, action):
00697 action(iterator, eb)
00698 rpki.async.iterator(actions, loop, cb)
00699
00700 def serve_rekey(self, cb, eb):
00701 """
00702 Handle a left-right rekey action for this parent.
00703 """
00704 def loop(iterator, ca):
00705 ca.rekey(iterator, eb)
00706 rpki.async.iterator(self.cas(), loop, cb)
00707
00708 def serve_revoke(self, cb, eb):
00709 """
00710 Handle a left-right revoke action for this parent.
00711 """
00712 def loop(iterator, ca):
00713 ca.revoke(cb = iterator, eb = eb)
00714 rpki.async.iterator(self.cas(), loop, cb)
00715
00716 def serve_revoke_forgotten(self, cb, eb):
00717 """
00718 Handle a left-right revoke_forgotten action for this parent.
00719
00720 This is a bit fiddly: we have to compare the result of an up-down
00721 list query with what we have locally and identify the SKIs of any
00722 certificates that have gone missing. This should never happen in
00723 ordinary operation, but can arise if we have somehow lost a
00724 private key, in which case there is nothing more we can do with
00725 the issued cert, so we have to clear it. As this really is not
00726 supposed to happen, we don't clear it automatically, instead we
00727 require an explicit trigger.
00728 """
00729
00730 def got_list(r_msg):
00731
00732 ca_map = dict((ca.parent_resource_class, ca) for ca in self.cas())
00733
00734 def rc_loop(rc_iterator, rc):
00735
00736 if rc.class_name in ca_map:
00737
00738 def ski_loop(ski_iterator, ski):
00739 rpki.log.warn("Revoking certificates missing from our database, class %r, SKI %s" % (rc.class_name, ski))
00740 rpki.up_down.revoke_pdu.query(ca, ski, lambda x: ski_iterator(), eb)
00741
00742 ca = ca_map[rc.class_name]
00743 skis_parent_knows_about = set(c.cert.gSKI() for c in rc.certs)
00744 skis_ca_knows_about = set(ca_detail.latest_ca_cert.gSKI() for ca_detail in ca.fetch_issue_response_candidates())
00745 skis_only_parent_knows_about = skis_parent_knows_about - skis_ca_knows_about
00746 rpki.async.iterator(skis_only_parent_knows_about, ski_loop, rc_iterator)
00747
00748 else:
00749 rc_iterator()
00750
00751 rpki.async.iterator(r_msg.payload.classes, rc_loop, cb)
00752
00753 rpki.up_down.list_pdu.query(self, got_list, eb)
00754
00755
00756 def query_up_down(self, q_pdu, cb, eb):
00757 """
00758 Client code for sending one up-down query PDU to this parent.
00759 """
00760
00761 rpki.log.trace()
00762
00763 bsc = self.bsc()
00764 if bsc is None:
00765 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00766
00767 if bsc.signing_cert is None:
00768 raise rpki.exceptions.BSCNotReady, "BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id)
00769
00770 q_msg = rpki.up_down.message_pdu.make_query(
00771 payload = q_pdu,
00772 sender = self.sender_name,
00773 recipient = self.recipient_name)
00774
00775 q_cms = rpki.up_down.cms_msg.wrap(q_msg, bsc.private_key_id,
00776 bsc.signing_cert,
00777 bsc.signing_cert_crl)
00778
00779 def unwrap(der):
00780 try:
00781 r_msg = rpki.up_down.cms_msg.unwrap(der, (self.gctx.bpki_ta,
00782 self.self().bpki_cert, self.self().bpki_glue,
00783 self.bpki_cms_cert, self.bpki_cms_glue))
00784 r_msg.payload.check_response()
00785 except (SystemExit, rpki.async.ExitNow):
00786 raise
00787 except Exception, e:
00788 eb(e)
00789 else:
00790 cb(r_msg)
00791
00792 rpki.https.client(server_ta = (self.gctx.bpki_ta,
00793 self.self().bpki_cert, self.self().bpki_glue,
00794 self.bpki_https_cert, self.bpki_https_glue),
00795 client_key = bsc.private_key_id,
00796 client_cert = bsc.signing_cert,
00797 msg = q_cms,
00798 url = self.peer_contact_uri,
00799 callback = unwrap,
00800 errback = eb)
00801
00802 class child_elt(data_elt):
00803 """
00804 <child/> element.
00805 """
00806
00807 element_name = "child"
00808 attributes = ("action", "tag", "self_handle", "child_handle", "bsc_handle")
00809 elements = ("bpki_cert", "bpki_glue")
00810 booleans = ("reissue", )
00811
00812 sql_template = rpki.sql.template("child", "child_id", "child_handle",
00813 "self_id", "bsc_id",
00814 ("bpki_cert", rpki.x509.X509),
00815 ("bpki_glue", rpki.x509.X509))
00816
00817 handles = (("self", self_elt), ("bsc", bsc_elt))
00818
00819 bpki_cert = None
00820 bpki_glue = None
00821 clear_https_ta_cache = False
00822
00823 def child_certs(self, ca_detail = None, ski = None, unique = False):
00824 """Fetch all child_cert objects that link to this child object."""
00825 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
00826
00827 def parents(self):
00828 """Fetch all parent objects that link to self object to which this child object links."""
00829 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00830
00831 def ca_from_class_name(self, class_name):
00832 """
00833 Fetch the CA corresponding to an up-down class_name.
00834 """
00835 if not class_name.isdigit():
00836 raise rpki.exceptions.BadClassNameSyntax, "Bad class name %s" % class_name
00837 ca = rpki.rpki_engine.ca_obj.sql_fetch(self.gctx, long(class_name))
00838 if ca is None:
00839 raise rpki.exceptions.ClassNameUnknown, "Unknown class name %s" % class_name
00840 parent = ca.parent()
00841 if self.self_id != parent.self_id:
00842 raise rpki.exceptions.ClassNameMismatch, "Class name mismatch: child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id)
00843 return ca
00844
00845 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00846 """
00847 Extra server actions for child_elt.
00848 """
00849 self.unimplemented_control("reissue")
00850 if self.clear_https_ta_cache:
00851 self.gctx.clear_https_ta_cache()
00852 self.clear_https_ta_cache = False
00853 cb()
00854
00855 def serve_destroy_hook(self, cb, eb):
00856 """
00857 Extra server actions when destroying a child_elt.
00858 """
00859 def loop(iterator, child_cert):
00860 child_cert.revoke(callback = iterator, errback = eb)
00861 rpki.async.iterator(self.child_certs(), loop, cb)
00862
00863 def endElement(self, stack, name, text):
00864 """
00865 Handle subelements of <child/> element. These require special
00866 handling because modifying them invalidates the HTTPS trust anchor
00867 cache.
00868 """
00869 rpki.xml_utils.data_elt.endElement(self, stack, name, text)
00870 if name in self.elements:
00871 self.clear_https_ta_cache = True
00872
00873 def serve_up_down(self, query, callback):
00874 """
00875 Outer layer of server handling for one up-down PDU from this child.
00876 """
00877
00878 rpki.log.trace()
00879
00880 bsc = self.bsc()
00881 if bsc is None:
00882 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00883 q_msg = rpki.up_down.cms_msg.unwrap(query, (self.gctx.bpki_ta,
00884 self.self().bpki_cert, self.self().bpki_glue,
00885 self.bpki_cert, self.bpki_glue))
00886 q_msg.payload.gctx = self.gctx
00887 if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id):
00888 raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender
00889
00890 def done(r_msg):
00891
00892
00893
00894
00895
00896 r_cms = rpki.up_down.cms_msg.wrap(r_msg, bsc.private_key_id,
00897 bsc.signing_cert, bsc.signing_cert_crl)
00898 callback(r_cms)
00899
00900 try:
00901 q_msg.serve_top_level(self, done)
00902 except (rpki.async.ExitNow, SystemExit):
00903 raise
00904 except rpki.exceptions.NoActiveCA, data:
00905 done(q_msg.serve_error(data))
00906 except Exception, data:
00907 rpki.log.traceback()
00908 done(q_msg.serve_error(data))
00909
00910 class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
00911 """
00912 <list_resources/> element.
00913 """
00914
00915 element_name = "list_resources"
00916 attributes = ("self_handle", "tag", "child_handle", "valid_until", "asn", "ipv4", "ipv6")
00917 valid_until = None
00918
00919 def startElement(self, stack, name, attrs):
00920 """
00921 Handle <list_resources/> element. This requires special handling
00922 due to the data types of some of the attributes.
00923 """
00924 assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
00925 self.read_attrs(attrs)
00926 if isinstance(self.valid_until, str):
00927 self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until)
00928 if self.asn is not None:
00929 self.asn = rpki.resource_set.resource_set_as(self.asn)
00930 if self.ipv4 is not None:
00931 self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
00932 if self.ipv6 is not None:
00933 self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
00934
00935 def toXML(self):
00936 """
00937 Generate <list_resources/> element. This requires special
00938 handling due to the data types of some of the attributes.
00939 """
00940 elt = self.make_elt()
00941 if isinstance(self.valid_until, int):
00942 elt.set("valid_until", self.valid_until.toXMLtime())
00943 return elt
00944
00945 class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace):
00946 """
00947 <list_roa_requests/> element.
00948 """
00949
00950 element_name = "list_roa_requests"
00951 attributes = ("self_handle", "tag", "asn", "ipv4", "ipv6")
00952
00953 def startElement(self, stack, name, attrs):
00954 """
00955 Handle <list_roa_requests/> element. This requires special handling
00956 due to the data types of some of the attributes.
00957 """
00958 assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack)
00959 self.read_attrs(attrs)
00960 if self.ipv4 is not None:
00961 self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4)
00962 if self.ipv6 is not None:
00963 self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6)
00964
00965 class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace):
00966 """
00967 <list_published_objects/> element.
00968 """
00969
00970 element_name = "list_published_objects"
00971 attributes = ("self_handle", "tag", "uri")
00972 text_attribute = "obj"
00973
00974 obj = None
00975
00976 def serve_dispatch(self, r_msg, cb, eb):
00977 """
00978 Handle a <list_published_objects/> query. The method name is a
00979 misnomer here, there's no action attribute and no dispatch, we
00980 just dump every published object for the specified <self/> and return.
00981 """
00982 for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents():
00983 for ca in parent.cas():
00984 ca_detail = ca.fetch_active()
00985 if ca_detail is not None:
00986 r_msg.append(self.make_reply(ca_detail.crl_uri(ca), ca_detail.latest_crl))
00987 r_msg.append(self.make_reply(ca_detail.manifest_uri(ca), ca_detail.latest_manifest))
00988 r_msg.extend(self.make_reply(c.uri(ca), c.cert) for c in ca_detail.child_certs())
00989 r_msg.extend(self.make_reply(r.uri(), r.roa) for r in ca_detail.roas() if r.roa is not None)
00990 cb()
00991
00992 def make_reply(self, uri, obj):
00993 """
00994 Generate one reply PDU.
00995 """
00996 r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle, uri = uri)
00997 r_pdu.obj = obj.get_Base64()
00998 return r_pdu
00999
01000 class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace):
01001 """
01002 <report_error/> element.
01003 """
01004
01005 element_name = "report_error"
01006 attributes = ("tag", "self_handle", "error_code")
01007 text_attribute = "error_text"
01008
01009 error_text = None
01010
01011 @classmethod
01012 def from_exception(cls, e, self_handle = None, tag = None):
01013 """
01014 Generate a <report_error/> element from an exception.
01015 """
01016 self = cls()
01017 self.self_handle = self_handle
01018 self.tag = tag
01019 self.error_code = e.__class__.__name__
01020 self.error_text = str(e)
01021 return self
01022
01023 class msg(rpki.xml_utils.msg, left_right_namespace):
01024 """
01025 Left-right PDU.
01026 """
01027
01028
01029
01030 version = 1
01031
01032
01033
01034 pdus = dict((x.element_name, x)
01035 for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt,
01036 list_resources_elt, list_roa_requests_elt, list_published_objects_elt,
01037 report_error_elt))
01038
01039 def serve_top_level(self, gctx, cb):
01040 """
01041 Serve one msg PDU.
01042 """
01043
01044 r_msg = self.__class__.reply()
01045
01046 def loop(iterator, q_pdu):
01047
01048 def fail(e):
01049 if not isinstance(e, rpki.exceptions.NotFound):
01050 rpki.log.traceback()
01051 r_msg.append(report_error_elt.from_exception(e, self_handle = q_pdu.self_handle, tag = q_pdu.tag))
01052 cb(r_msg)
01053
01054 try:
01055 q_pdu.gctx = gctx
01056 q_pdu.serve_dispatch(r_msg, iterator, fail)
01057 except (rpki.async.ExitNow, SystemExit):
01058 raise
01059 except Exception, e:
01060 fail(e)
01061
01062 def done():
01063 cb(r_msg)
01064
01065 rpki.async.iterator(self, loop, done)
01066
01067 class sax_handler(rpki.xml_utils.sax_handler):
01068 """
01069 SAX handler for Left-Right protocol.
01070 """
01071
01072 pdu = msg
01073 name = "msg"
01074 version = "1"
01075
01076 class cms_msg(rpki.x509.XML_CMS_object):
01077 """
01078 Class to hold a CMS-signed left-right PDU.
01079 """
01080
01081 encoding = "us-ascii"
01082 schema = rpki.relaxng.left_right
01083 saxify = sax_handler.saxify