00001 """RPKI "left-right" protocol.
00002
00003 $Id: left_right.py 1873 2008-06-12 02:49:41Z sra $
00004
00005 Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00006
00007 Permission to use, copy, modify, and distribute this software for any
00008 purpose with or without fee is hereby granted, provided that the above
00009 copyright notice and this permission notice appear in all copies.
00010
00011 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00012 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00013 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00014 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00015 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00016 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00017 PERFORMANCE OF THIS SOFTWARE.
00018 """
00019
00020 import base64, lxml.etree, time, traceback, os
00021 import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00022 import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
00023 import rpki.publication
00024
00025
00026 enforce_strict_up_down_xml_sender = False
00027
00028 class left_right_namespace(object):
00029 """XML namespace parameters for left-right protocol."""
00030
00031 xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/"
00032 nsmap = { None : xmlns }
00033
00034 class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, left_right_namespace):
00035 """Virtual class for top-level left-right protocol data elements."""
00036
00037 def self(this):
00038 """Fetch self object to which this object links."""
00039 return self_elt.sql_fetch(this.gctx, this.self_id)
00040
00041 def bsc(self):
00042 """Return BSC object to which this object links."""
00043 return bsc_elt.sql_fetch(self.gctx, self.bsc_id)
00044
00045 def make_reply_clone_hook(self, r_pdu):
00046 """Set self_id when cloning."""
00047 r_pdu.self_id = self.self_id
00048
00049 def serve_fetch_one(self):
00050 """Find the object on which a get, set, or destroy method should
00051 operate.
00052 """
00053 where = self.sql_template.index + " = %s AND self_id = %s"
00054 args = (getattr(self, self.sql_template.index), self.self_id)
00055 r = self.sql_fetch_where1(self.gctx, where, args)
00056 if r is None:
00057 raise rpki.exceptions.NotFound, "Lookup failed where %s" + (where % args)
00058 return r
00059
00060 def serve_fetch_all(self):
00061 """Find the objects on which a list method should operate."""
00062 return self.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00063
00064 def unimplemented_control(self, *controls):
00065 """Uniform handling for unimplemented control operations."""
00066 unimplemented = [x for x in controls if getattr(self, x, False)]
00067 if unimplemented:
00068 raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented)
00069
00070 class self_elt(data_elt):
00071 """<self/> element."""
00072
00073 element_name = "self"
00074 attributes = ("action", "tag", "self_id", "crl_interval", "regen_margin")
00075 elements = ("bpki_cert", "bpki_glue")
00076 booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now")
00077
00078 sql_template = rpki.sql.template("self", "self_id", "use_hsm", "crl_interval", "regen_margin",
00079 ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00080
00081 self_id = None
00082 use_hsm = False
00083 crl_interval = None
00084 regen_margin = None
00085 bpki_cert = None
00086 bpki_glue = None
00087
00088 def bscs(self):
00089 """Fetch all BSC objects that link to this self object."""
00090 return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00091
00092 def repositories(self):
00093 """Fetch all repository objects that link to this self object."""
00094 return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00095
00096 def parents(self):
00097 """Fetch all parent objects that link to this self object."""
00098 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00099
00100 def children(self):
00101 """Fetch all child objects that link to this self object."""
00102 return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00103
00104 def route_origins(self):
00105 """Fetch all route_origin objects that link to this self object."""
00106 return route_origin_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00107
00108 def serve_post_save_hook(self, q_pdu, r_pdu):
00109 """Extra server actions for self_elt."""
00110 rpki.log.trace()
00111 if q_pdu.rekey:
00112 self.serve_rekey()
00113 if q_pdu.revoke:
00114 self.serve_revoke()
00115 self.unimplemented_control("reissue", "run_now", "publish_world_now")
00116
00117 def serve_rekey(self):
00118 """Handle a left-right rekey action for this self."""
00119 rpki.log.trace()
00120 for parent in self.parents():
00121 parent.serve_rekey()
00122
00123 def serve_revoke(self):
00124 """Handle a left-right revoke action for this self."""
00125 rpki.log.trace()
00126 for parent in self.parents():
00127 parent.serve_revoke()
00128
00129 def serve_fetch_one(self):
00130 """Find the self object upon which a get, set, or destroy action
00131 should operate.
00132 """
00133 r = self.sql_fetch(self.gctx, self.self_id)
00134 if r is None:
00135 raise rpki.exceptions.NotFound
00136 return r
00137
00138 def serve_fetch_all(self):
00139 """Find the self objects upon which a list action should operate.
00140 This is different from the list action for all other objects,
00141 where list only works within a given self_id context.
00142 """
00143 return self.sql_fetch_all(self.gctx)
00144
00145 def client_poll(self):
00146 """Run the regular client poll cycle with each of this self's parents in turn."""
00147
00148 rpki.log.trace()
00149
00150 for parent in self.parents():
00151
00152
00153 r_msg = rpki.up_down.list_pdu.query(parent)
00154
00155 ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas())
00156 for rc in r_msg.payload.classes:
00157 if rc.class_name in ca_map:
00158 ca = ca_map[rc.class_name]
00159 del ca_map[rc.class_name]
00160 ca.check_for_updates(parent, rc)
00161 else:
00162 rpki.rpki_engine.ca_obj.create(parent, rc)
00163 for ca in ca_map.values():
00164 ca.delete(parent)
00165 self.gctx.sql.sweep()
00166
00167 def update_children(self):
00168 """Check for updated IRDB data for all of this self's children and
00169 issue new certs as necessary. Must handle changes both in
00170 resources and in expiration date.
00171 """
00172
00173 rpki.log.trace()
00174
00175 now = rpki.sundial.now()
00176
00177 for child in self.children():
00178 child_certs = child.child_certs()
00179 if not child_certs:
00180 continue
00181
00182
00183 irdb_resources = self.gctx.irdb_query(child.self_id, child.child_id)
00184
00185 for child_cert in child_certs:
00186 ca_detail = child_cert.ca_detail()
00187 if ca_detail.state != "active":
00188 continue
00189 old_resources = child_cert.cert.get_3779resources()
00190 new_resources = irdb_resources.intersection(old_resources)
00191 if old_resources != new_resources:
00192 rpki.log.debug("Need to reissue %s" % repr(child_cert))
00193 child_cert.reissue(
00194 ca_detail = ca_detail,
00195 resources = new_resources)
00196 elif old_resources.valid_until < now:
00197 ca = ca_detail.ca()
00198 parent = ca.parent()
00199 repository = parent.repository()
00200 child_cert.sql_delete()
00201 ca_detail.generate_manifest()
00202 repository.withdraw(child_cert.cert, child_cert.uri(ca))
00203
00204 def regenerate_crls_and_manifests(self):
00205 """Generate new CRLs and manifests as necessary for all of this
00206 self's CAs. Extracting nextUpdate from a manifest is hard at the
00207 moment due to implementation silliness, so for now we generate a
00208 new manifest whenever we generate a new CRL
00209
00210 This method also cleans up tombstones left behind by revoked
00211 ca_detail objects, since we're walking through the relevant
00212 portions of the database anyway.
00213 """
00214
00215 rpki.log.trace()
00216
00217 now = rpki.sundial.now()
00218 for parent in self.parents():
00219 repository = parent.repository()
00220 for ca in parent.cas():
00221 for ca_detail in ca.fetch_revoked():
00222 if now > ca_detail.latest_crl.getNextUpdate():
00223 ca_detail.delete(ca, repository)
00224 ca_detail = ca.fetch_active()
00225 if now > ca_detail.latest_crl.getNextUpdate():
00226 ca_detail.generate_crl()
00227 ca_detail.generate_manifest()
00228
00229 def update_roas(self):
00230 """Generate or update ROAs for this self's route_origin objects."""
00231
00232 for route_origin in self.route_origins():
00233 route_origin.update_roa()
00234
00235 class bsc_elt(data_elt):
00236 """<bsc/> (Business Signing Context) element."""
00237
00238 element_name = "bsc"
00239 attributes = ("action", "tag", "self_id", "bsc_id", "key_type", "hash_alg", "key_length")
00240 elements = ("signing_cert", "signing_cert_crl", "pkcs10_request")
00241 booleans = ("generate_keypair",)
00242
00243 sql_template = rpki.sql.template("bsc", "bsc_id", "self_id", "hash_alg",
00244 ("private_key_id", rpki.x509.RSA),
00245 ("pkcs10_request", rpki.x509.PKCS10),
00246 ("signing_cert", rpki.x509.X509),
00247 ("signing_cert_crl", rpki.x509.CRL))
00248
00249 private_key_id = None
00250 pkcs10_request = None
00251 signing_cert = None
00252 signing_cert_crl = None
00253
00254 def repositories(self):
00255 """Fetch all repository objects that link to this BSC object."""
00256 return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00257
00258 def parents(self):
00259 """Fetch all parent objects that link to this BSC object."""
00260 return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00261
00262 def children(self):
00263 """Fetch all child objects that link to this BSC object."""
00264 return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,))
00265
00266 def serve_pre_save_hook(self, q_pdu, r_pdu):
00267 """Extra server actions for bsc_elt -- handle key generation.
00268 For now this only allows RSA with SHA-256.
00269 """
00270 if q_pdu.generate_keypair:
00271 assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256")
00272 keypair = rpki.x509.RSA()
00273 keypair.generate(keylength = q_pdu.key_length or 2048)
00274 self.private_key_id = keypair
00275 self.pkcs10_request = rpki.x509.PKCS10.create(keypair)
00276 r_pdu.pkcs10_request = self.pkcs10_request
00277
00278 class parent_elt(data_elt):
00279 """<parent/> element."""
00280
00281 element_name = "parent"
00282 attributes = ("action", "tag", "self_id", "parent_id", "bsc_id", "repository_id",
00283 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00284 elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue")
00285 booleans = ("rekey", "reissue", "revoke")
00286
00287 sql_template = rpki.sql.template("parent", "parent_id", "self_id", "bsc_id", "repository_id",
00288 ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509),
00289 ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509),
00290 "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
00291
00292 bpki_cms_cert = None
00293 bpki_cms_glue = None
00294 bpki_https_cert = None
00295 bpki_https_glue = None
00296
00297 def repository(self):
00298 """Fetch repository object to which this parent object links."""
00299 return repository_elt.sql_fetch(self.gctx, self.repository_id)
00300
00301 def cas(self):
00302 """Fetch all CA objects that link to this parent object."""
00303 return rpki.rpki_engine.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,))
00304
00305 def serve_post_save_hook(self, q_pdu, r_pdu):
00306 """Extra server actions for parent_elt."""
00307 if q_pdu.rekey:
00308 self.serve_rekey()
00309 if q_pdu.revoke:
00310 self.serve_revoke()
00311 self.unimplemented_control("reissue")
00312
00313 def serve_rekey(self):
00314 """Handle a left-right rekey action for this parent."""
00315 for ca in self.cas():
00316 ca.rekey()
00317
00318 def serve_revoke(self):
00319 """Handle a left-right revoke action for this parent."""
00320 for ca in self.cas():
00321 ca.revoke()
00322
00323 def query_up_down(self, q_pdu):
00324 """Client code for sending one up-down query PDU to this parent.
00325
00326 I haven't figured out yet whether this method should do something
00327 clever like dispatching via a method in the response PDU payload,
00328 or just hand back the whole response to the caller. In the long
00329 run this will have to become event driven with a context object
00330 that has methods of its own, but as this method is common code for
00331 several different queries and I don't yet know what the response
00332 processing looks like, it's too soon to tell what will make sense.
00333
00334 For now, keep this dead simple lock step, rewrite it later.
00335 """
00336
00337 rpki.log.trace()
00338
00339 bsc = self.bsc()
00340 if bsc is None:
00341 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00342
00343 q_msg = rpki.up_down.message_pdu.make_query(
00344 payload = q_pdu,
00345 sender = self.sender_name,
00346 recipient = self.recipient_name)
00347
00348 q_cms = rpki.up_down.cms_msg.wrap(q_msg, bsc.private_key_id,
00349 bsc.signing_cert,
00350 bsc.signing_cert_crl)
00351
00352 der = rpki.https.client(server_ta = (self.gctx.bpki_ta,
00353 self.self().bpki_cert, self.self().bpki_glue,
00354 self.bpki_https_cert, self.bpki_https_glue),
00355 client_key = bsc.private_key_id,
00356 client_cert = bsc.signing_cert,
00357 msg = q_cms,
00358 url = self.peer_contact_uri)
00359
00360 r_msg = rpki.up_down.cms_msg.unwrap(der, (self.gctx.bpki_ta,
00361 self.self().bpki_cert, self.self().bpki_glue,
00362 self.bpki_cms_cert, self.bpki_cms_glue))
00363
00364 r_msg.payload.check_response()
00365 return r_msg
00366
00367
00368 class child_elt(data_elt):
00369 """<child/> element."""
00370
00371 element_name = "child"
00372 attributes = ("action", "tag", "self_id", "child_id", "bsc_id")
00373 elements = ("bpki_cert", "bpki_glue")
00374 booleans = ("reissue", )
00375
00376 sql_template = rpki.sql.template("child", "child_id", "self_id", "bsc_id",
00377 ("bpki_cert", rpki.x509.X509),
00378 ("bpki_glue", rpki.x509.X509))
00379
00380 bpki_cert = None
00381 bpki_glue = None
00382 clear_https_ta_cache = False
00383
00384 def child_certs(self, ca_detail = None, ski = None, unique = False):
00385 """Fetch all child_cert objects that link to this child object."""
00386 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique)
00387
00388 def parents(self):
00389 """Fetch all parent objects that link to self object to which this child object links."""
00390 return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
00391
00392 def ca_from_class_name(self, class_name):
00393 """Fetch the CA corresponding to an up-down class_name."""
00394 if not class_name.isdigit():
00395 raise rpki.exceptions.BadClassNameSyntax, "Bad class name %s" % class_name
00396 ca = rpki.rpki_engine.ca_obj.sql_fetch(self.gctx, long(class_name))
00397 parent = ca.parent()
00398 if self.self_id != parent.self_id:
00399 raise rpki.exceptions.ClassNameMismatch, "child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id)
00400 return ca
00401
00402 def serve_post_save_hook(self, q_pdu, r_pdu):
00403 """Extra server actions for child_elt."""
00404 self.unimplemented_control("reissue")
00405 if self.clear_https_ta_cache:
00406 self.gctx.clear_https_ta_cache()
00407 self.clear_https_ta_cache = False
00408
00409 def endElement(self, stack, name, text):
00410 """Handle subelements of <child/> element. These require special
00411 handling because modifying them invalidates the HTTPS trust anchor
00412 cache.
00413 """
00414 rpki.xml_utils.data_elt.endElement(self, stack, name, text)
00415 if name in self.elements:
00416 self.clear_https_ta_cache = True
00417
00418 def serve_up_down(self, query):
00419 """Outer layer of server handling for one up-down PDU from this child."""
00420
00421 rpki.log.trace()
00422
00423 bsc = self.bsc()
00424 if bsc is None:
00425 raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
00426 q_msg = rpki.up_down.cms_msg.unwrap(query, (self.gctx.bpki_ta,
00427 self.self().bpki_cert, self.self().bpki_glue,
00428 self.bpki_cert, self.bpki_glue))
00429 q_msg.payload.gctx = self.gctx
00430 if enforce_strict_up_down_xml_sender and q_msg.sender != str(self.child_id):
00431 raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender
00432 try:
00433 r_msg = q_msg.serve_top_level(self)
00434 except Exception, data:
00435 rpki.log.error(traceback.format_exc())
00436 r_msg = q_msg.serve_error(data)
00437
00438
00439
00440
00441
00442 r_cms = rpki.up_down.cms_msg.wrap(r_msg, bsc.private_key_id,
00443 bsc.signing_cert, bsc.signing_cert_crl)
00444 return r_cms
00445
00446 class repository_elt(data_elt):
00447 """<repository/> element."""
00448
00449 element_name = "repository"
00450 attributes = ("action", "tag", "self_id", "repository_id", "bsc_id", "peer_contact_uri")
00451 elements = ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue")
00452
00453 sql_template = rpki.sql.template("repository", "repository_id", "self_id", "bsc_id", "peer_contact_uri",
00454 ("bpki_cms_cert", rpki.x509.X509), ("bpki_cms_glue", rpki.x509.X509),
00455 ("bpki_https_cert", rpki.x509.X509), ("bpki_https_glue", rpki.x509.X509))
00456
00457 bpki_cms_cert = None
00458 bpki_cms_glue = None
00459 bpki_https_cert = None
00460 bpki_https_glue = None
00461
00462 use_pubd = True
00463
00464 def parents(self):
00465 """Fetch all parent objects that link to this repository object."""
00466 return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,))
00467
00468 @staticmethod
00469 def uri_to_filename(base, uri):
00470 """Convert a URI to a filename. [TEMPORARY]"""
00471 if not uri.startswith("rsync://"):
00472 raise rpki.exceptions.BadURISyntax
00473 filename = base + uri[len("rsync://"):]
00474 if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."):
00475 raise rpki.exceptions.BadURISyntax
00476 return filename
00477
00478 @classmethod
00479 def object_write(cls, base, uri, obj):
00480 """Write an object to disk. [TEMPORARY]"""
00481 rpki.log.trace()
00482 filename = cls.uri_to_filename(base, uri)
00483 dirname = os.path.dirname(filename)
00484 if not os.path.isdir(dirname):
00485 os.makedirs(dirname)
00486 f = open(filename, "wb")
00487 f.write(obj.get_DER())
00488 f.close()
00489
00490 @classmethod
00491 def object_delete(cls, base, uri):
00492 """Delete an object from disk. [TEMPORARY]"""
00493 rpki.log.trace()
00494 os.remove(cls.uri_to_filename(base, uri))
00495
00496 def call_pubd(self, *pdus):
00497 """Send a message to publication daemon and return the response."""
00498 rpki.log.trace()
00499 bsc = self.bsc()
00500 q_msg = rpki.publication.msg(pdus)
00501 q_msg.type = "query"
00502 q_cms = rpki.publication.cms_msg.wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
00503 bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_https_cert, self.bpki_https_glue)
00504 r_cms = rpki.https.client(
00505 client_key = bsc.private_key_id,
00506 client_cert = bsc.signing_cert,
00507 server_ta = bpki_ta_path,
00508 url = self.peer_contact_uri,
00509 msg = q_cms)
00510 r_msg = rpki.publication.cms_msg.unwrap(r_cms, bpki_ta_path)
00511 assert len(r_msg) == 1
00512 return r_msg[0]
00513
00514 def publish(self, obj, uri):
00515 """Placeholder for publication operation. [TEMPORARY]"""
00516 rpki.log.trace()
00517 rpki.log.info("Publishing %s as %s" % (repr(obj), repr(uri)))
00518 if self.use_pubd:
00519 self.call_pubd(rpki.publication.obj2elt[type(obj)].make_pdu(action = "publish", uri = uri, payload = obj))
00520 else:
00521 self.object_write(self.gctx.publication_kludge_base, uri, obj)
00522
00523 def withdraw(self, obj, uri):
00524 """Placeholder for publication withdrawal operation. [TEMPORARY]"""
00525 rpki.log.trace()
00526 rpki.log.info("Withdrawing %s from at %s" % (repr(obj), repr(uri)))
00527 if self.use_pubd:
00528 self.call_pubd(rpki.publication.obj2elt[type(obj)].make_pdu(action = "withdraw", uri = uri))
00529 else:
00530 self.object_delete(self.gctx.publication_kludge_base, uri)
00531
00532 class route_origin_elt(data_elt):
00533 """<route_origin/> element."""
00534
00535 element_name = "route_origin"
00536 attributes = ("action", "tag", "self_id", "route_origin_id", "as_number", "ipv4", "ipv6")
00537 booleans = ("suppress_publication",)
00538
00539 sql_template = rpki.sql.template("route_origin", "route_origin_id", "ca_detail_id",
00540 "self_id", "as_number",
00541 ("roa", rpki.x509.ROA),
00542 ("cert", rpki.x509.X509))
00543
00544 ca_detail_id = None
00545 cert = None
00546 roa = None
00547
00548 def sql_fetch_hook(self):
00549 """Extra SQL fetch actions for route_origin_elt -- handle prefix list."""
00550 self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4.from_sql(
00551 self.gctx.sql,
00552 """
00553 SELECT address, prefixlen, max_prefixlen FROM route_origin_prefix
00554 WHERE route_origin_id = %s AND address NOT LIKE '%:%'
00555 """, (self.route_origin_id,))
00556 self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6.from_sql(
00557 self.gctx.sql,
00558 """
00559 SELECT address, prefixlen, max_prefixlen FROM route_origin_prefix
00560 WHERE route_origin_id = %s AND address LIKE '%:%'
00561 """, (self.route_origin_id,))
00562
00563 def sql_insert_hook(self):
00564 """Extra SQL insert actions for route_origin_elt -- handle address ranges."""
00565 if self.ipv4 or self.ipv6:
00566 self.gctx.sql.executemany("""
00567 INSERT route_origin_prefix (route_origin_id, address, prefixlen, max_prefixlen)
00568 VALUES (%s, %s, %s, %s)""",
00569 ((self.route_origin_id, x.address, x.prefixlen, x.max_prefixlen)
00570 for x in (self.ipv4 or []) + (self.ipv6 or [])))
00571
00572 def sql_delete_hook(self):
00573 """Extra SQL delete actions for route_origin_elt -- handle address ranges."""
00574 self.gctx.sql.execute("DELETE FROM route_origin_prefix WHERE route_origin_id = %s", (self.route_origin_id,))
00575
00576 def ca_detail(self):
00577 """Fetch all ca_detail objects that link to this route_origin object."""
00578 return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00579
00580 def serve_post_save_hook(self, q_pdu, r_pdu):
00581 """Extra server actions for route_origin_elt."""
00582 self.unimplemented_control("suppress_publication")
00583
00584 def startElement(self, stack, name, attrs):
00585 """Handle <route_origin/> element. This requires special
00586 processing due to the data types of some of the attributes.
00587 """
00588 assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack)
00589 self.read_attrs(attrs)
00590 if self.as_number is not None:
00591 self.as_number = long(self.as_number)
00592 if self.ipv4 is not None:
00593 self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4)
00594 if self.ipv6 is not None:
00595 self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6)
00596
00597 def update_roa(self):
00598 """Bring this route_origin's ROA up to date if necesssary."""
00599
00600 if self.roa is None:
00601 return self.generate_roa()
00602
00603 ca_detail = self.ca_detail()
00604
00605 if ca_detail.state != "active":
00606 return self.regenerate_roa()
00607
00608 regen_margin = rpki.sundial.timedelta(seconds = self.self().regen_margin)
00609
00610 if rpki.sundial.now() + regen_margin > self.cert.getNotAfter():
00611 return self.regenerate_roa()
00612
00613 ca_resources = ca_detail.latest_ca_cert.get_3779resources()
00614 ee_resources = self.cert.get_3779resources()
00615
00616 if ee_resources.oversized(ca_resources):
00617 return self.regenerate_roa()
00618
00619 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
00620 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
00621
00622 if ee_resources.v4 != v4 or ee_resources.v6 != v6:
00623 return self.regenerate_roa()
00624
00625 def generate_roa(self):
00626 """Generate a ROA based on this <route_origin/> object.
00627
00628 At present this does not support ROAs with multiple signatures
00629 (neither does the current CMS code).
00630
00631 At present we have no way of performing a direct lookup from a
00632 desired set of resources to a covering certificate, so we have to
00633 search. This could be quite slow if we have a lot of active
00634 ca_detail objects. Punt on the issue for now, revisit if
00635 profiling shows this as a hotspot.
00636
00637 Once we have the right covering certificate, we generate the ROA
00638 payload, generate a new EE certificate, use the EE certificate to
00639 sign the ROA payload, publish the result, then throw away the
00640 private key for the EE cert, all per the ROA specification. This
00641 implies that generating a lot of ROAs will tend to thrash
00642 /dev/random, but there is not much we can do about that.
00643 """
00644
00645 if self.ipv4 is None and self.ipv6 is None:
00646 rpki.log.warn("Can't generate ROA for empty prefix list")
00647 return
00648
00649
00650
00651
00652
00653
00654
00655
00656 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
00657 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
00658
00659 ca_detail = self.ca_detail()
00660 if ca_detail is None or ca_detail.state != "active":
00661 ca_detail = None
00662 for parent in self.self().parents():
00663 for ca in parent.cas():
00664 ca_detail = ca.fetch_active()
00665 if ca_detail is not None:
00666 resources = ca_detail.latest_ca_cert.get_3779resources()
00667 if v4.issubset(resources.v4) and v6.issubset(resources.v6):
00668 break
00669 ca_detail = None
00670 if ca_detail is not None:
00671 break
00672
00673 if ca_detail is None:
00674 rpki.log.warn("generate_roa() could not find a covering certificate")
00675 return
00676
00677 resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6)
00678
00679 keypair = rpki.x509.RSA()
00680 keypair.generate()
00681
00682 sia = ((rpki.oids.name2oid["id-ad-signedObject"], ("uri", self.roa_uri(ca, keypair))),)
00683
00684 self.cert = ca_detail.issue_ee(ca, resources, keypair.get_RSApublic(), sia = sia)
00685 self.roa = rpki.x509.ROA.build(self.as_number, self.ipv4, self.ipv6, keypair, (self.cert,))
00686 self.ca_detail_id = ca_detail.ca_detail_id
00687 self.sql_store()
00688
00689 repository = parent.repository()
00690 repository.publish(self.roa, self.roa_uri(ca))
00691 repository.publish(self.cert, self.ee_uri(ca))
00692 ca_detail.generate_manifest()
00693
00694 def withdraw_roa(self, regenerate = False):
00695 """Withdraw ROA associated with this route_origin.
00696
00697 In order to preserve make-before-break properties without
00698 duplicating code, this method also handles generating a
00699 replacement ROA when requested.
00700 """
00701
00702 ca_detail = self.ca_detail()
00703 ca = ca_detail.ca()
00704 repository = ca.parent().repository()
00705 cert = self.cert
00706 roa = self.roa
00707 roa_uri = self.roa_uri(ca)
00708 ee_uri = self.ee_uri(ca)
00709
00710 if ca_detail.state != 'active':
00711 self.ca_detail_id = None
00712 if regenerate:
00713 self.generate_roa()
00714
00715 rpki.log.debug("Withdrawing ROA and revoking its EE cert")
00716 rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
00717 repository.withdraw(roa, roa_uri)
00718 repository.withdraw(cert, ee_uri)
00719 self.gctx.sql.sweep()
00720 ca_detail.generate_crl()
00721 ca_detail.generate_manifest()
00722
00723 def regenerate_roa(self):
00724 """Reissue ROA associated with this route_origin."""
00725 self.withdraw_roa(regenerate = True)
00726
00727 def roa_uri(self, ca, key = None):
00728 """Return the publication URI for this route_origin's ROA."""
00729 return ca.sia_uri + (key or self.cert).gSKI() + ".roa"
00730
00731 def ee_uri_tail(self):
00732 """Return the tail (filename) portion of the URI for this route_origin's ROA's EE certificate."""
00733 return self.cert.gSKI() + ".cer"
00734
00735 def ee_uri(self, ca):
00736 """Return the publication URI for this route_origin's ROA's EE certificate."""
00737 return ca.sia_uri + self.ee_uri_tail()
00738
00739 class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace):
00740 """<list_resources/> element."""
00741
00742 element_name = "list_resources"
00743 attributes = ("self_id", "tag", "child_id", "valid_until", "asn", "ipv4", "ipv6", "subject_name")
00744 valid_until = None
00745
00746 def startElement(self, stack, name, attrs):
00747 """Handle <list_resources/> element. This requires special
00748 handling due to the data types of some of the attributes.
00749 """
00750 assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
00751 self.read_attrs(attrs)
00752 if isinstance(self.valid_until, str):
00753 self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until)
00754 if self.asn is not None:
00755 self.asn = rpki.resource_set.resource_set_as(self.asn)
00756 if self.ipv4 is not None:
00757 self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
00758 if self.ipv6 is not None:
00759 self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
00760
00761 def toXML(self):
00762 """Generate <list_resources/> element. This requires special
00763 handling due to the data types of some of the attributes.
00764 """
00765 elt = self.make_elt()
00766 if isinstance(self.valid_until, int):
00767 elt.set("valid_until", self.valid_until.toXMLtime())
00768 return elt
00769
00770 class report_error_elt(rpki.xml_utils.base_elt, left_right_namespace):
00771 """<report_error/> element."""
00772
00773 element_name = "report_error"
00774 attributes = ("tag", "self_id", "error_code")
00775
00776 @classmethod
00777 def from_exception(cls, exc, self_id = None):
00778 """Generate a <report_error/> element from an exception."""
00779 self = cls()
00780 self.self_id = self_id
00781 self.error_code = exc.__class__.__name__
00782 return self
00783
00784 class msg(rpki.xml_utils.msg, left_right_namespace):
00785 """Left-right PDU."""
00786
00787
00788
00789 version = 1
00790
00791
00792
00793 pdus = dict((x.element_name, x)
00794 for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt,
00795 route_origin_elt, list_resources_elt, report_error_elt))
00796
00797 def serve_top_level(self, gctx):
00798 """Serve one msg PDU."""
00799 r_msg = self.__class__()
00800 r_msg.type = "reply"
00801 for q_pdu in self:
00802 q_pdu.gctx = gctx
00803 q_pdu.serve_dispatch(r_msg)
00804 return r_msg
00805
00806 class sax_handler(rpki.xml_utils.sax_handler):
00807 """SAX handler for Left-Right protocol."""
00808
00809 pdu = msg
00810 name = "msg"
00811 version = "1"
00812
00813 class cms_msg(rpki.x509.XML_CMS_object):
00814 """Class to hold a CMS-signed left-right PDU."""
00815
00816 encoding = "us-ascii"
00817 schema = rpki.relaxng.left_right
00818 saxify = sax_handler.saxify