diff options
Diffstat (limited to 'rpki')
-rw-r--r-- | rpki/publication.py | 7 | ||||
-rw-r--r-- | rpki/rootd.py | 283 | ||||
-rw-r--r-- | rpki/up_down.py | 56 |
3 files changed, 176 insertions, 170 deletions
diff --git a/rpki/publication.py b/rpki/publication.py index fc5b2627..ec2dabd4 100644 --- a/rpki/publication.py +++ b/rpki/publication.py @@ -54,8 +54,9 @@ def raise_if_error(pdu): """ Raise an appropriate error if this is a <report_error/> PDU. - As a convience, this will also accept a <msg/> PDU and raise an - appropriate error if it contains any <report_error/> PDUs. + As a convenience, this will also accept a <msg/> PDU and raise an + appropriate error if it contains any <report_error/> PDUs or if + the <msg/> is not a reply. """ if pdu.tag == tag_report_error: @@ -68,6 +69,8 @@ def raise_if_error(pdu): raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: %r, %r" % (code, pdu)) if pdu.tag == tag_msg: + if pdu.get("type") != "reply": + raise rpki.exceptions.BadPublicationReply("Unexpected response from pubd: expected reply, got %r" % pdu.get("type")) for p in pdu: raise_if_error(p) diff --git a/rpki/rootd.py b/rpki/rootd.py index 501b735d..4be38a0c 100644 --- a/rpki/rootd.py +++ b/rpki/rootd.py @@ -18,9 +18,7 @@ # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ -Trivial RPKI up-down protocol root server. Not recommended for -production use. Overrides a bunch of method definitions from the -rpki.* classes in order to reuse as much code as possible. +Trivial RPKI up-down protocol root server. """ import os @@ -47,68 +45,6 @@ from lxml.etree import Element, SubElement logger = logging.getLogger(__name__) -rootd = None - -class list_pdu(rpki.up_down.list_pdu): - def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): - r_msg.payload = rpki.up_down.list_response_pdu() - rootd.compose_response(r_msg, callback, errback) - -class issue_pdu(rpki.up_down.issue_pdu): - def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): - self.pkcs10.check_valid_request_ca() - r_msg.payload = rpki.up_down.issue_response_pdu() - rootd.compose_response(r_msg, callback, errback, self.pkcs10) - -class revoke_pdu(rpki.up_down.revoke_pdu): - def serve_pdu(self, q_msg, r_msg, ignored, callback, errback): - logger.debug("Revocation requested for SKI %s", self.ski) - subject_cert = rootd.get_subject_cert() - if subject_cert is None: - logger.debug("No subject certificate, nothing to revoke") - raise rpki.exceptions.NotInDatabase - if subject_cert.gSKI() != self.ski: - logger.debug("Subject certificate has different SKI %s, not revoking", subject_cert.gSKI()) - raise rpki.exceptions.NotInDatabase - logger.debug("Revoking certificate %s", self.ski) - now = rpki.sundial.now() - pubd_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap, - type = "query", version = rpki.publication.version) - rootd.revoke_subject_cert(now) - rootd.del_subject_cert() - rootd.del_subject_pkcs10() - r_msg.payload = rpki.up_down.revoke_response_pdu() - r_msg.payload.class_name = self.class_name - r_msg.payload.ski = self.ski - rootd.generate_crl_and_manifest(now, pubd_msg) - rootd.publish(callback, errback, pubd_msg) - -class error_response_pdu(rpki.up_down.error_response_pdu): - exceptions = rpki.up_down.error_response_pdu.exceptions.copy() - exceptions[rpki.exceptions.ClassNameUnknown, revoke_pdu] = 1301 - exceptions[rpki.exceptions.NotInDatabase, revoke_pdu] = 1302 - -class message_pdu(rpki.up_down.message_pdu): - - name2type = dict( - rpki.up_down.message_pdu.name2type, - list = list_pdu, - issue = issue_pdu, - revoke = revoke_pdu, - error_response = error_response_pdu) - - type2name = dict((v, k) for k, v in name2type.iteritems()) - - error_pdu_type = error_response_pdu - - def log_query(self, child): - logger.info("Serving %s query", self.type) - -class sax_handler(rpki.up_down.sax_handler): - pdu = message_pdu - -class cms_msg(rpki.up_down.cms_msg): - saxify = sax_handler.saxify class main(object): @@ -287,106 +223,141 @@ class main(object): self.revoked.append((self.get_subject_cert().getSerial(), now)) - def compose_response(self, r_msg, callback, errback, pkcs10 = None): + def publish(self, q_msg): + if q_msg is None: + return + assert len(q_msg) > 0 + + if not all(q_pdu.get("hash") is not None for q_pdu in q_msg): + logger.debug("Some publication PDUs are missing hashes, checking published data...") + q = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap, + type = "query", version = rpki.publication.version) + SubElement(q, rpki.publication.tag_list) + published_hash = dict((r.get("uri"), r.get("hash")) for r in self.call_pubd(q)) + for q_pdu in q_msg: + q_uri = q_pdu.get("uri") + if q_pdu.get("hash") is None and published_hash.get(q_uri) is not None: + logger.debug("Updating hash of %s to %s from previously published data", q_uri, published_hash[q_uri]) + q_pdu.set("hash", published_hash[q_uri]) + + r_msg = self.call_pubd(q_msg) + if len(q_msg) != len(r_msg): + raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %s, got %s" % (len(q_msg), len(r_msg))) + + + def call_pubd(self, q_msg): + for q_pdu in q_msg: + logger.info("Sending %s to pubd", q_pdu.get("uri")) + q_der = rpki.publication.cms_msg_no_sax().wrap(q_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl) + logger.debug("Sending request to pubd") + http = httplib.HTTPConnection(self.pubd_host, self.pubd_port) + http.request("POST", self.pubd_path, q_der, {"Content-Type" : rpki.http_simple.rpki_content_type}) + r = http.getresponse() + if r.status != 200: + raise rpki.exceptions.HTTPRequestFailed("HTTP request to pubd failed with status %r reason %r" % (r.status, r.reason)) + if r.getheader("Content-Type") != rpki.http_simple.rpki_content_type: + raise rpki.exceptions.HTTPRequestFailed("HTTP request to pubd failed, got Content-Type %r, expected %r" % ( + r.getheader("Content-Type"), rpki.http_simple.rpki_content_type)) + logger.debug("Received response from pubd") + r_der = r.read() + r_cms = rpki.publication.cms_msg_no_sax(DER = r_der) + r_msg = r_cms.unwrap((self.bpki_ta, self.pubd_bpki_cert)) + self.pubd_cms_timestamp = r_cms.check_replay(self.pubd_cms_timestamp, self.pubd_url) + rpki.publication.raise_if_error(r_msg) + return r_msg + + + def compose_response(self, r_msg, pkcs10 = None): subject_cert, pubd_msg = self.issue_subject_cert_maybe(pkcs10) - rc = rpki.up_down.class_elt() - rc.class_name = self.rpki_class_name - rc.cert_url = rpki.up_down.multi_uri(self.rpki_root_cert_uri) - rc.from_resource_bag(self.rpki_root_cert.get_3779resources()) - rc.issuer = self.rpki_root_cert - r_msg.payload.classes.append(rc) + bag = self.rpki_root_cert.get_3779resources() + rc = SubElement(r_msg, rpki.up_down.tag_class, + class_name = self.rpki_class_name, + cert_url = str(rpki.up_down.multi_uri(self.rpki_root_cert_uri)), + resource_set_as = str(bag.asn), + resource_set_ipv4 = str(bag.v4), + resource_set_ipv6 = str(bag.v6), + resource_set_notafter = str(bag.valid_until)) if subject_cert is not None: - rc.certs.append(rpki.up_down.certificate_elt()) - rc.certs[0].cert_url = rpki.up_down.multi_uri(self.rpki_subject_cert_uri) - rc.certs[0].cert = subject_cert - self.publish(callback, errback, pubd_msg) + c = SubElement(rc, rpki.up_down.tag_certificate, + cert_url = str(rpki.up_down.multi_uri(self.rpki_subject_cert_uri))) + c.text = subject_cert.get_Base64() + SubElement(rc, rpki.up_down.tag_issuer).text = self.rpki_root_cert.get_Base64() + self.publish(pubd_msg) - def publish(self, callback, errback, q_msg): + def handle_list(self, q_msg, r_msg): + self.compose_response(r_msg) - def done(r_msg): - if len(q_msg) != len(r_msg): - raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %s, got %s" % (len(q_msg), len(r_msg))) - callback() - def fix_hashes(r_msg): - published_hash = dict((r_pdu.get("uri"), r_pdu.get("hash")) for r_pdu in r_msg) - for q_pdu in q_msg: - if q_pdu.get("hash") is None and published_hash.get(q_pdu.get("uri")) is not None: - logger.debug("Updating hash of %s to %s from previously published data", q_pdu.get("uri"), published_hash[q_pdu.get("uri")]) - q_pdu.set("hash", published_hash[q_pdu.get("uri")]) - self.call_pubd(done, errback, q_msg) + def handle_issue(self, q_msg, r_msg): + # This is where we'd check q_msg[0].get("class_name") if this weren't rootd. + self.compose_response(r_msg, rpki.x509.PKCS10(Base64 = q_msg[0].text)) - assert q_msg is None or len(q_msg) > 0 - if q_msg is None: - callback() - elif all(q_pdu.get("hash") is not None for q_pdu in q_msg): - self.call_pubd(done, errback, q_msg) - else: - logger.debug("Some publication PDUs are missing hashes, checking...") - list_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap, - type = "query", version = rpki.publication.version) - SubElement(list_msg, rpki.publication.tag_list) - self.call_pubd(fix_hashes, errback, list_msg) - - - def call_pubd(self, callback, errback, q_msg): - try: - for q_pdu in q_msg: - logger.info("Sending %s to pubd", q_pdu.get("uri")) - q_der = rpki.publication.cms_msg_no_sax().wrap(q_msg, self.rootd_bpki_key, self.rootd_bpki_cert, self.rootd_bpki_crl) - logger.debug("Sending request to pubd") - http = httplib.HTTPConnection(self.pubd_host, self.pubd_port) - http.request("POST", self.pubd_path, q_der, {"Content-Type" : rpki.http_simple.rpki_content_type}) - r = http.getresponse() - if r.status != 200: - raise rpki.exceptions.HTTPRequestFailed("HTTP request to pubd failed with status %r reason %r" % (r.status, r.reason)) - if r.getheader("Content-Type") != rpki.http_simple.rpki_content_type: - raise rpki.exceptions.HTTPRequestFailed("HTTP request to pubd failed, got Content-Type %r, expected %r" % ( - r.getheader("Content-Type"), rpki.http_simple.rpki_content_type)) - logger.debug("Received response from pubd") - r_der = r.read() - r_cms = rpki.publication.cms_msg_no_sax(DER = r_der) - r_msg = r_cms.unwrap((self.bpki_ta, self.pubd_bpki_cert)) - self.pubd_cms_timestamp = r_cms.check_replay(self.pubd_cms_timestamp, self.pubd_url) - rpki.publication.raise_if_error(r_msg) - callback(r_msg) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - errback(e) - - - def up_down_handler(self, query, path, cb): + def handle_revoke(self, q_msg, r_msg): + class_name = q_msg[0].get("class_name") + ski = q_msg[0].get("ski") + logger.debug("Revocation requested for class %s SKI %s", class_name, ski) + subject_cert = self.get_subject_cert() + if subject_cert is None: + logger.debug("No subject certificate, nothing to revoke") + raise rpki.exceptions.NotInDatabase + if subject_cert.gSKI() != ski: + logger.debug("Subject certificate has different SKI %s, not revoking", subject_cert.gSKI()) + raise rpki.exceptions.NotInDatabase + logger.debug("Revoking certificate %s", ski) + now = rpki.sundial.now() + pubd_msg = Element(rpki.publication.tag_msg, nsmap = rpki.publication.nsmap, + type = "query", version = rpki.publication.version) + self.revoke_subject_cert(now) + self.del_subject_cert() + self.del_subject_pkcs10() + SubElement(r_msg, q_msg[0].tag, class_name = class_name, ski = ski) + self.generate_crl_and_manifest(now, pubd_msg) + self.publish(pubd_msg) + + + # Need to do something about mapping exceptions to up-down error + # codes, right now everything shows up as "internal error". + # + #exceptions = { + # rpki.exceptions.ClassNameUnknown : 1201, + # rpki.exceptions.NoActiveCA : 1202, + # (rpki.exceptions.ClassNameUnknown, revoke_pdu) : 1301, + # (rpki.exceptions.NotInDatabase, revoke_pdu) : 1302 } + # + # Might be that what we want here is a subclass of + # rpki.exceptions.RPKI_Exception which carries an extra data field + # for the up-down error code, so that we can add the correct code + # when we instantiate it. + # + # There are also a few that are also schema violations, which means + # we'd have to catch them before validating or pick them out of a + # message that failed validation or otherwise break current + # modularity. Maybe an optional pre-validation check method hook in + # rpki.x509.XML_CMS_object which we can use to intercept such things? + + + def handler(self, request, q_der): try: - q_cms = cms_msg(DER = query) + q_cms = rpki.up_down.cms_msg_no_sax(DER = q_der) q_msg = q_cms.unwrap((self.bpki_ta, self.child_bpki_cert)) - self.rpkid_cms_timestamp = q_cms.check_replay(self.rpkid_cms_timestamp, path) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - logger.exception("Problem decoding PDU") - return cb(400, reason = "Could not decode PDU: %s" % e) - - def done(r_msg): - cb(200, body = cms_msg().wrap( - r_msg, self.rootd_bpki_key, self.rootd_bpki_cert, - self.rootd_bpki_crl if self.include_bpki_crl else None)) - - try: - q_msg.serve_top_level(None, done) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: + q_type = q_msg.get("type") + logger.info("Serving %s query", q_type) + 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: - logger.exception("Exception serving up-down request %r", q_msg) - done(q_msg.serve_error(e)) - except (rpki.async.ExitNow, SystemExit): - raise + 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: - logger.exception("Exception while generating error report") - cb(500, reason = "Could not process PDU: %s" % 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) + 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: + logger.exception("Unhandled exception processing up-down message") + request.send_error(500, "Unhandled exception %s: %s" % (e.__class__.__name__, e)) def next_crl_number(self): @@ -490,6 +461,6 @@ class main(object): self.pubd_port = u.port or httplib.HTTP_PORT self.pubd_path = u.path - rpki.http.server(host = self.http_server_host, - port = self.http_server_port, - handlers = self.up_down_handler) + rpki.http_simple.server(host = self.http_server_host, + port = self.http_server_port, + handlers = self.handler) diff --git a/rpki/up_down.py b/rpki/up_down.py index c144c8a8..82abcebb 100644 --- a/rpki/up_down.py +++ b/rpki/up_down.py @@ -31,10 +31,51 @@ import rpki.log import rpki.xml_utils import rpki.relaxng +from lxml.etree import Element, SubElement + logger = logging.getLogger(__name__) -xmlns = rpki.relaxng.up_down.xmlns -nsmap = rpki.relaxng.up_down.nsmap + +xmlns = rpki.relaxng.up_down.xmlns +nsmap = rpki.relaxng.up_down.nsmap +version = "1" + +tag_certificate = xmlns + "certificate" +tag_class = xmlns + "class" +tag_description = xmlns + "description" +tag_issuer = xmlns + "issuer" +tag_message = xmlns + "message" +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_pdu, 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[:len(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): """ @@ -565,16 +606,7 @@ class error_response_pdu(base_elt): Up-Down protocol "error_response" PDU. """ - 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" } + codes = error_response_codes exceptions = { rpki.exceptions.NoActiveCA : 1202, |