00001 """
00002 Global context for rpkid.
00003
00004 $Id: rpki_engine.py 2573 2009-07-04 20:24:08Z 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 lxml.etree, re
00036 import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql
00037 import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log, rpki.async
00038
00039 class rpkid_context(object):
00040 """
00041 A container for various global rpkid parameters.
00042 """
00043
00044 def __init__(self, cfg):
00045
00046 self.sql = rpki.sql.session(cfg)
00047
00048 self.bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta"))
00049 self.irdb_cert = rpki.x509.X509(Auto_file = cfg.get("irdb-cert"))
00050 self.irbe_cert = rpki.x509.X509(Auto_file = cfg.get("irbe-cert"))
00051 self.rpkid_cert = rpki.x509.X509(Auto_file = cfg.get("rpkid-cert"))
00052 self.rpkid_key = rpki.x509.RSA( Auto_file = cfg.get("rpkid-key"))
00053
00054 self.irdb_url = cfg.get("irdb-url")
00055
00056 self.https_server_host = cfg.get("server-host", "")
00057 self.https_server_port = int(cfg.get("server-port", "4433"))
00058
00059 self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/")
00060
00061 def irdb_query(self, q_pdu, callback, errback):
00062 """
00063 Perform an IRDB callback query.
00064 """
00065
00066 rpki.log.trace()
00067
00068 q_msg = rpki.left_right.msg.query()
00069 q_msg.append(q_pdu)
00070 q_cms = rpki.left_right.cms_msg.wrap(q_msg, self.rpkid_key, self.rpkid_cert)
00071
00072 def unwrap(der):
00073 r_msg = rpki.left_right.cms_msg.unwrap(der, (self.bpki_ta, self.irdb_cert))
00074 if not r_msg.is_reply() or [r_pdu for r_pdu in r_msg if type(r_pdu) is not type(q_pdu)]:
00075 errback(rpki.exceptions.BadIRDBReply(
00076 "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")))
00077 else:
00078 callback(r_msg)
00079
00080 rpki.https.client(
00081 server_ta = (self.bpki_ta, self.irdb_cert),
00082 client_key = self.rpkid_key,
00083 client_cert = self.rpkid_cert,
00084 url = self.irdb_url,
00085 msg = q_cms,
00086 callback = unwrap,
00087 errback = errback)
00088
00089 def irdb_query_child_resources(self, self_handle, child_handle, callback, errback):
00090 """
00091 Ask IRDB about a child's resources.
00092 """
00093
00094 rpki.log.trace()
00095
00096 q_pdu = rpki.left_right.list_resources_elt()
00097 q_pdu.self_handle = self_handle
00098 q_pdu.child_handle = child_handle
00099
00100 def done(r_msg):
00101 if len(r_msg) == 1:
00102 callback(rpki.resource_set.resource_bag(
00103 asn = r_msg[0].asn,
00104 v4 = r_msg[0].ipv4,
00105 v6 = r_msg[0].ipv6,
00106 valid_until = r_msg[0].valid_until))
00107 else:
00108 errback(rpki.exceptions.BadIRDBReply(
00109 "Expected exactly one PDU from IRDB: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")))
00110
00111 self.irdb_query(q_pdu, done, errback)
00112
00113 def irdb_query_roa_requests(self, self_handle, callback, errback):
00114 """
00115 Ask IRDB about self's ROA requests.
00116 """
00117
00118 rpki.log.trace()
00119
00120 q_pdu = rpki.left_right.list_roa_requests_elt()
00121 q_pdu.self_handle = self_handle
00122
00123 self.irdb_query(q_pdu, callback, errback)
00124
00125 def left_right_handler(self, query, path, cb):
00126 """
00127 Process one left-right PDU.
00128 """
00129
00130 rpki.log.trace()
00131
00132 def done(r_msg):
00133 reply = rpki.left_right.cms_msg.wrap(r_msg, self.rpkid_key, self.rpkid_cert)
00134 self.sql.sweep()
00135 cb(200, reply)
00136
00137 try:
00138 self.sql.ping()
00139 q_msg = rpki.left_right.cms_msg.unwrap(query, (self.bpki_ta, self.irbe_cert))
00140 if not q_msg.is_query():
00141 raise rpki.exceptions.BadQuery, "Message type is not query"
00142 q_msg.serve_top_level(self, done)
00143 except (rpki.async.ExitNow, SystemExit):
00144 raise
00145 except Exception, data:
00146 rpki.log.traceback()
00147 cb(500, "Unhandled exception %s" % data)
00148
00149 up_down_url_regexp = re.compile("/up-down/([-A-Z0-9_]+)/([-A-Z0-9_]+)$", re.I)
00150
00151 def up_down_handler(self, query, path, cb):
00152 """
00153 Process one up-down PDU.
00154 """
00155
00156 rpki.log.trace()
00157
00158 def done(reply):
00159 self.sql.sweep()
00160 cb(200, reply)
00161
00162 try:
00163 self.sql.ping()
00164 match = self.up_down_url_regexp.search(path)
00165 if match is None:
00166 raise rpki.exceptions.BadContactURL, "Bad path: %s" % path
00167 self_handle, child_handle = match.groups()
00168 child = rpki.left_right.child_elt.sql_fetch_where1(self, "self.self_handle = %s AND child.child_handle = %s AND child.self_id = self.self_id",
00169 (self_handle, child_handle), "self")
00170 if child is None:
00171 raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_handle
00172 child.serve_up_down(query, done)
00173 except (rpki.async.ExitNow, SystemExit):
00174 raise
00175 except Exception, data:
00176 rpki.log.traceback()
00177 cb(400, "Could not process PDU: %s" % data)
00178
00179 def cronjob_handler(self, query, path, cb):
00180 """
00181 Periodic tasks. This is somewhat obsolete now that we have
00182 internal timers, but the test framework still uses this, and I
00183 haven't yet refactored this code to use the new timers.
00184 """
00185
00186 rpki.log.trace()
00187 self.sql.ping()
00188
00189 def loop(iterator, s):
00190
00191 def one():
00192 rpki.log.debug("Self %s polling parents" % s.self_id)
00193 s.client_poll(two)
00194
00195 def two():
00196 rpki.log.debug("Self %s updating children" % s.self_id)
00197 s.update_children(three)
00198
00199 def three():
00200 rpki.log.debug("Self %s updating ROAs" % s.self_id)
00201 s.update_roas(four)
00202
00203 def four():
00204 rpki.log.debug("Self %s regenerating CRLs and manifests" % s.self_id)
00205 s.regenerate_crls_and_manifests(iterator)
00206
00207 one()
00208
00209 def done():
00210 self.sql.sweep()
00211 cb(200, "OK")
00212
00213 rpki.async.iterator(rpki.left_right.self_elt.sql_fetch_all(self), loop, done)
00214
00215
00216
00217 https_ta_cache = None
00218
00219 def clear_https_ta_cache(self):
00220 """
00221 Clear dynamic TLS trust anchors.
00222 """
00223
00224 if self.https_ta_cache is not None:
00225 rpki.log.debug("Clearing HTTPS trusted cert cache")
00226 self.https_ta_cache = None
00227
00228 def build_https_ta_cache(self):
00229 """
00230 Build dynamic TLS trust anchors.
00231 """
00232
00233 if self.https_ta_cache is None:
00234
00235 selves = rpki.left_right.self_elt.sql_fetch_all(self)
00236 children = rpki.left_right.child_elt.sql_fetch_all(self)
00237
00238 self.https_ta_cache = rpki.https.build_https_ta_cache(
00239 [c.bpki_cert for c in children if c.bpki_cert is not None] +
00240 [c.bpki_glue for c in children if c.bpki_glue is not None] +
00241 [s.bpki_cert for s in selves if s.bpki_cert is not None] +
00242 [s.bpki_glue for s in selves if s.bpki_glue is not None] +
00243 [self.irbe_cert, self.irdb_cert, self.bpki_ta])
00244
00245 return self.https_ta_cache
00246
00247
00248 class ca_obj(rpki.sql.sql_persistent):
00249 """
00250 Internal CA object.
00251 """
00252
00253 sql_template = rpki.sql.template(
00254 "ca",
00255 "ca_id",
00256 "last_crl_sn",
00257 ("next_crl_update", rpki.sundial.datetime),
00258 "last_issued_sn", "last_manifest_sn",
00259 ("next_manifest_update", rpki.sundial.datetime),
00260 "sia_uri", "parent_id", "parent_resource_class")
00261
00262 last_crl_sn = 0
00263 last_issued_sn = 0
00264 last_manifest_sn = 0
00265
00266 def parent(self):
00267 """Fetch parent object to which this CA object links."""
00268 return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
00269
00270 def ca_details(self):
00271 """Fetch all ca_detail objects that link to this CA object."""
00272 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
00273
00274 def fetch_pending(self):
00275 """Fetch the pending ca_details for this CA, if any."""
00276 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,))
00277
00278 def fetch_active(self):
00279 """Fetch the active ca_detail for this CA, if any."""
00280 return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,))
00281
00282 def fetch_deprecated(self):
00283 """Fetch deprecated ca_details for this CA, if any."""
00284 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,))
00285
00286 def fetch_revoked(self):
00287 """Fetch revoked ca_details for this CA, if any."""
00288 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,))
00289
00290 def construct_sia_uri(self, parent, rc):
00291 """
00292 Construct the sia_uri value for this CA given configured
00293 information and the parent's up-down protocol list_response PDU.
00294 """
00295
00296 sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync()
00297 if not sia_uri or not sia_uri.startswith(parent.sia_base):
00298 sia_uri = parent.sia_base
00299 elif not sia_uri.endswith("/"):
00300 raise rpki.exceptions.BadURISyntax, "SIA URI must end with a slash: %s" % sia_uri
00301 return sia_uri + str(self.ca_id) + "/"
00302
00303 def check_for_updates(self, parent, rc, cb, eb):
00304 """
00305 Parent has signaled continued existance of a resource class we
00306 already knew about, so we need to check for an updated
00307 certificate, changes in resource coverage, revocation and reissue
00308 with the same key, etc.
00309 """
00310
00311 sia_uri = self.construct_sia_uri(parent, rc)
00312 sia_uri_changed = self.sia_uri != sia_uri
00313 if sia_uri_changed:
00314 self.sia_uri = sia_uri
00315 self.sql_mark_dirty()
00316
00317 rc_resources = rc.to_resource_bag()
00318 cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs)
00319
00320 def loop(iterator, ca_detail):
00321
00322 ski = ca_detail.latest_ca_cert.get_SKI()
00323
00324 if ski not in cert_map:
00325 rpki.log.warn("Certificate in database missing from list_response, class %s, SKI %s, maybe parent certificate went away?"
00326 % (repr(rc.class_name), ca_detail.latest_ca_cert.gSKI()))
00327 ca_detail.delete(self, parent.repository(), iterator, eb)
00328 return
00329
00330 def cleanup():
00331 del cert_map[ski]
00332 iterator()
00333
00334 if ca_detail.state in ("pending", "active"):
00335 current_resources = ca_detail.latest_ca_cert.get_3779resources()
00336 if (sia_uri_changed or
00337 ca_detail.latest_ca_cert != cert_map[ski].cert or
00338 current_resources.undersized(rc_resources) or
00339 current_resources.oversized(rc_resources)):
00340 ca_detail.update(
00341 parent = parent,
00342 ca = self,
00343 rc = rc,
00344 sia_uri_changed = sia_uri_changed,
00345 old_resources = current_resources,
00346 callback = cleanup,
00347 errback = eb)
00348 return
00349
00350 cleanup()
00351
00352 def done():
00353 if cert_map:
00354 rpki.log.warn("Certificates in list_response missing from our database, class %s, SKIs %s"
00355 % (repr(rc.class_name), ", ".join(c.cert.gSKI() for c in cert_map.values())))
00356 cb()
00357
00358 rpki.async.iterator(ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,)), loop, done)
00359
00360 @classmethod
00361 def create(cls, parent, rc, cb, eb):
00362 """
00363 Parent has signaled existance of a new resource class, so we need
00364 to create and set up a corresponding CA object.
00365 """
00366
00367 self = cls()
00368 self.gctx = parent.gctx
00369 self.parent_id = parent.parent_id
00370 self.parent_resource_class = rc.class_name
00371 self.sql_store()
00372 self.sia_uri = self.construct_sia_uri(parent, rc)
00373 ca_detail = ca_detail_obj.create(self)
00374
00375 def done(issue_response):
00376 ca_detail.activate(
00377 ca = self,
00378 cert = issue_response.payload.classes[0].certs[0].cert,
00379 uri = issue_response.payload.classes[0].certs[0].cert_url,
00380 callback = cb,
00381 errback = eb)
00382
00383 rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb)
00384
00385 def delete(self, parent, callback):
00386 """
00387 The list of current resource classes received from parent does not
00388 include the class corresponding to this CA, so we need to delete
00389 it (and its little dog too...).
00390
00391 All certs published by this CA are now invalid, so need to
00392 withdraw them, the CRL, and the manifest from the repository,
00393 delete all child_cert and ca_detail records associated with this
00394 CA, then finally delete this CA itself.
00395 """
00396
00397 def fail(e):
00398 rpki.log.traceback()
00399 rpki.log.warn("Could not delete CA %r, skipping: %s" % (self, e))
00400 callback()
00401
00402 def done():
00403 self.sql_delete()
00404 callback()
00405
00406 repository = parent.repository()
00407
00408 def loop(iterator, ca_detail):
00409 ca_detail.delete(self, repository, iterator, fail)
00410
00411 rpki.async.iterator(self.ca_details(), loop, done)
00412
00413 def next_serial_number(self):
00414 """
00415 Allocate a certificate serial number.
00416 """
00417 self.last_issued_sn += 1
00418 self.sql_mark_dirty()
00419 return self.last_issued_sn
00420
00421 def next_manifest_number(self):
00422 """
00423 Allocate a manifest serial number.
00424 """
00425 self.last_manifest_sn += 1
00426 self.sql_mark_dirty()
00427 return self.last_manifest_sn
00428
00429 def next_crl_number(self):
00430 """
00431 Allocate a CRL serial number.
00432 """
00433 self.last_crl_sn += 1
00434 self.sql_mark_dirty()
00435 return self.last_crl_sn
00436
00437 def rekey(self, cb, eb):
00438 """
00439 Initiate a rekey operation for this ca. Generate a new keypair.
00440 Request cert from parent using new keypair. Mark result as our
00441 active ca_detail. Reissue all child certs issued by this ca using
00442 the new ca_detail.
00443 """
00444
00445 rpki.log.trace()
00446
00447 parent = self.parent()
00448 old_detail = self.fetch_active()
00449 new_detail = ca_detail_obj.create(self)
00450
00451 def done(issue_response):
00452 new_detail.activate(
00453 ca = self,
00454 cert = issue_response.payload.classes[0].certs[0].cert,
00455 uri = issue_response.payload.classes[0].certs[0].cert_url,
00456 predecessor = old_detail,
00457 callback = cb,
00458 errback = eb)
00459
00460 rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb)
00461
00462 def revoke(self, cb, eb):
00463 """
00464 Revoke deprecated ca_detail objects associated with this ca.
00465 """
00466
00467 rpki.log.trace()
00468
00469 def loop(iterator, ca_detail):
00470 ca_detail.revoke(iterator, eb)
00471
00472 rpki.async.iterator(self.fetch_deprecated(), loop, cb)
00473
00474 class ca_detail_obj(rpki.sql.sql_persistent):
00475 """
00476 Internal CA detail object.
00477 """
00478
00479 sql_template = rpki.sql.template(
00480 "ca_detail",
00481 "ca_detail_id",
00482 ("private_key_id", rpki.x509.RSA),
00483 ("public_key", rpki.x509.RSApublic),
00484 ("latest_ca_cert", rpki.x509.X509),
00485 ("manifest_private_key_id", rpki.x509.RSA),
00486 ("manifest_public_key", rpki.x509.RSApublic),
00487 ("latest_manifest_cert", rpki.x509.X509),
00488 ("latest_manifest", rpki.x509.SignedManifest),
00489 ("latest_crl", rpki.x509.CRL),
00490 "state",
00491 "ca_cert_uri",
00492 "ca_id")
00493
00494 def sql_decode(self, vals):
00495 """
00496 Extra assertions for SQL decode of a ca_detail_obj.
00497 """
00498 rpki.sql.sql_persistent.sql_decode(self, vals)
00499 assert (self.public_key is None and self.private_key_id is None) or \
00500 self.public_key.get_DER() == self.private_key_id.get_public_DER()
00501 assert (self.manifest_public_key is None and self.manifest_private_key_id is None) or \
00502 self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
00503
00504 def ca(self):
00505 """Fetch CA object to which this ca_detail links."""
00506 return ca_obj.sql_fetch(self.gctx, self.ca_id)
00507
00508 def child_certs(self, child = None, ski = None, unique = False):
00509 """Fetch all child_cert objects that link to this ca_detail."""
00510 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique)
00511
00512 def revoked_certs(self):
00513 """Fetch all revoked_cert objects that link to this ca_detail."""
00514 return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00515
00516 def roas(self):
00517 """Fetch all ROA objects that link to this ca_detail."""
00518 return rpki.rpki_engine.roa_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00519
00520 def crl_uri(self, ca):
00521 """Return publication URI for this ca_detail's CRL."""
00522 return ca.sia_uri + self.crl_uri_tail()
00523
00524 def crl_uri_tail(self):
00525 """Return tail (filename portion) of publication URI for this ca_detail's CRL."""
00526 return self.public_key.gSKI() + ".crl"
00527
00528 def manifest_uri(self, ca):
00529 """Return publication URI for this ca_detail's manifest."""
00530 return ca.sia_uri + self.public_key.gSKI() + ".mnf"
00531
00532 def activate(self, ca, cert, uri, callback, errback, predecessor = None):
00533 """
00534 Activate this ca_detail.
00535 """
00536
00537 self.latest_ca_cert = cert
00538 self.ca_cert_uri = uri.rsync()
00539 self.generate_manifest_cert(ca)
00540
00541 def did_crl():
00542 self.generate_manifest(callback = did_manifest, errback = errback)
00543
00544 def did_manifest():
00545 self.state = "active"
00546 self.sql_mark_dirty()
00547 if predecessor is None:
00548 callback()
00549 else:
00550 predecessor.state = "deprecated"
00551 predecessor.sql_mark_dirty()
00552 rpki.async.iterator(predecessor.child_certs(), do_one_child_cert, done_child_certs)
00553
00554 def do_one_child_cert(iterator, child_cert):
00555 child_cert.reissue(self, iterator.ignore, errback)
00556
00557 def done_child_certs():
00558 rpki.async.iterator(predecessor.roas(), do_one_roa, callback)
00559
00560 def do_one_roa(iterator, roa):
00561 roa.regenerate_roa(iterator, errback)
00562
00563 self.generate_crl(callback = did_crl, errback = errback)
00564
00565 def delete(self, ca, repository, cb, eb):
00566 """
00567 Delete this ca_detail and all of the certs it issued.
00568 """
00569
00570 def withdraw_one_child(iterator, child_cert):
00571 repository.withdraw(child_cert.cert, child_cert.uri(ca), iterator, eb)
00572
00573 def child_certs_done():
00574 rpki.async.iterator(self.roas(), withdraw_one_roa, withdraw_manifest)
00575
00576 def withdraw_one_roa(iterator, roa):
00577 roa.withdraw_roa(iterator)
00578
00579 def withdraw_manifest():
00580 repository.withdraw(self.latest_manifest, self.manifest_uri(ca), withdraw_crl, eb)
00581
00582 def withdraw_crl():
00583 repository.withdraw(self.latest_crl, self.crl_uri(ca), done, eb)
00584
00585 def done():
00586 for cert in self.child_certs() + self.revoked_certs():
00587 cert.sql_delete()
00588 self.sql_delete()
00589 cb()
00590
00591 rpki.async.iterator(self.child_certs(), withdraw_one_child, child_certs_done)
00592
00593 def revoke(self, cb, eb):
00594 """
00595 Request revocation of all certificates whose SKI matches the key
00596 for this ca_detail.
00597
00598 Tasks:
00599
00600 - Request revocation of old keypair by parent.
00601
00602 - Revoke all child certs issued by the old keypair.
00603
00604 - Generate a final CRL, signed with the old keypair, listing all
00605 the revoked certs, with a next CRL time after the last cert or
00606 CRL signed by the old keypair will have expired.
00607
00608 - Generate a corresponding final manifest.
00609
00610 - Destroy old keypairs.
00611
00612 - Leave final CRL and manifest in place until their nextupdate
00613 time has passed.
00614 """
00615
00616 def parent_revoked(r_msg):
00617
00618 if r_msg.payload.ski != self.latest_ca_cert.gSKI():
00619 raise rpki.exceptions.SKIMismatch
00620
00621 ca = self.ca()
00622 parent = ca.parent()
00623 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00624
00625 self.nextUpdate = rpki.sundial.now()
00626
00627 if self.latest_manifest is not None:
00628 self.nextUpdate = self.nextUpdate.later(self.latest_manifest.getNextUpdate())
00629
00630 if self.latest_crl is not None:
00631 self.nextUpdate = self.nextUpdate.later(self.latest_crl.getNextUpdate())
00632
00633 def revoke_one_child(iterator, child_cert):
00634 self.nextUpdate = self.nextUpdate.later(child_cert.cert.getNotAfter())
00635 child_cert.revoke(iterator, eb)
00636
00637 def final_crl():
00638 self.nextUpdate += crl_interval
00639 self.generate_crl(callback = final_manifest, errback = eb, nextUpdate = self.nextUpdate)
00640
00641 def final_manifest():
00642 self.generate_manifest(callback = done, errback = eb, nextUpdate = self.nextUpdate)
00643
00644 def done():
00645 self.private_key_id = None
00646 self.manifest_private_key_id = None
00647 self.manifest_public_key = None
00648 self.latest_manifest_cert = None
00649 self.state = "revoked"
00650 self.sql_mark_dirty()
00651 cb()
00652
00653 rpki.async.iterator(self.child_certs(), revoke_one_child, final_crl)
00654
00655 rpki.up_down.revoke_pdu.query(self, parent_revoked, eb)
00656
00657 def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback):
00658 """
00659 Need to get a new certificate for this ca_detail and perhaps frob
00660 children of this ca_detail.
00661 """
00662
00663 def issued(issue_response):
00664 self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert
00665 new_resources = self.latest_ca_cert.get_3779resources()
00666
00667 def loop(iterator, child_cert):
00668 child_resources = child_cert.cert.get_3779resources()
00669 if sia_uri_changed or child_resources.oversized(new_resources):
00670 child_cert.reissue(
00671 ca_detail = self,
00672 resources = child_resources.intersection(new_resources),
00673 callback = iterator.ignore,
00674 errback = errback)
00675 else:
00676 iterator()
00677
00678 if sia_uri_changed or old_resources.oversized(new_resources):
00679 rpki.async.iterator(self.child_certs(), loop, callback)
00680 else:
00681 callback()
00682
00683 rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback)
00684
00685 @classmethod
00686 def create(cls, ca):
00687 """
00688 Create a new ca_detail object for a specified CA.
00689 """
00690 self = cls()
00691 self.gctx = ca.gctx
00692 self.ca_id = ca.ca_id
00693 self.state = "pending"
00694
00695 self.private_key_id = rpki.x509.RSA.generate()
00696 self.public_key = self.private_key_id.get_RSApublic()
00697
00698 self.manifest_private_key_id = rpki.x509.RSA.generate()
00699 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00700
00701 self.sql_store()
00702 return self
00703
00704 def issue_ee(self, ca, resources, subject_key, sia = None):
00705 """
00706 Issue a new EE certificate.
00707 """
00708
00709 return self.latest_ca_cert.issue(
00710 keypair = self.private_key_id,
00711 subject_key = subject_key,
00712 serial = ca.next_serial_number(),
00713 sia = sia,
00714 aia = self.ca_cert_uri,
00715 crldp = self.crl_uri(ca),
00716 resources = resources,
00717 notAfter = self.latest_ca_cert.getNotAfter(),
00718 is_ca = False)
00719
00720
00721 def generate_manifest_cert(self, ca):
00722 """
00723 Generate a new manifest certificate for this ca_detail.
00724 """
00725
00726 resources = rpki.resource_set.resource_bag(
00727 asn = rpki.resource_set.resource_set_as("<inherit>"),
00728 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"),
00729 v6 = rpki.resource_set.resource_set_ipv6("<inherit>"))
00730
00731 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00732
00733 def issue(self, ca, child, subject_key, sia, resources, callback, errback, child_cert = None):
00734 """
00735 Issue a new certificate to a child. Optional child_cert argument
00736 specifies an existing child_cert object to update in place; if not
00737 specified, we create a new one. Returns the child_cert object
00738 containing the newly issued cert.
00739 """
00740
00741 assert child_cert is None or (child_cert.child_id == child.child_id and
00742 child_cert.ca_detail_id == self.ca_detail_id)
00743
00744 cert = self.latest_ca_cert.issue(
00745 keypair = self.private_key_id,
00746 subject_key = subject_key,
00747 serial = ca.next_serial_number(),
00748 aia = self.ca_cert_uri,
00749 crldp = self.crl_uri(ca),
00750 sia = sia,
00751 resources = resources,
00752 notAfter = resources.valid_until)
00753
00754 if child_cert is None:
00755 child_cert = rpki.rpki_engine.child_cert_obj(
00756 gctx = child.gctx,
00757 child_id = child.child_id,
00758 ca_detail_id = self.ca_detail_id,
00759 cert = cert)
00760 rpki.log.debug("Created new child_cert %s" % repr(child_cert))
00761 else:
00762 child_cert.cert = cert
00763 rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert))
00764
00765 child_cert.ski = cert.get_SKI()
00766
00767 child_cert.sql_store()
00768
00769 def published():
00770 self.generate_manifest(done, errback)
00771
00772 def done():
00773 callback(child_cert)
00774
00775 ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca), published, errback)
00776
00777 def generate_crl(self, callback, errback, nextUpdate = None):
00778 """
00779 Generate a new CRL for this ca_detail. At the moment this is
00780 unconditional, that is, it is up to the caller to decide whether a
00781 new CRL is needed.
00782 """
00783
00784 ca = self.ca()
00785 parent = ca.parent()
00786 repository = parent.repository()
00787 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00788 now = rpki.sundial.now()
00789
00790 if nextUpdate is None:
00791 nextUpdate = now + crl_interval
00792
00793 certlist = []
00794 for revoked_cert in self.revoked_certs():
00795 if now > revoked_cert.expires + crl_interval:
00796 revoked_cert.sql_delete()
00797 else:
00798 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00799 certlist.sort()
00800
00801 self.latest_crl = rpki.x509.CRL.generate(
00802 keypair = self.private_key_id,
00803 issuer = self.latest_ca_cert,
00804 serial = ca.next_crl_number(),
00805 thisUpdate = now,
00806 nextUpdate = nextUpdate,
00807 revokedCertificates = certlist)
00808
00809 repository.publish(self.latest_crl, self.crl_uri(ca), callback = callback, errback = errback)
00810
00811 def generate_manifest(self, callback, errback, nextUpdate = None):
00812 """
00813 Generate a new manifest for this ca_detail.
00814 """
00815
00816 ca = self.ca()
00817 parent = ca.parent()
00818 repository = parent.repository()
00819 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00820 now = rpki.sundial.now()
00821
00822 if nextUpdate is None:
00823 nextUpdate = now + crl_interval
00824
00825 roas = [r for r in self.roas() if r.cert is not None and r.roa is not None]
00826
00827 if self.latest_manifest_cert is None or self.latest_manifest_cert.getNotAfter() < nextUpdate:
00828 self.generate_manifest_cert(ca)
00829
00830 certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \
00831 [(r.roa_uri_tail(), r.roa) for r in roas] + \
00832 [(self.crl_uri_tail(), self.latest_crl)]
00833
00834 self.latest_manifest = rpki.x509.SignedManifest.build(
00835 serial = ca.next_manifest_number(),
00836 thisUpdate = now,
00837 nextUpdate = nextUpdate,
00838 names_and_objs = certs,
00839 keypair = self.manifest_private_key_id,
00840 certs = self.latest_manifest_cert)
00841
00842 repository.publish(self.latest_manifest, self.manifest_uri(ca), callback = callback, errback = errback)
00843
00844 class child_cert_obj(rpki.sql.sql_persistent):
00845 """
00846 Certificate that has been issued to a child.
00847 """
00848
00849 sql_template = rpki.sql.template(
00850 "child_cert",
00851 "child_cert_id",
00852 ("cert", rpki.x509.X509),
00853 "child_id",
00854 "ca_detail_id",
00855 "ski")
00856
00857 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00858 """
00859 Initialize a child_cert_obj.
00860 """
00861 rpki.sql.sql_persistent.__init__(self)
00862 self.gctx = gctx
00863 self.child_id = child_id
00864 self.ca_detail_id = ca_detail_id
00865 self.cert = cert
00866 if child_id or ca_detail_id or cert:
00867 self.sql_mark_dirty()
00868
00869 def child(self):
00870 """Fetch child object to which this child_cert object links."""
00871 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00872
00873 def ca_detail(self):
00874 """Fetch ca_detail object to which this child_cert object links."""
00875 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00876
00877 def uri_tail(self):
00878 """Return the tail (filename) portion of the URI for this child_cert."""
00879 return self.cert.gSKI() + ".cer"
00880
00881 def uri(self, ca):
00882 """Return the publication URI for this child_cert."""
00883 return ca.sia_uri + self.uri_tail()
00884
00885 def revoke(self, callback, errback, withdraw = True):
00886 """
00887 Revoke a child cert.
00888 """
00889
00890 rpki.log.debug("Revoking %s" % repr(self))
00891 ca_detail = self.ca_detail()
00892 ca = ca_detail.ca()
00893 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00894
00895 def done():
00896 self.gctx.sql.sweep()
00897 self.sql_delete()
00898 callback()
00899
00900 if withdraw:
00901 ca.parent().repository().withdraw(self.cert, self.uri(ca), done, errback)
00902 else:
00903 rpki.log.info("Suppressing withdrawal of %r" % self.cert)
00904 done()
00905
00906 def reissue(self, ca_detail, callback = None, errback = None, resources = None, sia = None):
00907 """
00908 Reissue an existing cert, reusing the public key. If the cert we
00909 would generate is identical to the one we already have, we just
00910 return the one we already have. If we have to revoke the old
00911 certificate when generating the new one, we have to generate a new
00912 child_cert_obj, so calling code that needs the updated
00913 child_cert_obj must use the return value from this method.
00914 """
00915
00916
00917 assert callback is not None
00918 assert errback is not None
00919
00920 ca = ca_detail.ca()
00921 child = self.child()
00922
00923 old_resources = self.cert.get_3779resources()
00924 old_sia = self.cert.get_SIA()
00925 old_ca_detail = self.ca_detail()
00926
00927 if resources is None:
00928 resources = old_resources
00929
00930 if sia is None:
00931 sia = old_sia
00932
00933 assert resources.valid_until is not None and old_resources.valid_until is not None
00934
00935 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00936 return callback(self)
00937
00938 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
00939 new_issuer = ca_detail != old_ca_detail
00940
00941 rpki.log.debug("Reissuing %r, must_revoke %s, new_issuer %s" % (self, must_revoke, new_issuer))
00942
00943 if resources.valid_until != old_resources.valid_until:
00944 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
00945
00946 def revoke(child_cert):
00947
00948 uri = child_cert.uri(ca)
00949 rpki.log.debug("New child_cert %r uri %s" % (child_cert, uri))
00950
00951 def loop(iterator, x):
00952 rpki.log.debug("Revoking child_cert %r" % x)
00953 x.revoke(iterator, errback, withdraw = x.uri(ca) != uri)
00954
00955 def manifest():
00956 ca_detail.generate_manifest(done, errback)
00957
00958 def done():
00959 callback(child_cert)
00960
00961 certs_to_revoke = [x for x in child.child_certs(ca_detail = ca_detail, ski = self.ski) if x is not child_cert]
00962
00963 if certs_to_revoke:
00964 rpki.async.iterator(certs_to_revoke, loop, manifest)
00965 else:
00966 done()
00967
00968 ca_detail.issue(
00969 ca = ca,
00970 child = child,
00971 subject_key = self.cert.getPublicKey(),
00972 sia = sia,
00973 resources = resources,
00974 child_cert = None if must_revoke or new_issuer else self,
00975 callback = revoke if must_revoke else callback,
00976 errback = errback)
00977
00978 @classmethod
00979 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
00980 """
00981 Fetch all child_cert objects matching a particular set of
00982 parameters. This is a wrapper to consolidate various queries that
00983 would otherwise be inline SQL WHERE expressions. In most cases
00984 code calls this indirectly, through methods in other classes.
00985 """
00986
00987 args = []
00988 where = []
00989
00990 if child:
00991 where.append("child_id = %s")
00992 args.append(child.child_id)
00993
00994 if ca_detail:
00995 where.append("ca_detail_id = %s")
00996 args.append(ca_detail.ca_detail_id)
00997
00998 if ski:
00999 where.append("ski = %s")
01000 args.append(ski)
01001
01002 where = " AND ".join(where)
01003
01004 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
01005
01006 if unique:
01007 return cls.sql_fetch_where1(gctx, where, args)
01008 else:
01009 return cls.sql_fetch_where(gctx, where, args)
01010
01011 class revoked_cert_obj(rpki.sql.sql_persistent):
01012 """
01013 Tombstone for a revoked certificate.
01014 """
01015
01016 sql_template = rpki.sql.template(
01017 "revoked_cert",
01018 "revoked_cert_id",
01019 "serial",
01020 "ca_detail_id",
01021 ("revoked", rpki.sundial.datetime),
01022 ("expires", rpki.sundial.datetime))
01023
01024 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
01025 """Initialize a revoked_cert_obj."""
01026 rpki.sql.sql_persistent.__init__(self)
01027 self.gctx = gctx
01028 self.serial = serial
01029 self.revoked = revoked
01030 self.expires = expires
01031 self.ca_detail_id = ca_detail_id
01032 if serial or revoked or expires or ca_detail_id:
01033 self.sql_mark_dirty()
01034
01035 def ca_detail(self):
01036 """Fetch ca_detail object to which this revoked_cert_obj links."""
01037 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
01038
01039 @classmethod
01040 def revoke(cls, cert, ca_detail):
01041 """
01042 Revoke a certificate.
01043 """
01044 return cls(
01045 serial = cert.getSerial(),
01046 expires = cert.getNotAfter(),
01047 revoked = rpki.sundial.now(),
01048 gctx = ca_detail.gctx,
01049 ca_detail_id = ca_detail.ca_detail_id)
01050
01051 class roa_obj(rpki.sql.sql_persistent):
01052 """
01053 Route Origin Authorization.
01054 """
01055
01056 sql_template = rpki.sql.template(
01057 "roa",
01058 "roa_id",
01059 "ca_detail_id",
01060 "self_id",
01061 "asn",
01062 ("roa", rpki.x509.ROA),
01063 ("cert", rpki.x509.X509))
01064
01065 ca_detail_id = None
01066 cert = None
01067 roa = None
01068
01069 def self(self):
01070 """
01071 Fetch self object to which this roa_obj links.
01072 """
01073 return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
01074
01075 def sql_fetch_hook(self):
01076 """
01077 Extra SQL fetch actions for roa_obj -- handle prefix lists.
01078 """
01079 for version, datatype, attribute in ((4, rpki.resource_set.roa_prefix_set_ipv4, "ipv4"),
01080 (6, rpki.resource_set.roa_prefix_set_ipv6, "ipv6")):
01081 setattr(self, attribute, datatype.from_sql(
01082 self.gctx.sql,
01083 """
01084 SELECT prefix, prefixlen, max_prefixlen FROM roa_prefix
01085 WHERE roa_id = %s AND version = %s
01086 """,
01087 (self.roa_id, version)))
01088
01089 def sql_insert_hook(self):
01090 """
01091 Extra SQL insert actions for roa_obj -- handle prefix lists.
01092 """
01093 for version, prefix_set in ((4, self.ipv4), (6, self.ipv6)):
01094 if prefix_set:
01095 self.gctx.sql.executemany(
01096 """
01097 INSERT roa_prefix (roa_id, prefix, prefixlen, max_prefixlen, version)
01098 VALUES (%s, %s, %s, %s, %s)
01099 """,
01100 ((self.roa_id, x.prefix, x.prefixlen, x.max_prefixlen, version)
01101 for x in prefix_set))
01102
01103 def sql_delete_hook(self):
01104 """
01105 Extra SQL delete actions for roa_obj -- handle prefix lists.
01106 """
01107 self.gctx.sql.execute("DELETE FROM roa_prefix WHERE roa_id = %s", (self.roa_id,))
01108
01109 def ca_detail(self):
01110 """
01111 Fetch all ca_detail objects that link to this roa_obj.
01112 """
01113 return rpki.rpki_engine.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
01114
01115 def update_roa(self, callback):
01116 """
01117 Bring this roa_obj's ROA up to date if necesssary.
01118 """
01119
01120 def lose(e):
01121 rpki.log.traceback()
01122 rpki.log.warn("Could not update ROA %r, skipping: %s" % (self, e))
01123 callback()
01124 return
01125
01126 if self.roa is None:
01127 self.generate_roa(callback, lose)
01128 return
01129
01130 ca_detail = self.ca_detail()
01131
01132 if ca_detail is None or ca_detail.state != "active":
01133 self.regenerate_roa(callback, lose)
01134 return
01135
01136 regen_margin = rpki.sundial.timedelta(seconds = self.self().regen_margin)
01137
01138 if rpki.sundial.now() + regen_margin > self.cert.getNotAfter():
01139 self.regenerate_roa(callback, lose)
01140 return
01141
01142 ca_resources = ca_detail.latest_ca_cert.get_3779resources()
01143 ee_resources = self.cert.get_3779resources()
01144
01145 if ee_resources.oversized(ca_resources):
01146 self.regenerate_roa(callback, lose)
01147 return
01148
01149 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
01150 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
01151
01152 if ee_resources.v4 != v4 or ee_resources.v6 != v6:
01153 self.regenerate_roa(callback, lose)
01154 return
01155
01156 callback()
01157
01158 def generate_roa(self, callback, errback):
01159 """
01160 Generate a ROA.
01161
01162 At present this does not support ROAs with multiple signatures
01163 (neither does the current CMS code).
01164
01165 At present we have no way of performing a direct lookup from a
01166 desired set of resources to a covering certificate, so we have to
01167 search. This could be quite slow if we have a lot of active
01168 ca_detail objects. Punt on the issue for now, revisit if
01169 profiling shows this as a hotspot.
01170
01171 Once we have the right covering certificate, we generate the ROA
01172 payload, generate a new EE certificate, use the EE certificate to
01173 sign the ROA payload, publish the result, then throw away the
01174 private key for the EE cert, all per the ROA specification. This
01175 implies that generating a lot of ROAs will tend to thrash
01176 /dev/random, but there is not much we can do about that.
01177 """
01178
01179 if self.ipv4 is None and self.ipv6 is None:
01180 errback(rpki.exceptions.EmptyROAPrefixList())
01181 return
01182
01183
01184
01185
01186
01187 v4 = self.ipv4.to_resource_set() if self.ipv4 is not None else rpki.resource_set.resource_set_ipv4()
01188 v6 = self.ipv6.to_resource_set() if self.ipv6 is not None else rpki.resource_set.resource_set_ipv6()
01189
01190 ca_detail = self.ca_detail()
01191 if ca_detail is None or ca_detail.state != "active":
01192 ca_detail = None
01193 for parent in self.self().parents():
01194 for ca in parent.cas():
01195 ca_detail = ca.fetch_active()
01196 if ca_detail is not None:
01197 resources = ca_detail.latest_ca_cert.get_3779resources()
01198 if v4.issubset(resources.v4) and v6.issubset(resources.v6):
01199 break
01200 ca_detail = None
01201 if ca_detail is not None:
01202 break
01203
01204 if ca_detail is None:
01205 errback(rpki.exceptions.NoCoveringCertForROA("generate_roa() could not find a certificate covering %s %s" % (v4, v6)))
01206 return
01207
01208 ca = ca_detail.ca()
01209
01210 resources = rpki.resource_set.resource_bag(v4 = v4, v6 = v6)
01211
01212 keypair = rpki.x509.RSA.generate()
01213
01214 self.ca_detail_id = ca_detail.ca_detail_id
01215
01216 self.cert = ca_detail.issue_ee(ca, resources, keypair.get_RSApublic(),
01217 sia = ((rpki.oids.name2oid["id-ad-signedObject"],
01218 ("uri", self.roa_uri(keypair))),))
01219
01220 self.roa = rpki.x509.ROA.build(self.asn, self.ipv4, self.ipv6, keypair, (self.cert,))
01221
01222 self.sql_store()
01223
01224 def done():
01225 ca_detail.generate_manifest(callback, errback)
01226
01227 ca.parent().repository().publish(self.roa, self.roa_uri(), done, errback)
01228
01229 def withdraw_roa(self, callback, errback, regenerate = False):
01230 """
01231 Withdraw ROA associated with this roa_obj.
01232
01233 In order to preserve make-before-break properties without
01234 duplicating code, this method also handles generating a
01235 replacement ROA when requested.
01236 """
01237
01238 ca_detail = self.ca_detail()
01239 cert = self.cert
01240 roa = self.roa
01241 roa_uri = self.roa_uri()
01242 ee_uri = self.ee_uri()
01243
01244 if ca_detail.state != 'active':
01245 self.ca_detail_id = None
01246
01247 def one():
01248 rpki.log.debug("Withdrawing ROA and revoking its EE cert")
01249 rpki.rpki_engine.revoked_cert_obj.revoke(cert = cert, ca_detail = ca_detail)
01250 ca_detail.ca().parent().repository().withdraw(roa, roa_uri, two, errback)
01251
01252 def two():
01253 self.gctx.sql.sweep()
01254 ca_detail.generate_crl(three, errback)
01255
01256 def three():
01257 ca_detail.generate_manifest(callback, errback)
01258
01259 if regenerate:
01260 self.generate_roa(one, errback)
01261 else:
01262 one()
01263
01264 def regenerate_roa(self, callback, errback):
01265 """
01266 Reissue ROA associated with this roa_obj.
01267 """
01268 if self.ca_detail() is None:
01269 self.generate_roa(callback, errback)
01270 else:
01271 self.withdraw_roa(callback, errback, regenerate = True)
01272
01273 def roa_uri(self, key = None):
01274 """
01275 Return the publication URI for this roa_obj's ROA.
01276 """
01277 return self.ca_detail().ca().sia_uri + self.roa_uri_tail(key)
01278
01279 def roa_uri_tail(self, key = None):
01280 """
01281 Return the tail (filename portion) of the publication URI for this
01282 roa_obj's ROA.
01283 """
01284 return (key or self.cert).gSKI() + ".roa"
01285
01286 def ee_uri_tail(self):
01287 """
01288 Return the tail (filename) portion of the URI for this roa_obj's
01289 ROA's EE certificate.
01290 """
01291 return self.cert.gSKI() + ".cer"
01292
01293 def ee_uri(self):
01294 """
01295 Return the publication URI for this roa_obj's ROA's EE
01296 certificate.
01297 """
01298 return self.ca_detail().ca().sia_uri + self.ee_uri_tail()
01299