00001 """
00002 RPKI "left-right" protocol.
00003
00004 $Id: left_right.py 2510 2009-06-09 20:25:16Z 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 traceback
00036 import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00037 import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
00038 import rpki.publication, rpki.async
00039
00040
00041 enforce_strict_up_down_xml_sender = False
00042
00043 class left_right_namespace(object):
00044 """
00045 XML namespace parameters for left-right protocol.
00046 """
00047
00048 xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/"
00049 nsmap = { None : xmlns }
00050
00051 class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_namespace):
00052 """
00053 Virtual class for top-level left-right protocol data elements.
00054 """
00055
00056 handles = ()
00057
00058 self_id = 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 """Set self_handle when cloning."""
00070 r_pdu.self_handle = self.self_handle
00071
00072 @classmethod
00073 def serve_fetch_handle(cls, gctx, self_id, handle):
00074 """
00075 Find an object based on its handle.
00076 """
00077 return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id))
00078
00079 def serve_fetch_one_maybe(self):
00080 """
00081 Find the object on which a get, set, or destroy method should
00082 operate, or which would conflict with a create method.
00083 """
00084 where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3)
00085 args = (getattr(self, self.element_name + "_handle"), self.self_handle)
00086 return self.sql_fetch_where1(self.gctx, where, args, "self")
00087
00088 def serve_fetch_all(self):
00089 """
00090 Find the objects on which a list method should operate.
00091 """
00092 where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name
00093 return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self")
00094
00095 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00096 """
00097 Hook to do _handle => _id translation before saving.
00098 """
00099 for tag, elt in self.handles:
00100 id_name = tag + "_id"
00101 if getattr(r_pdu, id_name, None) is None:
00102 x = elt.serve_fetch_handle(self.gctx, self.self_id, getattr(q_pdu, tag + "_handle"))
00103 if x is None:
00104 raise rpki.exceptions.NotFound
00105 val = getattr(x, id_name)
00106 setattr(self, id_name, val)
00107 setattr(r_pdu, id_name, val)
00108 cb()
00109
00110 def unimplemented_control(self, *controls):
00111 """
00112 Uniform handling for unimplemented control operations.
00113 """
00114 unimplemented = [x for x in controls if getattr(self, x, False)]
00115 if unimplemented:
00116 raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented)
00117
00118 class self_elt(data_elt):
00119 """
00120 <self/> element.
00121 """
00122
00123 element_name = "self"
00124 attributes = ("action", "tag", "self_handle", "crl_interval", "regen_margin")
00125 elements = ("bpki_cert", "bpki_glue")
00126 booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now")
00127
00128 sql_template = rpki.sql.template("self", "self_id", "self_handle",
00129 "use_hsm", "crl_interval", "regen_margin",
00130 ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00131 handles = ()
00132
00133 use_hsm = False
00134 crl_interval = None
00135 regen_margin = None
00136 bpki_cert = None
00137 bpki_glue = None
00138
00139 def bscs(self):
00140 """Fetch all BSC objects that link to this self object."""
00141 return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00142
00143 def repositories(self):
00144 """Fetch all repository objects that link to this self object."""
00145 return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00146
00147 def parents(self):
00148 """Fetch all parent objects that link to this self object."""
00149 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00150
00151 def children(self):
00152 """Fetch all child objects that link to this self object."""
00153 return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00154
00155 def roas(self):
00156 """Fetch all ROA objects that link to this self object."""
00157 return rpki.rpki_engine.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00158
00159 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00160 """
00161 Extra server actions for self_elt.
00162 """
00163 rpki.log.trace()
00164 if q_pdu.rekey:
00165 self.serve_rekey(cb, eb)
00166 elif q_pdu.revoke:
00167 self.serve_revoke(cb, eb)
00168 else:
00169 self.unimplemented_control("reissue", "run_now", "publish_world_now")
00170 cb()
00171
00172 def serve_rekey(self, cb, eb):
00173 """
00174 Handle a left-right rekey action for this self.
00175 """
00176 rpki.log.trace()
00177
00178 def loop(iterator, parent):
00179 parent.serve_rekey(iterator, eb)
00180
00181 rpki.async.iterator(self.parents(), loop, cb)
00182
00183 def serve_revoke(self, cb, eb):
00184 """
00185 Handle a left-right revoke action for this self.
00186 """
00187 rpki.log.trace()
00188
00189 def loop(iterator, parent):
00190 parent.serve_revoke(iterator, eb)
00191
00192 rpki.async.iterator(self.parents(), loop, cb)
00193
00194 def serve_fetch_one_maybe(self):
00195 """
00196 Find the self object upon which a get, set, or destroy action
00197 should operate, or which would conflict with a create method.
00198 """
00199 return self.serve_fetch_handle(self.gctx, None, self.self_handle)
00200
00201 @classmethod
00202 def serve_fetch_handle(cls, gctx, self_id, self_handle):
00203 """
00204 Find a self object based on its self_handle.
00205 """
00206 return cls.sql_fetch_where1(gctx, "self_handle = %s", self_handle)
00207
00208 def serve_fetch_all(self):
00209 """
00210 Find the self objects upon which a list action should operate.
00211 This is different from the list action for all other objects,
00212 where list only works within a given self_id context.
00213 """
00214 return self.sql_fetch_all(self.gctx)
00215
00216 def client_poll(self, callback):
00217 """
00218 Run the regular client poll cycle with each of this self's parents
00219 in turn.
00220 """
00221
00222 rpki.log.trace()
00223
00224 def parent_loop(parent_iterator, parent):
00225
00226 def got_list(r_msg):
00227 ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas())
00228
00229 def class_loop(class_iterator, rc):
00230
00231 def class_update_failed(e):
00232 rpki.log.error(traceback.format_exc())
00233 rpki.log.warn("Couldn't update class, skipping: %s" % e)
00234 class_iterator()
00235
00236 def class_create_failed(e):
00237 rpki.log.error(traceback.format_exc())
00238 rpki.log.warn("Couldn't create class, skipping: %s" % e)
00239 class_iterator()
00240
00241 if rc.class_name in ca_map:
00242 ca = ca_map[rc.class_name]
00243 del ca_map[rc.class_name]
00244 ca.check_for_updates(parent, rc, class_iterator, class_update_failed)
00245 else:
00246 rpki.rpki_engine.ca_obj.create(parent, rc, class_iterator, class_create_failed)
00247
00248 def class_done():
00249
00250 def ca_loop(iterator, ca):
00251 ca.delete(parent, iterator)
00252
00253 def ca_done():
00254 self.gctx.sql.sweep()
00255 parent_iterator()
00256
00257 rpki.async.iterator(ca_map.values(), ca_loop, ca_done)
00258
00259 rpki.async.iterator(r_msg.payload.classes, class_loop, class_done)
00260
00261 def list_failed(e):
00262 rpki.log.error(traceback.format_exc())
00263 rpki.log.warn("Couldn't get resource class list from parent %r, skipping: %s" % (parent, e))
00264
00265 rpki.up_down.list_pdu.query(parent, got_list, list_failed)
00266
00267 rpki.async.iterator(self.parents(), parent_loop, callback)
00268
00269 def update_children(self, cb):
00270 """
00271 Check for updated IRDB data for all of this self's children and
00272 issue new certs as necessary. Must handle changes both in
00273 resources and in expiration date.
00274 """
00275
00276 rpki.log.trace()
00277
00278 now = rpki.sundial.now()
00279
00280 rsn = now + rpki.sundial.timedelta(seconds = self.regen_margin)
00281
00282 def loop1(iterator1, child):
00283
00284 def got_resources(irdb_resources):
00285
00286 def loop2(iterator2, child_cert):
00287
00288 ca_detail = child_cert.ca_detail()
00289
00290 if ca_detail.state == "active":
00291 old_resources = child_cert.cert.get_3779resources()
00292 new_resources = irdb_resources.intersection(old_resources)
00293
00294 if old_resources != new_resources or (old_resources.valid_until < rsn and irdb_resources.valid_until > now):
00295 rpki.log.debug("Need to reissue child certificate SKI %s" % child_cert.cert.gSKI())
00296
00297 def reissue_failed(e):
00298 rpki.log.error(traceback.format_exc())
00299 rpki.log.warn("Couldn't reissue child_cert %r, skipping: %s" % (child_cert, e))
00300 iterator2()
00301
00302 child_cert.reissue(
00303 ca_detail = ca_detail,
00304 resources = new_resources,
00305 callback = iterator2.ignore,
00306 errback = reissue_failed)
00307 return
00308
00309 if old_resources.valid_until < now:
00310 rpki.log.debug("Child certificate SKI %s has expired: cert.valid_until %s, irdb.valid_until %s"
00311 % (child_cert.cert.gSKI(), old_resources.valid_until, irdb_resources.valid_until))
00312 ca = ca_detail.ca()
00313 parent = ca.parent()
00314 repository = parent.repository()
00315 child_cert.sql_delete()
00316
00317 def withdraw():
00318 repository.withdraw(child_cert.cert, child_cert.uri(ca), iterator2, withdraw_failed)
00319
00320 def manifest_failed(e):
00321 rpki.log.error(traceback.format_exc())
00322 rpki.log.warn("Couldn't reissue manifest for %r, skipping: %s" % (ca_detail, e))
00323 iterator2()
00324
00325 def withdraw_failed(e):
00326 rpki.log.error(traceback.format_exc())
00327 rpki.log.warn("Couldn't withdraw old child_cert %r, skipping: %s" % (child_cert, e))
00328 iterator2()
00329
00330 ca_detail.generate_manifest(withdraw, manifest_failed)
00331 return
00332
00333 iterator2()
00334
00335 rpki.async.iterator(child_certs, loop2, iterator1)
00336
00337 def irdb_lookup_failed(e):
00338 rpki.log.error(traceback.format_exc())
00339 rpki.log.warn("Couldn't look up child's resources in IRDB, skipping child %r: %s" % (child, e))
00340 iterator1()
00341
00342 child_certs = child.child_certs()
00343 if child_certs:
00344 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, irdb_lookup_failed)
00345 else:
00346 iterator1()
00347
00348 rpki.async.iterator(self.children(), loop1, cb)
00349
00350
00351 def regenerate_crls_and_manifests(self, cb):
00352 """
00353 Generate new CRLs and manifests as necessary for all of this
00354 self's CAs. Extracting nextUpdate from a manifest is hard at the
00355 moment due to implementation silliness, so for now we generate a
00356 new manifest whenever we generate a new CRL
00357
00358 This method also cleans up tombstones left behind by revoked
00359 ca_detail objects, since we're walking through the relevant
00360 portions of the database anyway.
00361 """
00362
00363 rpki.log.trace()
00364
00365 now = rpki.sundial.now()
00366
00367 def loop1(iterator1, parent):
00368 repository = parent.repository()
00369
00370 def loop2(iterator2, ca):
00371
00372 def fail2(e):
00373 rpki.log.error(traceback.format_exc())
00374 rpki.log.warn("Couldn't regenerate CRLs and manifests for CA %r, skipping: %s" % (ca, e))
00375 iterator2()
00376
00377 def loop3(iterator3, ca_detail):
00378 ca_detail.delete(ca, repository, iterator3, fail2)
00379
00380 def done3():
00381
00382 ca_detail = ca.fetch_active()
00383
00384 def do_crl():
00385 ca_detail.generate_crl(do_manifest, fail2)
00386
00387 def do_manifest():
00388 ca_detail.generate_manifest(iterator2, fail2)
00389
00390 if ca_detail is not None and now > ca_detail.latest_crl.getNextUpdate():
00391 do_crl()
00392 else:
00393 iterator2()
00394
00395 rpki.async.iterator([x for x in ca.fetch_revoked() if now > x.latest_crl.getNextUpdate()], loop3, done3)
00396
00397 rpki.async.iterator(parent.cas(), loop2, iterator1)
00398
00399 rpki.async.iterator(self.parents(), loop1, cb)
00400
00401
00402 def update_roas(self, cb):
00403 """
00404 Generate or update ROAs for this self.
00405 """
00406
00407 def got_roa_requests(roa_requests):
00408
00409 roas = dict(((r.asn, str(r.ipv4), str(r.ipv6)), r) for r in self.roas())
00410
00411 def loop(iterator, roa_request):
00412
00413 def lose(e):
00414 rpki.log.error(traceback.format_exc())
00415 rpki.log.warn("Could not update ROA %r, skipping: %s" % (roa, e))
00416 iterator()
00417
00418 roa = roas.get((roa_request.asn, str(roa_request.ipv4), str(roa_request.ipv6)))
00419
00420 if roa is None:
00421
00422 roa = rpki.rpki_engine.roa_obj()
00423 roa.gctx = self.gctx
00424 roa.self_id = self.self_id
00425 roa.asn = roa_request.asn
00426 roa.ipv4 = roa_request.ipv4
00427 roa.ipv6 = roa_request.ipv6
00428 return roa.generate_roa(iterator, lose)
00429
00430 assert roa.roa is not None
00431
00432 ca_detail = roa.ca_detail()
00433
00434 if ca_detail is None or ca_detail.state != "active":
00435 return roa.regenerate_roa(iterator, lose)
00436
00437 regen_margin = rpki.sundial.timedelta(seconds = self.regen_margin)
00438
00439 if rpki.sundial.now() + regen_margin > roa.cert.getNotAfter():
00440 return roa.regenerate_roa(iterator, lose)
00441
00442 ca_resources = ca_detail.latest_ca_cert.get_3779resources()
00443 ee_resources = roa.cert.get_3779resources()
00444
00445 if ee_resources.oversized(ca_resources):
00446 return roa.regenerate_roa(iterator, lose)
00447
00448 v4 = roa.ipv4.to_resource_set() if roa.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
00449 v6 = roa.ipv6.to_resource_set() if roa.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
00450
00451 if ee_resources.v4 != v4 or ee_resources.v6 != v6:
00452 return roa.regenerate_roa(iterator, lose)
00453
00454 iterator()
00455
00456 def done():
00457
00458
00459
00460
00461 cb()
00462
00463 rpki.async.iterator(roa_requests, loop, done)
00464
00465 def roa_requests_failed(e):
00466 rpki.log.error(traceback.format_exc())
00467 rpki.log.warn("Could not fetch ROA requests for %s, skipping: %s" % (self.self_handle, e))
00468 cb()
00469
00470 self.gctx.irdb_query_roa_requests(self.self_handle, got_roa_requests, roa_requests_failed)
00471
00472 class bsc_elt(data_elt):
00473 """
00474 <bsc/> (Business Signing Context) element.
00475 """
00476
00477 element_name = "bsc"
00478 attributes = ("action", "tag", "self_handle", "bsc_handle", "key_type", "hash_alg", "key_length")
00479 elements = ("signing_cert", "signing_cert_crl", "pkcs10_request")
00480 booleans = ("generate_keypair",)
00481
00482 sql_template = rpki.sql.template("bsc", "bsc_id", "bsc_handle",
00483 "self_id", "hash_alg",
00484 ("private_key_id", rpki.x509.RSA),
00485 ("pkcs10_request", rpki.x509.PKCS10),
00486 ("signing_cert", rpki.x509.X509),
00487 ("signing_cert_crl", rpki.x509.CRL))
00488 handles = (("self", self_elt),)
00489
00490 private_key_id = None
00491 pkcs10_request = None
00492 signing_cert = None
00493 signing_cert_crl = None
00494
00495 def repositories(self):
00496 """Fetch all repository objects that link to this BSC object."""
00497 return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00498
00499 def parents(self):
00500 """Fetch all parent objects that link to this BSC object."""
00501 return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00502
00503 def children(self):
00504 """Fetch all child objects that link to this BSC object."""
00505 return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00506
00507 def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb):
00508 """
00509 Extra server actions for bsc_elt -- handle key generation. For
00510 now this only allows RSA with SHA-256.
00511 """
00512 if q_pdu.generate_keypair:
00513 assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
00514 self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048)
00515 self.pkcs10_request = rpki.x509.PKCS10.create(self.private_key_id)
00516 r_pdu.pkcs10_request = self.pkcs10_request
00517 data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb)
00518
00519 class repository_elt(data_elt):
00520 """
00521 <repository/> element.
00522 """
00523
00524 element_name = "repository"
00525 attributes = ("action", "tag", "self_handle", "repository_handle", "bsc_handle", "peer_contact_uri")
00526 elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue")
00527
00528 sql_template = rpki.sql.template("repository", "repository_id", "repository_handle",
00529 "self_id", "bsc_id", "peer_contact_uri",
00530 ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509),
00531 ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509))
00532 handles = (("self", self_elt), ("bsc", bsc_elt))
00533
00534 bpki_cms_cert = None
00535 bpki_cms_glue = None
00536 bpki_https_cert = None
00537 bpki_https_glue = None
00538
00539 def parents(self):
00540 """Fetch all parent objects that link to this repository object."""
00541 return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
00542
00543 def call_pubd(self, callback, errback, *pdus):
00544 """
00545 Send a message to publication daemon and return the response.
00546 """
00547 rpki.log.trace()
00548 bsc = self.bsc()
00549 q_msg = rpki.publication.msg(pdus)
00550 q_msg.type = "query"
00551 q_cms = rpki.publication.cms_msg.wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
00552 bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_https_cert, self.bpki_https_glue)
00553
00554 def done(r_cms):
00555 try:
00556 r_msg = rpki.publication.cms_msg.unwrap(r_cms, bpki_ta_path)
00557 if len(r_msg) != 1 or isinstance(r_msg[0], rpki.publication.report_error_elt):
00558 raise rpki.exceptions.BadPublicationReply, "Unexpected response from pubd: %s" % msg
00559 callback()
00560 except (rpki.async.ExitNow, SystemExit):
00561 raise
00562 except Exception, edata:
00563 errback(edata)
00564
00565 rpki.https.client(
00566 client_key = bsc.private_key_id,
00567 client_cert = bsc.signing_cert,
00568 server_ta = bpki_ta_path,
00569 url = self.peer_contact_uri,
00570 msg = q_cms,
00571 callback = done,
00572 errback = errback)
00573
00574 def publish(self, obj, uri, callback, errback):
00575 """
00576 Publish one object in the repository.
00577 """
00578 rpki.log.trace()
00579 rpki.log.info("Publishing %s as %s" % (repr(obj), repr(uri)))
00580 self.call_pubd(callback, errback, rpki.publication.obj2elt[type(obj)].make_pdu(action = "publish", uri = uri, payload = obj))
00581
00582 def withdraw(self, obj, uri, callback, errback):
00583 """
00584 Withdraw one object from the repository.
00585 """
00586 rpki.log.trace()
00587 rpki.log.info("Withdrawing %s from at %s" % (repr(obj), repr(uri)))
00588 self.call_pubd(callback, errback, rpki.publication.obj2elt[type(obj)].make_pdu(action = "withdraw", uri = uri))
00589
00590 class parent_elt(data_elt):
00591 """
00592 <parent/> element.
00593 """
00594
00595 element_name = "parent"
00596 attributes = ("action", "tag", "self_handle", "parent_handle", "bsc_handle", "repository_handle",
00597 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00598 elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue")
00599 booleans = ("rekey", "reissue", "revoke")
00600
00601 sql_template = rpki.sql.template("parent", "parent_id", "parent_handle",
00602 "self_id", "bsc_id", "repository_id",
00603 ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509),
00604 ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509),
00605 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00606 handles = (("self", self_elt), ("bsc", bsc_elt), ("repository", repository_elt))
00607
00608 bpki_cms_cert = None
00609 bpki_cms_glue = None
00610 bpki_https_cert = None
00611 bpki_https_glue = None
00612
00613 def repository(self):
00614 """Fetch repository object to which this parent object links."""
00615 return repository_elt.sql_fetch(self.gctx, self.repository_id)
00616
00617 def cas(self):
00618 """Fetch all CA objects that link to this parent object."""
00619 return rpki.rpki_engine.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
00620
00621 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00622 """
00623 Extra server actions for parent_elt.
00624 """
00625 if q_pdu.rekey:
00626 self.serve_rekey(cb, eb)
00627 elif q_pdu.revoke:
00628 self.serve_revoke(cb, eb)
00629 else:
00630 self.unimplemented_control("reissue")
00631 cb()
00632
00633 def serve_rekey(self, cb, eb):
00634 """
00635 Handle a left-right rekey action for this parent.
00636 """
00637
00638 def loop(iterator, ca):
00639 ca.rekey(iterator, eb)
00640
00641 rpki.async.iterator(self.cas(), loop, cb)
00642
00643 def serve_revoke(self, cb, eb):
00644 """
00645 Handle a left-right revoke action for this parent.
00646 """
00647
00648 def loop(iterator, ca):
00649 ca.revoke(iterator, eb)
00650
00651 rpki.async.iterator(self.cas(), loop, cb)
00652
00653 def query_up_down(self, q_pdu, cb, eb):
00654 """
00655 Client code for sending one up-down query PDU to this parent.
00656 """
00657
00658 rpki.log.trace()
00659
00660 bsc = self.bsc()
00661 if bsc is None:
00662 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00663
00664 q_msg = rpki.up_down.message_pdu.make_query(
00665 payload = q_pdu,
00666 sender = self.sender_name,
00667 recipient = self.recipient_name)
00668
00669 q_cms = rpki.up_down.cms_msg.wrap(q_msg, bsc.private_key_id,
00670 bsc.signing_cert,
00671 bsc.signing_cert_crl)
00672
00673 def unwrap(der):
00674 r_msg = rpki.up_down.cms_msg.unwrap(der, (self.gctx.bpki_ta,
00675 self.self().bpki_cert, self.self().bpki_glue,
00676 self.bpki_cms_cert, self.bpki_cms_glue))
00677 r_msg.payload.check_response()
00678 cb(r_msg)
00679
00680 rpki.https.client(server_ta = (self.gctx.bpki_ta,
00681 self.self().bpki_cert, self.self().bpki_glue,
00682 self.bpki_https_cert, self.bpki_https_glue),
00683 client_key = bsc.private_key_id,
00684 client_cert = bsc.signing_cert,
00685 msg = q_cms,
00686 url = self.peer_contact_uri,
00687 callback = unwrap,
00688 errback = eb)
00689
00690 class child_elt(data_elt):
00691 """
00692 <child/> element.
00693 """
00694
00695 element_name = "child"
00696 attributes = ("action", "tag", "self_handle", "child_handle", "bsc_handle")
00697 elements = ("bpki_cert", "bpki_glue")
00698 booleans = ("reissue", )
00699
00700 sql_template = rpki.sql.template("child", "child_id", "child_handle",
00701 "self_id", "bsc_id",
00702 ("bpki_cert", rpki.x509.X509),
00703 ("bpki_glue", rpki.x509.X509))
00704
00705 handles = (("self", self_elt), ("bsc", bsc_elt))
00706
00707 bpki_cert = None
00708 bpki_glue = None
00709 clear_https_ta_cache = False
00710
00711 def child_certs(self, ca_detail = None, ski = None, unique = False):
00712 """Fetch all child_cert objects that link to this child object."""
00713 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
00714
00715 def parents(self):
00716 """Fetch all parent objects that link to self object to which this child object links."""
00717 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00718
00719 def ca_from_class_name(self, class_name):
00720 """
00721 Fetch the CA corresponding to an up-down class_name.
00722 """
00723 if not class_name.isdigit():
00724 raise rpki.exceptions.BadClassNameSyntax, "Bad class name %s" % class_name
00725 ca = rpki.rpki_engine.ca_obj.sql_fetch(self.gctx, long(class_name))
00726 if ca is None:
00727 raise rpki.exceptions.ClassNameUnknown, "Unknown class name %s" % class_name
00728 parent = ca.parent()
00729 if self.self_id != parent.self_id:
00730 raise rpki.exceptions.ClassNameMismatch, "Class name mismatch: child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id)
00731 return ca
00732
00733 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00734 """
00735 Extra server actions for child_elt.
00736 """
00737 self.unimplemented_control("reissue")
00738 if self.clear_https_ta_cache:
00739 self.gctx.clear_https_ta_cache()
00740 self.clear_https_ta_cache = False
00741 cb()
00742
00743 def endElement(self, stack, name, text):
00744 """
00745 Handle subelements of <child/> element. These require special
00746 handling because modifying them invalidates the HTTPS trust anchor
00747 cache.
00748 """
00749 rpki.xml_utils.data_elt.endElement(self, stack, name, text)
00750 if name in self.elements:
00751 self.clear_https_ta_cache = True
00752
00753 def serve_up_down(self, query, callback):
00754 """
00755 Outer layer of server handling for one up-down PDU from this child.
00756 """
00757
00758 rpki.log.trace()
00759
00760 bsc = self.bsc()
00761 if bsc is None:
00762 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00763 q_msg = rpki.up_down.cms_msg.unwrap(query, (self.gctx.bpki_ta,
00764 self.self().bpki_cert, self.self().bpki_glue,
00765 self.bpki_cert, self.bpki_glue))
00766 q_msg.payload.gctx = self.gctx
00767 if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id):
00768 raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender
00769
00770 def done(r_msg):
00771
00772
00773
00774
00775
00776 r_cms = rpki.up_down.cms_msg.wrap(r_msg, bsc.private_key_id,
00777 bsc.signing_cert, bsc.signing_cert_crl)
00778 callback(r_cms)
00779
00780 try:
00781 q_msg.serve_top_level(self, done)
00782 except (rpki.async.ExitNow, SystemExit):
00783 raise
00784 except rpki.exceptions.NoActiveCA, data:
00785 done(q_msg.serve_error(data))
00786 except Exception, data:
00787 rpki.log.error(traceback.format_exc())
00788 done(q_msg.serve_error(data))
00789
00790 class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
00791 """
00792 <list_resources/> element.
00793 """
00794
00795 element_name = "list_resources"
00796 attributes = ("self_handle", "tag", "child_handle", "valid_until", "asn", "ipv4", "ipv6")
00797 valid_until = None
00798
00799 def startElement(self, stack, name, attrs):
00800 """
00801 Handle <list_resources/> element. This requires special handling
00802 due to the data types of some of the attributes.
00803 """
00804 assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
00805 self.read_attrs(attrs)
00806 if isinstance(self.valid_until, str):
00807 self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until)
00808 if self.asn is not None:
00809 self.asn = rpki.resource_set.resource_set_as(self.asn)
00810 if self.ipv4 is not None:
00811 self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
00812 if self.ipv6 is not None:
00813 self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
00814
00815 def toXML(self):
00816 """
00817 Generate <list_resources/> element. This requires special
00818 handling due to the data types of some of the attributes.
00819 """
00820 elt = self.make_elt()
00821 if isinstance(self.valid_until, int):
00822 elt.set("valid_until", self.valid_until.toXMLtime())
00823 return elt
00824
00825 class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace):
00826 """
00827 <list_roa_requests/> element.
00828 """
00829
00830 element_name = "list_roa_requests"
00831 attributes = ("self_handle", "tag", "asn", "ipv4", "ipv6")
00832
00833 def startElement(self, stack, name, attrs):
00834 """
00835 Handle <list_roa_requests/> element. This requires special handling
00836 due to the data types of some of the attributes.
00837 """
00838 assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack)
00839 self.read_attrs(attrs)
00840 if self.ipv4 is not None:
00841 self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4)
00842 if self.ipv6 is not None:
00843 self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6)
00844
00845 class report_error_elt(rpki.xml_utils.base_elt, left_right_namespace):
00846 """
00847 <report_error/> element.
00848 """
00849
00850 element_name = "report_error"
00851 attributes = ("tag", "self_handle", "error_code")
00852
00853 @classmethod
00854 def from_exception(cls, e, self_handle = None):
00855 """
00856 Generate a <report_error/> element from an exception.
00857 """
00858 self = cls()
00859 self.self_handle = self_handle
00860 self.error_code = e.__class__.__name__
00861 self.text = str(e)
00862 return self
00863
00864 class msg(rpki.xml_utils.msg, left_right_namespace):
00865 """
00866 Left-right PDU.
00867 """
00868
00869
00870
00871 version = 1
00872
00873
00874
00875 pdus = dict((x.element_name, x)
00876 for x in (self_elt, child_elt, parent_elt, bsc_elt,
00877 repository_elt, list_resources_elt,
00878 list_roa_requests_elt, report_error_elt))
00879
00880 def serve_top_level(self, gctx, cb):
00881 """
00882 Serve one msg PDU.
00883 """
00884 r_msg = self.__class__()
00885 r_msg.type = "reply"
00886
00887 def loop(iterator, q_pdu):
00888
00889 def fail(e):
00890 rpki.log.error(traceback.format_exc())
00891 r_msg.append(report_error_elt.from_exception(e, self_handle = q_pdu.self_handle))
00892 cb(r_msg)
00893
00894 try:
00895 q_pdu.gctx = gctx
00896 q_pdu.serve_dispatch(r_msg, iterator, fail)
00897 except (rpki.async.ExitNow, SystemExit):
00898 raise
00899 except Exception, edata:
00900 fail(edata)
00901
00902 def done():
00903 cb(r_msg)
00904
00905 rpki.async.iterator(self, loop, done)
00906
00907 class sax_handler(rpki.xml_utils.sax_handler):
00908 """
00909 SAX handler for Left-Right protocol.
00910 """
00911
00912 pdu = msg
00913 name = "msg"
00914 version = "1"
00915
00916 class cms_msg(rpki.x509.XML_CMS_object):
00917 """
00918 Class to hold a CMS-signed left-right PDU.
00919 """
00920
00921 encoding = "us-ascii"
00922 schema = rpki.relaxng.left_right
00923 saxify = sax_handler.saxify