00001 """
00002 Global context for rpkid.
00003
00004 $Id: rpki_engine.py 2481 2009-06-01 05:07:46Z sra $
00005
00006 Copyright (C) 2009 Internet Systems Consortium ("ISC")
00007
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019
00020 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00021
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034
00035 import traceback, lxml.etree
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, self_id, child_id, callback, errback):
00062 """
00063 Perform an IRDB callback query.
00064 """
00065
00066 rpki.log.trace()
00067
00068 q_msg = rpki.left_right.msg()
00069 q_msg.type = "query"
00070 q_msg.append(rpki.left_right.list_resources_elt())
00071 q_msg[0].self_id = self_id
00072 q_msg[0].child_id = child_id
00073 q_cms = rpki.left_right.cms_msg.wrap(q_msg, self.rpkid_key, self.rpkid_cert)
00074
00075 def unwrap(der):
00076 r_msg = rpki.left_right.cms_msg.unwrap(der, (self.bpki_ta, self.irdb_cert))
00077 if len(r_msg) == 0 or not isinstance(r_msg[0], rpki.left_right.list_resources_elt) or r_msg.type != "reply":
00078 errback(rpki.exceptions.BadIRDBReply(
00079 "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")))
00080 else:
00081 callback(rpki.resource_set.resource_bag(
00082 asn = r_msg[0].asn,
00083 v4 = r_msg[0].ipv4,
00084 v6 = r_msg[0].ipv6,
00085 valid_until = r_msg[0].valid_until))
00086
00087 rpki.https.client(
00088 server_ta = (self.bpki_ta, self.irdb_cert),
00089 client_key = self.rpkid_key,
00090 client_cert = self.rpkid_cert,
00091 url = self.irdb_url,
00092 msg = q_cms,
00093 callback = unwrap,
00094 errback = errback)
00095
00096 def left_right_handler(self, query, path, cb):
00097 """
00098 Process one left-right PDU.
00099 """
00100
00101 rpki.log.trace()
00102
00103 def done(r_msg):
00104 reply = rpki.left_right.cms_msg.wrap(r_msg, self.rpkid_key, self.rpkid_cert)
00105 self.sql.sweep()
00106 cb(200, reply)
00107
00108 try:
00109 self.sql.ping()
00110 q_msg = rpki.left_right.cms_msg.unwrap(query, (self.bpki_ta, self.irbe_cert))
00111 if q_msg.type != "query":
00112 raise rpki.exceptions.BadQuery, "Message type is not query"
00113 q_msg.serve_top_level(self, done)
00114 except (rpki.async.ExitNow, SystemExit):
00115 raise
00116 except Exception, data:
00117 rpki.log.error(traceback.format_exc())
00118 cb(500, "Unhandled exception %s" % data)
00119
00120 def up_down_handler(self, query, path, cb):
00121 """
00122 Process one up-down PDU.
00123 """
00124
00125 rpki.log.trace()
00126
00127 def done(reply):
00128 self.sql.sweep()
00129 cb(200, reply)
00130
00131 try:
00132 self.sql.ping()
00133 child_id = path.partition("/up-down/")[2]
00134 if not child_id.isdigit():
00135 raise rpki.exceptions.BadContactURL, "Bad path: %s" % path
00136 child = rpki.left_right.child_elt.sql_fetch(self, long(child_id))
00137 if child is None:
00138 raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_id
00139 child.serve_up_down(query, done)
00140 except (rpki.async.ExitNow, SystemExit):
00141 raise
00142 except Exception, data:
00143 rpki.log.error(traceback.format_exc())
00144 cb(400, "Could not process PDU: %s" % data)
00145
00146 def cronjob_handler(self, query, path, cb):
00147 """
00148 Periodic tasks. This is somewhat obsolete now that we have
00149 internal timers, but the test framework still uses this, and I
00150 haven't yet refactored this code to use the new timers.
00151 """
00152
00153 rpki.log.trace()
00154 self.sql.ping()
00155
00156 def loop(iterator, s):
00157
00158 def one():
00159 rpki.log.debug("Self %s polling parents" % s.self_id)
00160 s.client_poll(two)
00161
00162 def two():
00163 rpki.log.debug("Self %s updating children" % s.self_id)
00164 s.update_children(three)
00165
00166 def three():
00167 rpki.log.debug("Self %s updating ROAs" % s.self_id)
00168 s.update_roas(four)
00169
00170 def four():
00171 rpki.log.debug("Self %s regenerating CRLs and manifests" % s.self_id)
00172 s.regenerate_crls_and_manifests(iterator)
00173
00174 one()
00175
00176 def done():
00177 self.sql.sweep()
00178 cb(200, "OK")
00179
00180 rpki.async.iterator(rpki.left_right.self_elt.sql_fetch_all(self), loop, done)
00181
00182
00183
00184 https_ta_cache = None
00185
00186 def clear_https_ta_cache(self):
00187 """
00188 Clear dynamic TLS trust anchors.
00189 """
00190
00191 if self.https_ta_cache is not None:
00192 rpki.log.debug("Clearing HTTPS trusted cert cache")
00193 self.https_ta_cache = None
00194
00195 def build_https_ta_cache(self):
00196 """
00197 Build dynamic TLS trust anchors.
00198 """
00199
00200 if self.https_ta_cache is None:
00201
00202 selves = rpki.left_right.self_elt.sql_fetch_all(self)
00203 children = rpki.left_right.child_elt.sql_fetch_all(self)
00204
00205 self.https_ta_cache = rpki.https.build_https_ta_cache(
00206 [c.bpki_cert for c in children if c.bpki_cert is not None] +
00207 [c.bpki_glue for c in children if c.bpki_glue is not None] +
00208 [s.bpki_cert for s in selves if s.bpki_cert is not None] +
00209 [s.bpki_glue for s in selves if s.bpki_glue is not None] +
00210 [self.irbe_cert, self.irdb_cert, self.bpki_ta])
00211
00212 return self.https_ta_cache
00213
00214
00215 class ca_obj(rpki.sql.sql_persistent):
00216 """
00217 Internal CA object.
00218 """
00219
00220 sql_template = rpki.sql.template(
00221 "ca",
00222 "ca_id",
00223 "last_crl_sn",
00224 ("next_crl_update", rpki.sundial.datetime),
00225 "last_issued_sn", "last_manifest_sn",
00226 ("next_manifest_update", rpki.sundial.datetime),
00227 "sia_uri", "parent_id", "parent_resource_class")
00228
00229 last_crl_sn = 0
00230 last_issued_sn = 0
00231 last_manifest_sn = 0
00232
00233 def parent(self):
00234 """Fetch parent object to which this CA object links."""
00235 return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
00236
00237 def ca_details(self):
00238 """Fetch all ca_detail objects that link to this CA object."""
00239 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
00240
00241 def fetch_pending(self):
00242 """Fetch the pending ca_details for this CA, if any."""
00243 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,))
00244
00245 def fetch_active(self):
00246 """Fetch the active ca_detail for this CA, if any."""
00247 return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,))
00248
00249 def fetch_deprecated(self):
00250 """Fetch deprecated ca_details for this CA, if any."""
00251 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,))
00252
00253 def fetch_revoked(self):
00254 """Fetch revoked ca_details for this CA, if any."""
00255 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,))
00256
00257 def construct_sia_uri(self, parent, rc):
00258 """
00259 Construct the sia_uri value for this CA given configured
00260 information and the parent's up-down protocol list_response PDU.
00261 """
00262
00263 sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync()
00264 if not sia_uri or not sia_uri.startswith(parent.sia_base):
00265 sia_uri = parent.sia_base
00266 elif not sia_uri.endswith("/"):
00267 raise rpki.exceptions.BadURISyntax, "SIA URI must end with a slash: %s" % sia_uri
00268 return sia_uri + str(self.ca_id) + "/"
00269
00270 def check_for_updates(self, parent, rc, cb, eb):
00271 """
00272 Parent has signaled continued existance of a resource class we
00273 already knew about, so we need to check for an updated
00274 certificate, changes in resource coverage, revocation and reissue
00275 with the same key, etc.
00276 """
00277
00278 sia_uri = self.construct_sia_uri(parent, rc)
00279 sia_uri_changed = self.sia_uri != sia_uri
00280 if sia_uri_changed:
00281 self.sia_uri = sia_uri
00282 self.sql_mark_dirty()
00283
00284 rc_resources = rc.to_resource_bag()
00285 cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs)
00286
00287 def loop(iterator, ca_detail):
00288
00289 ski = ca_detail.latest_ca_cert.get_SKI()
00290
00291 if ski not in cert_map:
00292 rpki.log.warn("Certificate in database missing from list_response, class %s, SKI %s, maybe parent certificate went away?"
00293 % (repr(rc.class_name), ca_detail.latest_ca_cert.gSKI()))
00294 ca_detail.delete(self, parent.repository(), iterator, eb)
00295 return
00296
00297 def cleanup():
00298 del cert_map[ski]
00299 iterator()
00300
00301 if ca_detail.state in ("pending", "active"):
00302 current_resources = ca_detail.latest_ca_cert.get_3779resources()
00303 if (sia_uri_changed or
00304 ca_detail.latest_ca_cert != cert_map[ski].cert or
00305 current_resources.undersized(rc_resources) or
00306 current_resources.oversized(rc_resources)):
00307 ca_detail.update(
00308 parent = parent,
00309 ca = self,
00310 rc = rc,
00311 sia_uri_changed = sia_uri_changed,
00312 old_resources = current_resources,
00313 callback = cleanup,
00314 errback = eb)
00315 return
00316
00317 cleanup()
00318
00319 def done():
00320 if cert_map:
00321 rpki.log.warn("Certificates in list_response missing from our database, class %s, SKIs %s"
00322 % (repr(rc.class_name), ", ".join(c.cert.gSKI() for c in cert_map.values())))
00323 cb()
00324
00325 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)
00326
00327 @classmethod
00328 def create(cls, parent, rc, cb, eb):
00329 """
00330 Parent has signaled existance of a new resource class, so we need
00331 to create and set up a corresponding CA object.
00332 """
00333
00334 self = cls()
00335 self.gctx = parent.gctx
00336 self.parent_id = parent.parent_id
00337 self.parent_resource_class = rc.class_name
00338 self.sql_store()
00339 self.sia_uri = self.construct_sia_uri(parent, rc)
00340 ca_detail = ca_detail_obj.create(self)
00341
00342 def done(issue_response):
00343 ca_detail.activate(
00344 ca = self,
00345 cert = issue_response.payload.classes[0].certs[0].cert,
00346 uri = issue_response.payload.classes[0].certs[0].cert_url,
00347 callback = cb,
00348 errback = eb)
00349
00350 rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb)
00351
00352 def delete(self, parent, callback):
00353 """
00354 The list of current resource classes received from parent does not
00355 include the class corresponding to this CA, so we need to delete
00356 it (and its little dog too...).
00357
00358 All certs published by this CA are now invalid, so need to
00359 withdraw them, the CRL, and the manifest from the repository,
00360 delete all child_cert and ca_detail records associated with this
00361 CA, then finally delete this CA itself.
00362 """
00363
00364 def fail(e):
00365 rpki.log.error(traceback.format_exc())
00366 rpki.log.warn("Could not delete CA %r, skipping: %s" % (self, e))
00367 callback()
00368
00369 def done():
00370 self.sql_delete()
00371 callback()
00372
00373 repository = parent.repository()
00374
00375 def loop(iterator, ca_detail):
00376 ca_detail.delete(self, repository, iterator, fail)
00377
00378 rpki.async.iterator(self.ca_details(), loop, done)
00379
00380 def next_serial_number(self):
00381 """
00382 Allocate a certificate serial number.
00383 """
00384 self.last_issued_sn += 1
00385 self.sql_mark_dirty()
00386 return self.last_issued_sn
00387
00388 def next_manifest_number(self):
00389 """
00390 Allocate a manifest serial number.
00391 """
00392 self.last_manifest_sn += 1
00393 self.sql_mark_dirty()
00394 return self.last_manifest_sn
00395
00396 def next_crl_number(self):
00397 """
00398 Allocate a CRL serial number.
00399 """
00400 self.last_crl_sn += 1
00401 self.sql_mark_dirty()
00402 return self.last_crl_sn
00403
00404 def rekey(self, cb, eb):
00405 """
00406 Initiate a rekey operation for this ca. Generate a new keypair.
00407 Request cert from parent using new keypair. Mark result as our
00408 active ca_detail. Reissue all child certs issued by this ca using
00409 the new ca_detail.
00410 """
00411
00412 rpki.log.trace()
00413
00414 parent = self.parent()
00415 old_detail = self.fetch_active()
00416 new_detail = ca_detail_obj.create(self)
00417
00418 def done(issue_response):
00419 new_detail.activate(
00420 ca = self,
00421 cert = issue_response.payload.classes[0].certs[0].cert,
00422 uri = issue_response.payload.classes[0].certs[0].cert_url,
00423 predecessor = old_detail,
00424 callback = cb,
00425 errback = eb)
00426
00427 rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb)
00428
00429 def revoke(self, cb, eb):
00430 """
00431 Revoke deprecated ca_detail objects associated with this ca.
00432 """
00433
00434 rpki.log.trace()
00435
00436 def loop(iterator, ca_detail):
00437 ca_detail.revoke(iterator, eb)
00438
00439 rpki.async.iterator(self.fetch_deprecated(), loop, cb)
00440
00441 class ca_detail_obj(rpki.sql.sql_persistent):
00442 """
00443 Internal CA detail object.
00444 """
00445
00446 sql_template = rpki.sql.template(
00447 "ca_detail",
00448 "ca_detail_id",
00449 ("private_key_id", rpki.x509.RSA),
00450 ("public_key", rpki.x509.RSApublic),
00451 ("latest_ca_cert", rpki.x509.X509),
00452 ("manifest_private_key_id", rpki.x509.RSA),
00453 ("manifest_public_key", rpki.x509.RSApublic),
00454 ("latest_manifest_cert", rpki.x509.X509),
00455 ("latest_manifest", rpki.x509.SignedManifest),
00456 ("latest_crl", rpki.x509.CRL),
00457 "state",
00458 "ca_cert_uri",
00459 "ca_id")
00460
00461 def sql_decode(self, vals):
00462 """
00463 Extra assertions for SQL decode of a ca_detail_obj.
00464 """
00465 rpki.sql.sql_persistent.sql_decode(self, vals)
00466 assert (self.public_key is None and self.private_key_id is None) or \
00467 self.public_key.get_DER() == self.private_key_id.get_public_DER()
00468 assert (self.manifest_public_key is None and self.manifest_private_key_id is None) or \
00469 self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
00470
00471 def ca(self):
00472 """Fetch CA object to which this ca_detail links."""
00473 return ca_obj.sql_fetch(self.gctx, self.ca_id)
00474
00475 def child_certs(self, child = None, ski = None, unique = False):
00476 """Fetch all child_cert objects that link to this ca_detail."""
00477 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique)
00478
00479 def revoked_certs(self):
00480 """Fetch all revoked_cert objects that link to this ca_detail."""
00481 return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00482
00483 def route_origins(self):
00484 """Fetch all route_origin objects that link to this ca_detail."""
00485 return rpki.left_right.route_origin_elt.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00486
00487 def crl_uri(self, ca):
00488 """Return publication URI for this ca_detail's CRL."""
00489 return ca.sia_uri + self.crl_uri_tail()
00490
00491 def crl_uri_tail(self):
00492 """Return tail (filename portion) of publication URI for this ca_detail's CRL."""
00493 return self.public_key.gSKI() + ".crl"
00494
00495 def manifest_uri(self, ca):
00496 """Return publication URI for this ca_detail's manifest."""
00497 return ca.sia_uri + self.public_key.gSKI() + ".mnf"
00498
00499 def activate(self, ca, cert, uri, callback, errback, predecessor = None):
00500 """
00501 Activate this ca_detail.
00502 """
00503
00504 self.latest_ca_cert = cert
00505 self.ca_cert_uri = uri.rsync()
00506 self.generate_manifest_cert(ca)
00507
00508 def did_crl():
00509 self.generate_manifest(callback = did_manifest, errback = errback)
00510
00511 def did_manifest():
00512 self.state = "active"
00513 self.sql_mark_dirty()
00514 if predecessor is None:
00515 callback()
00516 else:
00517 predecessor.state = "deprecated"
00518 predecessor.sql_mark_dirty()
00519 rpki.async.iterator(predecessor.child_certs(), do_one_child_cert, done_child_certs)
00520
00521 def do_one_child_cert(iterator, child_cert):
00522 child_cert.reissue(self, iterator.ignore, errback)
00523
00524 def done_child_certs():
00525 rpki.async.iterator(predecessor.route_origins(), do_one_route_origin, callback)
00526
00527 def do_one_route_origin(iterator, route_origin):
00528 route_origin.regenerate_roa(iterator, errback)
00529
00530 self.generate_crl(callback = did_crl, errback = errback)
00531
00532 def delete(self, ca, repository, cb, eb):
00533 """
00534 Delete this ca_detail and all of the certs it issued.
00535 """
00536
00537 def withdraw_one_child(iterator, child_cert):
00538 repository.withdraw(child_cert.cert, child_cert.uri(ca), iterator, eb)
00539
00540 def child_certs_done():
00541 rpki.async.iterator(self.route_origins(), withdraw_one_roa, withdraw_manifest)
00542
00543 def withdraw_one_roa(iterator, route_origin):
00544 route_origin.withdraw_roa(iterator)
00545
00546 def withdraw_manifest():
00547 repository.withdraw(self.latest_manifest, self.manifest_uri(ca), withdraw_crl, eb)
00548
00549 def withdraw_crl():
00550 repository.withdraw(self.latest_crl, self.crl_uri(ca), done, eb)
00551
00552 def done():
00553 for cert in self.child_certs() + self.revoked_certs():
00554 cert.sql_delete()
00555 self.sql_delete()
00556 cb()
00557
00558 rpki.async.iterator(self.child_certs(), withdraw_one_child, child_certs_done)
00559
00560 def revoke(self, cb, eb):
00561 """
00562 Request revocation of all certificates whose SKI matches the key
00563 for this ca_detail.
00564
00565 Tasks:
00566
00567 - Request revocation of old keypair by parent.
00568
00569 - Revoke all child certs issued by the old keypair.
00570
00571 - Generate a final CRL, signed with the old keypair, listing all
00572 the revoked certs, with a next CRL time after the last cert or
00573 CRL signed by the old keypair will have expired.
00574
00575 - Generate a corresponding final manifest.
00576
00577 - Destroy old keypairs.
00578
00579 - Leave final CRL and manifest in place until their nextupdate
00580 time has passed.
00581 """
00582
00583 def parent_revoked(r_msg):
00584
00585 if r_msg.payload.ski != self.latest_ca_cert.gSKI():
00586 raise rpki.exceptions.SKIMismatch
00587
00588 ca = self.ca()
00589 parent = ca.parent()
00590 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00591
00592 self.nextUpdate = rpki.sundial.now()
00593
00594 if self.latest_manifest is not None:
00595 self.nextUpdate = self.nextUpdate.later(self.latest_manifest.getNextUpdate())
00596
00597 if self.latest_crl is not None:
00598 self.nextUpdate = self.nextUpdate.later(self.latest_crl.getNextUpdate())
00599
00600 def revoke_one_child(iterator, child_cert):
00601 self.nextUpdate = self.nextUpdate.later(child_cert.cert.getNotAfter())
00602 child_cert.revoke(iterator, eb)
00603
00604 def final_crl():
00605 self.nextUpdate += crl_interval
00606 self.generate_crl(callback = final_manifest, errback = eb, nextUpdate = self.nextUpdate)
00607
00608 def final_manifest():
00609 self.generate_manifest(callback = done, errback = eb, nextUpdate = self.nextUpdate)
00610
00611 def done():
00612 self.private_key_id = None
00613 self.manifest_private_key_id = None
00614 self.manifest_public_key = None
00615 self.latest_manifest_cert = None
00616 self.state = "revoked"
00617 self.sql_mark_dirty()
00618 cb()
00619
00620 rpki.async.iterator(self.child_certs(), revoke_one_child, final_crl)
00621
00622 rpki.up_down.revoke_pdu.query(self, parent_revoked, eb)
00623
00624 def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback):
00625 """
00626 Need to get a new certificate for this ca_detail and perhaps frob
00627 children of this ca_detail.
00628 """
00629
00630 def issued(issue_response):
00631 self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert
00632 new_resources = self.latest_ca_cert.get_3779resources()
00633
00634 def loop(iterator, child_cert):
00635 child_resources = child_cert.cert.get_3779resources()
00636 if sia_uri_changed or child_resources.oversized(new_resources):
00637 child_cert.reissue(
00638 ca_detail = self,
00639 resources = child_resources.intersection(new_resources),
00640 callback = iterator.ignore,
00641 errback = errback)
00642 else:
00643 iterator()
00644
00645 if sia_uri_changed or old_resources.oversized(new_resources):
00646 rpki.async.iterator(self.child_certs(), loop, callback)
00647 else:
00648 callback()
00649
00650 rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback)
00651
00652 @classmethod
00653 def create(cls, ca):
00654 """
00655 Create a new ca_detail object for a specified CA.
00656 """
00657 self = cls()
00658 self.gctx = ca.gctx
00659 self.ca_id = ca.ca_id
00660 self.state = "pending"
00661
00662 self.private_key_id = rpki.x509.RSA.generate()
00663 self.public_key = self.private_key_id.get_RSApublic()
00664
00665 self.manifest_private_key_id = rpki.x509.RSA.generate()
00666 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00667
00668 self.sql_store()
00669 return self
00670
00671 def issue_ee(self, ca, resources, subject_key, sia = None):
00672 """
00673 Issue a new EE certificate.
00674 """
00675
00676 return self.latest_ca_cert.issue(
00677 keypair = self.private_key_id,
00678 subject_key = subject_key,
00679 serial = ca.next_serial_number(),
00680 sia = sia,
00681 aia = self.ca_cert_uri,
00682 crldp = self.crl_uri(ca),
00683 resources = resources,
00684 notAfter = self.latest_ca_cert.getNotAfter(),
00685 is_ca = False)
00686
00687
00688 def generate_manifest_cert(self, ca):
00689 """
00690 Generate a new manifest certificate for this ca_detail.
00691 """
00692
00693 resources = rpki.resource_set.resource_bag(
00694 asn = rpki.resource_set.resource_set_as("<inherit>"),
00695 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"),
00696 v6 = rpki.resource_set.resource_set_ipv6("<inherit>"))
00697
00698 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00699
00700 def issue(self, ca, child, subject_key, sia, resources, callback, errback, child_cert = None):
00701 """
00702 Issue a new certificate to a child. Optional child_cert argument
00703 specifies an existing child_cert object to update in place; if not
00704 specified, we create a new one. Returns the child_cert object
00705 containing the newly issued cert.
00706 """
00707
00708 assert child_cert is None or (child_cert.child_id == child.child_id and
00709 child_cert.ca_detail_id == self.ca_detail_id)
00710
00711 cert = self.latest_ca_cert.issue(
00712 keypair = self.private_key_id,
00713 subject_key = subject_key,
00714 serial = ca.next_serial_number(),
00715 aia = self.ca_cert_uri,
00716 crldp = self.crl_uri(ca),
00717 sia = sia,
00718 resources = resources,
00719 notAfter = resources.valid_until)
00720
00721 if child_cert is None:
00722 child_cert = rpki.rpki_engine.child_cert_obj(
00723 gctx = child.gctx,
00724 child_id = child.child_id,
00725 ca_detail_id = self.ca_detail_id,
00726 cert = cert)
00727 rpki.log.debug("Created new child_cert %s" % repr(child_cert))
00728 else:
00729 child_cert.cert = cert
00730 rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert))
00731
00732 child_cert.ski = cert.get_SKI()
00733
00734 child_cert.sql_store()
00735
00736 def published():
00737 self.generate_manifest(done, errback)
00738
00739 def done():
00740 callback(child_cert)
00741
00742 ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca), published, errback)
00743
00744 def generate_crl(self, callback, errback, nextUpdate = None):
00745 """
00746 Generate a new CRL for this ca_detail. At the moment this is
00747 unconditional, that is, it is up to the caller to decide whether a
00748 new CRL is needed.
00749 """
00750
00751 ca = self.ca()
00752 parent = ca.parent()
00753 repository = parent.repository()
00754 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00755 now = rpki.sundial.now()
00756
00757 if nextUpdate is None:
00758 nextUpdate = now + crl_interval
00759
00760 certlist = []
00761 for revoked_cert in self.revoked_certs():
00762 if now > revoked_cert.expires + crl_interval:
00763 revoked_cert.sql_delete()
00764 else:
00765 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00766 certlist.sort()
00767
00768 self.latest_crl = rpki.x509.CRL.generate(
00769 keypair = self.private_key_id,
00770 issuer = self.latest_ca_cert,
00771 serial = ca.next_crl_number(),
00772 thisUpdate = now,
00773 nextUpdate = nextUpdate,
00774 revokedCertificates = certlist)
00775
00776 repository.publish(self.latest_crl, self.crl_uri(ca), callback = callback, errback = errback)
00777
00778 def generate_manifest(self, callback, errback, nextUpdate = None):
00779 """
00780 Generate a new manifest for this ca_detail.
00781 """
00782
00783 ca = self.ca()
00784 parent = ca.parent()
00785 repository = parent.repository()
00786 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00787 now = rpki.sundial.now()
00788
00789 if nextUpdate is None:
00790 nextUpdate = now + crl_interval
00791
00792 route_origins = [r for r in self.route_origins() if r.cert is not None and r.roa is not None]
00793
00794 if self.latest_manifest_cert is None or self.latest_manifest_cert.getNotAfter() < nextUpdate:
00795 self.generate_manifest_cert(ca)
00796
00797 certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \
00798 [(r.roa_uri_tail(), r.roa) for r in route_origins] + \
00799 [(r.ee_uri_tail(), r.cert) for r in route_origins] + \
00800 [(self.crl_uri_tail(), self.latest_crl)]
00801
00802 self.latest_manifest = rpki.x509.SignedManifest.build(
00803 serial = ca.next_manifest_number(),
00804 thisUpdate = now,
00805 nextUpdate = nextUpdate,
00806 names_and_objs = certs,
00807 keypair = self.manifest_private_key_id,
00808 certs = self.latest_manifest_cert)
00809
00810 repository.publish(self.latest_manifest, self.manifest_uri(ca), callback = callback, errback = errback)
00811
00812 class child_cert_obj(rpki.sql.sql_persistent):
00813 """
00814 Certificate that has been issued to a child.
00815 """
00816
00817 sql_template = rpki.sql.template(
00818 "child_cert",
00819 "child_cert_id",
00820 ("cert", rpki.x509.X509),
00821 "child_id",
00822 "ca_detail_id",
00823 "ski")
00824
00825 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00826 """
00827 Initialize a child_cert_obj.
00828 """
00829 rpki.sql.sql_persistent.__init__(self)
00830 self.gctx = gctx
00831 self.child_id = child_id
00832 self.ca_detail_id = ca_detail_id
00833 self.cert = cert
00834 if child_id or ca_detail_id or cert:
00835 self.sql_mark_dirty()
00836
00837 def child(self):
00838 """Fetch child object to which this child_cert object links."""
00839 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00840
00841 def ca_detail(self):
00842 """Fetch ca_detail object to which this child_cert object links."""
00843 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00844
00845 def uri_tail(self):
00846 """Return the tail (filename) portion of the URI for this child_cert."""
00847 return self.cert.gSKI() + ".cer"
00848
00849 def uri(self, ca):
00850 """Return the publication URI for this child_cert."""
00851 return ca.sia_uri + self.uri_tail()
00852
00853 def revoke(self, callback, errback, withdraw = True):
00854 """
00855 Revoke a child cert.
00856 """
00857
00858 rpki.log.debug("Revoking %s" % repr(self))
00859 ca_detail = self.ca_detail()
00860 ca = ca_detail.ca()
00861 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00862
00863 def done():
00864 self.gctx.sql.sweep()
00865 self.sql_delete()
00866 callback()
00867
00868 if withdraw:
00869 ca.parent().repository().withdraw(self.cert, self.uri(ca), done, errback)
00870 else:
00871 rpki.log.info("Suppressing withdrawal of %r" % self.cert)
00872 done()
00873
00874 def reissue(self, ca_detail, callback = None, errback = None, resources = None, sia = None):
00875 """
00876 Reissue an existing cert, reusing the public key. If the cert we
00877 would generate is identical to the one we already have, we just
00878 return the one we already have. If we have to revoke the old
00879 certificate when generating the new one, we have to generate a new
00880 child_cert_obj, so calling code that needs the updated
00881 child_cert_obj must use the return value from this method.
00882 """
00883
00884
00885 assert callback is not None
00886 assert errback is not None
00887
00888 ca = ca_detail.ca()
00889 child = self.child()
00890
00891 old_resources = self.cert.get_3779resources()
00892 old_sia = self.cert.get_SIA()
00893 old_ca_detail = self.ca_detail()
00894
00895 if resources is None:
00896 resources = old_resources
00897
00898 if sia is None:
00899 sia = old_sia
00900
00901 assert resources.valid_until is not None and old_resources.valid_until is not None
00902
00903 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00904 return callback(self)
00905
00906 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
00907 new_issuer = ca_detail != old_ca_detail
00908
00909 rpki.log.debug("Reissuing %r, must_revoke %s, new_issuer %s" % (self, must_revoke, new_issuer))
00910
00911 if resources.valid_until != old_resources.valid_until:
00912 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
00913
00914 def revoke(child_cert):
00915
00916 uri = child_cert.uri(ca)
00917 rpki.log.debug("New child_cert %r uri %s" % (child_cert, uri))
00918
00919 def loop(iterator, x):
00920 rpki.log.debug("Revoking child_cert %r" % x)
00921 x.revoke(iterator, errback, withdraw = x.uri(ca) != uri)
00922
00923 def manifest():
00924 ca_detail.generate_manifest(done, errback)
00925
00926 def done():
00927 callback(child_cert)
00928
00929 certs_to_revoke = [x for x in child.child_certs(ca_detail = ca_detail, ski = self.ski) if x is not child_cert]
00930
00931 if certs_to_revoke:
00932 rpki.async.iterator(certs_to_revoke, loop, manifest)
00933 else:
00934 done()
00935
00936 ca_detail.issue(
00937 ca = ca,
00938 child = child,
00939 subject_key = self.cert.getPublicKey(),
00940 sia = sia,
00941 resources = resources,
00942 child_cert = None if must_revoke or new_issuer else self,
00943 callback = revoke if must_revoke else callback,
00944 errback = errback)
00945
00946 @classmethod
00947 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
00948 """
00949 Fetch all child_cert objects matching a particular set of
00950 parameters. This is a wrapper to consolidate various queries that
00951 would otherwise be inline SQL WHERE expressions. In most cases
00952 code calls this indirectly, through methods in other classes.
00953 """
00954
00955 args = []
00956 where = []
00957
00958 if child:
00959 where.append("child_id = %s")
00960 args.append(child.child_id)
00961
00962 if ca_detail:
00963 where.append("ca_detail_id = %s")
00964 args.append(ca_detail.ca_detail_id)
00965
00966 if ski:
00967 where.append("ski = %s")
00968 args.append(ski)
00969
00970 where = " AND ".join(where)
00971
00972 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
00973
00974 if unique:
00975 return cls.sql_fetch_where1(gctx, where, args)
00976 else:
00977 return cls.sql_fetch_where(gctx, where, args)
00978
00979 class revoked_cert_obj(rpki.sql.sql_persistent):
00980 """
00981 Tombstone for a revoked certificate.
00982 """
00983
00984 sql_template = rpki.sql.template(
00985 "revoked_cert",
00986 "revoked_cert_id",
00987 "serial",
00988 "ca_detail_id",
00989 ("revoked", rpki.sundial.datetime),
00990 ("expires", rpki.sundial.datetime))
00991
00992 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
00993 """Initialize a revoked_cert_obj."""
00994 rpki.sql.sql_persistent.__init__(self)
00995 self.gctx = gctx
00996 self.serial = serial
00997 self.revoked = revoked
00998 self.expires = expires
00999 self.ca_detail_id = ca_detail_id
01000 if serial or revoked or expires or ca_detail_id:
01001 self.sql_mark_dirty()
01002
01003 def ca_detail(self):
01004 """Fetch ca_detail object to which this revoked_cert_obj links."""
01005 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
01006
01007 @classmethod
01008 def revoke(cls, cert, ca_detail):
01009 """
01010 Revoke a certificate.
01011 """
01012 return cls(
01013 serial = cert.getSerial(),
01014 expires = cert.getNotAfter(),
01015 revoked = rpki.sundial.now(),
01016 gctx = ca_detail.gctx,
01017 ca_detail_id = ca_detail.ca_detail_id)