diff options
Diffstat (limited to 'rpkid.stable/rpki/publication.py')
-rw-r--r-- | rpkid.stable/rpki/publication.py | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/rpkid.stable/rpki/publication.py b/rpkid.stable/rpki/publication.py new file mode 100644 index 00000000..fe52b631 --- /dev/null +++ b/rpkid.stable/rpki/publication.py @@ -0,0 +1,282 @@ +"""RPKI "publication" protocol. + +$Id$ + +Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +""" + +import base64, lxml.etree, time, traceback, os +import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils +import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa + +class publication_namespace(object): + """XML namespace parameters for publication protocol.""" + + xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/" + nsmap = { None : xmlns } + +class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, publication_namespace): + """Virtual class for control channel objects.""" + + def serve_dispatch(self, r_msg, client): + """Action dispatch handler. This needs special handling because + we need to make sure that this PDU arrived via the control channel. + """ + if client is not None: + raise rpki.exceptions.BadQuery, "Control query received on client channel" + rpki.xml_utils.data_elt.serve_dispatch(self, r_msg) + +class config_elt(control_elt): + """<config/> element. This is a little weird because there should + never be more than one row in the SQL config table, but we have to + put the BPKI CRL somewhere and SQL is the least bad place available. + + So we reuse a lot of the SQL machinery, but we nail config_id at 1, + we don't expose it in the XML protocol, and we only support the get + and set actions. + """ + + attributes = ("action", "tag") + element_name = "config" + elements = ("bpki_crl",) + + sql_template = rpki.sql.template("config", "config_id", ("bpki_crl", rpki.x509.CRL)) + + wired_in_config_id = 1 + + def startElement(self, stack, name, attrs): + """StartElement() handler for config object. This requires + special handling because of the weird way we treat config_id. + """ + control_elt.startElement(self, stack, name, attrs) + self.config_id = self.wired_in_config_id + + @classmethod + def fetch(cls, gctx): + """Fetch the config object from SQL. This requires special + handling because of the weird way we treat config_id. + """ + return cls.sql_fetch(gctx, cls.wired_in_config_id) + + def serve_set(self, r_msg): + """Handle a set action. This requires special handling because + config we don't support the create method. + """ + if self.sql_fetch(self.gctx, self.config_id) is None: + control_elt.serve_create(self, r_msg) + else: + control_elt.serve_set(self, r_msg) + + def serve_fetch_one(self): + """Find the config object on which a get or set method should + operate. + """ + r = self.sql_fetch(self.gctx, self.config_id) + if r is None: + raise rpki.exceptions.NotFound + return r + +class client_elt(control_elt): + """<client/> element.""" + + element_name = "client" + attributes = ("action", "tag", "client_id", "base_uri") + elements = ("bpki_cert", "bpki_glue") + + sql_template = rpki.sql.template("client", "client_id", "base_uri", ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509)) + + base_uri = None + bpki_cert = None + bpki_glue = None + + clear_https_ta_cache = False + + def endElement(self, stack, name, text): + """Handle subelements of <client/> element. These require special + handling because modifying them invalidates the HTTPS trust anchor + cache. + """ + control_elt.endElement(self, stack, name, text) + if name in self.elements: + self.clear_https_ta_cache = True + + def serve_post_save_hook(self, q_pdu, r_pdu): + """Extra server actions for client_elt.""" + if self.clear_https_ta_cache: + self.gctx.clear_https_ta_cache() + self.clear_https_ta_cache = False + + def serve_fetch_one(self): + """Find the client object on which a get, set, or destroy method + should operate. + """ + r = self.sql_fetch(self.gctx, self.client_id) + if r is None: + raise rpki.exceptions.NotFound + return r + + def serve_fetch_all(self): + """Find client objects on which a list method should operate.""" + return self.sql_fetch_all(self.gctx) + + def check_allowed_uri(self, uri): + if not uri.startswith(self.base_uri): + raise rpki.exceptions.ForbiddenURI + +class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace): + """Virtual class for publishable objects. These have very similar + syntax, differences lie in underlying datatype and methods. XML + methods are a little different from the pattern used for objects + that support the create/set/get/list/destroy actions, but + publishable objects don't go in SQL either so these classes would be + different in any case. + """ + + attributes = ("action", "tag", "client_id", "uri") + payload = None + + def endElement(self, stack, name, text): + """Handle a publishable element element.""" + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) + if text: + self.payload = self.payload_type(Base64 = text) + stack.pop() + + def toXML(self): + """Generate XML element for publishable object.""" + elt = self.make_elt() + if self.payload: + elt.text = base64.b64encode(self.payload.get_DER()) + return elt + + def serve_dispatch(self, r_msg, client): + """Action dispatch handler.""" + if client is None: + raise rpki.exceptions.BadQuery, "Client query received on control channel" + dispatch = { "publish" : self.serve_publish, + "withdraw" : self.serve_withdraw } + if self.action not in dispatch: + raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action + client.check_allowed_uri(self.uri) + dispatch[self.action]() + r_pdu = self.__class__() + r_pdu.action = self.action + r_pdu.tag = self.tag + r_pdu.uri = self.uri + r_msg.append(r_pdu) + + def serve_publish(self): + """Publish an object.""" + rpki.log.info("Publishing %s as %s" % (repr(self.payload), repr(self.uri))) + filename = self.uri_to_filename() + dirname = os.path.dirname(filename) + if not os.path.isdir(dirname): + os.makedirs(dirname) + f = open(filename, "wb") + f.write(self.payload.get_DER()) + f.close() + + def serve_withdraw(self): + """Withdraw an object.""" + rpki.log.info("Withdrawing %s" % repr(self.uri)) + os.remove(self.uri_to_filename()) + + def uri_to_filename(self): + """Convert a URI to a local filename.""" + if not self.uri.startswith("rsync://"): + raise rpki.exceptions.BadURISyntax + filename = self.gctx.publication_base + self.uri[len("rsync://"):] + if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."): + raise rpki.exceptions.BadURISyntax + return filename + +class certificate_elt(publication_object_elt): + """<certificate/> element.""" + + element_name = "certificate" + payload_type = rpki.x509.X509 + +class crl_elt(publication_object_elt): + """<crl/> element.""" + + element_name = "crl" + payload_type = rpki.x509.CRL + +class manifest_elt(publication_object_elt): + """<manifest/> element.""" + + element_name = "manifest" + payload_type = rpki.x509.SignedManifest + +class roa_elt(publication_object_elt): + """<roa/> element.""" + + element_name = "roa" + payload_type = rpki.x509.ROA + +## @var obj2elt +# Map of data types to publication element wrapper types + +obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt)) + +class report_error_elt(rpki.xml_utils.base_elt, publication_namespace): + """<report_error/> element.""" + + element_name = "report_error" + attributes = ("tag", "error_code") + + @classmethod + def from_exception(cls, exc): + """Generate a <report_error/> element from an exception.""" + self = cls() + self.error_code = exc.__class__.__name__ + return self + +class msg(rpki.xml_utils.msg, publication_namespace): + """Publication PDU.""" + + ## @var version + # Protocol version + version = 1 + + ## @var pdus + # Dispatch table of PDUs for this protocol. + pdus = dict((x.element_name, x) + for x in (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt)) + + def serve_top_level(self, gctx, client): + """Serve one msg PDU.""" + if self.type != "query": + raise rpki.exceptions.BadQuery, "Message type is not query" + r_msg = self.__class__() + r_msg.type = "reply" + for q_pdu in self: + q_pdu.gctx = gctx + q_pdu.serve_dispatch(r_msg, client) + return r_msg + +class sax_handler(rpki.xml_utils.sax_handler): + """SAX handler for publication protocol.""" + + pdu = msg + name = "msg" + version = "1" + +class cms_msg(rpki.x509.XML_CMS_object): + """Class to hold a CMS-signed publication PDU.""" + + encoding = "us-ascii" + schema = rpki.relaxng.publication + saxify = sax_handler.saxify |