diff options
author | Rob Austein <sra@hactrn.net> | 2015-10-13 22:45:35 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2015-10-13 22:45:35 +0000 |
commit | 2365c0202c068d579c5cc602db4f0caace7f3d8e (patch) | |
tree | 263f047b49037afc2455d72d058784c5cc808054 /rpki | |
parent | 0714a72bf29150d269e0dc45c2a868459dc5d023 (diff) |
Checkpoint.
svn path=/branches/tk705/; revision=6114
Diffstat (limited to 'rpki')
-rw-r--r-- | rpki/rpkid.py | 21 | ||||
-rw-r--r-- | rpki/rpkidb/models.py | 559 |
2 files changed, 528 insertions, 52 deletions
diff --git a/rpki/rpkid.py b/rpki/rpkid.py index c3b1ccb2..5ffd99d1 100644 --- a/rpki/rpkid.py +++ b/rpki/rpkid.py @@ -467,16 +467,31 @@ class main(object): elif action == "destroy": obj = model.objects.xml_get_for_delete(q_pdu) - obj.xml_pre_delete_hook() + try: + hook = obj.xml_pre_delete_hook + except AttributeError: + pass + else: + hook() obj.delete() obj.xml_template.acknowledge(obj, q_pdu, r_msg) elif action in ("create", "set"): obj = model.objects.xml_get_or_create(q_pdu) obj.xml_template.decode(obj, q_pdu) - obj.xml_pre_save_hook(q_pdu) + try: + hook = obj.xml_pre_save_hook + except AttributeError: + pass + else: + hook(q_pdu) obj.save() - obj.xml_post_save_hook(q_pdu) + try: + hook = obj.xml_post_save_hook + except AttributeError: + pass + else: + hook(q_pdu) obj.xml_template.acknowledge(obj, q_pdu, r_msg) else: diff --git a/rpki/rpkidb/models.py b/rpki/rpkidb/models.py index 154d759b..6bfeed61 100644 --- a/rpki/rpkidb/models.py +++ b/rpki/rpkidb/models.py @@ -50,6 +50,7 @@ class XMLTemplate(object): signing_cert = rpki.x509.X509, signing_cert_crl = rpki.x509.CRL) + def __init__(self, name, attributes = (), booleans = (), elements = (), readonly = (), handles = ()): self.name = name self.handles = handles @@ -58,6 +59,7 @@ class XMLTemplate(object): self.elements = elements self.readonly = readonly + def encode(self, obj, r_msg): """ Encode an ORM object as XML. @@ -84,6 +86,7 @@ class XMLTemplate(object): if v is not None and not v.empty(): SubElement(r_pdu, rpki.left_right.xmlns + k).text = v.get_Base64() + def acknowledge(self, obj, q_pdu, r_msg): """ Add an acknowledgement PDU in response to a create, set, or @@ -104,6 +107,7 @@ class XMLTemplate(object): assert not obj.pkcs11_request.empty() SubElement(r_pdu, rpki.left_right.xmlns + "pkcs11_request").text = obj.pkcs11_request.get_Base64() + def decode(self, obj, q_pdu): """ Decode XML into an ORM object. @@ -141,6 +145,7 @@ class XMLManager(models.Manager): holding an XMLTemplate object (above). """ + def xml_get_or_create(self, xml): name = self.model.xml_template.name action = xml.get("action") @@ -152,6 +157,7 @@ class XMLManager(models.Manager): d["self__self_handle"] = xml.get("self_handle") return self.model(**d) if action == "create" else self.get(**d) + def xml_list(self, xml): name = self.model.xml_template.name action = xml.get("action") @@ -163,6 +169,7 @@ class XMLManager(models.Manager): d["self__self_handle"] = xml.get("self_handle") return self.filter(**d) if d else self.all() + def xml_get_for_delete(self, xml): name = self.model.xml_template.name action = xml.get("action") @@ -189,22 +196,25 @@ class Self(models.Model): booleans = ("use_hsm",), elements = ("bpki_cert", "bpki_glue")) + def xml_pre_delete_hook(self): raise NotImplementedError - def xml_pre_save_hook(self, q_pdu): - pass def xml_post_save_hook(self, q_pdu, cb, eb): + if q_pdu.get("clear_replay_protection"): + for parent in self.parents.all(): + parent.clear_replay_protection() + for child in self.children.all(): + child.clear_replay_protection() + for repository in self.repositories.all(): + repository.clear_replay_protection() actions = [] - rekey = q_pdu.get("rekey") - revoke = q_pdu.get("revoke") - reissue = q_pdu.get("reissue") - revoke_forgotten = q_pdu.get("revoke_forgotten") - publish_world_now = q_pdu.get("publish_world_now") - run_now = q_pdu.get("run_now") - clear_replay_protection = q_pdu.get("clear_replay_protection") - if rekey or revoke or reissue or revoke_forgotten or clear_replay_protection: + rekey = q_pdu.get("rekey") + revoke = q_pdu.get("revoke") + reissue = q_pdu.get("reissue") + revoke_forgotten = q_pdu.get("revoke_forgotten") + if rekey or revoke or reissue or revoke_forgotten: for parent in self.parents.all(): if rekey: actions.append(parent.serve_rekey) @@ -214,21 +224,15 @@ class Self(models.Model): actions.append(parent.serve_reissue) if revoke_forgotten: actions.append(parent.serve_revoke_forgotten) - if clear_replay_protection: - actions.append(parent.serve_clear_replay_protection) - if clear_replay_protection: - for child in self.children.all(): - actions.append(child.serve_clear_replay_protection) - for repository in self.repositories.all(): - actions.append(repository.serve_clear_replay_protection) - if publish_world_now: + if q_pdu.get("publish_world_now"): actions.append(self.serve_publish_world_now) - if run_now: + if q_pdu.get("run_now"): actions.append(self.serve_run_now) def loop(iterator, action): action(iterator, eb) rpki.async.iterator(actions, loop, cb) + def serve_publish_world_now(self, cb, eb): publisher = rpki.rpkid.publication_queue() repositories = set() @@ -242,11 +246,13 @@ class Self(models.Model): q_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap, type = "query", version = rpki.publication.version) SubElement(q_msg, rpki.publication.tag_list, tag = "list") + def list_handler(r_pdu): rpki.publication.raise_if_error(r_pdu) assert r_pdu.tag == rpki.publication.tag_list assert r_pdu.get("uri") not in objects objects[r_pdu.get("uri")] = (r_pdu.get("hash"), repository) + repository.call_pubd(iterator, eb, q_msg, length_check = False, handlers = dict(list = list_handler)) def reconcile(uri, obj, repository): @@ -256,22 +262,18 @@ class Self(models.Model): publisher.queue(uri = uri, new_obj = obj, old_hash = h, repository = repository) def done(): - for parent in self.parents.all(): - repository = parent.repository - for ca in parent.cas.all(): - ca_detail = ca.active_ca_detail - if ca_detail is not None: - reconcile(uri = ca_detail.crl_uri, obj = ca_detail.latest_crl, repository = repository) - reconcile(uri = ca_detail.manifest_uri, obj = ca_detail.latest_manifest, repository = repository) - for c in ca_detail.child_certs.all(): - reconcile(uri = c.uri, obj = c.cert, repository = repository) - for r in ca_detail.roas.all(): - if r.roa is not None: - reconcile(uri = r.uri, obj = r.roa, repository = repository) - for g in ca_detail.ghostbusters.all(): - reconcile(uri = g.uri, obj = g.ghostbuster, repository = repository) - for c in ca_detail.ee_certificates.all(): - reconcile(uri = c.uri, obj = c.cert, repository = repository) + for ca_detail in CADetail.objects.filter(ca__parent__self = self, state = "active"): + repository = ca_detail.ca.parent.repository + reconcile(uri = ca_detail.crl_uri, obj = ca_detail.latest_crl, repository = repository) + reconcile(uri = ca_detail.manifest_uri, obj = ca_detail.latest_manifest, repository = repository) + for c in ca_detail.child_certs.all(): + reconcile(uri = c.uri, obj = c.cert, repository = repository) + for r in ca_detail.roas.filter(roa__isnull = False): + reconcile(uri = r.uri, obj = r.roa, repository = repository) + for g in ca_detail.ghostbusters.all(): + reconcile(uri = g.uri, obj = g.ghostbuster, repository = repository) + for c in ca_detail.ee_certificates.all(): + reconcile(uri = c.uri, obj = c.cert, repository = repository) for u in objects: h, r = objects[h] publisher.queue(uri = u, old_hash = h, repository = r) @@ -279,6 +281,7 @@ class Self(models.Model): rpki.async.iterator(self.parents.all(), loop, done) + def serve_run_now(self, cb, eb): logger.debug("Forced immediate run of periodic actions for self %s[%d]", self.self_handle, self.self_id) completion = rpki.rpkid_tasks.CompletionHandler(cb) @@ -287,6 +290,31 @@ class Self(models.Model): self.gctx.task_run() + def schedule_cron_tasks(self, completion): + if self.cron_tasks is None: + self.cron_tasks = tuple(task(self) for task in rpki.rpkid_tasks.task_classes) + for task in self.cron_tasks: + 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. + """ + + return set(ca_detail + for ca_detail in CADetail.objects.filter(ca__parent__self = self, state = "active") + if ca_detail.covers(resources)) + + class BSC(models.Model): bsc_handle = models.SlugField(max_length = 255) private_key_id = KeyField() @@ -305,8 +333,6 @@ class BSC(models.Model): elements = ("signing_cert", "signing_cert_crl"), readonly = ("pkcs10_request",)) - def xml_pre_delete_hook(self): - pass def xml_pre_save_hook(self, q_pdu): # Handle key generation, only supports RSA with SHA-256 for now. @@ -315,9 +341,6 @@ class BSC(models.Model): self.private_key_id = rpki.x509.RSA.generate(keylength = int(q_pdu.get("key_length", 2048))) self.pkcs10_request = rpki.x509.PKCS10.create(keypair = self.private_key_id) - def xml_post_save_hook(self, q_pdu): - pass - class Repository(models.Model): repository_handle = models.SlugField(max_length = 255) @@ -338,9 +361,73 @@ class Repository(models.Model): attributes = ("peer_contact_uri",), elements = ("bpki_cert", "bpki_glue")) - def xml_pre_delete_hook(self): raise NotImplementedError - def xml_pre_save_hook(self, q_pdu): raise NotImplementedError - def xml_post_save_hook(self, q_pdu): raise NotImplementedError + + def xml_post_save_hook(self, q_pdu, cb, eb): + if q_pdu.get("clear_replay_protection"): + self.clear_replay_protection() + cb() + + + def clear_replay_protection(self): + self.last_cms_timestamp = None + self.save() + + + def call_pubd(self, callback, errback, q_msg, handlers = {}, length_check = True): + """ + Send a message to publication daemon and return the response. + + As a convenience, attempting to send an empty message returns + immediate success without sending anything. + + handlers is a dict of handler functions to process the response + PDUs. If the tag value in the response PDU appears in the dict, + the associated handler is called to process the PDU. If no tag + matches, a default handler is called to check for errors; a + handler value of False suppresses calling of the default handler. + """ + + try: + if len(q_msg) == 0: + return callback() + + for q_pdu in q_msg: + logger.info("Sending %r to pubd", q_pdu) + + bsc = self.bsc + q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl) + bpki_ta_path = (self.gctx.bpki_ta, self.self.bpki_cert, self.self.bpki_glue, self.bpki_cert, self.bpki_glue) + + def done(r_der): + try: + logger.debug("Received response from pubd") + r_cms = rpki.publication.cms_msg(DER = r_der) + r_msg = r_cms.unwrap(bpki_ta_path) + r_cms.check_replay_sql(self, self.peer_contact_uri) + for r_pdu in r_msg: + handler = handlers.get(r_pdu.get("tag"), rpki.publication.raise_if_error) + if handler: + logger.debug("Calling pubd handler %r", handler) + handler(r_pdu) + if length_check and len(q_msg) != len(r_msg): + raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg)) + callback() + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + errback(e) + + logger.debug("Sending request to pubd") + rpki.http.client( + url = self.peer_contact_uri, + msg = q_der, + callback = done, + errback = errback) + + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + errback(e) class Parent(models.Model): @@ -366,9 +453,197 @@ class Parent(models.Model): attributes = ("peer_contact_uri", "sia_base", "sender_name", "recipient_name"), elements = ("bpki_cms_cert", "bpki_cms_glue")) - def xml_pre_delete_hook(self): raise NotImplementedError - def xml_pre_save_hook(self, q_pdu): raise NotImplementedError - def xml_post_save_hook(self, q_pdu): raise NotImplementedError + + def xml_pre_delete_hook(self, cb, eb): + self.destroy(cb, delete_parent = False) + + + def xml_post_save_hook(self, q_pdu, cb, eb): + if q_pdu.get("clear_replay_protection"): + self.clear_replay_protection() + actions = [] + if q_pdu.get("rekey"): + actions.append(self.serve_rekey) + if q_pdu.get("revoke"): + actions.append(self.serve_revoke) + if q_pdu.get("reissue"): + actions.append(self.serve_reissue) + if q_pdu.get("revoke_forgotten"): + actions.append(self.serve_revoke_forgotten) + def loop(iterator, action): + action(iterator, eb) + rpki.async.iterator(actions, loop, cb) + + + def serve_rekey(self, cb, eb): + def loop(iterator, ca): + ca.rekey(iterator, eb) + rpki.async.iterator(self.cas.all(), loop, cb) + + + def serve_revoke(self, cb, eb): + def loop(iterator, ca): + ca.revoke(cb = iterator, eb = eb) + rpki.async.iterator(self.cas.all(), loop, cb) + + + def serve_reissue(self, cb, eb): + def loop(iterator, ca): + ca.reissue(cb = iterator, eb = eb) + rpki.async.iterator(self.cas.all(), loop, cb) + + + def clear_replay_protection(self): + self.last_cms_timestamp = None + self.save() + + + def get_skis(self, cb, eb): + """ + Fetch SKIs that this parent thinks we have. In theory this should + agree with our own database, but in practice stuff can happen, so + sometimes we need to know what our parent thinks. + + Result is a dictionary with the resource class name as key and a + set of SKIs as value. + """ + + def done(r_msg): + cb(dict((rc.get("class_name"), + set(rpki.x509.X509(Base64 = c.text).gSKI() + for c in rc.getiterator(rpki.up_down.tag_certificate))) + for rc in r_msg.getiterator(rpki.up_down.tag_class))) + self.up_down_list_query(done, eb) + + + def revoke_skis(self, rc_name, skis_to_revoke, cb, eb): + """ + Revoke a set of SKIs within a particular resource class. + """ + + def loop(iterator, ski): + def revoked(r_pdu): + iterator() + logger.debug("Asking parent %r to revoke class %r, SKI %s", self, rc_name, ski) + self.up_down_revoke_query(rc_name, ski, revoked, eb) + rpki.async.iterator(skis_to_revoke, loop, cb) + + + def serve_revoke_forgotten(self, cb, eb): + """ + Handle a left-right revoke_forgotten action for this parent. + + This is a bit fiddly: we have to compare the result of an up-down + list query with what we have locally and identify the SKIs of any + certificates that have gone missing. This should never happen in + ordinary operation, but can arise if we have somehow lost a + private key, in which case there is nothing more we can do with + the issued cert, so we have to clear it. As this really is not + supposed to happen, we don't clear it automatically, instead we + require an explicit trigger. + """ + + def got_skis(skis_from_parent): + def loop(iterator, item): + rc_name, skis_to_revoke = item + if rc_name in ca_map: + for ca_detail in ca_map[rc_name].issue_response_candidate_ca_details: + skis_to_revoke.discard(ca_detail.latest_ca_cert.gSKI()) + self.revoke_skis(rc_name, skis_to_revoke, iterator, eb) + ca_map = dict((ca.parent_resource_class, ca) for ca in self.cas.all()) + rpki.async.iterator(skis_from_parent.items(), loop, cb) + self.get_skis(got_skis, eb) + + + def destroy(self, cb, delete_parent = True): + """ + Delete all the CA stuff under this parent, and perhaps the parent + itself. + """ + + # parent_elt.delete() renamed to .destroy() here to avoid conflict + # with built-in ORM .delete() method. + + def loop(iterator, ca): + ca.destroy(self, iterator) + def revoke(): + self.serve_revoke_forgotten(done, fail) + def fail(e): + logger.warning("Trouble getting parent to revoke certificates, blundering onwards: %s", e) + done() + def done(): + if delete_parent: + self.delete() + cb() + rpki.async.iterator(self.cas, loop, revoke) + + + def _compose_up_down_query(self, query_type): + return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version, + sender = self.sender_name, recipient = self.recipient_name, type = query_type) + + + def up_down_list_query(self, cb, eb): + q_msg = self._compose_up_down_query("list") + self.query_up_down(q_msg, cb, eb) + + + def up_down_issue_query(self, ca, ca_detail, cb, eb): + pkcs10 = rpki.x509.PKCS10.create( + keypair = ca_detail.private_key_id, + is_ca = True, + caRepository = ca.sia_uri, + rpkiManifest = ca_detail.manifest_uri, + rpkiNotify = rpki.publication.rrdp_sia_uri_kludge) + q_msg = self._compose_up_down_query("issue") + q_pdu = SubElement(q_msg, rpki.up_down.tag_request, class_name = ca.parent_resource_class) + q_pdu.text = pkcs10.get_Base64() + self.query_up_down(q_msg, cb, eb) + + + def up_down_revoke_query(self, class_name, ski, cb, eb): + q_msg = self._compose_up_down_query("revoke") + SubElement(q_msg, rpki.up_down.tag_key, class_name = class_name, ski = ski) + self.query_up_down(q_msg, cb, eb) + + + def query_up_down(self, q_msg, cb, eb): + + if self.bsc is None: + raise rpki.exceptions.BSCNotFound("Could not find BSC") + + if self.bsc.signing_cert is None: + raise rpki.exceptions.BSCNotReady("BSC %r is not yet usable" % eslf.bsc.bsc_handle) + + q_der = rpki.up_down.cms_msg().wrap(q_msg, + self.bsc.private_key_id, + self.bsc.signing_cert, + self.bsc.signing_cert_crl) + + def unwrap(r_der): + try: + r_cms = rpki.up_down.cms_msg(DER = r_der) + r_msg = r_cms.unwrap((self.gctx.bpki_ta, + self.self.bpki_cert, + self.self.bpki_glue, + self.bpki_cms_cert, + self.bpki_cms_glue)) + r_cms.check_replay_sql(self, self.peer_contact_uri) + rpki.up_down.check_response(r_msg, q_msg.get("type")) + + except (SystemExit, rpki.async.ExitNow): + raise + except Exception, e: + eb(e) + else: + cb(r_msg) + + rpki.http.client( + msg = q_der, + url = self.peer_contact_uri, + callback = unwrap, + errback = eb, + content_type = rpki.up_down.content_type) class CA(models.Model): @@ -381,6 +656,18 @@ class CA(models.Model): parent_resource_class = models.TextField(null = True) parent = models.ForeignKey(Parent, related_name = "cas") + # So it turns out that there's always a 1:1 mapping between the + # class_name we receive from our parent and the class_name we issue + # to our children: in spite of the obfuscated way that we used to + # handle class names, we never actually added a way for the back-end + # to create new classes. Not clear we want to encourage this, but + # if we wanted to support it, simple approach would probably be an + # optional class_name attribute in the left-right <list_resources/> + # response; if not present, we'd use parent's class_name as now, + # otherwise we'd use the supplied class_name. + + + class CADetail(models.Model): public_key = KeyField(null = True) private_key_id = KeyField(null = True) @@ -413,9 +700,183 @@ class Child(models.Model): handles = (BSC,), elements = ("bpki_cert", "bpki_glue")) - def xml_pre_delete_hook(self): raise NotImplementedError - def xml_pre_save_hook(self, q_pdu): raise NotImplementedError - def xml_post_save_hook(self, q_pdu): raise NotImplementedError + + def xml_pre_delete_hook(self, cb, eb): + publisher = rpki.rpkid.publication_queue() + for child_cert in self.child_certs.all(): + child_cert.revoke(publisher = publisher, generate_crl_and_manifest = True) + publisher.call_pubd(cb, eb) + + + def xml_post_save_hook(self, q_pdu, cb, eb): + if q_pdu.get("clear_replay_protection"): + self.clear_replay_protection() + if q_pdu.get("reissue"): + self.serve_reissue(cb, eb) + else: + cb() + + + def serve_reissue(self, cb, eb): + publisher = rpki.rpkid.publication_queue() + for child_cert in self.child_certs.all(): + child_cert.reissue(child_cert.ca_detail, publisher, force = True) + publisher.call_pubd(cb, eb) + + + def clear_replay_protection(self): + self.last_cms_timestamp = None + self.save() + + + def up_down_handle_list(self, q_msg, r_msg, callback, errback): + def got_resources(irdb_resources): + if irdb_resources.valid_until < rpki.sundial.now(): + logger.debug("Child %s's resources expired %s", self.child_handle, irdb_resources.valid_until) + else: + for ca_detail in CADetail.objects.filter(ca__parent__self = self.self, state = "active"): + resources = ca_detail.latest_ca_cert.get_3779resources() & irdb_resources + if resources.empty(): + logger.debug("No overlap between received resources and what child %s should get ([%s], [%s])", + self.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources) + continue + rc = SubElement(r_msg, rpki.up_down.tag_class, + class_name = ca_detail.ca.parent_resource_class, + cert_url = ca_detail.ca_cert_uri, + resource_set_as = str(resources.asn), + resource_set_ipv4 = str(resources.v4), + resource_set_ipv6 = str(resources.v6), + resource_set_notafter = str(resources.valid_until)) + for child_cert in self.child_certs.filter(ca_detail = ca_detail): + c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri) + c.text = child_cert.cert.get_Base64() + SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64() + callback() + self.gctx.irdb_query_child_resources(self.self.self_handle, self.child_handle, got_resources, errback) + + + def up_down_handle_issue(self, q_msg, r_msg, callback, errback): + + def got_resources(irdb_resources): + + def done(): + rc = SubElement(r_msg, rpki.up_down.tag_class, + class_name = class_name, + cert_url = ca_detail.ca_cert_uri, + resource_set_as = str(resources.asn), + resource_set_ipv4 = str(resources.v4), + resource_set_ipv6 = str(resources.v6), + resource_set_notafter = str(resources.valid_until)) + c = SubElement(rc, rpki.up_down.tag_certificate, cert_url = child_cert.uri) + c.text = child_cert.cert.get_Base64() + SubElement(rc, rpki.up_down.tag_issuer).text = ca_detail.latest_ca_cert.get_Base64() + callback() + + if irdb_resources.valid_until < rpki.sundial.now(): + raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % ( + self.child_handle, irdb_resources.valid_until)) + + resources = irdb_resources & ca_detail.latest_ca_cert.get_3779resources() + resources.valid_until = irdb_resources.valid_until + req_key = pkcs10.getPublicKey() + req_sia = pkcs10.get_SIA() + + # Generate new cert or regenerate old one if necessary + + publisher = rpki.rpkid.publication_queue() + + try: + child_cert = self.child_certs.get(ca_detail = ca_detail, ski = req_key.get_SKI()) + + except ChildCert.NotFound: + child_cert = ca_detail.issue( + ca = ca_detail.ca, + child = self, + subject_key = req_key, + sia = req_sia, + resources = resources, + publisher = publisher) + + else: + child_cert = child_cert.reissue( + ca_detail = ca_detail, + sia = req_sia, + resources = resources, + publisher = publisher) + + publisher.call_pubd(done, errback) + + req = q_msg[0] + assert req.tag == rpki.up_down.tag_request + + # Subsetting not yet implemented, this is the one place where we have to handle it, by reporting that we're lame. + + if any(req.get(a) for a in ("req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")): + raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry") + + class_name = req.get("class_name") + pkcs10 = rpki.x509.PKCS10(Base64 = req.text) + pkcs10.check_valid_request_ca() + ca_detail = CADetail.objects.get(ca__parent__self = self.self, + ca__parent_class_name = class_name, + state = "active") + self.gctx.irdb_query_child_resources(self.self.self_handle, self.child_handle, got_resources, errback) + + + def up_down_handle_revoke(self, q_msg, r_msg, callback, errback): + def done(): + SubElement(r_msg, key.tag, class_name = class_name, ski = key.get("ski")) + callback() + key = q_msg[0] + assert key.tag == rpki.up_down.tag_key + class_name = key.get("class_name") + ski = base64.urlsafe_b64decode(key.get("ski") + "=") + publisher = rpki.rpkid.publication_queue() + for child_cert in ChildCert.objects.filter(ca_detail__ca__parent__self = self.self, + ca_detail__ca__parent_class_name = class_name, + ski = ski): + child_cert.revoke(publisher = publisher) + publisher.call_pubd(done, errback) + + + def serve_up_down(self, q_der, callback): + """ + Outer layer of server handling for one up-down PDU from this child. + """ + + def done(): + callback(rpki.up_down.cms_msg().wrap(r_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)) + + def lose(e): + logger.exception("Unhandled exception serving child %r", self) + rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type) + done() + + if self.bsc is None: + raise rpki.exceptions.BSCNotFound("Could not find BSC") + q_cms = rpki.up_down.cms_msg(DER = q_der) + q_msg = q_cms.unwrap((self.gctx.bpki_ta, + self.self.bpki_cert, + self.self.bpki_glue, + self.bpki_cert, + self.bpki_glue)) + q_cms.check_replay_sql(self, "child", self.child_handle) + q_type = q_msg.get("type") + logger.info("Serving %s query from child %s [sender %s, recipient %s]", + q_type, self.child_handle, q_msg.get("sender"), q_msg.get("recipient")) + if enforce_strict_up_down_xml_sender and q_msg.get("sender") != self.child_handle: + raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.get("sender")) + + r_msg = Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version, + sender = q_msg.get("recipient"), recipient = q_msg.get("sender"), type = q_type + "_response") + + try: + getattr(self, "up_down_handle_" + q_type)(q_msg, r_msg, done, lose) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + lose(e) + class ChildCert(models.Model): |