00001 """Global context for rpkid.
00002
00003 $Id: rpki_engine.py 2000 2008-07-16 00:58:32Z 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
00231 ski = ca_detail.latest_ca_cert.get_SKI()
00232
00233 if ski not in cert_map:
00234 rpki.log.warn("Certificate in database missing from list_response, SKI %s, this should never happen" % ":".join(("%02X" % ord(i) for i in ski)))
00235 ca_detail.delete(self, parent.repository())
00236 continue
00237
00238 if ca_detail.state in ("pending", "active"):
00239 current_resources = ca_detail.latest_ca_cert.get_3779resources()
00240 if sia_uri_changed or \
00241 ca_detail.latest_ca_cert != cert_map[ski].cert or \
00242 current_resources.undersized(rc_resources) or \
00243 current_resources.oversized(rc_resources):
00244 ca_detail.update(
00245 parent = parent,
00246 ca = self,
00247 rc = rc,
00248 sia_uri_changed = sia_uri_changed,
00249 old_resources = current_resources)
00250
00251 del cert_map[ski]
00252
00253 if cert_map:
00254 rpki.log.warn("Certificates in list_response missing from our database, SKIs %s" % ", ".join(c.cert.hSKI() for c in cert_map.values()))
00255
00256 @classmethod
00257 def create(cls, parent, rc):
00258 """Parent has signaled existance of a new resource class, so we
00259 need to create and set up a corresponding CA object.
00260 """
00261
00262 self = cls()
00263 self.gctx = parent.gctx
00264 self.parent_id = parent.parent_id
00265 self.parent_resource_class = rc.class_name
00266 self.sql_store()
00267 self.sia_uri = self.construct_sia_uri(parent, rc)
00268 ca_detail = ca_detail_obj.create(self)
00269
00270
00271 issue_response = rpki.up_down.issue_pdu.query(parent, self, ca_detail)
00272
00273 ca_detail.activate(
00274 ca = self,
00275 cert = issue_response.payload.classes[0].certs[0].cert,
00276 uri = issue_response.payload.classes[0].certs[0].cert_url)
00277
00278 def delete(self, parent):
00279 """The list of current resource classes received from parent does
00280 not include the class corresponding to this CA, so we need to
00281 delete it (and its little dog too...).
00282
00283 All certs published by this CA are now invalid, so need to
00284 withdraw them, the CRL, and the manifest from the repository,
00285 delete all child_cert and ca_detail records associated with this
00286 CA, then finally delete this CA itself.
00287 """
00288
00289 repository = parent.repository()
00290 for ca_detail in self.ca_details():
00291 ca_detail.delete(self, repository)
00292 self.sql_delete()
00293
00294 def next_serial_number(self):
00295 """Allocate a certificate serial number."""
00296 self.last_issued_sn += 1
00297 self.sql_mark_dirty()
00298 return self.last_issued_sn
00299
00300 def next_manifest_number(self):
00301 """Allocate a manifest serial number."""
00302 self.last_manifest_sn += 1
00303 self.sql_mark_dirty()
00304 return self.last_manifest_sn
00305
00306 def next_crl_number(self):
00307 """Allocate a CRL serial number."""
00308 self.last_crl_sn += 1
00309 self.sql_mark_dirty()
00310 return self.last_crl_sn
00311
00312 def rekey(self):
00313 """Initiate a rekey operation for this ca. Generate a new
00314 keypair. Request cert from parent using new keypair. Mark result
00315 as our active ca_detail. Reissue all child certs issued by this
00316 ca using the new ca_detail.
00317 """
00318
00319 rpki.log.trace()
00320
00321 parent = self.parent()
00322 old_detail = self.fetch_active()
00323 new_detail = ca_detail_obj.create(self)
00324
00325
00326 issue_response = rpki.up_down.issue_pdu.query(parent, self, new_detail)
00327
00328 new_detail.activate(
00329 ca = self,
00330 cert = issue_response.payload.classes[0].certs[0].cert,
00331 uri = issue_response.payload.classes[0].certs[0].cert_url,
00332 predecessor = old_detail)
00333
00334 def revoke(self):
00335 """Revoke deprecated ca_detail objects associated with this ca."""
00336
00337 rpki.log.trace()
00338
00339 for ca_detail in self.fetch_deprecated():
00340 ca_detail.revoke()
00341
00342 class ca_detail_obj(rpki.sql.sql_persistant):
00343 """Internal CA detail object."""
00344
00345 sql_template = rpki.sql.template(
00346 "ca_detail",
00347 "ca_detail_id",
00348 ("private_key_id", rpki.x509.RSA),
00349 ("public_key", rpki.x509.RSApublic),
00350 ("latest_ca_cert", rpki.x509.X509),
00351 ("manifest_private_key_id", rpki.x509.RSA),
00352 ("manifest_public_key", rpki.x509.RSApublic),
00353 ("latest_manifest_cert", rpki.x509.X509),
00354 ("latest_manifest", rpki.x509.SignedManifest),
00355 ("latest_crl", rpki.x509.CRL),
00356 "state",
00357 "ca_cert_uri",
00358 "ca_id")
00359
00360 def sql_decode(self, vals):
00361 """Extra assertions for SQL decode of a ca_detail_obj."""
00362 rpki.sql.sql_persistant.sql_decode(self, vals)
00363 assert (self.public_key is None and self.private_key_id is None) or \
00364 self.public_key.get_DER() == self.private_key_id.get_public_DER()
00365 assert (self.manifest_public_key is None and self.manifest_private_key_id is None) or \
00366 self.manifest_public_key.get_DER() == self.manifest_private_key_id.get_public_DER()
00367
00368 def ca(self):
00369 """Fetch CA object to which this ca_detail links."""
00370 return ca_obj.sql_fetch(self.gctx, self.ca_id)
00371
00372 def child_certs(self, child = None, ski = None, unique = False):
00373 """Fetch all child_cert objects that link to this ca_detail."""
00374 return rpki.rpki_engine.child_cert_obj.fetch(self.gctx, child, self, ski, unique)
00375
00376 def revoked_certs(self):
00377 """Fetch all revoked_cert objects that link to this ca_detail."""
00378 return revoked_cert_obj.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00379
00380 def route_origins(self):
00381 """Fetch all route_origin objects that link to this ca_detail."""
00382 return rpki.left_right.route_origin_elt.sql_fetch_where(self.gctx, "ca_detail_id = %s", (self.ca_detail_id,))
00383
00384 def crl_uri(self, ca):
00385 """Return publication URI for this ca_detail's CRL."""
00386 return ca.sia_uri + self.crl_uri_tail()
00387
00388 def crl_uri_tail(self):
00389 """Return tail (filename portion) of publication URI for this ca_detail's CRL."""
00390 return self.public_key.gSKI() + ".crl"
00391
00392 def manifest_uri(self, ca):
00393 """Return publication URI for this ca_detail's manifest."""
00394 return ca.sia_uri + self.public_key.gSKI() + ".mnf"
00395
00396 def activate(self, ca, cert, uri, predecessor = None):
00397 """Activate this ca_detail."""
00398
00399 self.latest_ca_cert = cert
00400 self.ca_cert_uri = uri.rsync()
00401 self.generate_manifest_cert(ca)
00402 self.generate_crl()
00403 self.generate_manifest()
00404 self.state = "active"
00405 self.sql_mark_dirty()
00406
00407 if predecessor is not None:
00408 predecessor.state = "deprecated"
00409 predecessor.sql_mark_dirty()
00410 for child_cert in predecessor.child_certs():
00411 child_cert.reissue(self)
00412 for route_origin in predecessor.route_origins():
00413 route_origin.regenerate_roa()
00414
00415 def delete(self, ca, repository):
00416 """Delete this ca_detail and all of the certs it issued."""
00417
00418 for child_cert in self.child_certs():
00419 repository.withdraw(child_cert.cert, child_cert.uri(ca))
00420 child_cert.sql_delete()
00421 for revoked__cert in self.revoked_certs():
00422 revoked_cert.sql_delete()
00423 for route_origin in self.route_origins():
00424 route_origin.withdraw_roa()
00425 repository.withdraw(self.latest_manifest, self.manifest_uri(ca))
00426 repository.withdraw(self.latest_crl, self.crl_uri(ca))
00427 self.sql_delete()
00428
00429 def revoke(self):
00430 """Request revocation of all certificates whose SKI matches the key for this ca_detail.
00431
00432 Tasks:
00433
00434 - Request revocation of old keypair by parent.
00435
00436 - Revoke all child certs issued by the old keypair.
00437
00438 - Generate a final CRL, signed with the old keypair, listing all
00439 the revoked certs, with a next CRL time after the last cert or
00440 CRL signed by the old keypair will have expired.
00441
00442 - Destroy old keypair (and manifest keypair).
00443
00444 - Leave final CRL in place until its next CRL time has passed.
00445 """
00446
00447
00448 r_msg = rpki.up_down.revoke_pdu.query(self)
00449
00450 if r_msg.payload.ski != self.latest_ca_cert.gSKI():
00451 raise rpki.exceptions.SKIMismatch
00452
00453 ca = self.ca()
00454 parent = ca.parent()
00455 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00456
00457 nextUpdate = rpki.sundial.now()
00458
00459 if self.latest_manifest is not None:
00460 nextUpdate = nextUpdate.later(self.latest_manifest.getNextUpdate())
00461
00462 if self.latest_crl is not None:
00463 nextUpdate = nextUpdate.later(self.latest_crl.getNextUpdate())
00464
00465 for child_cert in self.child_certs():
00466 nextUpdate = nextUpdate.later(child_cert.cert.getNotAfter())
00467 child_cert.revoke()
00468
00469 nextUpdate += crl_interval
00470
00471 self.generate_crl(nextUpdate)
00472 self.generate_manifest(nextUpdate)
00473
00474 self.private_key_id = None
00475 self.manifest_private_key_id = None
00476 self.manifest_public_key = None
00477 self.latest_manifest_cert = None
00478 self.state = "revoked"
00479 self.sql_mark_dirty()
00480
00481 def update(self, parent, ca, rc, sia_uri_changed, old_resources):
00482 """Need to get a new certificate for this ca_detail and perhaps
00483 frob children of this ca_detail.
00484 """
00485
00486
00487 issue_response = rpki.up_down.issue_pdu.query(parent, ca, self)
00488
00489 self.latest_ca_cert = issue_response.payload.classes[0].certs[0].cert
00490 new_resources = self.latest_ca_cert.get_3779resources()
00491
00492 if sia_uri_changed or old_resources.oversized(new_resources):
00493 for child_cert in self.child_certs():
00494 child_resources = child_cert.cert.get_3779resources()
00495 if sia_uri_changed or child_resources.oversized(new_resources):
00496 child_cert.reissue(
00497 ca_detail = self,
00498 resources = child_resources.intersection(new_resources))
00499
00500 @classmethod
00501 def create(cls, ca):
00502 """Create a new ca_detail object for a specified CA."""
00503 self = cls()
00504 self.gctx = ca.gctx
00505 self.ca_id = ca.ca_id
00506 self.state = "pending"
00507
00508 self.private_key_id = rpki.x509.RSA.generate()
00509 self.public_key = self.private_key_id.get_RSApublic()
00510
00511 self.manifest_private_key_id = rpki.x509.RSA.generate()
00512 self.manifest_public_key = self.manifest_private_key_id.get_RSApublic()
00513
00514 self.sql_store()
00515 return self
00516
00517 def issue_ee(self, ca, resources, subject_key, sia = None):
00518 """Issue a new EE certificate."""
00519
00520 return self.latest_ca_cert.issue(
00521 keypair = self.private_key_id,
00522 subject_key = subject_key,
00523 serial = ca.next_serial_number(),
00524 sia = sia,
00525 aia = self.ca_cert_uri,
00526 crldp = self.crl_uri(ca),
00527 resources = resources,
00528 notAfter = self.latest_ca_cert.getNotAfter(),
00529 is_ca = False)
00530
00531
00532 def generate_manifest_cert(self, ca):
00533 """Generate a new manifest certificate for this ca_detail."""
00534
00535 resources = rpki.resource_set.resource_bag(
00536 asn = rpki.resource_set.resource_set_as("<inherit>"),
00537 v4 = rpki.resource_set.resource_set_ipv4("<inherit>"),
00538 v6 = rpki.resource_set.resource_set_ipv6("<inherit>"))
00539
00540 self.latest_manifest_cert = self.issue_ee(ca, resources, self.manifest_public_key)
00541
00542 def issue(self, ca, child, subject_key, sia, resources, child_cert = None):
00543 """Issue a new certificate to a child. Optional child_cert
00544 argument specifies an existing child_cert object to update in
00545 place; if not specified, we create a new one. Returns the
00546 child_cert object containing the newly issued cert.
00547 """
00548
00549 assert child_cert is None or (child_cert.child_id == child.child_id and
00550 child_cert.ca_detail_id == self.ca_detail_id)
00551
00552 cert = self.latest_ca_cert.issue(
00553 keypair = self.private_key_id,
00554 subject_key = subject_key,
00555 serial = ca.next_serial_number(),
00556 aia = self.ca_cert_uri,
00557 crldp = self.crl_uri(ca),
00558 sia = sia,
00559 resources = resources,
00560 notAfter = resources.valid_until)
00561
00562 if child_cert is None:
00563 child_cert = rpki.rpki_engine.child_cert_obj(
00564 gctx = child.gctx,
00565 child_id = child.child_id,
00566 ca_detail_id = self.ca_detail_id,
00567 cert = cert)
00568 rpki.log.debug("Created new child_cert %s" % repr(child_cert))
00569 else:
00570 child_cert.cert = cert
00571 rpki.log.debug("Reusing existing child_cert %s" % repr(child_cert))
00572
00573 child_cert.ski = cert.get_SKI()
00574
00575 child_cert.sql_store()
00576
00577 ca.parent().repository().publish(child_cert.cert, child_cert.uri(ca))
00578
00579 self.generate_manifest()
00580
00581 return child_cert
00582
00583 def generate_crl(self, nextUpdate = None):
00584 """Generate a new CRL for this ca_detail. At the moment this is
00585 unconditional, that is, it is up to the caller to decide whether a
00586 new CRL is needed.
00587 """
00588
00589 ca = self.ca()
00590 parent = ca.parent()
00591 repository = parent.repository()
00592 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00593 now = rpki.sundial.now()
00594
00595 if nextUpdate is None:
00596 nextUpdate = now + crl_interval
00597
00598 certlist = []
00599 for revoked_cert in self.revoked_certs():
00600 if now > revoked_cert.expires + crl_interval:
00601 revoked_cert.sql_delete()
00602 else:
00603 certlist.append((revoked_cert.serial, revoked_cert.revoked.toASN1tuple(), ()))
00604 certlist.sort()
00605
00606 self.latest_crl = rpki.x509.CRL.generate(
00607 keypair = self.private_key_id,
00608 issuer = self.latest_ca_cert,
00609 serial = ca.next_crl_number(),
00610 thisUpdate = now,
00611 nextUpdate = nextUpdate,
00612 revokedCertificates = certlist)
00613
00614 repository.publish(self.latest_crl, self.crl_uri(ca))
00615
00616 def generate_manifest(self, nextUpdate = None):
00617 """Generate a new manifest for this ca_detail."""
00618
00619 ca = self.ca()
00620 parent = ca.parent()
00621 repository = parent.repository()
00622 crl_interval = rpki.sundial.timedelta(seconds = parent.self().crl_interval)
00623 now = rpki.sundial.now()
00624
00625 if nextUpdate is None:
00626 nextUpdate = now + crl_interval
00627
00628 route_origins = [r for r in self.route_origins() if r.cert is not None and r.roa is not None]
00629
00630 certs = [(c.uri_tail(), c.cert) for c in self.child_certs()] + \
00631 [(r.roa_uri_tail(), r.roa) for r in route_origins] + \
00632 [(r.ee_uri_tail(), r.cert) for r in route_origins] + \
00633 [(self.crl_uri_tail(), self.latest_crl)]
00634
00635 self.latest_manifest = rpki.x509.SignedManifest.build(
00636 serial = ca.next_manifest_number(),
00637 thisUpdate = now,
00638 nextUpdate = nextUpdate,
00639 names_and_objs = certs,
00640 keypair = self.manifest_private_key_id,
00641 certs = self.latest_manifest_cert)
00642
00643 repository.publish(self.latest_manifest, self.manifest_uri(ca))
00644
00645 class child_cert_obj(rpki.sql.sql_persistant):
00646 """Certificate that has been issued to a child."""
00647
00648 sql_template = rpki.sql.template(
00649 "child_cert",
00650 "child_cert_id",
00651 ("cert", rpki.x509.X509),
00652 "child_id",
00653 "ca_detail_id",
00654 "ski")
00655
00656 def __init__(self, gctx = None, child_id = None, ca_detail_id = None, cert = None):
00657 """Initialize a child_cert_obj."""
00658 self.gctx = gctx
00659 self.child_id = child_id
00660 self.ca_detail_id = ca_detail_id
00661 self.cert = cert
00662 if child_id or ca_detail_id or cert:
00663 self.sql_mark_dirty()
00664
00665 def child(self):
00666 """Fetch child object to which this child_cert object links."""
00667 return rpki.left_right.child_elt.sql_fetch(self.gctx, self.child_id)
00668
00669 def ca_detail(self):
00670 """Fetch ca_detail object to which this child_cert object links."""
00671 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00672
00673 def uri_tail(self):
00674 """Return the tail (filename) portion of the URI for this child_cert."""
00675 return self.cert.gSKI() + ".cer"
00676
00677 def uri(self, ca):
00678 """Return the publication URI for this child_cert."""
00679 return ca.sia_uri + self.uri_tail()
00680
00681 def revoke(self):
00682 """Revoke a child cert."""
00683 rpki.log.debug("Revoking %s" % repr(self))
00684 ca_detail = self.ca_detail()
00685 ca = ca_detail.ca()
00686 revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
00687 repository = ca.parent().repository()
00688 repository.withdraw(self.cert, self.uri(ca))
00689 self.gctx.sql.sweep()
00690 self.sql_delete()
00691
00692 def reissue(self, ca_detail, resources = None, sia = None):
00693 """Reissue an existing cert, reusing the public key. If the cert
00694 we would generate is identical to the one we already have, we just
00695 return the one we already have. If we have to revoke the old
00696 certificate when generating the new one, we have to generate a new
00697 child_cert_obj, so calling code that needs the updated
00698 child_cert_obj must use the return value from this method.
00699 """
00700
00701 ca = ca_detail.ca()
00702 child = self.child()
00703
00704 old_resources = self.cert.get_3779resources()
00705 old_sia = self.cert.get_SIA()
00706 old_ca_detail = self.ca_detail()
00707
00708 if resources is None:
00709 resources = old_resources
00710
00711 if sia is None:
00712 sia = old_sia
00713
00714 assert resources.valid_until is not None and old_resources.valid_until is not None
00715
00716 if resources == old_resources and sia == old_sia and ca_detail == old_ca_detail:
00717 return self
00718
00719 must_revoke = old_resources.oversized(resources) or old_resources.valid_until > resources.valid_until
00720 new_issuer = ca_detail != old_ca_detail
00721
00722 if resources.valid_until != old_resources.valid_until:
00723 rpki.log.debug("Validity changed: %s %s" % ( old_resources.valid_until, resources.valid_until))
00724
00725 if must_revoke or new_issuer:
00726 child_cert = None
00727 else:
00728 child_cert = self
00729
00730 child_cert = ca_detail.issue(
00731 ca = ca,
00732 child = child,
00733 subject_key = self.cert.getPublicKey(),
00734 sia = sia,
00735 resources = resources,
00736 child_cert = child_cert)
00737
00738 if must_revoke:
00739 for cert in child.child_certs(ca_detail = ca_detail, ski = self.ski):
00740 if cert is not child_cert:
00741 cert.revoke()
00742
00743 return child_cert
00744
00745 @classmethod
00746 def fetch(cls, gctx = None, child = None, ca_detail = None, ski = None, unique = False):
00747 """Fetch all child_cert objects matching a particular set of
00748 parameters. This is a wrapper to consolidate various queries that
00749 would otherwise be inline SQL WHERE expressions. In most cases
00750 code calls this indirectly, through methods in other classes.
00751 """
00752
00753 args = []
00754 where = []
00755
00756 if child:
00757 where.append("child_id = %s")
00758 args.append(child.child_id)
00759
00760 if ca_detail:
00761 where.append("ca_detail_id = %s")
00762 args.append(ca_detail.ca_detail_id)
00763
00764 if ski:
00765 where.append("ski = %s")
00766 args.append(ski)
00767
00768 where = " AND ".join(where)
00769
00770 gctx = gctx or (child and child.gctx) or (ca_detail and ca_detail.gctx) or None
00771
00772 if unique:
00773 return cls.sql_fetch_where1(gctx, where, args)
00774 else:
00775 return cls.sql_fetch_where(gctx, where, args)
00776
00777 class revoked_cert_obj(rpki.sql.sql_persistant):
00778 """Tombstone for a revoked certificate."""
00779
00780 sql_template = rpki.sql.template(
00781 "revoked_cert",
00782 "revoked_cert_id",
00783 "serial",
00784 "ca_detail_id",
00785 ("revoked", rpki.sundial.datetime),
00786 ("expires", rpki.sundial.datetime))
00787
00788 def __init__(self, gctx = None, serial = None, revoked = None, expires = None, ca_detail_id = None):
00789 """Initialize a revoked_cert_obj."""
00790 self.gctx = gctx
00791 self.serial = serial
00792 self.revoked = revoked
00793 self.expires = expires
00794 self.ca_detail_id = ca_detail_id
00795 if serial or revoked or expires or ca_detail_id:
00796 self.sql_mark_dirty()
00797
00798 def ca_detail(self):
00799 """Fetch ca_detail object to which this revoked_cert_obj links."""
00800 return ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
00801
00802 @classmethod
00803 def revoke(cls, cert, ca_detail):
00804 """Revoke a certificate."""
00805 return cls(
00806 serial = cert.getSerial(),
00807 expires = cert.getNotAfter(),
00808 revoked = rpki.sundial.now(),
00809 gctx = ca_detail.gctx,
00810 ca_detail_id = ca_detail.ca_detail_id)