diff options
author | Rob Austein <sra@hactrn.net> | 2014-02-19 06:11:10 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2014-02-19 06:11:10 +0000 |
commit | 0fdf6bbe24d0469850307f5f90f0f373901c814d (patch) | |
tree | 983e745c72c384c4f1e31ad3e99a7895da56d335 /rpkid/rpki | |
parent | 8d74dd5bfac736d573b5f6fd2080e9bf1b7f1f43 (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.py | 28 | ||||
-rw-r--r-- | rpkid/rpki/rpkid.py | 240 | ||||
-rw-r--r-- | rpkid/rpki/rpkid_tasks.py | 88 |
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 |