00001 """Global context for rpkid.
00002
00003 $Id: rpki_engine.py 1912 2008-06-21 07:55:01Z 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.generate()
00495 self.public_key = self.private_key_id.get_RSApublic()
00496
00497 self.manifest_private_key_id = rpki.x509.RSA.generate()
00498 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00499
00500 self.sql_store()
00501 return self
00502
00503 def issue_ee(self, ca, resources, subject_key, sia = None):
00504 """Issue a new EE certificate."""
00505
00506 return self.latest_ca_cert.issue(
00507 keypair = self.private_key_id,
00508 subject_key = subject_key,
00509 serial = ca.next_serial_number(),
00510 sia = sia,
00511 aia = self.ca_cert_uri,
00512 crldp = self.crl_uri(ca),
00513 resources = resources,
00514 notAfter = self.latest_ca_cert.getNotAfter(),
00515 is_ca = False)
00516
00517
00518 def generate_manifest_cert(self, ca):
00519 """Generate a new manifest certificate for this ca_detail."""
00520
00521 resources = rpki.resource_set.resource_bag(
00522 asn = rpki.resource_set.resource_set_as("<inherit>"),
00523 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"),
00524 v6 = rpki.resource_set.resource_set_ipv6("<inherit>"))
00525
00526 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00527
00528 def issue(self, ca, child, subject_key, sia, resources, child_cert = None):
00529 """Issue a new certificate to a child. Optional child_cert
00530 argument specifies an existing child_cert object to update in
00531 place; if not specified, we create a new one. Returns the
00532 child_cert object containing the newly issued cert.
00533 """
00534
00535 assert child_cert is None or (child_cert.child_id == child.child_id and
00536 child_cert.ca_detail_id == self.ca_detail_id)
00537
00538 cert = self.latest_ca_cert.issue(
00539 keypair = self.private_key_id,
00540 subject_key = subject_key,
00541 serial = ca.next_serial_number(),
00542 aia = self.ca_cert_uri,
00543 crldp = self.crl_uri(ca),
00544 sia = sia,
00545 resources = resources,
00546 notAfter = resources.valid_until)
00547
00548 if child_cert is None:
00549 child_cert = rpki.rpki_engine.child_cert_obj(
00550 gctx = child.gctx,
00551 child_id = child.child_id,
00552 ca_detail_id = self.ca_detail_id,
00553 cert = cert)
00554 rpki.log.debug("Created new child_cert %s" % repr(child_cert))
00555 else:
00556 child_cert.cert = cert
00557 rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert))
00558
00559 child_cert.ski = cert.get_SKI()
00560
00561 child_cert.sql_store()
00562
00563 ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca))
00564
00565 self.generate_manifest()
00566
00567 return child_cert
00568
00569 def generate_crl(self, nextUpdate = None):
00570 """Generate a new CRL for this ca_detail. At the moment this is
00571 unconditional, that is, it is up to the caller to decide whether a
00572 new CRL is needed.
00573 """
00574
00575 ca = self.ca()
00576 parent = ca.parent()
00577 repository = parent.repository()
00578 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00579 now = rpki.sundial.now()
00580
00581 if nextUpdate is None:
00582 nextUpdate = now + crl_interval
00583
00584 certlist = []
00585 for revoked_cert in self.revoked_certs():
00586 if now > revoked_cert.expires + crl_interval:
00587 revoked_cert.sql_delete()
00588 else:
00589 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00590 certlist.sort()
00591
00592 self.latest_crl = rpki.x509.CRL.generate(
00593 keypair = self.private_key_id,
00594 issuer = self.latest_ca_cert,
00595 serial = ca.next_crl_number(),
00596 thisUpdate = now,
00597 nextUpdate = nextUpdate,
00598 revokedCertificates = certlist)
00599
00600 repository.publish(self.latest_crl, self.crl_uri(ca))
00601
00602 def generate_manifest(self, nextUpdate = None):
00603 """Generate a new manifest for this ca_detail."""
00604
00605 ca = self.ca()
00606 parent = ca.parent()
00607 repository = parent.repository()
00608 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00609 now = rpki.sundial.now()
00610
00611 if nextUpdate is None:
00612 nextUpdate = now + crl_interval
00613
00614 certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \
00615 [(r.ee_uri_tail(), r.cert) for r in self.route_origins() if r.cert is not None]
00616
00617 self.latest_manifest = rpki.x509.SignedManifest.build(
00618 serial = ca.next_manifest_number(),
00619 thisUpdate = now,
00620 nextUpdate = nextUpdate,
00621 names_and_objs = certs,
00622 keypair = self.manifest_private_key_id,
00623 certs = self.latest_manifest_cert)
00624
00625 repository.publish(self.latest_manifest, self.manifest_uri(ca))
00626
00627 class child_cert_obj(rpki.sql.sql_persistant):
00628 """Certificate that has been issued to a child."""
00629
00630 sql_template = rpki.sql.template(
00631 "child_cert",
00632 "child_cert_id",
00633 ("cert", rpki.x509.X509),
00634 "child_id",
00635 "ca_detail_id",
00636 "ski")
00637
00638 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00639 """Initialize a child_cert_obj."""
00640 self.gctx = gctx
00641 self.child_id = child_id
00642 self.ca_detail_id = ca_detail_id
00643 self.cert = cert
00644 if child_id or ca_detail_id or cert:
00645 self.sql_mark_dirty()
00646
00647 def child(self):
00648 """Fetch child object to which this child_cert object links."""
00649 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00650
00651 def ca_detail(self):
00652 """Fetch ca_detail object to which this child_cert object links."""
00653 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00654
00655 def uri_tail(self):
00656 """Return the tail (filename) portion of the URI for this child_cert."""
00657 return self.cert.gSKI() + ".cer"
00658
00659 def uri(self, ca):
00660 """Return the publication URI for this child_cert."""
00661 return ca.sia_uri + self.uri_tail()
00662
00663 def revoke(self):
00664 """Revoke a child cert."""
00665 rpki.log.debug("Revoking %s" % repr(self))
00666 ca_detail = self.ca_detail()
00667 ca = ca_detail.ca()
00668 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00669 repository = ca.parent().repository()
00670 repository.withdraw(self.cert, self.uri(ca))
00671 self.gctx.sql.sweep()
00672 self.sql_delete()
00673
00674 def reissue(self, ca_detail, resources = None, sia = None):
00675 """Reissue an existing cert, reusing the public key. If the cert
00676 we would generate is identical to the one we already have, we just
00677 return the one we already have. If we have to revoke the old
00678 certificate when generating the new one, we have to generate a new
00679 child_cert_obj, so calling code that needs the updated
00680 child_cert_obj must use the return value from this method.
00681 """
00682
00683 ca = ca_detail.ca()
00684 child = self.child()
00685
00686 old_resources = self.cert.get_3779resources()
00687 old_sia = self.cert.get_SIA()
00688 old_ca_detail = self.ca_detail()
00689
00690 if resources is None:
00691 resources = old_resources
00692
00693 if sia is None:
00694 sia = old_sia
00695
00696 assert resources.valid_until is not None and old_resources.valid_until is not None
00697
00698 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00699 return self
00700
00701 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
00702 new_issuer = ca_detail != old_ca_detail
00703
00704 if resources.valid_until != old_resources.valid_until:
00705 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
00706
00707 if must_revoke or new_issuer:
00708 child_cert = None
00709 else:
00710 child_cert = self
00711
00712 child_cert = ca_detail.issue(
00713 ca = ca,
00714 child = child,
00715 subject_key = self.cert.getPublicKey(),
00716 sia = sia,
00717 resources = resources,
00718 child_cert = child_cert)
00719
00720 if must_revoke:
00721 for cert in child.child_certs(ca_detail = ca_detail, ski = self.ski):
00722 if cert is not child_cert:
00723 cert.revoke()
00724
00725 return child_cert
00726
00727 @classmethod
00728 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
00729 """Fetch all child_cert objects matching a particular set of
00730 parameters. This is a wrapper to consolidate various queries that
00731 would otherwise be inline SQL WHERE expressions. In most cases
00732 code calls this indirectly, through methods in other classes.
00733 """
00734
00735 args = []
00736 where = []
00737
00738 if child:
00739 where.append("child_id = %s")
00740 args.append(child.child_id)
00741
00742 if ca_detail:
00743 where.append("ca_detail_id = %s")
00744 args.append(ca_detail.ca_detail_id)
00745
00746 if ski:
00747 where.append("ski = %s")
00748 args.append(ski)
00749
00750 where = " AND ".join(where)
00751
00752 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
00753
00754 if unique:
00755 return cls.sql_fetch_where1(gctx, where, args)
00756 else:
00757 return cls.sql_fetch_where(gctx, where, args)
00758
00759 class revoked_cert_obj(rpki.sql.sql_persistant):
00760 """Tombstone for a revoked certificate."""
00761
00762 sql_template = rpki.sql.template(
00763 "revoked_cert",
00764 "revoked_cert_id",
00765 "serial",
00766 "ca_detail_id",
00767 ("revoked", rpki.sundial.datetime),
00768 ("expires", rpki.sundial.datetime))
00769
00770 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
00771 """Initialize a revoked_cert_obj."""
00772 self.gctx = gctx
00773 self.serial = serial
00774 self.revoked = revoked
00775 self.expires = expires
00776 self.ca_detail_id = ca_detail_id
00777 if serial or revoked or expires or ca_detail_id:
00778 self.sql_mark_dirty()
00779
00780 def ca_detail(self):
00781 """Fetch ca_detail object to which this revoked_cert_obj links."""
00782 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00783
00784 @classmethod
00785 def revoke(cls, cert, ca_detail):
00786 """Revoke a certificate."""
00787 return cls(
00788 serial = cert.getSerial(),
00789 expires = cert.getNotAfter(),
00790 revoked = rpki.sundial.now(),
00791 gctx = ca_detail.gctx,
00792 ca_detail_id = ca_detail.ca_detail_id)