aboutsummaryrefslogtreecommitdiff
path: root/scripts/rpki/left_right.py
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2008-02-27 19:02:11 +0000
committerRob Austein <sra@hactrn.net>2008-02-27 19:02:11 +0000
commite1bc9584d821857a9a1869f38b934812ff60f7fb (patch)
treec9c1f45818ab9f4e6dacefc1428c9a9dcb67c0fd /scripts/rpki/left_right.py
parenta9ecdddda7c364cd62dbd1c16fc0f19615fe288e (diff)
Filename cleanup
svn path=/rpkid/Makefile; revision=1531
Diffstat (limited to 'scripts/rpki/left_right.py')
-rw-r--r--scripts/rpki/left_right.py1002
1 files changed, 0 insertions, 1002 deletions
diff --git a/scripts/rpki/left_right.py b/scripts/rpki/left_right.py
deleted file mode 100644
index 8a5e3433..00000000
--- a/scripts/rpki/left_right.py
+++ /dev/null
@@ -1,1002 +0,0 @@
-# $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.
-
-"""RPKI "left-right" protocol."""
-
-import base64, lxml.etree, time, traceback, os
-import rpki.sax_utils, rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions
-import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log
-
-xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/"
-
-nsmap = { None : xmlns }
-
-class base_elt(object):
- """Virtual base type for left-right message elements."""
-
- attributes = ()
- elements = ()
- booleans = ()
-
- def startElement(self, stack, name, attrs):
- """Default startElement() handler: just process attributes."""
- self.read_attrs(attrs)
-
- def endElement(self, stack, name, text):
- """Default endElement() handler: just pop the stack."""
- stack.pop()
-
- def read_attrs(self, attrs):
- """Template-driven attribute reader."""
- for key in self.attributes:
- val = attrs.get(key, None)
- if isinstance(val, str) and val.isdigit():
- val = long(val)
- setattr(self, key, val)
- for key in self.booleans:
- setattr(self, key, attrs.get(key, False))
-
- def make_elt(self):
- """XML element constructor."""
- elt = lxml.etree.Element("{%s}%s" % (xmlns, self.element_name), nsmap = nsmap)
- for key in self.attributes:
- val = getattr(self, key, None)
- if val is not None:
- elt.set(key, str(val))
- for key in self.booleans:
- if getattr(self, key, False):
- elt.set(key, "yes")
- return elt
-
- def make_b64elt(self, elt, name, value = None):
- """Constructor for Base64-encoded subelement."""
- if value is None:
- value = getattr(self, name, None)
- if value is not None:
- lxml.etree.SubElement(elt, "{%s}%s" % (xmlns, name), nsmap = nsmap).text = base64.b64encode(value)
-
- def __str__(self):
- """Convert a base_elt object to string format."""
- lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
-
-class data_elt(base_elt, rpki.sql.sql_persistant):
- """Virtual class for top-level left-right protocol data elements."""
-
- def self(this, gctx):
- """Fetch self object to which this object links."""
- return self_elt.sql_fetch(gctx, this.self_id)
-
- def bsc(self, gctx):
- """Return BSC object to which this object links."""
- return bsc_elt.sql_fetch(gctx, self.bsc_id)
-
- @classmethod
- def make_pdu(cls, **kargs):
- """Generic left-right PDU constructor."""
- self = cls()
- for k,v in kargs.items():
- setattr(self, k, v)
- return self
-
- def make_reply(self, r_pdu = None):
- """Construct a reply PDU."""
- if r_pdu is None:
- r_pdu = self.__class__()
- r_pdu.self_id = self.self_id
- setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
- else:
- for b in r_pdu.booleans:
- setattr(r_pdu, b, False)
- r_pdu.action = self.action
- r_pdu.type = "reply"
- r_pdu.tag = self.tag
- return r_pdu
-
- def serve_pre_save_hook(self, gctx, q_pdu, r_pdu):
- """Overridable hook."""
- pass
-
- def serve_post_save_hook(self, gctx, q_pdu, r_pdu):
- """Overridable hook."""
- pass
-
- def serve_create(self, gctx, r_msg):
- """Handle a create action."""
- r_pdu = self.make_reply()
- self.serve_pre_save_hook(gctx, self, r_pdu)
- self.sql_store(gctx)
- setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
- self.serve_post_save_hook(gctx, self, r_pdu)
- r_msg.append(r_pdu)
-
- def serve_fetch_one(self, gctx):
- """Find the object on which a get, set, or destroy method should
- operate. This is a separate method because the self object needs
- to override it.
- """
- where = self.sql_template.index + " = %s AND self_id = %s"
- args = (getattr(self, self.sql_template.index), self.self_id)
- r = self.sql_fetch_where1(gctx, where, args)
- if r is None:
- raise rpki.exceptions.NotFound, "Lookup failed where %s" + (where % args)
- return r
-
- def serve_set(self, gctx, r_msg):
- """Handle a set action."""
- db_pdu = self.serve_fetch_one(gctx)
- r_pdu = self.make_reply()
- for a in db_pdu.sql_template.columns[1:]:
- v = getattr(self, a)
- if v is not None:
- setattr(db_pdu, a, v)
- db_pdu.sql_mark_dirty()
- db_pdu.serve_pre_save_hook(gctx, self, r_pdu)
- db_pdu.sql_store(gctx)
- db_pdu.serve_post_save_hook(gctx, self, r_pdu)
- r_msg.append(r_pdu)
-
- def serve_get(self, gctx, r_msg):
- """Handle a get action."""
- r_pdu = self.serve_fetch_one(gctx)
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def serve_list(self, gctx, r_msg):
- """Handle a list action for non-self objects."""
- for r_pdu in self.sql_fetch_where(gctx, "self_id = %s", (self.self_id,)):
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def serve_destroy(self, gctx, r_msg):
- """Handle a destroy action."""
- db_pdu = self.serve_fetch_one(gctx)
- db_pdu.sql_delete(gctx)
- r_msg.append(self.make_reply())
-
- def serve_dispatch(self, gctx, r_msg):
- """Action dispatch handler."""
- dispatch = { "create" : self.serve_create,
- "set" : self.serve_set,
- "get" : self.serve_get,
- "list" : self.serve_list,
- "destroy" : self.serve_destroy }
- if self.type != "query" or self.action not in dispatch:
- raise rpki.exceptions.BadQuery, "Unexpected query: type %s, action %s" % (self.type, self.action)
- dispatch[self.action](gctx, r_msg)
-
- def unimplemented_control(self, *controls):
- """Uniform handling for unimplemented control operations."""
- unimplemented = [x for x in controls if getattr(self, x, False)]
- if unimplemented:
- raise rpki.exceptions.NotImplementedYet, "Unimplemented control %s" % ", ".join(unimplemented)
-
-class extension_preference_elt(base_elt):
- """Container for extension preferences."""
-
- element_name = "extension_preference"
- attributes = ("name",)
-
- def startElement(self, stack, name, attrs):
- """Handle <extension_preference/> elements."""
- assert name == "extension_preference", "Unexpected name %s, stack %s" % (name, stack)
- self.read_attrs(attrs)
-
- def endElement(self, stack, name, text):
- """Handle <extension_preference/> elements."""
- self.value = text
- stack.pop()
-
- def toXML(self):
- """Generate <extension_preference/> elements."""
- elt = self.make_elt()
- elt.text = self.value
- return elt
-
-class self_elt(data_elt):
- """<self/> element."""
-
- element_name = "self"
- attributes = ("action", "type", "tag", "self_id", "crl_interval")
- elements = ("extension_preference",)
- booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now", "clear_extension_preferences")
-
- sql_template = rpki.sql.template("self", "self_id", "use_hsm", "crl_interval")
-
- self_id = None
- use_hsm = False
- crl_interval = None
-
- def __init__(self):
- """Initialize a self_elt."""
- self.prefs = []
-
- def sql_fetch_hook(self, gctx):
- """Extra SQL fetch actions for self_elt -- handle extension preferences."""
- gctx.cur.execute("SELECT pref_name, pref_value FROM self_pref WHERE self_id = %s", (self.self_id,))
- for name, value in gctx.cur.fetchall():
- e = extension_preference_elt()
- e.name = name
- e.value = value
- self.prefs.append(e)
-
- def sql_insert_hook(self, gctx):
- """Extra SQL insert actions for self_elt -- handle extension preferences."""
- if self.prefs:
- gctx.cur.executemany("INSERT self_pref (self_id, pref_name, pref_value) VALUES (%s, %s, %s)",
- ((e.name, e.value, self.self_id) for e in self.prefs))
-
- def sql_delete_hook(self, gctx):
- """Extra SQL delete actions for self_elt -- handle extension preferences."""
- gctx.cur.execute("DELETE FROM self_pref WHERE self_id = %s", (self.self_id,))
-
- def bscs(self, gctx):
- """Fetch all BSC objects that link to this self object."""
- return bsc_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def repositories(self, gctx):
- """Fetch all repository objects that link to this self object."""
- return repository_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def parents(self, gctx):
- """Fetch all parent objects that link to this self object."""
- return parent_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def children(self, gctx):
- """Fetch all child objects that link to this self object."""
- return child_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def route_origins(self, gctx):
- """Fetch all route_origin objects that link to this self object."""
- return route_origin_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def serve_pre_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for self_elt -- handle extension preferences."""
- rpki.log.trace()
- if self is not q_pdu:
- if q_pdu.clear_extension_preferences:
- self.prefs = []
- self.prefs.extend(q_pdu.prefs)
-
- def serve_post_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for self_elt."""
- rpki.log.trace()
- if q_pdu.rekey:
- self.serve_rekey(gctx)
- if q_pdu.revoke:
- self.serve_revoke(gctx)
- self.unimplemented_control("reissue", "run_now", "publish_world_now")
-
- def serve_rekey(self, gctx):
- """Handle a left-right rekey action for this self."""
- rpki.log.trace()
- for parent in self.parents(gctx):
- parent.serve_rekey(gctx)
-
- def serve_revoke(self, gctx):
- """Handle a left-right revoke action for this self."""
- rpki.log.trace()
- for parent in self.parents(gctx):
- parent.serve_revoke(gctx)
-
- def serve_fetch_one(self, gctx):
- """Find the self object on which a get, set, or destroy method
- should operate.
- """
- r = self.sql_fetch(gctx, self.self_id)
- if r is None:
- raise rpki.exceptions.NotFound
- return r
-
- def serve_list(self, gctx, r_msg):
- """Handle a list action for self objects. This is different from
- the list action for all other objects, where list only works
- within a given self_id context.
- """
- for r_pdu in self.sql_fetch_all(gctx):
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def startElement(self, stack, name, attrs):
- """Handle <self/> element."""
- if name == "extension_preference":
- pref = extension_preference_elt()
- self.prefs.append(pref)
- stack.append(pref)
- pref.startElement(stack, name, attrs)
- else:
- assert name == "self", "Unexpected name %s, stack %s" % (name, stack)
- self.read_attrs(attrs)
-
- def endElement(self, stack, name, text):
- """Handle <self/> element."""
- assert name == "self", "Unexpected name %s, stack %s" % (name, stack)
- stack.pop()
-
- def toXML(self):
- """Generate <self/> element."""
- elt = self.make_elt()
- elt.extend([i.toXML() for i in self.prefs])
- return elt
-
- def client_poll(self, gctx):
- """Run the regular client poll cycle with each of this self's parents in turn."""
-
- rpki.log.trace()
-
- for parent in self.parents(gctx):
-
- # This will need a callback when we go event-driven
- r_msg = rpki.up_down.list_pdu.query(gctx, parent)
-
- ca_map = dict((ca.parent_resource_class, ca) for ca in parent.cas(gctx))
- for rc in r_msg.payload.classes:
- if rc.class_name in ca_map:
- ca = ca_map[rc.class_name]
- del ca_map[rc.class_name]
- ca.check_for_updates(gctx, parent, rc)
- else:
- rpki.sql.ca_obj.create(gctx, parent, rc)
- for ca in ca_map.values():
- ca.delete(gctx, parent) # CA not listed by parent
- rpki.sql.sql_sweep(gctx)
-
- def update_children(self, gctx):
- """Check for updated IRDB data for all of this self's children and
- issue new certs as necessary. Must handle changes both in
- resources and in expiration date.
- """
-
- rpki.log.trace()
-
- now = rpki.sundial.datetime.utcnow()
-
- for child in self.children(gctx):
- child_certs = child.child_certs(gctx)
- if not child_certs:
- continue
-
- # This will require a callback when we go event-driven
- irdb_resources = rpki.left_right.irdb_query(gctx, child.self_id, child.child_id)
-
- for child_cert in child_certs:
- ca_detail = child_cert.ca_detail(gctx)
- if ca_detail.state != "active":
- continue
- old_resources = child_cert.cert.get_3779resources()
- new_resources = irdb_resources.intersection(old_resources)
- if old_resources != new_resources:
- rpki.log.debug("Need to reissue %s" % repr(child_cert))
- child_cert.reissue(
- gctx = gctx,
- ca_detail = ca_detail,
- resources = new_resources)
- elif old_resources.valid_until < now:
- parent = ca.parent(gctx)
- repository = parent.repository(gctx)
- child_cert.sql_delete(gctx)
- ca_detail.generate_manifest(gctx)
- repository.withdraw(gctx, child_cert.cert, child_cert.uri(ca))
-
- def regenerate_crls_and_manifests(self, gctx):
- """Generate new CRLs and manifests as necessary for all of this
- self's CAs. Extracting nextUpdate from a manifest is hard at the
- moment due to implementation silliness, so for now we generate a
- new manifest whenever we generate a new CRL
-
- This method also cleans up tombstones left behind by revoked
- ca_detail objects, since we're walking through the relevant
- portions of the database anyway.
- """
-
- rpki.log.trace()
-
- now = rpki.sundial.datetime.utcnow()
- for parent in self.parents(gctx):
- repository = parent.repository(gctx)
- for ca in parent.cas(gctx):
- for ca_detail in ca.fetch_revoked(gctx):
- if now > ca_detail.latest_crl.getNextUpdate():
- ca_detail.delete(gctx, ca, repository)
- ca_detail = ca.fetch_active(gctx)
- if now > ca_detail.latest_crl.getNextUpdate():
- ca_detail.generate_crl(gctx)
- ca_detail.generate_manifest(gctx)
-
-class bsc_elt(data_elt):
- """<bsc/> (Business Signing Context) element."""
-
- element_name = "bsc"
- attributes = ("action", "type", "tag", "self_id", "bsc_id", "key_type", "hash_alg", "key_length")
- elements = ('signing_cert',)
- booleans = ("generate_keypair", "clear_signing_certs")
-
- sql_template = rpki.sql.template("bsc", "bsc_id", "self_id",
- ("public_key", rpki.x509.RSApublic),
- ("private_key_id", rpki.x509.RSA), "hash_alg")
-
- pkcs10_cert_request = None
- public_key = None
- private_key_id = None
-
- def __init__(self):
- """Initialize bsc_elt."""
- self.signing_cert = rpki.x509.X509_chain()
-
- def sql_fetch_hook(self, gctx):
- """Extra SQL fetch actions for bsc_elt -- handle signing certs."""
- gctx.cur.execute("SELECT cert FROM bsc_cert WHERE bsc_id = %s", (self.bsc_id,))
- self.signing_cert[:] = [rpki.x509.X509(DER = x) for (x,) in gctx.cur.fetchall()]
-
- def sql_insert_hook(self, gctx):
- """Extra SQL insert actions for bsc_elt -- handle signing certs."""
- if self.signing_cert:
- gctx.cur.executemany("INSERT bsc_cert (cert, bsc_id) VALUES (%s, %s)",
- ((x.get_DER(), self.bsc_id) for x in self.signing_cert))
-
- def sql_delete_hook(self, gctx):
- """Extra SQL delete actions for bsc_elt -- handle signing certs."""
- gctx.cur.execute("DELETE FROM bsc_cert WHERE bsc_id = %s", (self.bsc_id,))
-
- def repositories(self, gctx):
- """Fetch all repository objects that link to this BSC object."""
- return repository_elt.sql_fetch_where(gctx, "bsc_id = %s", (self.bsc_id,))
-
- def parents(self, gctx):
- """Fetch all parent objects that link to this BSC object."""
- return parent_elt.sql_fetch_where(gctx, "bsc_id = %s", (self.bsc_id,))
-
- def children(self, gctx):
- """Fetch all child objects that link to this BSC object."""
- return child_elt.sql_fetch_where(gctx, "bsc_id = %s", (self.bsc_id,))
-
- def serve_pre_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for bsc_elt -- handle signing certs and key generation."""
- if self is not q_pdu:
- if q_pdu.clear_signing_certs:
- self.signing_cert[:] = []
- self.signing_cert.extend(q_pdu.signing_cert)
- if q_pdu.generate_keypair:
- #
- # For the moment we only support 2048-bit RSA with SHA-256, no
- # HSM. Assertion just checks that the schema hasn't changed out
- # from under this code.
- #
- assert (q_pdu.key_type is None or q_pdu.key_type == "rsa") and \
- (q_pdu.hash_alg is None or q_pdu.hash_alg == "sha256") and \
- (q_pdu.key_length is None or q_pdu.key_length == 2048)
- keypair = rpki.x509.RSA()
- keypair.generate()
- self.private_key_id = keypair
- self.public_key = keypair.get_RSApublic()
- r_pdu.pkcs10_cert_request = rpki.x509.PKCS10.create(keypair)
-
- def startElement(self, stack, name, attrs):
- """Handle <bsc/> element."""
- if not name in ("signing_cert", "public_key", "pkcs10_cert_request"):
- 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.append(rpki.x509.X509(Base64 = text))
- elif name == "public_key":
- self.public_key = rpki.x509.RSApublic(Base64 = text)
- elif name == "pkcs10_cert_request":
- self.pkcs10_cert_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()
- for cert in self.signing_cert:
- self.make_b64elt(elt, "signing_cert", cert.get_DER())
- if self.pkcs10_cert_request is not None:
- self.make_b64elt(elt, "pkcs10_cert_request", self.pkcs10_cert_request.get_DER())
- if self.public_key is not None:
- self.make_b64elt(elt, "public_key", self.public_key.get_DER())
- return elt
-
-class parent_elt(data_elt):
- """<parent/> element."""
-
- element_name = "parent"
- attributes = ("action", "type", "tag", "self_id", "parent_id", "bsc_id", "repository_id",
- "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
- elements = ("cms_ta", "https_ta")
- booleans = ("rekey", "reissue", "revoke")
-
- sql_template = rpki.sql.template("parent", "parent_id", "self_id", "bsc_id", "repository_id",
- ("cms_ta", rpki.x509.X509), ("https_ta", rpki.x509.X509),
- "peer_contact_uri", "sia_base", "sender_name", "recipient_name")
-
- cms_ta = None
- https_ta = None
-
- def repository(self, gctx):
- """Fetch repository object to which this parent object links."""
- return repository_elt.sql_fetch(gctx, self.repository_id)
-
- def cas(self, gctx):
- """Fetch all CA objects that link to this parent object."""
- return rpki.sql.ca_obj.sql_fetch_where(gctx, "parent_id = %s", (self.parent_id,))
-
- def serve_post_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for parent_elt."""
- if q_pdu.rekey:
- self.serve_rekey(gctx)
- if q_pdu.revoke:
- self.serve_revoke(gctx)
- self.unimplemented_control("reissue")
-
- def serve_rekey(self, gctx):
- """Handle a left-right rekey action for this parent."""
- for ca in self.cas(gctx):
- ca.rekey(gctx)
-
- def serve_revoke(self, gctx):
- """Handle a left-right revoke action for this parent."""
- for ca in self.cas(gctx):
- ca.revoke(gctx)
-
- def startElement(self, stack, name, attrs):
- """Handle <parent/> element."""
- if name not in ("cms_ta", "https_ta"):
- 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 == "cms_ta":
- self.cms_ta = rpki.x509.X509(Base64 = text)
- elif name == "https_ta":
- self.https_ta = 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.cms_ta and not self.cms_ta.empty():
- self.make_b64elt(elt, "cms_ta", self.cms_ta.get_DER())
- if self.https_ta and not self.https_ta.empty():
- self.make_b64elt(elt, "https_ta", self.https_ta.get_DER())
- return elt
-
- def query_up_down(self, gctx, q_pdu):
- """Client code for sending one up-down query PDU to this parent.
-
- I haven't figured out yet whether this method should do something
- clever like dispatching via a method in the response PDU payload,
- or just hand back the whole response to the caller. In the long
- run this will have to become event driven with a context object
- that has methods of its own, but as this method is common code for
- several different queries and I don't yet know what the response
- processing looks like, it's too soon to tell what will make sense.
-
- For now, keep this dead simple lock step, rewrite it later.
- """
-
- rpki.log.trace()
-
- bsc = self.bsc(gctx)
- if bsc is None:
- raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
- q_msg = rpki.up_down.message_pdu.make_query(
- payload = q_pdu,
- sender = self.sender_name,
- recipient = self.recipient_name)
- q_elt = q_msg.toXML()
- try:
- rpki.relaxng.up_down.assertValid(q_elt)
- except lxml.etree.DocumentInvalid:
- rpki.log.error("Message does not pass schema check: " + lxml.etree.tostring(q_elt, pretty_print = True))
- raise
- q_cms = rpki.cms.xml_sign(q_elt, bsc.private_key_id, bsc.signing_cert, encoding = "UTF-8")
- r_cms = rpki.https.client(x509TrustList = rpki.x509.X509_chain(self.https_ta),
- privateKey = gctx.https_key,
- certChain = gctx.https_certs,
- msg = q_cms,
- url = self.peer_contact_uri)
- r_elt = rpki.cms.xml_verify(r_cms, self.cms_ta)
- rpki.relaxng.up_down.assertValid(r_elt)
- r_msg = rpki.up_down.sax_handler.saxify(r_elt)
- r_msg.payload.check_response()
- return r_msg
-
-
-class child_elt(data_elt):
- """<child/> element."""
-
- element_name = "child"
- attributes = ("action", "type", "tag", "self_id", "child_id", "bsc_id")
- elements = ("cms_ta",)
- booleans = ("reissue", )
-
- sql_template = rpki.sql.template("child", "child_id", "self_id", "bsc_id", ("cms_ta", rpki.x509.X509))
-
- cms_ta = None
-
- def child_certs(self, gctx, ca_detail = None, ski = None, revoked = False, unique = False):
- """Fetch all child_cert objects that link to this child object."""
- return rpki.sql.child_cert_obj.fetch(gctx, self, ca_detail, ski, revoked, unique)
-
- def parents(self, gctx):
- """Fetch all parent objects that link to self object to which this child object links."""
- return parent_elt.sql_fetch_where(gctx, "self_id = %s", (self.self_id,))
-
- def ca_from_class_name(self, gctx, class_name):
- """Fetch the CA corresponding to an up-down class_name."""
- if not class_name.isdigit():
- raise rpki.exceptions.BadClassNameSyntax, "Bad class name %s" % class_name
- ca = rpki.sql.ca_obj.sql_fetch(gctx, long(class_name))
- parent = ca.parent(gctx)
- if self.self_id != parent.self_id:
- raise rpki.exceptions.ClassNameMismatch, "child.self_id = %d, parent.self_id = %d" % (self.self_id, parent.self_id)
- return ca
-
- def serve_post_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for child_elt."""
- self.unimplemented_control("reissue")
-
- def startElement(self, stack, name, attrs):
- """Handle <child/> element."""
- if name != "cms_ta":
- 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 == "cms_ta":
- self.cms_ta = rpki.x509.X509(Base64 = text)
- 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.cms_ta:
- self.make_b64elt(elt, "cms_ta", self.cms_ta.get_DER())
- return elt
-
- def serve_up_down(self, gctx, query):
- """Outer layer of server handling for one up-down PDU from this child."""
-
- rpki.log.trace()
-
- bsc = self.bsc(gctx)
- if bsc is None:
- raise rpki.exceptions.BSCNotFound, "Could not find BSC %s" % self.bsc_id
- q_elt = rpki.cms.xml_verify(query, self.cms_ta)
- rpki.relaxng.up_down.assertValid(q_elt)
- q_msg = rpki.up_down.sax_handler.saxify(q_elt)
- #if q_msg.sender != str(self.child_id):
- # raise rpki.exceptions.BadSender, "Unexpected XML sender %s" % q_msg.sender
- try:
- r_msg = q_msg.serve_top_level(gctx, self)
- except Exception, data:
- rpki.log.error(traceback.format_exc())
- r_msg = q_msg.serve_error(data)
- #
- # Exceptions from this point on are problematic, as we have no
- # sane way of reporting errors in the error reporting mechanism.
- # May require refactoring, ignore the issue for now.
- #
- r_elt = r_msg.toXML()
- try:
- rpki.relaxng.up_down.assertValid(r_elt)
- except:
- rpki.log.debug(lxml.etree.tostring(r_elt, pretty_print = True, encoding = "UTF-8"))
- rpki.log.error(traceback.format_exc())
- raise
- return rpki.cms.xml_sign(r_elt, bsc.private_key_id, bsc.signing_cert, encoding = "UTF-8")
-
-class repository_elt(data_elt):
- """<repository/> element."""
-
- element_name = "repository"
- attributes = ("action", "type", "tag", "self_id", "repository_id", "bsc_id", "peer_contact_uri")
- elements = ("cms_ta", "https_ta")
-
- sql_template = rpki.sql.template("repository", "repository_id", "self_id", "bsc_id",
- ("cms_ta", rpki.x509.X509), "peer_contact_uri",
- ("https_ta", rpki.x509.X509))
-
- cms_ta = None
- https_ta = None
-
- def parents(self, gctx):
- """Fetch all parent objects that link to this repository object."""
- return parent_elt.sql_fetch_where(gctx, "repository_id = %s", (self.repository_id,))
-
- def startElement(self, stack, name, attrs):
- """Handle <repository/> element."""
- if name not in ("cms_ta", "https_ta"):
- 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 == "cms_ta":
- self.cms_ta = rpki.x509.X509(Base64 = text)
- elif name == "https_ta":
- self.https_ta = 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.cms_ta:
- self.make_b64elt(elt, "cms_ta", self.cms_ta.get_DER())
- if self.https_ta:
- self.make_b64elt(elt, "https_ta", self.https_ta.get_DER())
- return elt
-
- @staticmethod
- def uri_to_filename(base, uri):
- """Convert a URI to a filename. [TEMPORARY]"""
- if not uri.startswith("rsync://"):
- raise rpki.exceptions.BadURISyntax
- filename = base + uri[len("rsync://"):]
- if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."):
- raise rpki.exceptions.BadURISyntax
- return filename
-
- @classmethod
- def object_write(cls, base, uri, obj):
- """Write an object to disk. [TEMPORARY]"""
- rpki.log.trace()
- filename = cls.uri_to_filename(base, uri)
- dirname = os.path.dirname(filename)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
- f = open(filename, "wb")
- f.write(obj.get_DER())
- f.close()
-
- @classmethod
- def object_delete(cls, base, uri):
- """Delete an object from disk. [TEMPORARY]"""
- rpki.log.trace()
- os.remove(cls.uri_to_filename(base, uri))
-
- def publish(self, gctx, obj, uri):
- """Placeholder for publication operation. [TEMPORARY]"""
- rpki.log.trace()
- rpki.log.info("Publishing %s to repository %s at %s" % (repr(obj), repr(self), repr(uri)))
- self.object_write(gctx.publication_kludge_base, uri, obj)
-
- def withdraw(self, gctx, obj, uri):
- """Placeholder for publication withdrawal operation. [TEMPORARY]"""
- rpki.log.trace()
- rpki.log.info("Withdrawing %s from repository %s at %s" % (repr(obj), repr(self), repr(uri)))
- self.object_delete(gctx.publication_kludge_base, uri)
-
-class route_origin_elt(data_elt):
- """<route_origin/> element."""
-
- element_name = "route_origin"
- attributes = ("action", "type", "tag", "self_id", "route_origin_id", "as_number", "ipv4", "ipv6")
- booleans = ("suppress_publication",)
-
- sql_template = rpki.sql.template("route_origin", "route_origin_id", "self_id", "as_number",
- "ca_detail_id", "roa")
-
- ca_detail_id = None
- roa = None
-
- def sql_fetch_hook(self, gctx):
- """Extra SQL fetch actions for route_origin_elt -- handle address ranges."""
- self.ipv4 = rpki.resource_set.resource_set_ipv4.from_sql(gctx.cur, """
- SELECT start_ip, end_ip FROM route_origin_range
- WHERE route_origin_id = %s AND start_ip NOT LIKE '%:%'
- """, (self.route_origin_id,))
- self.ipv6 = rpki.resource_set.resource_set_ipv6.from_sql(gctx.cur, """
- SELECT start_ip, end_ip FROM route_origin_range
- WHERE route_origin_id = %s AND start_ip LIKE '%:%'
- """, (self.route_origin_id,))
-
- def sql_insert_hook(self, gctx):
- """Extra SQL insert actions for route_origin_elt -- handle address ranges."""
- if self.ipv4 + self.ipv6:
- gctx.cur.executemany("""
- INSERT route_origin_range (route_origin_id, start_ip, end_ip)
- VALUES (%s, %s, %s)""",
- ((self.route_origin_id, x.min, x.max) for x in self.ipv4 + self.ipv6))
-
- def sql_delete_hook(self, gctx):
- """Extra SQL delete actions for route_origin_elt -- handle address ranges."""
- gctx.cur.execute("DELETE FROM route_origin_range WHERE route_origin_id = %s", (self.route_origin_id,))
-
- def ca_detail(self, gctx):
- """Fetch all ca_detail objects that link to this route_origin object."""
- return rpki.sql.ca_detail_obj.sql_fetch(gctx, self.ca_detail_id)
-
- def serve_post_save_hook(self, gctx, q_pdu, r_pdu):
- """Extra server actions for route_origin_elt."""
- self.unimplemented_control("suppress_publication")
-
- def startElement(self, stack, name, attrs):
- """Handle <route_origin/> element."""
- assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack)
- self.read_attrs(attrs)
- if self.as_number is not None:
- self.as_number = long(self.as_number)
- if self.ipv4 is not None:
- self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
- if self.ipv6 is not None:
- self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv4)
-
- 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()
-
-class list_resources_elt(base_elt):
- """<list_resources/> element."""
-
- element_name = "list_resources"
- attributes = ("type", "self_id", "tag", "child_id", "valid_until", "as", "ipv4", "ipv6", "subject_name")
- valid_until = None
-
- def startElement(self, stack, name, attrs):
- """Handle <list_resources/> element."""
- assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack)
- self.read_attrs(attrs)
- if isinstance(self.valid_until, str):
- self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until)
- if self.as is not None:
- self.as = rpki.resource_set.resource_set_as(self.as)
- if self.ipv4 is not None:
- self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4)
- if self.ipv6 is not None:
- self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6)
-
- def toXML(self):
- """Generate <list_resources/> element."""
- elt = self.make_elt()
- if isinstance(self.valid_until, int):
- elt.set("valid_until", self.valid_until.toXMLtime())
- return elt
-
-class report_error_elt(base_elt):
- """<report_error/> element."""
-
- 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."""
- self = cls()
- self.self_id = self_id
- self.error_code = exc.__class__.__name__
- return self
-
-class msg(list):
- """Left-right 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 (self_elt, child_elt, parent_elt, bsc_elt, repository_elt,
- route_origin_elt, list_resources_elt, report_error_elt))
-
- def startElement(self, stack, name, attrs):
- """Handle left-right PDU."""
- if name == "msg":
- assert self.version == int(attrs["version"])
- else:
- elt = self.pdus[name]()
- self.append(elt)
- stack.append(elt)
- elt.startElement(stack, name, attrs)
-
- def endElement(self, stack, name, text):
- """Handle left-right PDU."""
- assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
- assert len(stack) == 1
- stack.pop()
-
- def __str__(self):
- """Convert msg object to string."""
- lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
-
- def toXML(self):
- """Generate left-right PDU."""
- elt = lxml.etree.Element("{%s}msg" % (xmlns), nsmap = nsmap, version = str(self.version))
- elt.extend([i.toXML() for i in self])
- return elt
-
- def serve_top_level(self, gctx):
- """Serve one msg PDU."""
- r_msg = self.__class__()
- for q_pdu in self:
- q_pdu.serve_dispatch(gctx, r_msg)
- return r_msg
-
-class sax_handler(rpki.sax_utils.handler):
- """SAX handler for Left-Right protocol."""
-
- ## @var pdu
- # Top-level PDU class
- pdu = msg
-
- def create_top_level(self, name, attrs):
- """Top-level PDU for this protocol is <msg/>."""
- assert name == "msg" and attrs["version"] == "1"
- return self.pdu()
-
-def irdb_query(gctx, self_id, child_id = None):
- """Perform an IRDB callback query. In the long run this should not
- be a blocking routine, it should instead issue a query and set up a
- handler to receive the response. For the moment, though, we are
- doing simple lock step and damn the torpedos. Not yet doing
- anything useful with subject name. Most likely this function should
- really be wrapped up in a class that carries both the query result
- and also the intermediate state needed for the event-driven code
- that this function will need to become.
- """
-
- rpki.log.trace()
-
- q_msg = msg()
- q_msg.append(list_resources_elt())
- q_msg[0].type = "query"
- q_msg[0].self_id = self_id
- q_msg[0].child_id = child_id
- q_elt = q_msg.toXML()
- rpki.relaxng.left_right.assertValid(q_elt)
- q_cms = rpki.cms.xml_sign(q_elt, gctx.cms_key, gctx.cms_certs)
- r_cms = rpki.https.client(
- privateKey = gctx.https_key,
- certChain = gctx.https_certs,
- x509TrustList = gctx.https_ta,
- url = gctx.irdb_url,
- msg = q_cms)
- r_elt = rpki.cms.xml_verify(r_cms, gctx.cms_ta_irdb)
- rpki.relaxng.left_right.assertValid(r_elt)
- r_msg = rpki.left_right.sax_handler.saxify(r_elt)
- if len(r_msg) == 0 or not isinstance(r_msg[0], list_resources_elt) or r_msg[0].type != "reply":
- raise rpki.exceptions.BadIRDBReply, "Unexpected response to IRDB query: %s" % lxml.etree.tostring(r_msg.toXML(), pretty_print = True, encoding = "us-ascii")
- return rpki.resource_set.resource_bag(
- as = r_msg[0].as,
- v4 = r_msg[0].ipv4,
- v6 = r_msg[0].ipv6,
- valid_until = r_msg[0].valid_until)