aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki')
-rw-r--r--rpkid/rpki/exceptions.py3
-rw-r--r--rpkid/rpki/left_right.py182
-rw-r--r--rpkid/rpki/publication.py123
-rw-r--r--rpkid/rpki/relaxng.py60
-rw-r--r--rpkid/rpki/xml_utils.py32
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: