aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2014-02-19 06:11:10 +0000
committerRob Austein <sra@hactrn.net>2014-02-19 06:11:10 +0000
commit0fdf6bbe24d0469850307f5f90f0f373901c814d (patch)
tree983e745c72c384c4f1e31ad3e99a7895da56d335 /rpkid/rpki
parent8d74dd5bfac736d573b5f6fd2080e9bf1b7f1f43 (diff)
Add ee_cert_obj and its maintenance task to rpkid. Not tested yet.
svn path=/branches/tk671/; revision=5668
Diffstat (limited to 'rpkid/rpki')
-rw-r--r--rpkid/rpki/left_right.py28
-rw-r--r--rpkid/rpki/rpkid.py240
-rw-r--r--rpkid/rpki/rpkid_tasks.py88
3 files changed, 353 insertions, 3 deletions
diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py
index b1f851fa..cb25046c 100644
--- a/rpkid/rpki/left_right.py
+++ b/rpkid/rpki/left_right.py
@@ -205,6 +205,13 @@ class self_elt(data_elt):
"""
return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
+ @property
+ def ee_certificates(self):
+ """
+ Fetch all EE certificate objects that link to this self object.
+ """
+ return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
+
def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
"""
@@ -368,7 +375,27 @@ class self_elt(data_elt):
self.gctx.task_add(task)
completion.register(task)
+ def find_covering_ca_details(self, resources):
+ """
+ Return all active ca_detail_objs for this <self/> which cover a
+ particular set of resources.
+
+ If we expected there to be a large number of ca_detail_objs, we
+ could add index tables and write fancy SQL query to do this, but
+ for the expected common case where there are only one or two
+ active ca_detail_objs per <self/>, it's probably not worth it. In
+ any case, this is an optimization we can leave for later.
+ """
+ results = set()
+ for parent in self.parents:
+ for ca in parent.cas:
+ for ca_detail in ca.active_ca_details:
+ if ca_detail.covers(resources):
+ results.add(ca_detail)
+ return results
+
+
class bsc_elt(data_elt):
"""
<bsc/> (Business Signing Context) element.
@@ -1040,6 +1067,7 @@ class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_names
elements = ("pkcs10",)
pkcs10 = None
+ valid_until = None
def __repr__(self):
return rpki.log.log_repr(self, self.self_handle, self.gski, self.router_id, self.asn, self.ipv4, self.ipv6)
diff --git a/rpkid/rpki/rpkid.py b/rpkid/rpki/rpkid.py
index 60a823a8..e825caf2 100644
--- a/rpkid/rpki/rpkid.py
+++ b/rpkid/rpki/rpkid.py
@@ -249,6 +249,18 @@ class main(object):
self.irdb_query(callback, errback, *q_pdus)
+ def irdb_query_ee_certificate_requests(self, self_handle, callback, errback):
+ """
+ Ask IRDB about self's EE certificate requests.
+ """
+
+ rpki.log.trace()
+
+ q_pdu = rpki.left_right.list_ee_certificate_requests_elt()
+ q_pdu.self_handle = self_handle
+
+ self.irdb_query(callback, errback, q_pdu)
+
def left_right_handler(self, query, path, cb):
"""
Process one left-right PDU.
@@ -869,6 +881,15 @@ class ca_detail_obj(rpki.sql.sql_persistent):
"""
return self.latest_ca_cert.getNotAfter() <= rpki.sundial.now()
+ def covers(self, target):
+ """
+ Test whether this ca-detail covers a given set of resources.
+ """
+
+ assert not target.asn.inherit and not target.v4.inherit and not target.v6.inherit
+ me = self.latest_ca_cert.get_3779resources()
+ return target.asn <= me.asn and target.v4 <= me.v4 and target.v6 <= me.v6
+
def activate(self, ca, cert, uri, callback, errback, predecessor = None):
"""
Activate this ca_detail.
@@ -1090,11 +1111,15 @@ class ca_detail_obj(rpki.sql.sql_persistent):
self.sql_store()
return self
- def issue_ee(self, ca, resources, subject_key, sia):
+ def issue_ee(self, ca, resources, subject_key, sia,
+ cn = None, sn = None, notAfter = None):
"""
Issue a new EE certificate.
"""
+ if notAfter is None:
+ notAfter = self.latest_ca_cert.getNotAfter()
+
return self.latest_ca_cert.issue(
keypair = self.private_key_id,
subject_key = subject_key,
@@ -1103,8 +1128,10 @@ class ca_detail_obj(rpki.sql.sql_persistent):
aia = self.ca_cert_uri,
crldp = self.crl_uri,
resources = resources,
- notAfter = self.latest_ca_cert.getNotAfter(),
- is_ca = False)
+ notAfter = notAfter,
+ is_ca = False,
+ cn = cn,
+ sn = sn)
def generate_manifest_cert(self):
"""
@@ -2116,6 +2143,213 @@ class ghostbuster_obj(rpki.sql.sql_persistent):
return self.cert.gSKI() + ".gbr"
+class ee_cert_obj(rpki.sql.sql_persistent):
+ """
+ EE certificate (router certificate or generic).
+ """
+
+ sql_template = rpki.sql.template(
+ "ee_cert",
+ "ee_cert_id",
+ "self_id",
+ "ca_detail_id",
+ "ski",
+ ("cert", rpki.x509.X509),
+ ("published", rpki.sundial.datetime))
+
+ def __repr__(self):
+ return rpki.log.log_repr(self.cert.getSubject(), self.uri)
+
+ def __init__(self, gctx = None, self_id = None, ca_detail_id = None, cert = None):
+ rpki.sql.sql_persistent.__init__(self)
+ self.gctx = gctx
+ self.self_id = self_id
+ self.ca_detail_id = ca_detail_id
+ self.cert = cert
+ self.ski = None if cert is None else cert.get_SKI()
+ self.published = None
+ if self_id or ca_detail_id or cert:
+ self.sql_mark_dirty()
+
+ @property
+ @rpki.sql.cache_reference
+ def self(self):
+ """
+ Fetch self object to which this ee_cert_obj links.
+ """
+ return rpki.left_right.self_elt.sql_fetch(self.gctx, self.self_id)
+
+ @property
+ @rpki.sql.cache_reference
+ def ca_detail(self):
+ """
+ Fetch ca_detail object to which this ee_cert_obj links.
+ """
+ return rpki.rpkid.ca_detail_obj.sql_fetch(self.gctx, self.ca_detail_id)
+
+ @ca_detail.deleter
+ def ca_detail(self):
+ try:
+ del self._ca_detail
+ except AttributeError:
+ pass
+
+ @property
+ def gski(self):
+ """
+ Calculate g(SKI), for ease of comparison with XML.
+
+ Although, really, one has to ask why we don't just store g(SKI)
+ in rpkid.sql instead of ski....
+ """
+ return base64.urlsafe_b64encode(self.ski).rstrip("=")
+
+ @gski.setter
+ def gski(self, val):
+ self.ski = base64.urlsafe_b64decode(s + ("=" * ((4 - len(s)) % 4)))
+
+ @property
+ def uri(self):
+ """
+ Return the publication URI for this ee_cert_obj.
+ """
+ return self.ca_detail.ca.sia_uri + self.uri_tail
+
+ @property
+ def uri_tail(self):
+ """
+ Return the tail (filename portion) of the publication URI for this
+ ee_cert_obj.
+ """
+ return self.cert.gSKI() + ".cer"
+
+ def revoke(self, publisher, generate_crl_and_manifest = True):
+ """
+ Revoke and withdraw an EE certificate.
+ """
+
+ ca_detail = self.ca_detail
+ ca = ca_detail.ca
+ rpki.log.debug("Revoking %r %r" % (self, self.uri))
+ revoked_cert_obj.revoke(cert = self.cert, ca_detail = ca_detail)
+ publisher.withdraw(cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca.parent.repository)
+ self.gctx.sql.sweep()
+ self.sql_delete()
+ if generate_crl_and_manifest:
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+ def reissue(self, publisher, resources = None, force = False):
+ """
+ Reissue an existing EE cert, reusing the public key. If the EE
+ cert we would generate is identical to the one we already have, we
+ just return the one we already have. If we have to revoke the old
+ EE cert when generating the new one, we have to generate a new
+ ee_cert_obj, so calling code that needs the updated ee_cert_obj
+ must use the return value from this method.
+ """
+
+ ca_detail = self.ca_detail
+ ca = ca_detail.ca
+ old_resources = self.cert.get_3779resources()
+
+ needed = False
+
+ if resources is None:
+ resources = old_resources
+
+ assert resources.valid_until is not None and old_resources.valid_until is not None
+
+ assert ca_detail.covers(resources)
+
+ if resources.valid_until != old_resources.valid_until:
+ rpki.log.debug("Validity changed for %r: old %s new %s" % (
+ self, old_resources.valid_until, resources.valid_until))
+ needed = True
+
+ if resources != old_resources:
+ rpki.log.debug("Resources changed for %r: old %s new %s" % (
+ self, old_resources, resources))
+ needed = True
+
+ must_revoke = (old_resources.oversized(resources) or
+ old_resources.valid_until > resources.valid_until)
+ if must_revoke:
+ rpki.log.debug("Must revoke any existing cert(s) for %r" % self)
+ needed = True
+
+ if not needed and force:
+ rpki.log.debug("No change needed for %r, forcing reissuance anyway" % self)
+ needed = True
+
+ if not needed:
+ rpki.log.debug("No change to %r" % self)
+ return self
+
+ if must_revoke:
+ for x in self.sql_fetch_where(self.gctx, "self_id = %s AND ca_detail_id = %s AND ski = %s",
+ (self.self_id, self.ca_detail_id, self.ski)):
+ rpki.log.debug("Revoking ee_cert %r" % x)
+ x.revoke(publisher = publisher)
+ ca_detail.generate_crl(publisher = publisher)
+ ca_detail.generate_manifest(publisher = publisher)
+
+ return self.create(
+ ca_detail = ca_detail,
+ subject_name = self.cert.getSubject(),
+ subject_key = self.cert.getPublicKey(),
+ resources = resources,
+ publisher = publisher)
+
+ @classmethod
+ def create(cls, ca_detail, subject_name, subject_key, resources, publisher):
+ """
+ Generate a new certificate and stuff it in a new ee_cert_obj.
+ """
+
+ cn, sn = subject_name.get_cn_and_dn()
+ ca = ca_detail.ca
+
+ cert = ca_detail.issue_ee(
+ ca = ca,
+ subject_key = subject_key,
+ sia = None,
+ resources = resources,
+ notAfter = resources.valid_until,
+ cn = cn,
+ sn = sn)
+
+ self = cls(
+ gctx = ca_detail.gctx,
+ self_id = ca.self.self_id,
+ ca_detail_id = ca_detail.ca_detail_id,
+ cert = cert)
+
+ publisher.publish(
+ cls = rpki.publication.certificate_elt,
+ uri = self.uri,
+ obj = self.cert,
+ repository = ca.parent.repository,
+ handler = self.published_callback)
+
+ ca_detail.generate_manifest(publisher = publisher)
+
+ rpki.log.debug("New ee_cert %r" % self)
+
+ return self
+
+ def published_callback(self, pdu):
+ """
+ Publication callback: check result and mark published.
+ """
+ pdu.raise_if_error()
+ self.published = None
+ self.sql_mark_dirty()
+
+
class publication_queue(object):
"""
Utility to simplify publication from within rpkid.
diff --git a/rpkid/rpki/rpkid_tasks.py b/rpkid/rpki/rpkid_tasks.py
index af8e7c6c..7554fb89 100644
--- a/rpkid/rpki/rpkid_tasks.py
+++ b/rpkid/rpki/rpkid_tasks.py
@@ -567,6 +567,94 @@ class UpdateGhostbustersTask(AbstractTask):
@queue_task
+class UpdateEECertificatesTask(AbstractTask):
+ """
+ Generate or update EE certificates for this self.
+
+ Not yet sure what kind of scaling constraints this task might have,
+ so keeping it simple for initial version, we can optimize later.
+ """
+
+ def start(self):
+ rpki.log.trace()
+ self.gctx.checkpoint()
+ rpki.log.debug("Self %s[%d] updating EE certificates" % (self.self_handle, self.self_id))
+
+ self.gctx.irdb_query_ee_certificate_requests(self.self_handle,
+ self.got_requests,
+ self.get_requests_failed)
+
+ def got_requests(self, requests):
+
+ try:
+ self.gctx.checkpoint()
+ if self.gctx.sql.dirty:
+ rpki.log.warn("Unexpected dirty SQL cache, flushing")
+ self.gctx.sql.sweep()
+
+ publisher = rpki.rpkid.publication_queue()
+
+ existing = dict()
+ for ee in self.ee_certificates:
+ gski = ee.gski
+ if gski not in existing:
+ existing[gski] = set()
+ existing[gski].add(ee)
+
+ for req in requests:
+ ees = existing.pop(req.gski, ())
+ ca_details = self.find_covering_ca_details(resources)
+
+ for ee in ees:
+ if ee.ca_detail in ca_details:
+ rpki.log.debug("Updating existing EE certificate for %s %s" % (req.gski, resources))
+ ee.reissue(
+ resources = resources,
+ publisher = publisher)
+ ca_details.remove(ee.ca_detail)
+ else:
+ rpki.log.debug("Existing EE certificate for %s %s is no longer covered" % (req.gski, resources))
+ ee.revoke(publisher = publisher)
+
+ for ca_detail in ca_details:
+ rpki.log.debug("No existing EE certificate for %s %s" % (req.gski, resources))
+ rpki.rpkid.ee_cert_obj.create(
+ ca_detail = ca_detail,
+ subject_name = req.pkcs10.getSubject(),
+ subject_key = req.pkcs10.getPublicKey(),
+ resources = resources,
+ publisher = publisher)
+
+ # Anything left is an orphan
+ for ees in existing.values():
+ for ee in ees:
+ ee.revoke(publisher = publisher)
+
+ self.gctx.sql.sweep()
+
+ self.gctx.checkpoint()
+ publisher.call_pubd(self.exit, self.publication_failed)
+
+ except (SystemExit, rpki.async.ExitNow):
+ raise
+ except Exception, e:
+ rpki.log.traceback()
+ rpki.log.warn("Could not update EE certificates for %s, skipping: %s" % (self.self_handle, e))
+ self.exit()
+
+ def publication_failed(self, e):
+ rpki.log.traceback()
+ rpki.log.warn("Couldn't publish EE certificate updates for %s, skipping: %s" % (self.self_handle, e))
+ self.gctx.checkpoint()
+ self.exit()
+
+ def get_requests_failed(self, e):
+ rpki.log.traceback()
+ rpki.log.warn("Could not fetch EE certificate requests for %s, skipping: %s" % (self.self_handle, e))
+ self.exit()
+
+
+@queue_task
class RegenerateCRLsAndManifestsTask(AbstractTask):
"""
Generate new CRLs and manifests as necessary for all of this self's