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