diff options
Diffstat (limited to 'rpkid/rpki')
-rw-r--r-- | rpkid/rpki/exceptions.py | 3 | ||||
-rw-r--r-- | rpkid/rpki/left_right.py | 182 | ||||
-rw-r--r-- | rpkid/rpki/publication.py | 123 | ||||
-rw-r--r-- | rpkid/rpki/relaxng.py | 60 | ||||
-rw-r--r-- | rpkid/rpki/xml_utils.py | 32 |
5 files changed, 180 insertions, 220 deletions
diff --git a/rpkid/rpki/exceptions.py b/rpkid/rpki/exceptions.py index 91f7e351..b3db4737 100644 --- a/rpkid/rpki/exceptions.py +++ b/rpkid/rpki/exceptions.py @@ -120,3 +120,6 @@ class MissingCMSCRL(RPKI_Exception): class UnparsableCMSDER(RPKI_Exception): """Alleged CMS DER wasn't parsable.""" + +class CMSCRLNotSet(RPKI_Exception): + """CMS CRL has not been configured.""" diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py index 900d5e16..04f35529 100644 --- a/rpkid/rpki/left_right.py +++ b/rpkid/rpki/left_right.py @@ -141,31 +141,6 @@ class self_elt(data_elt): """ return self.sql_fetch_all(self.gctx) - def startElement(self, stack, name, attrs): - """Handle <self/> element.""" - if name not in ("bpki_cert", "bpki_glue"): - assert name == "self", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def endElement(self, stack, name, text): - """Handle <self/> element.""" - if name == "bpki_cert": - self.bpki_cert = rpki.x509.X509(Base64 = text) - elif name == "bpki_glue": - self.bpki_glue = rpki.x509.X509(Base64 = text) - else: - assert name == "self", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <self/> element.""" - elt = self.make_elt() - if self.bpki_cert and not self.bpki_cert.empty(): - self.make_b64elt(elt, "bpki_cert", self.bpki_cert.get_DER()) - if self.bpki_glue and not self.bpki_glue.empty(): - self.make_b64elt(elt, "bpki_glue", self.bpki_glue.get_DER()) - return elt - def client_poll(self): """Run the regular client poll cycle with each of this self's parents in turn.""" @@ -261,7 +236,7 @@ class bsc_elt(data_elt): element_name = "bsc" attributes = ("action", "tag", "self_id", "bsc_id", "key_type", "hash_alg", "key_length") - elements = ("signing_cert", "signing_cert_crl") + elements = ("pkcs10_request", "signing_cert", "signing_cert_crl") booleans = ("generate_keypair",) sql_template = rpki.sql.template("bsc", "bsc_id", "self_id", "hash_alg", @@ -299,35 +274,6 @@ class bsc_elt(data_elt): self.pkcs10_request = rpki.x509.PKCS10.create(keypair) r_pdu.pkcs10_request = self.pkcs10_request - def startElement(self, stack, name, attrs): - """Handle <bsc/> element.""" - if name not in ("pkcs10_request", "signing_cert", "signing_cert_crl"): - assert name == "bsc", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def endElement(self, stack, name, text): - """Handle <bsc/> element.""" - if name == "signing_cert": - self.signing_cert = rpki.x509.X509(Base64 = text) - elif name == "signing_cert_crl": - self.signing_cert_crl = rpki.x509.CRL(Base64 = text) - elif name == "pkcs10_request": - self.pkcs10_request = rpki.x509.PKCS10(Base64 = text) - else: - assert name == "bsc", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <bsc/> element.""" - elt = self.make_elt() - if self.signing_cert is not None: - self.make_b64elt(elt, "signing_cert", self.signing_cert.get_DER()) - if self.signing_cert_crl is not None: - self.make_b64elt(elt, "signing_cert_crl", self.signing_cert_crl.get_DER()) - if self.pkcs10_request is not None: - self.make_b64elt(elt, "pkcs10_request", self.pkcs10_request.get_DER()) - return elt - class parent_elt(data_elt): """<parent/> element.""" @@ -373,39 +319,6 @@ class parent_elt(data_elt): for ca in self.cas(): ca.revoke() - def startElement(self, stack, name, attrs): - """Handle <parent/> element.""" - if name not in ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue"): - assert name == "parent", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def endElement(self, stack, name, text): - """Handle <parent/> element.""" - if name == "bpki_cms_cert": - self.bpki_cms_cert = rpki.x509.X509(Base64 = text) - elif name == "bpki_cms_glue": - self.bpki_cms_glue = rpki.x509.X509(Base64 = text) - elif name == "bpki_https_cert": - self.bpki_https_cert = rpki.x509.X509(Base64 = text) - elif name == "bpki_https_glue": - self.bpki_https_glue = rpki.x509.X509(Base64 = text) - else: - assert name == "parent", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <parent/> element.""" - elt = self.make_elt() - if self.bpki_cms_cert and not self.bpki_cms_cert.empty(): - self.make_b64elt(elt, "bpki_cms_cert", self.bpki_cms_cert.get_DER()) - if self.bpki_cms_glue and not self.bpki_cms_glue.empty(): - self.make_b64elt(elt, "bpki_cms_glue", self.bpki_cms_glue.get_DER()) - if self.bpki_https_cert and not self.bpki_https_cert.empty(): - self.make_b64elt(elt, "bpki_https_cert", self.bpki_https_cert.get_DER()) - if self.bpki_https_glue and not self.bpki_https_glue.empty(): - self.make_b64elt(elt, "bpki_https_glue", self.bpki_https_glue.get_DER()) - return elt - def query_up_down(self, q_pdu): """Client code for sending one up-down query PDU to this parent. @@ -492,32 +405,14 @@ class child_elt(data_elt): self.gctx.clear_https_ta_cache() self.clear_https_ta_cache = False - def startElement(self, stack, name, attrs): - """Handle <child/> element.""" - if name not in ("bpki_cert", "bpki_glue"): - assert name == "child", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - def endElement(self, stack, name, text): - """Handle <child/> element.""" - if name == "bpki_cert": - self.bpki_cert = rpki.x509.X509(Base64 = text) - self.clear_https_ta_cache = True - elif name == "bpki_glue": - self.bpki_glue = rpki.x509.X509(Base64 = text) + """Handle subelements of <child/> element. These require special + handling because modifying them invalidates the HTTPS trust anchor + cache. + """ + rpki.xml_utils.data_elt.endElement(self, stack, name, text) + if name in self.elements: self.clear_https_ta_cache = True - else: - assert name == "child", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <child/> element.""" - elt = self.make_elt() - if self.bpki_cert and not self.bpki_cert.empty(): - self.make_b64elt(elt, "bpki_cert", self.bpki_cert.get_DER()) - if self.bpki_glue and not self.bpki_glue.empty(): - self.make_b64elt(elt, "bpki_glue", self.bpki_glue.get_DER()) - return elt def serve_up_down(self, query): """Outer layer of server handling for one up-down PDU from this child.""" @@ -569,39 +464,6 @@ class repository_elt(data_elt): """Fetch all parent objects that link to this repository object.""" return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,)) - def startElement(self, stack, name, attrs): - """Handle <repository/> element.""" - if name not in ("bpki_cms_cert", "bpki_cms_glue", "bpki_https_cert", "bpki_https_glue"): - assert name == "repository", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def endElement(self, stack, name, text): - """Handle <repository/> element.""" - if name == "bpki_cms_cert": - self.bpki_cms_cert = rpki.x509.X509(Base64 = text) - elif name == "bpki_cms_glue": - self.bpki_cms_glue = rpki.x509.X509(Base64 = text) - elif name == "bpki_https_cert": - self.bpki_https_cert = rpki.x509.X509(Base64 = text) - elif name == "bpki_https_glue": - self.bpki_https_glue = rpki.x509.X509(Base64 = text) - else: - assert name == "repository", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <repository/> element.""" - elt = self.make_elt() - if self.bpki_cms_cert: - self.make_b64elt(elt, "bpki_cms_cert", self.bpki_cms_cert.get_DER()) - if self.bpki_cms_glue: - self.make_b64elt(elt, "bpki_cms_glue", self.bpki_cms_glue.get_DER()) - if self.bpki_https_cert: - self.make_b64elt(elt, "bpki_https_cert", self.bpki_https_cert.get_DER()) - if self.bpki_https_glue: - self.make_b64elt(elt, "bpki_https_glue", self.bpki_https_glue.get_DER()) - return elt - @staticmethod def uri_to_filename(base, uri): """Convert a URI to a filename. [TEMPORARY]""" @@ -715,7 +577,9 @@ class route_origin_elt(data_elt): self.unimplemented_control("suppress_publication") def startElement(self, stack, name, attrs): - """Handle <route_origin/> element.""" + """Handle <route_origin/> element. This requires special + processing due to the data types of some of the attributes. + """ assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.as_number is not None: @@ -725,15 +589,6 @@ class route_origin_elt(data_elt): if self.ipv6 is not None: self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6) - def endElement(self, stack, name, text): - """Handle <route_origin/> element.""" - assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <route_origin/> element.""" - return self.make_elt() - def update_roa(self): """Bring this route_origin's ROA up to date if necesssary.""" @@ -884,7 +739,9 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): valid_until = None def startElement(self, stack, name, attrs): - """Handle <list_resources/> element.""" + """Handle <list_resources/> element. This requires special + handling due to the data types of some of the attributes. + """ assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if isinstance(self.valid_until, str): @@ -897,7 +754,9 @@ class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6) def toXML(self): - """Generate <list_resources/> element.""" + """Generate <list_resources/> element. This requires special + handling due to the data types of some of the attributes. + """ elt = self.make_elt() if isinstance(self.valid_until, int): elt.set("valid_until", self.valid_until.toXMLtime()) @@ -909,15 +768,6 @@ class report_error_elt(rpki.xml_utils.base_elt, left_right_namespace): element_name = "report_error" attributes = ("tag", "self_id", "error_code") - def startElement(self, stack, name, attrs): - """Handle <report_error/> element.""" - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def toXML(self): - """Generate <report_error/> element.""" - return self.make_elt() - @classmethod def from_exception(cls, exc, self_id = None): """Generate a <report_error/> element from an exception.""" diff --git a/rpkid/rpki/publication.py b/rpkid/rpki/publication.py index 07fffa83..82ff30ff 100644 --- a/rpkid/rpki/publication.py +++ b/rpkid/rpki/publication.py @@ -26,7 +26,68 @@ class publication_namespace(object): xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/" nsmap = { None : xmlns } -class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, publication_namespace): +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" @@ -41,32 +102,14 @@ class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, publication_n clear_https_ta_cache = False - def startElement(self, stack, name, attrs): - """Handle <client/> element.""" - if name not in ("bpki_cert", "bpki_glue"): - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - def endElement(self, stack, name, text): - """Handle <client/> element.""" - if name == "bpki_cert": - self.bpki_cert = rpki.x509.X509(Base64 = text) - self.clear_https_ta_cache = True - elif name == "bpki_glue": - self.bpki_glue = rpki.x509.X509(Base64 = 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 - else: - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """Generate <client/> element.""" - elt = self.make_elt() - if self.bpki_cert and not self.bpki_cert.empty(): - self.make_b64elt(elt, "bpki_cert", self.bpki_cert.get_DER()) - if self.bpki_glue and not self.bpki_glue.empty(): - self.make_b64elt(elt, "bpki_glue", self.bpki_glue.get_DER()) - return elt def serve_post_save_hook(self, q_pdu, r_pdu): """Extra server actions for client_elt.""" @@ -87,29 +130,22 @@ class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant, publication_n """Find client objects on which a list method should operate.""" return self.sql_fetch_all(self.gctx) - def serve_dispatch(self, r_msg, client): - """Action dispatch handler.""" - if client is not None: - raise rpki.exceptions.BadQuery, "Client query received on control channel" - rpki.xml_utils.data_elt.serve_dispatch(self, r_msg) - 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. + 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 startElement(self, stack, name, attrs): - """Handle a publishable element.""" - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - def endElement(self, stack, name, text): """Handle a publishable element element.""" assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) @@ -127,7 +163,7 @@ class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace): def serve_dispatch(self, r_msg, client): """Action dispatch handler.""" if client is None: - raise rpki.exceptions.BadQuery, "Control query received on client channel" + 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: @@ -200,15 +236,6 @@ class report_error_elt(rpki.xml_utils.base_elt, publication_namespace): element_name = "report_error" attributes = ("tag", "error_code") - def startElement(self, stack, name, attrs): - """Handle <report_error/> element.""" - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - - def toXML(self): - """Generate <report_error/> element.""" - return self.make_elt() - @classmethod def from_exception(cls, exc): """Generate a <report_error/> element from an exception.""" @@ -226,7 +253,7 @@ class msg(rpki.xml_utils.msg, publication_namespace): ## @var pdus # Dispatch table of PDUs for this protocol. pdus = dict((x.element_name, x) - for x in (client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt)) + 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.""" diff --git a/rpkid/rpki/relaxng.py b/rpkid/rpki/relaxng.py index 87230bcf..017209b6 100644 --- a/rpkid/rpki/relaxng.py +++ b/rpkid/rpki/relaxng.py @@ -801,11 +801,6 @@ left_right = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" enc </attribute> </optional> <optional> - <attribute name="exact_match"> - <data type="boolean"/> - </attribute> - </optional> - <optional> <attribute name="ipv4"> <ref name="ipv4_list"/> </attribute> @@ -1257,6 +1252,7 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <!-- PDUs allowed in a query --> <define name="query_elt"> <choice> + <ref name="config_query"/> <ref name="client_query"/> <ref name="certificate_query"/> <ref name="crl_query"/> @@ -1267,6 +1263,7 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <!-- PDUs allowed in a reply --> <define name="reply_elt"> <choice> + <ref name="config_reply"/> <ref name="client_reply"/> <ref name="certificate_reply"/> <ref name="crl_reply"/> @@ -1300,6 +1297,59 @@ publication = lxml.etree.RelaxNG(lxml.etree.fromstring('''<?xml version="1.0" en <ref name="uri_t"/> </attribute> </define> + <!-- + <config/> element (use restricted to repository operator) + config_id attribute and list command omitted deliberately, see code for details + --> + <define name="config_payload"> + <optional> + <element name="bpki_crl"> + <ref name="base64"/> + </element> + </optional> + </define> + <define name="config_query" combine="choice"> + <element name="config"> + <attribute name="action"> + <value>set</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="config_payload"/> + </element> + </define> + <define name="config_reply" combine="choice"> + <element name="config"> + <attribute name="action"> + <value>set</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + </element> + </define> + <define name="config_query" combine="choice"> + <element name="config"> + <attribute name="action"> + <value>get</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + </element> + </define> + <define name="config_reply" combine="choice"> + <element name="config"> + <attribute name="action"> + <value>get</value> + </attribute> + <optional> + <ref name="tag"/> + </optional> + <ref name="config_payload"/> + </element> + </define> <!-- <client/> element (use restricted to repository operator) --> <define name="client_id"> <attribute name="client_id"> diff --git a/rpkid/rpki/xml_utils.py b/rpkid/rpki/xml_utils.py index 97f9c5f7..5e9c613e 100644 --- a/rpkid/rpki/xml_utils.py +++ b/rpkid/rpki/xml_utils.py @@ -117,12 +117,19 @@ class base_elt(object): def startElement(self, stack, name, attrs): """Default startElement() handler: just process attributes.""" - self.read_attrs(attrs) + if name not in self.elements: + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) + self.read_attrs(attrs) def endElement(self, stack, name, text): """Default endElement() handler: just pop the stack.""" + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) stack.pop() + def toXML(self): + """Default toXML() element generator.""" + return self.make_elt() + def read_attrs(self, attrs): """Template-driven attribute reader.""" for key in self.attributes: @@ -172,6 +179,29 @@ class data_elt(base_elt): attribute. """ + def endElement(self, stack, name, text): + """Default endElement handler for SQL-based objects. This assumes + that sub-elements are Base64-encoded using the sql_template mechanism. + """ + if name in self.elements: + elt_type = self.sql_template.map.get(name) + assert elt_type is not None, "Couldn't find element type for %s, stack %s" % (name, stack) + setattr(self, name, elt_type(Base64 = text)) + else: + assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) + stack.pop() + + def toXML(self): + """Default element generator for SQL-based objects. This assumes + that sub-elements are Base64-encoded DER objects. + """ + elt = self.make_elt() + for i in self.elements: + x = getattr(self, i, None) + if x and not x.empty(): + self.make_b64elt(elt, i, x.get_DER()) + return elt + def make_reply(self, r_pdu = None): """Construct a reply PDU.""" if r_pdu is None: |