diff options
author | Rob Austein <sra@hactrn.net> | 2014-09-25 04:13:23 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2014-09-25 04:13:23 +0000 |
commit | 370b80a69e48dd36bdfd0922dcf65318d6db68f4 (patch) | |
tree | 09cd5e3a49078fe32aacb18393135805752094a7 | |
parent | 86bf27d64be4125c9390e485825fa77adc325e0d (diff) |
Convert remaining rpkid up-down code from SAX to etree.
svn path=/branches/tk705/; revision=5970
-rw-r--r-- | rpki/exceptions.py | 5 | ||||
-rw-r--r-- | rpki/left_right.py | 81 | ||||
-rw-r--r-- | rpki/rootd.py | 3 | ||||
-rw-r--r-- | rpki/rpkid.py | 127 | ||||
-rw-r--r-- | rpki/rpkid_tasks.py | 6 | ||||
-rw-r--r-- | rpki/up_down.py | 771 |
6 files changed, 187 insertions, 806 deletions
diff --git a/rpki/exceptions.py b/rpki/exceptions.py index 86c7fa27..3ca8bd81 100644 --- a/rpki/exceptions.py +++ b/rpki/exceptions.py @@ -375,3 +375,8 @@ class WrongEKU(RPKI_Exception): """ Extended Key Usage extension does not match profile. """ + +class UnexpectedUpDownResponse(RPKI_Exception): + """ + Up-down message is not of the expected type. + """ diff --git a/rpki/left_right.py b/rpki/left_right.py index 12632fb1..3b2c9b9f 100644 --- a/rpki/left_right.py +++ b/rpki/left_right.py @@ -740,10 +740,11 @@ class parent_elt(data_elt): """ def done(r_msg): - cb(dict((rc.class_name, set(c.cert.gSKI() for c in rc.certs)) - for rc in r_msg.payload.classes)) - - rpki.up_down.list_pdu.query(self, done, eb) + 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): @@ -752,12 +753,10 @@ class parent_elt(data_elt): """ def loop(iterator, ski): + def revoked(r_pdu): + iterator() logger.debug("Asking parent %r to revoke class %r, SKI %s", self, rc_name, ski) - q_pdu = rpki.up_down.revoke_pdu() - q_pdu.class_name = rc_name - q_pdu.ski = ski - self.query_up_down(q_pdu, lambda r_pdu: iterator(), eb) - + self.up_down_revoke_query(rc_name, ski, revoked, eb) rpki.async.iterator(skis_to_revoke, loop, cb) @@ -826,7 +825,51 @@ class parent_elt(data_elt): self.delete(cb, delete_parent = False) - def query_up_down(self, q_pdu, cb, eb): + def _compose_up_down_query(self, query_type): + """ + Compose top level element of an up-down query to this parent. + """ + + 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): + """ + Send an up-down list query to this parent. + """ + + 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): + """ + Send an up-down issue query to this parent. + """ + + pkcs10 = rpki.x509.PKCS10.create( + keypair = ca_detail.private_key_id, + is_ca = True, + caRepository = ca.sia_uri, + rpkiManifest = ca_detail.manifest_uri) + 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): + """ + Send an up-down revoke query to this parent. + """ + + 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): """ Client code for sending one up-down query PDU to this parent. """ @@ -838,25 +881,21 @@ class parent_elt(data_elt): if bsc.signing_cert is None: raise rpki.exceptions.BSCNotReady("BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id)) - q_msg = rpki.up_down.message_pdu.make_query( - payload = q_pdu, - sender = self.sender_name, - recipient = self.recipient_name) - - q_der = rpki.up_down.cms_msg().wrap(q_msg, bsc.private_key_id, - bsc.signing_cert, - bsc.signing_cert_crl) + q_der = rpki.up_down.cms_msg_no_sax().wrap(q_msg, bsc.private_key_id, + bsc.signing_cert, + bsc.signing_cert_crl) def unwrap(r_der): try: - r_cms = rpki.up_down.cms_msg(DER = r_der) + r_cms = rpki.up_down.cms_msg_no_sax(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) - r_msg.payload.check_response() + rpki.up_down.check_response(r_msg, q_msg.get("type")) + except (SystemExit, rpki.async.ExitNow): raise except Exception, e: @@ -1131,7 +1170,7 @@ class child_elt(data_elt): def lose(e, quiet = False): logger.exception("Unhandled exception serving child %r", self) - rpki.up_down.generate_error_response(r_msg, description = e) + rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type) done() bsc = self.bsc diff --git a/rpki/rootd.py b/rpki/rootd.py index 2bd7ce44..987d8356 100644 --- a/rpki/rootd.py +++ b/rpki/rootd.py @@ -355,9 +355,8 @@ class main(object): self.rpkid_cms_timestamp = q_cms.check_replay(self.rpkid_cms_timestamp, request.path) getattr(self, "handle_" + q_type)(q_msg, r_msg) except Exception, e: - # Should catch specific exceptions here to give better error codes. logger.exception("Exception processing up-down %s message", q_type) - rpki.up_down.generate_error_response(r_msg, description = e) + rpki.up_down.generate_error_response_from_exception(r_msg, e, q_type) request.send_cms_response(rpki.up_down.cms_msg_no_sax().wrap(r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl if self.include_bpki_crl else None)) except Exception, e: diff --git a/rpki/rpkid.py b/rpki/rpkid.py index 2be7e00f..07a81f79 100644 --- a/rpki/rpkid.py +++ b/rpki/rpkid.py @@ -119,17 +119,6 @@ class main(object): self.publication_kludge_base = self.cfg.get("publication-kludge-base", "publication/") - # Icky hack to let Iain do some testing quickly, should go away - # once we sort out whether we can make this change permanent. - # - # OK, the stuff to add router certificate support makes enough - # other changes that we're going to need a migration program in - # any case, so might as well throw the switch here too, or at - # least find out if it (still) works as expected. - - self.merge_publication_directories = self.cfg.getboolean("merge_publication_directories", - True) - self.use_internal_cron = self.cfg.getboolean("use-internal-cron", True) self.initial_delay = random.randint(self.cfg.getint("initial-delay-min", 10), @@ -519,16 +508,12 @@ class ca_obj(rpki.sql.sql_persistent): information and the parent's up-down protocol list_response PDU. """ - sia_uri = rc.suggested_sia_head and rc.suggested_sia_head.rsync() - if not sia_uri or not sia_uri.startswith(parent.sia_base): + sia_uri = rc.get("suggested_sia_head", "") + if not sia_uri.startswith("rsync://") or not sia_uri.startswith(parent.sia_base): sia_uri = parent.sia_base if not sia_uri.endswith("/"): raise rpki.exceptions.BadURISyntax("SIA URI must end with a slash: %s" % sia_uri) - # With luck this can go away sometime soon. - if self.gctx.merge_publication_directories: - return sia_uri - else: - return sia_uri + str(self.ca_id) + "/" + return sia_uri def check_for_updates(self, parent, rc, cb, eb): """ @@ -545,29 +530,40 @@ class ca_obj(rpki.sql.sql_persistent): self.sia_uri = sia_uri self.sql_mark_dirty() - rc_resources = rc.to_resource_bag() - cert_map = dict((c.cert.get_SKI(), c) for c in rc.certs) + class_name = rc.get("class_name") + + rc_resources = rpki.resource_set.resource_bag( + rc.get("resource_set_as"), + rc.get("resource_set_ipv4"), + rc.get("resource_set_ipv6"), + rc.get("resource_set_notafter")) + + cert_map = {} + for c in rc.getiterator(rpki.up_down.tag_certificate): + x = rpki.x509.X509(Base64 = c.text) + u = rpki.up_down.multi_uri(c.get("cert_url")).rsync() + cert_map[x.gSKI()] = (x, u) def loop(iterator, ca_detail): self.gctx.checkpoint() - rc_cert = cert_map.pop(ca_detail.public_key.get_SKI(), None) + rc_cert, rc_cert_uri = cert_map.pop(ca_detail.public_key.gSKI(), (None, None)) if rc_cert is None: logger.warning("SKI %s in resource class %s is in database but missing from list_response to %s from %s, " "maybe parent certificate went away?", - ca_detail.public_key.gSKI(), rc.class_name, parent.self.self_handle, parent.parent_handle) + ca_detail.public_key.gSKI(), class_name, parent.self.self_handle, parent.parent_handle) publisher = publication_queue() ca_detail.delete(ca = ca_detail.ca, publisher = publisher) return publisher.call_pubd(iterator, eb) else: - if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert.cert_url.rsync(): - logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert.cert_url.rsync()) - ca_detail.ca_cert_uri = rc_cert.cert_url.rsync() + if ca_detail.state == "active" and ca_detail.ca_cert_uri != rc_cert_uri: + logger.debug("AIA changed: was %s now %s", ca_detail.ca_cert_uri, rc_cert_uri) + ca_detail.ca_cert_uri = rc_cert_uri ca_detail.sql_mark_dirty() if ca_detail.state in ("pending", "active"): @@ -579,7 +575,7 @@ class ca_obj(rpki.sql.sql_persistent): if (ca_detail.state == "pending" or sia_uri_changed or - ca_detail.latest_ca_cert != rc_cert.cert or + ca_detail.latest_ca_cert != rc_cert or ca_detail.latest_ca_cert.getNotAfter() != rc_resources.valid_until or current_resources.undersized(rc_resources) or current_resources.oversized(rc_resources)): @@ -597,9 +593,7 @@ class ca_obj(rpki.sql.sql_persistent): def done(): if cert_map: logger.warning("Unknown certificate SKI%s %s in resource class %s in list_response to %s from %s, maybe you want to \"revoke_forgotten\"?", - "" if len(cert_map) == 1 else "s", - ", ".join(c.cert.gSKI() for c in cert_map.values()), - rc.class_name, parent.self.self_handle, parent.parent_handle) + "" if len(cert_map) == 1 else "s", ", ".join(cert_map), class_name, parent.self.self_handle, parent.parent_handle) self.gctx.sql.sweep() self.gctx.checkpoint() cb() @@ -607,26 +601,25 @@ class ca_obj(rpki.sql.sql_persistent): ca_details = self.issue_response_candidate_ca_details if True: - skis_parent = set(x.cert.gSKI() - for x in cert_map.itervalues()) + skis_parent = set(cert_map) skis_me = set(x.latest_ca_cert.gSKI() for x in ca_details if x.latest_ca_cert is not None) for ski in skis_parent & skis_me: logger.debug("Parent %s agrees that %s has SKI %s in resource class %s", - parent.parent_handle, parent.self.self_handle, ski, rc.class_name) + parent.parent_handle, parent.self.self_handle, ski, class_name) for ski in skis_parent - skis_me: logger.debug("Parent %s thinks %s has SKI %s in resource class %s but I don't think so", - parent.parent_handle, parent.self.self_handle, ski, rc.class_name) + parent.parent_handle, parent.self.self_handle, ski, class_name) for ski in skis_me - skis_parent: logger.debug("I think %s has SKI %s in resource class %s but parent %s doesn't think so", - parent.self.self_handle, ski, rc.class_name, parent.parent_handle) + parent.self.self_handle, ski, class_name, parent.parent_handle) if ca_details: rpki.async.iterator(ca_details, loop, done) else: logger.warning("Existing resource class %s to %s from %s with no certificates, rekeying", - rc.class_name, parent.self.self_handle, parent.parent_handle) + class_name, parent.self.self_handle, parent.parent_handle) self.gctx.checkpoint() self.rekey(cb, eb) @@ -640,7 +633,7 @@ class ca_obj(rpki.sql.sql_persistent): self = cls() self.gctx = parent.gctx self.parent_id = parent.parent_id - self.parent_resource_class = rc.class_name + self.parent_resource_class = rc.get("class_name") self.sql_store() try: self.sia_uri = self.construct_sia_uri(parent, rc) @@ -649,18 +642,18 @@ class ca_obj(rpki.sql.sql_persistent): raise ca_detail = ca_detail_obj.create(self) - def done(issue_response): - c = issue_response.payload.classes[0].certs[0] - logger.debug("CA %r received certificate %s", self, c.cert_url) + def done(r_msg): + c = r_msg[0][0] + logger.debug("CA %r received certificate %s", self, c.get("cert_url")) ca_detail.activate( ca = self, - cert = c.cert, - uri = c.cert_url, + cert = rpki.x509.X509(Base64 = c.text), + uri = c.get("cert_url"), callback = cb, errback = eb) logger.debug("Sending issue request to %r from %r", parent, self.create) - rpki.up_down.issue_pdu.query(parent, self, ca_detail, done, eb) + parent.up_down_issue_query(self, ca_detail, done, eb) def delete(self, parent, callback): """ @@ -727,19 +720,19 @@ class ca_obj(rpki.sql.sql_persistent): old_detail = self.active_ca_detail new_detail = ca_detail_obj.create(self) - def done(issue_response): - c = issue_response.payload.classes[0].certs[0] - logger.debug("CA %r received certificate %s", self, c.cert_url) + def done(r_msg): + c = r_msg[0][0] + logger.debug("CA %r received certificate %s", self, c.get("cert_url")) new_detail.activate( ca = self, - cert = c.cert, - uri = c.cert_url, + cert = rpki.x509.X509(Base64 = c.text), + uri = c.get("cert_url"), predecessor = old_detail, callback = cb, errback = eb) logger.debug("Sending issue request to %r from %r", parent, self.rekey) - rpki.up_down.issue_pdu.query(parent, self, new_detail, done, eb) + parent.up_down_issue_query(self, new_detail, done, eb) def revoke(self, cb, eb, revoke_all = False): """ @@ -942,7 +935,7 @@ class ca_detail_obj(rpki.sql.sql_persistent): publisher = publication_queue() self.latest_ca_cert = cert - self.ca_cert_uri = uri.rsync() + self.ca_cert_uri = uri self.generate_manifest_cert() self.state = "active" self.generate_crl(publisher = publisher) @@ -1033,13 +1026,18 @@ class ca_detail_obj(rpki.sql.sql_persistent): ca = self.ca parent = ca.parent + class_name = ca.parent_resource_class + gski = self.latest_ca_cert.gSKI() def parent_revoked(r_msg): - if r_msg.payload.ski != self.latest_ca_cert.gSKI(): + if r_msg[0].get("class_name") != class_name: + raise rpki.exceptions.ResourceClassMismatch + + if r_msg[0].get("ski") != gski: raise rpki.exceptions.SKIMismatch - logger.debug("Parent revoked %s, starting cleanup", self.latest_ca_cert.gSKI()) + logger.debug("Parent revoked %s, starting cleanup", gski) crl_interval = rpki.sundial.timedelta(seconds = parent.self.crl_interval) @@ -1077,8 +1075,9 @@ class ca_detail_obj(rpki.sql.sql_persistent): self.sql_mark_dirty() publisher.call_pubd(cb, eb) - logger.debug("Asking parent to revoke CA certificate %s", self.latest_ca_cert.gSKI()) - rpki.up_down.revoke_pdu.query(ca, self.latest_ca_cert.gSKI(), parent_revoked, eb) + logger.debug("Asking parent to revoke CA certificate %s", gski) + parent.up_down_revoke_query(class_name, gski, parent_revoked, eb) + def update(self, parent, ca, rc, sia_uri_changed, old_resources, callback, errback): """ @@ -1086,24 +1085,27 @@ class ca_detail_obj(rpki.sql.sql_persistent): children of this ca_detail. """ - def issued(issue_response): - c = issue_response.payload.classes[0].certs[0] - logger.debug("CA %r received certificate %s", self, c.cert_url) + def issued(r_msg): + c = r_msg[0][0] + cert = rpki.x509.X509(Base64 = c.text) + cert_url = c.get("cert_url") + + logger.debug("CA %r received certificate %s", self, cert_url) if self.state == "pending": return self.activate( ca = ca, - cert = c.cert, - uri = c.cert_url, + cert = cert, + uri = cert_url, callback = callback, errback = errback) - validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != c.cert.getNotAfter() + validity_changed = self.latest_ca_cert is None or self.latest_ca_cert.getNotAfter() != cert.getNotAfter() publisher = publication_queue() - if self.latest_ca_cert != c.cert: - self.latest_ca_cert = c.cert + if self.latest_ca_cert != cert: + self.latest_ca_cert = cert self.sql_mark_dirty() self.generate_manifest_cert() self.generate_crl(publisher = publisher) @@ -1131,7 +1133,8 @@ class ca_detail_obj(rpki.sql.sql_persistent): publisher.call_pubd(callback, errback) logger.debug("Sending issue request to %r from %r", parent, self.update) - rpki.up_down.issue_pdu.query(parent, ca, self, issued, errback) + parent.up_down_issue_query(ca, self, issued, errback) + @classmethod def create(cls, ca): diff --git a/rpki/rpkid_tasks.py b/rpki/rpkid_tasks.py index 959d4223..5405834f 100644 --- a/rpki/rpkid_tasks.py +++ b/rpki/rpkid_tasks.py @@ -176,12 +176,12 @@ class PollParentTask(AbstractTask): def parent_loop(self, parent_iterator, parent): self.parent_iterator = parent_iterator self.parent = parent - rpki.up_down.list_pdu.query(parent, self.got_list, self.list_failed) + parent.up_down_list_query(self.got_list, self.list_failed) def got_list(self, r_msg): self.ca_map = dict((ca.parent_resource_class, ca) for ca in self.parent.cas) self.gctx.checkpoint() - rpki.async.iterator(r_msg.payload.classes, self.class_loop, self.class_done) + rpki.async.iterator(r_msg.getiterator(rpki.up_down.tag_class), self.class_loop, self.class_done) def list_failed(self, e): logger.exception("Couldn't get resource class list from parent %r, skipping", self.parent) @@ -191,7 +191,7 @@ class PollParentTask(AbstractTask): self.gctx.checkpoint() self.class_iterator = class_iterator try: - ca = self.ca_map.pop(rc.class_name) + ca = self.ca_map.pop(rc.get("class_name")) except KeyError: rpki.rpkid.ca_obj.create(self.parent, rc, class_iterator, self.class_create_failed) else: diff --git a/rpki/up_down.py b/rpki/up_down.py index 49d330bd..7b392640 100644 --- a/rpki/up_down.py +++ b/rpki/up_down.py @@ -31,7 +31,7 @@ import rpki.log import rpki.xml_utils import rpki.relaxng -from lxml.etree import Element, SubElement +from lxml.etree import Element, SubElement, tostring as ElementToString logger = logging.getLogger(__name__) @@ -49,105 +49,12 @@ tag_request = xmlns + "request" tag_status = xmlns + "status" -error_response_codes = { - 1101 : "Already processing request", - 1102 : "Version number error", - 1103 : "Unrecognised request type", - 1201 : "Request - no such resource class", - 1202 : "Request - no resources allocated in resource class", - 1203 : "Request - badly formed certificate request", - 1301 : "Revoke - no such resource class", - 1302 : "Revoke - no such key", - 2001 : "Internal Server Error - Request not performed" } - - -def generate_error_response(r_msg, status = 2001, description = None): - """ - Generate an error response. If STATUS is given, it specifies the - numeric code to use, otherwise we default to "internal error". - If DESCRIPTION is specified, we use it as the description, otherwise - we just use the default string associated with STATUS. - """ - - assert status in error_response_codes - del r_msg[:] - r_msg.set("type", "error_response") - SubElement(r_msg, tag_status).text = str(status) - se = SubElement(r_msg, tag_description) - se.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US") - se.text = str(description or error_response_codes[status]) - - -class base_elt(object): - """ - Generic PDU object. - - Virtual class, just provides some default methods. - """ - - def startElement(self, stack, name, attrs): - """ - Ignore startElement() if there's no specific handler. - - Some elements have no attributes and we only care about their - text content. - """ - - pass - - def endElement(self, stack, name, text): - """ - Ignore endElement() if there's no specific handler. - - If we don't need to do anything else, just pop the stack. - """ - - stack.pop() - - def make_elt(self, name, *attrs): - """ - Construct a element, copying over a set of attributes. - """ - - elt = lxml.etree.Element(xmlns + name, nsmap = nsmap) - for key in attrs: - val = getattr(self, key, None) - if val is not None: - elt.set(key, str(val)) - return elt - - def make_b64elt(self, elt, name, value): - """ - Construct a sub-element with Base64 text content. - """ - - if value is not None and not value.empty(): - lxml.etree.SubElement(elt, xmlns + name, nsmap = nsmap).text = value.get_Base64() - - def serve_pdu(self, q_msg, r_msg, child, callback, errback): - """ - Default PDU handler to catch unexpected types. - """ - - raise rpki.exceptions.BadQuery("Unexpected query type %s" % q_msg.type) - - def check_response(self): - """ - Placeholder for response checking. - """ - - pass - class multi_uri(list): """ - Container for a set of URIs. + Container for a set of URIs. This probably could be simplified. """ def __init__(self, ini): - """ - Initialize a set of URIs, which includes basic some syntax checking. - """ - list.__init__(self) if isinstance(ini, (list, tuple)): self[:] = ini @@ -160,10 +67,6 @@ class multi_uri(list): raise TypeError def __str__(self): - """ - Convert a multi_uri back to a string representation. - """ - return ",".join(self) def rsync(self): @@ -176,653 +79,85 @@ class multi_uri(list): return s return None -class certificate_elt(base_elt): - """ - Up-Down protocol representation of an issued certificate. - """ - - def startElement(self, stack, name, attrs): - """ - Handle attributes of <certificate/> element. - """ - - assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack) - self.cert_url = multi_uri(attrs["cert_url"]) - self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as")) - self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4")) - self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) - - def endElement(self, stack, name, text): - """ - Handle text content of a <certificate/> element. - """ - - assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack) - self.cert = rpki.x509.X509(Base64 = text) - stack.pop() - - def toXML(self): - """ - Generate a <certificate/> element. - """ - - elt = self.make_elt("certificate", "cert_url", - "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6") - elt.text = self.cert.get_Base64() - return elt - -class class_elt(base_elt): - """ - Up-Down protocol representation of a resource class. - """ - - issuer = None - - def __init__(self): - """ - Initialize class_elt. - """ - - base_elt.__init__(self) - self.certs = [] - - def startElement(self, stack, name, attrs): - """ - Handle <class/> elements and their children. - """ - - if name == "certificate": - cert = certificate_elt() - self.certs.append(cert) - stack.append(cert) - cert.startElement(stack, name, attrs) - elif name != "issuer": - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - self.class_name = attrs["class_name"] - self.cert_url = multi_uri(attrs["cert_url"]) - self.suggested_sia_head = attrs.get("suggested_sia_head") - self.resource_set_as = rpki.resource_set.resource_set_as(attrs["resource_set_as"]) - self.resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs["resource_set_ipv4"]) - self.resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs["resource_set_ipv6"]) - self.resource_set_notafter = rpki.sundial.datetime.fromXMLtime(attrs.get("resource_set_notafter")) - - def endElement(self, stack, name, text): - """ - Handle <class/> elements and their children. - """ - - if name == "issuer": - self.issuer = rpki.x509.X509(Base64 = text) - else: - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """ - Generate a <class/> element. - """ - - elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as", - "resource_set_ipv4", "resource_set_ipv6", - "resource_set_notafter", "suggested_sia_head") - elt.extend(i.toXML() for i in self.certs) - self.make_b64elt(elt, "issuer", self.issuer) - return elt - - def to_resource_bag(self): - """ - Build a resource_bag from from this <class/> element. - """ - - return rpki.resource_set.resource_bag(self.resource_set_as, - self.resource_set_ipv4, - self.resource_set_ipv6, - self.resource_set_notafter) - - def from_resource_bag(self, bag): - """ - Set resources of this class element from a resource_bag. - """ - - self.resource_set_as = bag.asn - self.resource_set_ipv4 = bag.v4 - self.resource_set_ipv6 = bag.v6 - self.resource_set_notafter = bag.valid_until - -class list_pdu(base_elt): - """ - Up-Down protocol "list" PDU. - """ - - def toXML(self): - """ - Generate (empty) payload of "list" PDU. - """ - - return [] - - def serve_pdu(self, q_msg, r_msg, child, callback, errback): - """ - Serve one "list" PDU. - """ - - def handle(irdb_resources): - - r_msg.payload = list_response_pdu() - - if irdb_resources.valid_until < rpki.sundial.now(): - logger.debug("Child %s's resources expired %s", child.child_handle, irdb_resources.valid_until) - else: - for parent in child.parents: - for ca in parent.cas: - ca_detail = ca.active_ca_detail - if not ca_detail: - logger.debug("No active ca_detail, can't issue to %s", child.child_handle) - continue - 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])", - child.child_handle, ca_detail.latest_ca_cert.get_3779resources(), irdb_resources) - continue - rc = class_elt() - rc.class_name = str(ca.ca_id) - rc.cert_url = multi_uri(ca_detail.ca_cert_uri) - rc.from_resource_bag(resources) - for child_cert in child.fetch_child_certs(ca_detail = ca_detail): - c = certificate_elt() - c.cert_url = multi_uri(child_cert.uri) - c.cert = child_cert.cert - rc.certs.append(c) - rc.issuer = ca_detail.latest_ca_cert - r_msg.payload.classes.append(rc) - - callback() - - self.gctx.irdb_query_child_resources(child.self.self_handle, child.child_handle, handle, errback) - - @classmethod - def query(cls, parent, cb, eb): - """ - Send a "list" query to parent. - """ - - try: - logger.info('Sending "list" request to parent %s', parent.parent_handle) - parent.query_up_down(cls(), cb, eb) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - eb(e) - -class class_response_syntax(base_elt): - """ - Syntax for Up-Down protocol "list_response" and "issue_response" PDUs. - """ - - def __init__(self): - """ - Initialize class_response_syntax. - """ - - base_elt.__init__(self) - self.classes = [] - - def startElement(self, stack, name, attrs): - """ - Handle "list_response" and "issue_response" PDUs. - """ - - assert name == "class", "Unexpected name %s, stack %s" % (name, stack) - c = class_elt() - self.classes.append(c) - stack.append(c) - c.startElement(stack, name, attrs) - - def toXML(self): - """ - Generate payload of "list_response" and "issue_response" PDUs. - """ - return [c.toXML() for c in self.classes] - -class list_response_pdu(class_response_syntax): - """ - Up-Down protocol "list_response" PDU. - """ - - pass - -class issue_pdu(base_elt): - """ - Up-Down protocol "issue" PDU. - """ - - def startElement(self, stack, name, attrs): - """ - Handle "issue" PDU. - """ - - assert name == "request", "Unexpected name %s, stack %s" % (name, stack) - self.class_name = attrs["class_name"] - self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as")) - self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4")) - self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) - - def endElement(self, stack, name, text): - """ - Handle "issue" PDU. - """ - - assert name == "request", "Unexpected name %s, stack %s" % (name, stack) - self.pkcs10 = rpki.x509.PKCS10(Base64 = text) - stack.pop() - - def toXML(self): - """ - Generate payload of "issue" PDU. - """ - - elt = self.make_elt("request", "class_name", "req_resource_set_as", - "req_resource_set_ipv4", "req_resource_set_ipv6") - elt.text = self.pkcs10.get_Base64() - return [elt] - - def serve_pdu(self, q_msg, r_msg, child, callback, errback): - """ - Serve one issue request PDU. - """ - - # Subsetting not yet implemented, this is the one place where we - # have to handle it, by reporting that we're lame. - - if self.req_resource_set_as or \ - self.req_resource_set_ipv4 or \ - self.req_resource_set_ipv6: - raise rpki.exceptions.NotImplementedYet("req_* attributes not implemented yet, sorry") - - # Check the request - self.pkcs10.check_valid_request_ca() - ca = child.ca_from_class_name(self.class_name) - ca_detail = ca.active_ca_detail - if ca_detail is None: - raise rpki.exceptions.NoActiveCA("No active CA for class %r" % self.class_name) - - # Check current cert, if any - - def got_resources(irdb_resources): - - if irdb_resources.valid_until < rpki.sundial.now(): - raise rpki.exceptions.IRDBExpired("IRDB entry for child %s expired %s" % ( - child.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 = self.pkcs10.getPublicKey() - req_sia = self.pkcs10.get_SIA() - child_cert = child.fetch_child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True) - - # Generate new cert or regenerate old one if necessary - - publisher = rpki.rpkid.publication_queue() - - if child_cert is None: - child_cert = ca_detail.issue( - ca = ca, - child = child, - 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) - - def done(): - c = certificate_elt() - c.cert_url = multi_uri(child_cert.uri) - c.cert = child_cert.cert - rc = class_elt() - rc.class_name = self.class_name - rc.cert_url = multi_uri(ca_detail.ca_cert_uri) - rc.from_resource_bag(resources) - rc.certs.append(c) - rc.issuer = ca_detail.latest_ca_cert - r_msg.payload = issue_response_pdu() - r_msg.payload.classes.append(rc) - callback() - - self.gctx.sql.sweep() - assert child_cert and child_cert.sql_in_db - publisher.call_pubd(done, errback) - - self.gctx.irdb_query_child_resources(child.self.self_handle, child.child_handle, got_resources, errback) - - @classmethod - def query(cls, parent, ca, ca_detail, callback, errback): - """ - Send an "issue" request to parent associated with ca. - """ - - assert ca_detail is not None and ca_detail.state in ("pending", "active") - self = cls() - self.class_name = ca.parent_resource_class - self.pkcs10 = rpki.x509.PKCS10.create( - keypair = ca_detail.private_key_id, - is_ca = True, - caRepository = ca.sia_uri, - rpkiManifest = ca_detail.manifest_uri) - logger.info('Sending "issue" request to parent %s', parent.parent_handle) - parent.query_up_down(self, callback, errback) - -class issue_response_pdu(class_response_syntax): - """ - Up-Down protocol "issue_response" PDU. - """ - - def check_response(self): - """ - Check whether this looks like a reasonable issue_response PDU. - XML schema should be tighter for this response. - """ - - if len(self.classes) != 1 or len(self.classes[0].certs) != 1: - raise rpki.exceptions.BadIssueResponse - -class revoke_syntax(base_elt): - """ - Syntax for Up-Down protocol "revoke" and "revoke_response" PDUs. - """ - - def startElement(self, stack, name, attrs): - """ - Handle "revoke" PDU. - """ +error_response_codes = { + 1101 : "Already processing request", + 1102 : "Version number error", + 1103 : "Unrecognised request type", + 1201 : "Request - no such resource class", + 1202 : "Request - no resources allocated in resource class", + 1203 : "Request - badly formed certificate request", + 1301 : "Revoke - no such resource class", + 1302 : "Revoke - no such key", + 2001 : "Internal Server Error - Request not performed" } - self.class_name = attrs["class_name"] - self.ski = attrs["ski"] - def toXML(self): - """ - Generate payload of "revoke" PDU. - """ +exception_map = { + rpki.exceptions.NoActiveCA : 1202, + (rpki.exceptions.ClassNameUnknown, "revoke") : 1301, + rpki.exceptions.ClassNameUnknown : 1201, + (rpki.exceptions.NotInDatabase, "revoke") : 1302 } - return [self.make_elt("key", "class_name", "ski")] -class revoke_pdu(revoke_syntax): +def check_response(r_msg, q_type): """ - Up-Down protocol "revoke" PDU. + Additional checks beyond the XML schema for whether this looks like + a reasonable up-down response message. """ - def get_SKI(self): - """ - Convert g(SKI) encoding from PDU back to raw SKI. - """ - - return base64.urlsafe_b64decode(self.ski + "=") - - def serve_pdu(self, q_msg, r_msg, child, cb, eb): - """ - Serve one revoke request PDU. - """ + r_type = r_msg.get("type") - def done(): - r_msg.payload = revoke_response_pdu() - r_msg.payload.class_name = self.class_name - r_msg.payload.ski = self.ski - cb() - - ca = child.ca_from_class_name(self.class_name) - publisher = rpki.rpkid.publication_queue() - for ca_detail in ca.ca_details: - for child_cert in child.fetch_child_certs(ca_detail = ca_detail, ski = self.get_SKI()): - child_cert.revoke(publisher = publisher) - self.gctx.sql.sweep() - publisher.call_pubd(done, eb) - - @classmethod - def query(cls, ca, gski, cb, eb): - """ - Send a "revoke" request for certificate(s) named by gski to parent associated with ca. - """ + if r_type == "error_response": + raise rpki.exceptions.UpstreamError(error_response_codes[int(r_msg.findtext(tag_status))]) - parent = ca.parent - self = cls() - self.class_name = ca.parent_resource_class - self.ski = gski - logger.info('Sending "revoke" request for SKI %s to parent %s', gski, parent.parent_handle) - parent.query_up_down(self, cb, eb) + if r_type != q_type + "_response": + raise UnexpectedUpDownResponse -class revoke_response_pdu(revoke_syntax): - """ - Up-Down protocol "revoke_response" PDU. - """ + if r_type == "issue_response" and (len(r_msg) != 1 or len(r_msg[0]) != 2): + logger.debug("Weird issue_response %r: len(r_msg) %s len(r_msg[0]) %s", + r_msg, len(r_msg), len(r_msg[0]) if len(r_msg) else None) + logger.debug("Offending message\n%s", ElementToString(r_msg)) + raise rpki.exceptions.BadIssueResponse - pass -class error_response_pdu(base_elt): +def generate_error_response(r_msg, status = 2001, description = None): """ - Up-Down protocol "error_response" PDU. + Generate an error response. If status is given, it specifies the + numeric code to use, otherwise we default to "internal error". + If description is specified, we use it as the description, otherwise + we just use the default string associated with status. """ - codes = error_response_codes - - exceptions = { - rpki.exceptions.NoActiveCA : 1202, - (rpki.exceptions.ClassNameUnknown, revoke_pdu) : 1301, - rpki.exceptions.ClassNameUnknown : 1201, - (rpki.exceptions.NotInDatabase, revoke_pdu) : 1302 } - - def __init__(self, exception = None, request_payload = None): - """ - Initialize an error_response PDU from an exception object. - """ - - base_elt.__init__(self) - if exception is not None: - logger.debug("Constructing up-down error response from exception %s", exception) - exception_type = type(exception) - request_type = None if request_payload is None else type(request_payload) - logger.debug("Constructing up-down error response: exception_type %s, request_type %s", - exception_type, request_type) - if False: - self.status = self.exceptions.get((exception_type, request_type), - self.exceptions.get(exception_type, 2001)) - else: - self.status = self.exceptions.get((exception_type, request_type)) - if self.status is None: - logger.debug("No request-type-specific match, trying exception match") - self.status = self.exceptions.get(exception_type) - if self.status is None: - logger.debug("No exception match either, defaulting") - self.status = 2001 - self.description = str(exception) - logger.debug("Chosen status code: %s", self.status) - - def endElement(self, stack, name, text): - """ - Handle "error_response" PDU. - """ - - if name == "status": - code = int(text) - if code not in self.codes: - raise rpki.exceptions.BadStatusCode("%s is not a known status code" % code) - self.status = code - elif name == "description": - self.description = text - else: - assert name == "message", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - stack[-1].endElement(stack, name, text) - - def toXML(self): - """ - Generate payload of "error_response" PDU. - """ - - assert self.status in self.codes - elt = self.make_elt("status") - elt.text = str(self.status) - payload = [elt] - if self.description: - elt = self.make_elt("description") - elt.text = str(self.description) - elt.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US") - payload.append(elt) - return payload - - def check_response(self): - """ - Handle an error response. For now, just raise an exception, - perhaps figure out something more clever to do later. - """ + assert status in error_response_codes + del r_msg[:] + r_msg.set("type", "error_response") + SubElement(r_msg, tag_status).text = str(status) + se = SubElement(r_msg, tag_description) + se.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US") + se.text = str(description or error_response_codes[status]) - raise rpki.exceptions.UpstreamError(self.codes[self.status]) -class message_pdu(base_elt): +def generate_error_response_from_exception(r_msg, e, q_type): """ - Up-Down protocol message wrapper PDU. + Construct an error response from an exception. q_type + specifies the kind of query to which this is a response, since the + same exception can generate different codes in response to different + queries. """ - version = 1 - - name2type = dict( - list = list_pdu, - list_response = list_response_pdu, - issue = issue_pdu, - issue_response = issue_response_pdu, - revoke = revoke_pdu, - revoke_response = revoke_response_pdu, - error_response = error_response_pdu) - - type2name = dict((v, k) for k, v in name2type.iteritems()) - - error_pdu_type = error_response_pdu - - def toXML(self): - """ - Generate payload of message PDU. - """ - - elt = self.make_elt("message", "version", "sender", "recipient", "type") - elt.extend(self.payload.toXML()) - return elt + t = type(e) + code = (exception_map.get((t, q_type)) or exception_map.get(t) or 2001) + generate_error_response(r_msg, code, e) - def startElement(self, stack, name, attrs): - """ - Handle message PDU. - - Payload of the <message/> element varies depending on the "type" - attribute, so after some basic checks we have to instantiate the - right class object to handle whatever kind of PDU this is. - """ - - assert name == "message", "Unexpected name %s, stack %s" % (name, stack) - assert self.version == int(attrs["version"]) - self.sender = attrs["sender"] - self.recipient = attrs["recipient"] - self.type = attrs["type"] - self.payload = self.name2type[attrs["type"]]() - stack.append(self.payload) - - def __str__(self): - """ - Convert a message PDU to a string. - """ - return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8") - - def serve_top_level(self, child, callback): - """ - Serve one message request PDU. - """ - - r_msg = message_pdu() - r_msg.sender = self.recipient - r_msg.recipient = self.sender - - def done(): - r_msg.type = self.type2name[type(r_msg.payload)] - callback(r_msg) - - def lose(e): - logger.exception("Unhandled exception serving child %r", child) - callback(self.serve_error(e)) - - try: - self.log_query(child) - self.payload.serve_pdu(self, r_msg, child, done, lose) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - lose(e) - - def log_query(self, child): - """ - Log query we're handling. Separate method so rootd can override. - """ - - logger.info("Serving %s query from child %s [sender %s, recipient %s]", - self.type, child.child_handle, self.sender, self.recipient) - - def serve_error(self, exception): - """ - Generate an error_response message PDU. - """ - - r_msg = message_pdu() - r_msg.sender = self.recipient - r_msg.recipient = self.sender - r_msg.payload = self.error_pdu_type(exception, self.payload) - r_msg.type = self.type2name[type(r_msg.payload)] - return r_msg - - @classmethod - def make_query(cls, payload, sender, recipient): - """ - Construct one message PDU. - """ - - assert not cls.type2name[type(payload)].endswith("_response") - if sender is None: - sender = "tweedledee" - if recipient is None: - recipient = "tweedledum" - self = cls() - self.sender = sender - self.recipient = recipient - self.payload = payload - self.type = self.type2name[type(payload)] - return self - -class sax_handler(rpki.xml_utils.sax_handler): - """ - SAX handler for Up-Down protocol. - """ - - pdu = message_pdu - name = "message" - version = "1" - -class cms_msg(rpki.x509.XML_CMS_object): +class cms_msg_no_sax(rpki.x509.XML_CMS_object): """ Class to hold a CMS-signed up-down PDU. + + Name is a transition kludge: once we ditch SAX, this will become cms_msg. """ encoding = "UTF-8" schema = rpki.relaxng.up_down - saxify = sax_handler.saxify allow_extra_certs = True allow_extra_crls = True - -class cms_msg_no_sax(cms_msg): - """ - Class to hold a CMS-signed up-down PDU. - - Name is a transition kludge: once we ditch SAX, this will become cms_msg. - """ - - saxify = None |