00001 """Global context for rpkid.
00002
00003 $Id: rpki_engine.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 traceback, os, time, getopt, sys, MySQLdb, lxml.etree
00021 import rpki.resource_set, rpki.up_down, rpki.left_right, rpki.x509, rpki.sql
00022 import rpki.https, rpki.config, rpki.exceptions, rpki.relaxng, rpki.log
00023
00024 class rpkid_context(object):
00025 """A container for various global rpkid parameters."""
00026
00027 def __init__(self, cfg):
00028
00029 self.sql = rpki.sql.session(cfg)
00030
00031 self.bpki_ta = rpki.x509.X509(Auto_file = cfg.get("bpki-ta"))
00032 self.irdb_cert = rpki.x509.X509(Auto_file = cfg.get("irdb-cert"))
00033 self.irbe_cert = rpki.x509.X509(Auto_file = cfg.get("irbe-cert"))
00034 self.rpkid_cert = rpki.x509.X509(Auto_file = cfg.get("rpkid-cert"))
00035 self.rpkid_key = rpki.x509.RSA( Auto_file = cfg.get("rpkid-key"))
00036
00037 self.irdb_url = cfg.get("irdb-url")
00038
00039 self.https_server_host = cfg.get("server-host", "")
00040 self.https_server_port = int(cfg.get("server-port", "4433"))
00041
00042 self.publication_kludge_base = cfg.get("publication-kludge-base", "publication/")
00043
00044 def irdb_query(self, self_id, child_id = None):
00045 """Perform an IRDB callback query. In the long run this should not
00046 be a blocking routine, it should instead issue a query and set up a
00047 handler to receive the response. For the moment, though, we are
00048 doing simple lock step and damn the torpedos. Not yet doing
00049 anything useful with subject name. Most likely this function should
00050 really be wrapped up in a class that carries both the query result
00051 and also the intermediate state needed for the event-driven code
00052 that this function will need to become.
00053 """
00054
00055 rpki.log.trace()
00056
00057 q_msg = rpki.left_right.msg()
00058 q_msg.type = "query"
00059 q_msg.append(rpki.left_right.list_resources_elt())
00060 q_msg[0].self_id = self_id
00061 q_msg[0].child_id = child_id
00062 q_cms = rpki.left_right.cms_msg.wrap(q_msg, self.rpkid_key, self.rpkid_cert)
00063 der = rpki.https.client(
00064 server_ta = (self.bpki_ta, self.irdb_cert),
00065 client_key = self.rpkid_key,
00066 client_cert = self.rpkid_cert,
00067 url = self.irdb_url,
00068 msg = q_cms)
00069 r_msg = rpki.left_right.cms_msg.unwrap(der, (self.bpki_ta, self.irdb_cert))
00070 if len(r_msg) == 0 or not isinstance(r_msg[0], rpki.left_right.list_resources_elt) or r_msg.type != "reply":
00071 raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")
00072 return rpki.resource_set.resource_bag(
00073 asn = r_msg[0].asn,
00074 v4 = r_msg[0].ipv4,
00075 v6 = r_msg[0].ipv6,
00076 valid_until = r_msg[0].valid_until)
00077
00078 def left_right_handler(self, query, path):
00079 """Process one left-right PDU."""
00080 rpki.log.trace()
00081 try:
00082 self.sql.ping()
00083 q_msg = rpki.left_right.cms_msg.unwrap(query, (self.bpki_ta, self.irbe_cert))
00084 if q_msg.type != "query":
00085 raise rpki.exceptions.BadQuery, "Message type is not query"
00086 r_msg = q_msg.serve_top_level(self)
00087 reply = rpki.left_right.cms_msg.wrap(r_msg, self.rpkid_key, self.rpkid_cert)
00088 self.sql.sweep()
00089 return 200, reply
00090 except Exception, data:
00091 rpki.log.error(traceback.format_exc())
00092 return 500, "Unhandled exception %s" % data
00093
00094 def up_down_handler(self, query, path):
00095 """Process one up-down PDU."""
00096 rpki.log.trace()
00097 try:
00098 self.sql.ping()
00099 child_id = path.partition("/up-down/")[2]
00100 if not child_id.isdigit():
00101 raise rpki.exceptions.BadContactURL, "Bad path: %s" % path
00102 child = rpki.left_right.child_elt.sql_fetch(self, long(child_id))
00103 if child is None:
00104 raise rpki.exceptions.ChildNotFound, "Could not find child %s" % child_id
00105 reply = child.serve_up_down(query)
00106 self.sql.sweep()
00107 return 200, reply
00108 except Exception, data:
00109 rpki.log.error(traceback.format_exc())
00110 return 400, "Could not process PDU: %s" % data
00111
00112 def cronjob_handler(self, query, path):
00113 """Periodic tasks. As simple as possible for now, may need to break
00114 this up into separate handlers later.
00115 """
00116
00117 rpki.log.trace()
00118 try:
00119 self.sql.ping()
00120 for s in rpki.left_right.self_elt.sql_fetch_all(self):
00121 s.client_poll()
00122 s.update_children()
00123 s.update_roas()
00124 s.regenerate_crls_and_manifests()
00125 self.sql.sweep()
00126 return 200, "OK"
00127 except Exception, data:
00128 rpki.log.error(traceback.format_exc())
00129 return 500, "Unhandled exception %s" % data
00130
00131
00132
00133 https_ta_cache = None
00134
00135 def clear_https_ta_cache(self):
00136 """Clear dynamic TLS trust anchors."""
00137
00138 if self.https_ta_cache is not None:
00139 rpki.log.debug("Clearing HTTPS trusted cert cache")
00140 self.https_ta_cache = None
00141
00142 def build_https_ta_cache(self):
00143 """Build dynamic TLS trust anchors."""
00144
00145 if self.https_ta_cache is None:
00146
00147 selves = rpki.left_right.self_elt.sql_fetch_all(self)
00148 children = rpki.left_right.child_elt.sql_fetch_all(self)
00149
00150 self.https_ta_cache = rpki.https.build_https_ta_cache(
00151 [c.bpki_cert for c in children if c.bpki_cert is not None] +
00152 [c.bpki_glue for c in children if c.bpki_glue is not None] +
00153 [s.bpki_cert for s in selves if s.bpki_cert is not None] +
00154 [s.bpki_glue for s in selves if s.bpki_glue is not None] +
00155 [self.irbe_cert, self.irdb_cert, self.bpki_ta])
00156
00157 return self.https_ta_cache
00158
00159
00160 class ca_obj(rpki.sql.sql_persistant):
00161 """Internal CA object."""
00162
00163 sql_template = rpki.sql.template(
00164 "ca",
00165 "ca_id",
00166 "last_crl_sn",
00167 ("next_crl_update", rpki.sundial.datetime),
00168 "last_issued_sn", "last_manifest_sn",
00169 ("next_manifest_update", rpki.sundial.datetime),
00170 "sia_uri", "parent_id", "parent_resource_class")
00171
00172 last_crl_sn = 0
00173 last_issued_sn = 0
00174 last_manifest_sn = 0
00175
00176 def parent(self):
00177 """Fetch parent object to which this CA object links."""
00178 return rpki.left_right.parent_elt.sql_fetch(self.gctx, self.parent_id)
00179
00180 def ca_details(self):
00181 """Fetch all ca_detail objects that link to this CA object."""
00182 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s", (self.ca_id,))
00183
00184 def fetch_pending(self):
00185 """Fetch the pending ca_details for this CA, if any."""
00186 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'pending'", (self.ca_id,))
00187
00188 def fetch_active(self):
00189 """Fetch the active ca_detail for this CA, if any."""
00190 return ca_detail_obj.sql_fetch_where1(self.gctx, "ca_id = %s AND state = 'active'", (self.ca_id,))
00191
00192 def fetch_deprecated(self):
00193 """Fetch deprecated ca_details for this CA, if any."""
00194 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'deprecated'", (self.ca_id,))
00195
00196 def fetch_revoked(self):
00197 """Fetch revoked ca_details for this CA, if any."""
00198 return ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND state = 'revoked'", (self.ca_id,))
00199
00200 def construct_sia_uri(self, parent, rc):
00201 """Construct the sia_uri value for this CA given configured
00202 information and the parent's up-down protocol list_response PDU.
00203 """
00204
00205 repository = parent.repository()
00206 sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync()
00207 if not sia_uri or not sia_uri.startswith(parent.sia_base):
00208 sia_uri = parent.sia_base
00209 elif not sia_uri.endswith("/"):
00210 raise rpki.exceptions.BadURISyntax, "SIA URI must end with a slash: %s" % sia_uri
00211 return sia_uri + str(self.ca_id) + "/"
00212
00213 def check_for_updates(self, parent, rc):
00214 """Parent has signaled continued existance of a resource class we
00215 already knew about, so we need to check for an updated
00216 certificate, changes in resource coverage, revocation and reissue
00217 with the same key, etc.
00218 """
00219
00220 sia_uri = self.construct_sia_uri(parent, rc)
00221 sia_uri_changed = self.sia_uri != sia_uri
00222 if sia_uri_changed:
00223 self.sia_uri = sia_uri
00224 self.sql_mark_dirty()
00225
00226 rc_resources = rc.to_resource_bag()
00227 cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs)
00228
00229 for ca_detail in ca_detail_obj.sql_fetch_where(self.gctx, "ca_id = %s AND latest_ca_cert IS NOT NULL AND state != 'revoked'", (self.ca_id,)):
00230 ski = ca_detail.latest_ca_cert.get_SKI()
00231 if ca_detail.state in ("pending", "active"):
00232 current_resources = ca_detail.latest_ca_cert.get_3779resources()
00233 if sia_uri_changed or \
00234 ca_detail.latest_ca_cert != cert_map[ski].cert or \
00235 current_resources.undersized(rc_resources) or \
00236 current_resources.oversized(rc_resources):
00237 ca_detail.update(
00238 parent = parent,
00239 ca = self,
00240 rc = rc,
00241 sia_uri_changed = sia_uri_changed,
00242 old_resources = current_resources)
00243 del cert_map[ski]
00244 assert not cert_map, "Certificates in list_response missing from our database, SKIs %s" % ", ".join(c.cert.hSKI() for c in cert_map.values())
00245
00246 @classmethod
00247 def create(cls, parent, rc):
00248 """Parent has signaled existance of a new resource class, so we
00249 need to create and set up a corresponding CA object.
00250 """
00251
00252 self = cls()
00253 self.gctx = parent.gctx
00254 self.parent_id = parent.parent_id
00255 self.parent_resource_class = rc.class_name
00256 self.sql_store()
00257 self.sia_uri = self.construct_sia_uri(parent, rc)
00258 ca_detail = ca_detail_obj.create(self)
00259
00260
00261 issue_response = rpki.up_down.issue_pdu.query(parent, self, ca_detail)
00262
00263 ca_detail.activate(
00264 ca = self,
00265 cert = issue_response.payload.classes[0].certs[0].cert,
00266 uri = issue_response.payload.classes[0].certs[0].cert_url)
00267
00268 def delete(self, parent):
00269 """The list of current resource classes received from parent does
00270 not include the class corresponding to this CA, so we need to
00271 delete it (and its little dog too...).
00272
00273 All certs published by this CA are now invalid, so need to
00274 withdraw them, the CRL, and the manifest from the repository,
00275 delete all child_cert and ca_detail records associated with this
00276 CA, then finally delete this CA itself.
00277 """
00278
00279 repository = parent.repository()
00280 for ca_detail in self.ca_details():
00281 ca_detail.delete(ca, repository)
00282 self.sql_delete()
00283
00284 def next_serial_number(self):
00285 """Allocate a certificate serial number."""
00286 self.last_issued_sn += 1
00287 self.sql_mark_dirty()
00288 return self.last_issued_sn
00289
00290 def next_manifest_number(self):
00291 """Allocate a manifest serial number."""
00292 self.last_manifest_sn += 1
00293 self.sql_mark_dirty()
00294 return self.last_manifest_sn
00295
00296 def next_crl_number(self):
00297 """Allocate a CRL serial number."""
00298 self.last_crl_sn += 1
00299 self.sql_mark_dirty()
00300 return self.last_crl_sn
00301
00302 def rekey(self):
00303 """Initiate a rekey operation for this ca. Generate a new
00304 keypair. Request cert from parent using new keypair. Mark result
00305 as our active ca_detail. Reissue all child certs issued by this
00306 ca using the new ca_detail.
00307 """
00308
00309 rpki.log.trace()
00310
00311 parent = self.parent()
00312 old_detail = self.fetch_active()
00313 new_detail = ca_detail_obj.create(self)
00314
00315
00316 issue_response = rpki.up_down.issue_pdu.query(parent, self, new_detail)
00317
00318 new_detail.activate(
00319 ca = self,
00320 cert = issue_response.payload.classes[0].certs[0].cert,
00321 uri = issue_response.payload.classes[0].certs[0].cert_url,
00322 predecessor = old_detail)
00323
00324 def revoke(self):
00325 """Revoke deprecated ca_detail objects associated with this ca."""
00326
00327 rpki.log.trace()
00328
00329 for ca_detail in self.fetch_deprecated():
00330 ca_detail.revoke()
00331
00332 class ca_detail_obj(rpki.sql.sql_persistant):
00333 """Internal CA detail object."""
00334
00335 sql_template = rpki.sql.template(
00336 "ca_detail",
00337 "ca_detail_id",
00338 ("private_key_id", rpki.x509.RSA),
00339 ("public_key", rpki.x509.RSApublic),
00340 ("latest_ca_cert", rpki.x509.X509),
00341 ("manifest_private_key_id", rpki.x509.RSA),
00342 ("manifest_public_key", rpki.x509.RSApublic),
00343 ("latest_manifest_cert", rpki.x509.X509),
00344 ("latest_manifest", rpki.x509.SignedManifest),
00345 ("latest_crl", rpki.x509.CRL),
00346 "state",
00347 "ca_cert_uri",
00348 "ca_id")
00349
00350 def sql_decode(self, vals):
00351 """Extra assertions for SQL decode of a ca_detail_obj."""
00352 rpki.sql.sql_persistant.sql_decode(self, vals)
00353 assert (self.public_key is None and self.private_key_id is None) or \
00354 self.public_key.get_DER() == self.private_key_id.get_public_DER()
00355 assert (self.manifest_public_key is None and self.manifest_private_key_id is None) or \
00356 self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
00357
00358 def ca(self):
00359 """Fetch CA object to which this ca_detail links."""
00360 return ca_obj.sql_fetch(self.gctx, self.ca_id)
00361
00362 def child_certs(self, child = None, ski = None, unique = False):
00363 """Fetch all child_cert objects that link to this ca_detail."""
00364 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique)
00365
00366 def revoked_certs(self):
00367 """Fetch all revoked_cert objects that link to this ca_detail."""
00368 return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00369
00370 def route_origins(self):
00371 """Fetch all route_origin objects that link to this ca_detail."""
00372 return rpki.left_right.route_origin_elt.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00373
00374 def crl_uri(self, ca):
00375 """Return publication URI for this ca_detail's CRL."""
00376 return ca.sia_uri + self.public_key.gSKI() + ".crl"
00377
00378 def manifest_uri(self, ca):
00379 """Return publication URI for this ca_detail's manifest."""
00380 return ca.sia_uri + self.public_key.gSKI() + ".mnf"
00381
00382 def activate(self, ca, cert, uri, predecessor = None):
00383 """Activate this ca_detail."""
00384
00385 self.latest_ca_cert = cert
00386 self.ca_cert_uri = uri.rsync()
00387 self.generate_manifest_cert(ca)
00388 self.generate_crl()
00389 self.generate_manifest()
00390 self.state = "active"
00391 self.sql_mark_dirty()
00392
00393 if predecessor is not None:
00394 predecessor.state = "deprecated"
00395 predecessor.sql_mark_dirty()
00396 for child_cert in predecessor.child_certs():
00397 child_cert.reissue(self)
00398 for route_origin in predecessor.route_origins():
00399 route_origin.regenerate_roa()
00400
00401 def delete(self, ca, repository):
00402 """Delete this ca_detail and all of the certs it issued."""
00403
00404 for child_cert in self.child_certs():
00405 repository.withdraw(child_cert.cert, child_cert.uri(ca))
00406 child_cert.sql_delete()
00407 for revoked__cert in self.revoked_certs():
00408 revoked_cert.sql_delete()
00409 for route_origin in self.route_origins():
00410 route_origin.withdraw_roa()
00411 repository.withdraw(self.latest_manifest, self.manifest_uri(ca))
00412 repository.withdraw(self.latest_crl, self.crl_uri())
00413 self.sql_delete()
00414
00415 def revoke(self):
00416 """Request revocation of all certificates whose SKI matches the key for this ca_detail.
00417
00418 Tasks:
00419
00420 - Request revocation of old keypair by parent.
00421
00422 - Revoke all child certs issued by the old keypair.
00423
00424 - Generate a final CRL, signed with the old keypair, listing all
00425 the revoked certs, with a next CRL time after the last cert or
00426 CRL signed by the old keypair will have expired.
00427
00428 - Destroy old keypair (and manifest keypair).
00429
00430 - Leave final CRL in place until its next CRL time has passed.
00431 """
00432
00433
00434 r_msg = rpki.up_down.revoke_pdu.query(self)
00435
00436 if r_msg.payload.ski != self.latest_ca_cert.gSKI():
00437 raise rpki.exceptions.SKIMismatch
00438
00439 ca = self.ca()
00440 parent = ca.parent()
00441 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00442
00443 nextUpdate = rpki.sundial.now()
00444
00445 if self.latest_manifest is not None:
00446 nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate())
00447
00448 if self.latest_crl is not None:
00449 nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate())
00450
00451 for child_cert in self.child_certs():
00452 nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter())
00453 child_cert.revoke()
00454
00455 nextUpdate += crl_interval
00456
00457 self.generate_crl(nextUpdate)
00458 self.generate_manifest(nextUpdate)
00459
00460 self.private_key_id = None
00461 self.manifest_private_key_id = None
00462 self.manifest_public_key = None
00463 self.latest_manifest_cert = None
00464 self.state = "revoked"
00465 self.sql_mark_dirty()
00466
00467 def update(self, parent, ca, rc, sia_uri_changed, old_resources):
00468 """Need to get a new certificate for this ca_detail and perhaps
00469 frob children of this ca_detail.
00470 """
00471
00472
00473 issue_response = rpki.up_down.issue_pdu.query(parent, ca, self)
00474
00475 self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert
00476 new_resources = self.latest_ca_cert.get_3779resources()
00477
00478 if sia_uri_changed or old_resources.oversized(new_resources):
00479 for child_cert in self.child_certs():
00480 child_resources = child_cert.cert.get_3779resources()
00481 if sia_uri_changed or child_resources.oversized(new_resources):
00482 child_cert.reissue(
00483 ca_detail = self,
00484 resources = child_resources.intersection(new_resources))
00485
00486 @classmethod
00487 def create(cls, ca):
00488 """Create a new ca_detail object for a specified CA."""
00489 self = cls()
00490 self.gctx = ca.gctx
00491 self.ca_id = ca.ca_id
00492 self.state = "pending"
00493
00494 self.private_key_id = rpki.x509.RSA()
00495 self.private_key_id.generate()
00496 self.public_key = self.private_key_id.get_RSApublic()
00497
00498 self.manifest_private_key_id = rpki.x509.RSA()
00499 self.manifest_private_key_id.generate()
00500 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00501
00502 self.sql_store()
00503 return self
00504
00505 def issue_ee(self, ca, resources, subject_key, sia = None):
00506 """Issue a new EE certificate."""
00507
00508 return self.latest_ca_cert.issue(
00509 keypair = self.private_key_id,
00510 subject_key = subject_key,
00511 serial = ca.next_serial_number(),
00512 sia = sia,
00513 aia = self.ca_cert_uri,
00514 crldp = self.crl_uri(ca),
00515 resources = resources,
00516 notAfter = self.latest_ca_cert.getNotAfter(),
00517 is_ca = False)
00518
00519
00520 def generate_manifest_cert(self, ca):
00521 """Generate a new manifest certificate for this ca_detail."""
00522
00523 resources = rpki.resource_set.resource_bag(
00524 asn = rpki.resource_set.resource_set_as("<inherit>"),
00525 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"),
00526 v6 = rpki.resource_set.resource_set_ipv6("<inherit>"))
00527
00528 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00529
00530 def issue(self, ca, child, subject_key, sia, resources, child_cert = None):
00531 """Issue a new certificate to a child. Optional child_cert
00532 argument specifies an existing child_cert object to update in
00533 place; if not specified, we create a new one. Returns the
00534 child_cert object containing the newly issued cert.
00535 """
00536
00537 assert child_cert is None or (child_cert.child_id == child.child_id and
00538 child_cert.ca_detail_id == self.ca_detail_id)
00539
00540 cert = self.latest_ca_cert.issue(
00541 keypair = self.private_key_id,
00542 subject_key = subject_key,
00543 serial = ca.next_serial_number(),
00544 aia = self.ca_cert_uri,
00545 crldp = self.crl_uri(ca),
00546 sia = sia,
00547 resources = resources,
00548 notAfter = resources.valid_until)
00549
00550 if child_cert is None:
00551 child_cert = rpki.rpki_engine.child_cert_obj(
00552 gctx = child.gctx,
00553 child_id = child.child_id,
00554 ca_detail_id = self.ca_detail_id,
00555 cert = cert)
00556 rpki.log.debug("Created new child_cert %s" % repr(child_cert))
00557 else:
00558 child_cert.cert = cert
00559 rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert))
00560
00561 child_cert.ski = cert.get_SKI()
00562
00563 child_cert.sql_store()
00564
00565 ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca))
00566
00567 self.generate_manifest()
00568
00569 return child_cert
00570
00571 def generate_crl(self, nextUpdate = None):
00572 """Generate a new CRL for this ca_detail. At the moment this is
00573 unconditional, that is, it is up to the caller to decide whether a
00574 new CRL is needed.
00575 """
00576
00577 ca = self.ca()
00578 parent = ca.parent()
00579 repository = parent.repository()
00580 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00581 now = rpki.sundial.now()
00582
00583 if nextUpdate is None:
00584 nextUpdate = now + crl_interval
00585
00586 certlist = []
00587 for revoked_cert in self.revoked_certs():
00588 if now > revoked_cert.expires + crl_interval:
00589 revoked_cert.sql_delete()
00590 else:
00591 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00592 certlist.sort()
00593
00594 self.latest_crl = rpki.x509.CRL.generate(
00595 keypair = self.private_key_id,
00596 issuer = self.latest_ca_cert,
00597 serial = ca.next_crl_number(),
00598 thisUpdate = now,
00599 nextUpdate = nextUpdate,
00600 revokedCertificates = certlist)
00601
00602 repository.publish(self.latest_crl, self.crl_uri(ca))
00603
00604 def generate_manifest(self, nextUpdate = None):
00605 """Generate a new manifest for this ca_detail."""
00606
00607 ca = self.ca()
00608 parent = ca.parent()
00609 repository = parent.repository()
00610 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00611 now = rpki.sundial.now()
00612
00613 if nextUpdate is None:
00614 nextUpdate = now + crl_interval
00615
00616 certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \
00617 [(r.ee_uri_tail(), r.cert) for r in self.route_origins() if r.cert is not None]
00618
00619 self.latest_manifest = rpki.x509.SignedManifest.build(
00620 serial = ca.next_manifest_number(),
00621 thisUpdate = now,
00622 nextUpdate = nextUpdate,
00623 names_and_objs = certs,
00624 keypair = self.manifest_private_key_id,
00625 certs = self.latest_manifest_cert)
00626
00627 repository.publish(self.latest_manifest, self.manifest_uri(ca))
00628
00629 class child_cert_obj(rpki.sql.sql_persistant):
00630 """Certificate that has been issued to a child."""
00631
00632 sql_template = rpki.sql.template(
00633 "child_cert",
00634 "child_cert_id",
00635 ("cert", rpki.x509.X509),
00636 "child_id",
00637 "ca_detail_id",
00638 "ski")
00639
00640 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00641 """Initialize a child_cert_obj."""
00642 self.gctx = gctx
00643 self.child_id = child_id
00644 self.ca_detail_id = ca_detail_id
00645 self.cert = cert
00646 if child_id or ca_detail_id or cert:
00647 self.sql_mark_dirty()
00648
00649 def child(self):
00650 """Fetch child object to which this child_cert object links."""
00651 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00652
00653 def ca_detail(self):
00654 """Fetch ca_detail object to which this child_cert object links."""
00655 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00656
00657 def uri_tail(self):
00658 """Return the tail (filename) portion of the URI for this child_cert."""
00659 return self.cert.gSKI() + ".cer"
00660
00661 def uri(self, ca):
00662 """Return the publication URI for this child_cert."""
00663 return ca.sia_uri + self.uri_tail()
00664
00665 def revoke(self):
00666 """Revoke a child cert."""
00667 rpki.log.debug("Revoking %s" % repr(self))
00668 ca_detail = self.ca_detail()
00669 ca = ca_detail.ca()
00670 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00671 repository = ca.parent().repository()
00672 repository.withdraw(self.cert, self.uri(ca))
00673 self.gctx.sql.sweep()
00674 self.sql_delete()
00675
00676 def reissue(self, ca_detail, resources = None, sia = None):
00677 """Reissue an existing cert, reusing the public key. If the cert
00678 we would generate is identical to the one we already have, we just
00679 return the one we already have. If we have to revoke the old
00680 certificate when generating the new one, we have to generate a new
00681 child_cert_obj, so calling code that needs the updated
00682 child_cert_obj must use the return value from this method.
00683 """
00684
00685 ca = ca_detail.ca()
00686 child = self.child()
00687
00688 old_resources = self.cert.get_3779resources()
00689 old_sia = self.cert.get_SIA()
00690 old_ca_detail = self.ca_detail()
00691
00692 if resources is None:
00693 resources = old_resources
00694
00695 if sia is None:
00696 sia = old_sia
00697
00698 assert resources.valid_until is not None and old_resources.valid_until is not None
00699
00700 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00701 return self
00702
00703 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
00704 new_issuer = ca_detail != old_ca_detail
00705
00706 if resources.valid_until != old_resources.valid_until:
00707 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
00708
00709 if must_revoke or new_issuer:
00710 child_cert = None
00711 else:
00712 child_cert = self
00713
00714 child_cert = ca_detail.issue(
00715 ca = ca,
00716 child = child,
00717 subject_key = self.cert.getPublicKey(),
00718 sia = sia,
00719 resources = resources,
00720 child_cert = child_cert)
00721
00722 if must_revoke:
00723 for cert in child.child_certs(ca_detail = ca_detail, ski = self.ski):
00724 if cert is not child_cert:
00725 cert.revoke()
00726
00727 return child_cert
00728
00729 @classmethod
00730 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
00731 """Fetch all child_cert objects matching a particular set of
00732 parameters. This is a wrapper to consolidate various queries that
00733 would otherwise be inline SQL WHERE expressions. In most cases
00734 code calls this indirectly, through methods in other classes.
00735 """
00736
00737 args = []
00738 where = []
00739
00740 if child:
00741 where.append("child_id = %s")
00742 args.append(child.child_id)
00743
00744 if ca_detail:
00745 where.append("ca_detail_id = %s")
00746 args.append(ca_detail.ca_detail_id)
00747
00748 if ski:
00749 where.append("ski = %s")
00750 args.append(ski)
00751
00752 where = " AND ".join(where)
00753
00754 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
00755
00756 if unique:
00757 return cls.sql_fetch_where1(gctx, where, args)
00758 else:
00759 return cls.sql_fetch_where(gctx, where, args)
00760
00761 class revoked_cert_obj(rpki.sql.sql_persistant):
00762 """Tombstone for a revoked certificate."""
00763
00764 sql_template = rpki.sql.template(
00765 "revoked_cert",
00766 "revoked_cert_id",
00767 "serial",
00768 "ca_detail_id",
00769 ("revoked", rpki.sundial.datetime),
00770 ("expires", rpki.sundial.datetime))
00771
00772 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
00773 """Initialize a revoked_cert_obj."""
00774 self.gctx = gctx
00775 self.serial = serial
00776 self.revoked = revoked
00777 self.expires = expires
00778 self.ca_detail_id = ca_detail_id
00779 if serial or revoked or expires or ca_detail_id:
00780 self.sql_mark_dirty()
00781
00782 def ca_detail(self):
00783 """Fetch ca_detail object to which this revoked_cert_obj links."""
00784 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00785
00786 @classmethod
00787 def revoke(cls, cert, ca_detail):
00788 """Revoke a certificate."""
00789 return cls(
00790 serial = cert.getSerial(),
00791 expires = cert.getNotAfter(),
00792 revoked = rpki.sundial.now(),
00793 gctx = ca_detail.gctx,
00794 ca_detail_id = ca_detail.ca_detail_id)