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