# $Id$ """RPKI "left-right" protocol.""" import base64, rpki.sax_utils, rpki.resource_set, lxml.etree, rpki.x509, rpki.sql 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 = () 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: setattr(self, key, attrs.get(key, None)) 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): lxml.etree.tostring(self.toXML(), pretty_print=True, encoding="us-ascii") class data_elt(base_elt): """Virtual type for a left-right protocol message elements representing top-level persistant data elements. """ pass class extension_preference_elt(base_elt, rpki.sql.sql_persistant): """Container for extension preferences.""" element_name = "extension_preference" attributes = ("name",) sql_select_cmd = """SELECT pref_name, pref_value FROM self_pref WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT self_pref (self_id, pref_name, pref_value) VALUES (%(self_id)s, %(name)s, %(value)s""" sql_update_cmd = """UPDATE self_pref SET pref_value = %(value)s WHERE self_id = %(self_id)s AND pref_name = %(name)s""" sql_delete_cmd = """DELETE FROM self_pref WHERE self_id = %(self_id)s AND pref_name = %(name)s""" def sql_decode(self, sql_parent, name, value): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.name = name self.value = value def sql_encode(self): return { "self_id" : self.self_obj.self_id, "name" : self.name, "value" : self.value } def startElement(self, stack, name, attrs): """Handle elements.""" assert name == "extension_preference", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): """Handle elements.""" self.value = text stack.pop() def toXML(self): """Generate elements.""" elt = self.make_elt() elt.text = self.value return elt class bsc_elt(data_elt, rpki.sql.sql_persistant): """ (Business Signing Context) element.""" element_name = "bsc" attributes = ("action", "type", "self_id", "bsc_id", "key_type", "hash_alg", "key_length") booleans = ("generate_keypair",) sql_id_name = "bsc_id" sql_select_cmd = """SELECT bsc_id, pub_key, priv_key_id FROM bsc WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT bsc (self_id, pub_key, priv_key_id) VALUES (%(self_id)s, %(pub_key)s, %(priv_key_id)s""" sql_update_cmd = """UPDATE bsc SET self_id = %(self_id)s, pub_key = %(pub_key)s, priv_key_id = %(priv_key_id)s WHERE bsc_id = %(bsc_id)s""" sql_delete_cmd = """DELETE FROM bsc WHERE bsc_id = %(bsc_id)s""" pkcs10_cert_request = None public_key = None def __init__(self): self.signing_cert = [] def sql_decode(self, sql_parent, bsc_id, pub_key, priv_key_id): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.bsc_id = bsc_id self.self_id = self_id self.pub_key = pub_key self.priv_key_id = priv_key_id def sql_encode(self): return { "self_id" : self.self_obj.self_id, "bsc_id" : self.bsc_id, "pub_key" : self.pub_key, "priv_key_id" : self.priv_key_id } def sql_fetch_hook(self, db, cur): 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 cur.fetchall()] def sql_insert_hook(self, db, cur): 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, db, cur): cur.execute("""DELETE FROM bsc_cert WHERE bsc_id = %s""", self.bsc_id) def startElement(self, stack, name, attrs): """Handle 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 element.""" if name == "signing_cert": self.signing_cert.append(rpki.x509.X509(DER=base64.b64decode(text))) elif name == "public_key": self.public_key = base64.b64decode(text) elif name == "pkcs10_cert_request": self.pkcs10_cert_request = rpki.x509.PKCS10_Request(DER=base64.b64decode(text)) else: assert name == "bsc", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate 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()) self.make_b64elt(elt, "public_key") return elt class parent_elt(data_elt, rpki.sql.sql_persistant): """ element.""" element_name = "parent" attributes = ("action", "type", "self_id", "parent_id", "bsc_link", "repository_link", "peer_contact", "sia_base") booleans = ("rekey", "reissue", "revoke") sql_id_name = "parent_id" sql_select_cmd = """SELECT parent_id, ta, uri, sia_base, bsc_id, repos_id FROM parent WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT parent (ta, url, sia_base, self_id, bsc_id, repos_id) VALUES (%(ta)s, %(url)s, %(sia_base)s, %(self_id)s, %(bsc_id)s, %(repos_id)s)""" sql_update_cmd = """UPDATE repos SET ta = %(ta)s, uri = %(uri)s, sia_base = %(sia_base)s, self_id = %(self_id)s, bsc_id = %(bsc_id)s, repos_id = %(repos_id)s WHERE parent_id = %(parent_id)s""" sql_delete_cmd = """DELETE FROM parent WHERE parent_id = %(parent_id)s""" sql_children = (("cas", rpki.sql.ca_obj),) def sql_decode(self, sql_parent, parent_id, ta, uri, sia_base, bsc_id, repos_id): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.bsc_obj = bsc_elt.sql_cache_find(bsc_id) self.repository_obj = repository_elt.sql_cache_find(repos_id) self.parent_id = parent_id self.peer_contact = uri self.peer_ta = rpki.x509.X509(DER=ta) def sql_encode(self): return { "self_id" : self.self_obj.self_id, "bsc_id" : self.bsc_obj.bsc_id, "repos_id" : self.repository_obj.repository_id, "parent_id" : self.parent_id, "uri" : self.peer_contact, "ta" : self.peer_ta.get_DER(), "sia_head" : self.sia_head } peer_ta = None def startElement(self, stack, name, attrs): """Handle element.""" if name != "peer_ta": assert name == "parent", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): """Handle element.""" if name == "peer_ta": self.peer_ta = rpki.x509.X509(DER=base64.b64decode(text)) else: assert name == "parent", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.get_DER()) return elt class child_elt(data_elt, rpki.sql.sql_persistant): """ element.""" element_name = "child" attributes = ("action", "type", "self_id", "child_id", "bsc_link", "child_db_id") booleans = ("reissue", ) sql_id_name = "child_id" sql_select_cmd = """SELECT child_id, ta, bsc_id FROM child WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT child (ta, self_id, bsc_id) VALUES (%(ta)s, %(self_id)s, %(bsc_id)s)""" sql_update_cmd = """UPDATE repos SET ta = %(ta)s, self_id = %(self_id)s, bsc_id = %(bsc_id)s WHERE child_id = %(child_id)s""" sql_delete_cmd = """DELETE FROM child WHERE child_id = %(child_id)s""" def sql_decode(self, sql_parent, child_id, ta, bsc_id): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.bsc_obj = bsc_elt.sql_cache_find(bsc_id) self.child_id = child_id self.peer_ta = rpki.x509.X509(DER=ta) def sql_encode(self): return { "self_id" : self.self_obj.self_id, "bsc_id" : self.bsc_obj.bsc_id, "child_id" : self.child_id, "ta" : self.peer_ta.get_DER() } def sql_fetch_hook(self, db, cur): cur.execute("""SELECT ca_id FROM child_ca_link WHERE child_id = %s""", self.child_id) self.cas = [rpki.sql.ca.sql_cache_find(ca_id) for (ca_id,) in cur.fetchall()] for ca in self.cas: ca.children.append(self) cur.execute("""SELECT ca_detail_id, cert FROM child_ca_certificate WHERE child_id = %s""", self.child_id) self.certs = [] for (ca_detail_id, cert) in cur.fetchall(): ca_detail = rpki.sql.ca_detail.sql_cache_find(ca_detail_id) c = rpki.x509.X509(DER=cert) c.child_id = self.child_id c.ca_detail_id = ca_detail_id self.certs.append(c) ca_detail.certs.append(c) def sql_insert_hook(self, db, cur): cur.executemany("""INSERT child_ca_link (ca_id, child_id) VALUES (%s, %s)""", [(x.ca_id, self.child_id) for x in self.cas]) cur.executemany("""INSERT child_ca_certificate (child_id, ca_detail_id, cert) VALUES (%s, %s, %s)""", [(self.child_id, c.ca_detail_id, c) for c in self.certs]) def sql_delete_hook(self, db, cur): cur.execute("""DELETE FROM child_ca_link where child_id = %s""", self.child_id) cur.execute("""DELETE FROM child_ca_certificate where child_id = %s""", self.child_id) peer_ta = None def startElement(self, stack, name, attrs): """Handle element.""" if name != "peer_ta": assert name == "child", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): """Handle element.""" if name == "peer_ta": self.peer_ta = rpki.x509.X509(DER=base64.b64decode(text)) else: assert name == "child", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.get_DER()) return elt class repository_elt(data_elt, rpki.sql.sql_persistant): """ element.""" element_name = "repository" attributes = ("action", "type", "self_id", "repository_id", "bsc_link", "peer_contact") sql_id_name = "repos_id" sql_select_cmd = """SELECT bsc_id, repos_id, uri, ta FROM repos WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT repos (uri, ta, bsc_id, self_id) VALUES (%(uri)s, %(ta)s, %(bsc_id)s, %(self_id)s)""" sql_update_cmd = """UPDATE repos SET uri = %(uri)s, ta = %(ta)s, bsc_id = %(bsc_id)s, self_id = %(self_id)s WHERE repos_id = %(repos_id)s""" sql_delete_cmd = """DELETE FROM repos WHERE repos_id = %(repos_id)s""" def sql_decode(self, sql_parent, bsc_id, repos_id, uri, ta): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.bsc_obj = bsc_elt.sql_cache_find(bsc_id) self.repository_id = repos_id self.peer_contact = uri self.peer_ta = rpki.x509.X509(DER=ta) def sql_encode(self): return { "self_id" : self.self_obj.self_id, "bsc_id" : self.bsc_obj.bsc_id, "repos_id" : self.repository_id, "uri" : self.peer_contact, "ta" : self.peer_ta.get_DER() } peer_ta = None def startElement(self, stack, name, attrs): """Handle element.""" if name != "peer_ta": assert name == "repository", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): """Handle element.""" if name == "peer_ta": self.peer_ta = rpki.x509.X509(DER=base64.b64decode(text)) else: assert name == "repository", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.get_DER()) return elt class route_origin_elt(data_elt, rpki.sql.sql_persistant): """ element.""" element_name = "route_origin" attributes = ("action", "type", "self_id", "route_origin_id", "asn", "ipv4", "ipv6") booleans = ("suppress_publication",) sql_id_name = "route_origin_id" sql_select_cmd = """SELECT route_origin_id, as_number FROM route_origin WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT route_origin (as_number, self_id) VALUES (%(as_number)s, %(self_id)s)""" sql_update_cmd = """UPDATE route_origin SET as_number = %(as_number)s, self_id = %(self_id)s WHERE repos_id = %(route_origin_id)s""" sql_delete_cmd = """DELETE FROM route_origin WHERE repos_id = %(route_origin_id)s""" def sql_decode(self, sql_parent, route_origin_id, as_number): assert isinstance(sql_parent, self_elt) self.self_obj = sql_parent self.asn = as_number self.route_origin = route_origin_id def sql_encode(self): return { "self_id" : self.self_obj.self_id, "route_origin_id" : self.route_origin_id, "as_number" : self.asn } def sql_fetch_hook(self, db, cur): self.ipv4 = rpki.resource_set.resource_set_ipv4() self.ipv4.from_sql(cur, """SELECT start_ip, end_ip FROM route_origin_prefix WHERE route_origin_id = %s AND start_ip NOT LIKE '%:%'""", self.route_origin_id) self.ipv6 = rpki.resource_set.resource_set_ipv6() self.ipv4.from_sql(cur, """SELECT start_ip, end_ip FROM route_origin_prefix WHERE route_origin_id = %s AND start_ip LIKE '%:%'""", self.route_origin_id) cur.execute("""SELECT roa, ca_detail_id FROM roa WHERE route_origin_id = %s""", self.route_origin_id) self.roas = cur.fetchall() def sql_insert_hook(self, db, cur): cur.executemany("""INSERT route_origin_prefix (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]) cur.executemany("""INSERT roa (route_origin_id, roa, ca_detail_id) VALUES (%s, %s, %s)""", [(self.route_origin_id, x[0], x[1]) for x in self.roas]) def sql_delete_hook(self, db, cur): cur.execute("""DELETE FROM route_origin_prefix WHERE route_origin_id = %s""", self.route_origin_id) cur.execute("""DELETE FROM roa WHERE route_origin_id = %s""", self.route_origin_id) def startElement(self, stack, name, attrs): """Handle element.""" assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.asn is not None: self.asn = long(self.asn) 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 element.""" assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" return self.make_elt() class self_elt(data_elt, rpki.sql.sql_persistant): """ element.""" element_name = "self" attributes = ("action", "type", "self_id") booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now") sql_id_name = "self_id" sql_select_cmd = """SELECT self_id, use_hsm FROM self WHERE self_id = %(self_id)s""" sql_insert_cmd = """INSERT self (use_hsm) VALUES (%(use_hsm)s""" sql_update_cmd = """UPDATE self SET use_hsm = %(use_hsm)s WHERE self_id = %(self_id)s""" sql_delete_cmd = """DELETE FROM self WHERE self_id = %(self_id)s""" sql_children = (("prefs", extension_preference_elt), ("bscs", bsc_elt), ("repos", repository_elt), ("parents", parent_elt), ("children", child_elt), ("route_origins", route_origin_elt)) self_id = None def __init__(self): for k,v in self.sql_children: setattr(self, k, []) def sql_decode(self, sql_parent, self_id, use_hsm): assert sql_parent is None self.self_id = self_id self.use_hsm = use_hsm def sql_encode(self): return { "self_id" : self.self_id, "use_hsm" : self.use_hsm } def startElement(self, stack, name, attrs): """Handle 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 element.""" assert name == "self", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" elt = self.make_elt() elt.extend([i.toXML() for i in self.prefs]) return elt class resource_class_elt(base_elt): """ element.""" element_name = "resource_class" attributes = ("as", "req_as", "ipv4", "req_ipv4", "ipv6", "req_ipv6", "subject_name") def startElement(self, stack, name, attrs): """Handle element.""" assert name == "resource_class", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.as is not None: self.as = rpki.resource_set.resource_set_as(self.as) if self.req_as is not None: self.req_as = rpki.resource_set.resource_set_as(self.req_as) if self.ipv4 is not None: self.ipv4 = rpki.resource_set.resource_set_ipv4(self.ipv4) if self.req_ipv4 is not None: self.req_ipv4 = rpki.resource_set.resource_set_ipv4(self.req_ipv4) if self.ipv6 is not None: self.ipv6 = rpki.resource_set.resource_set_ipv6(self.ipv6) if self.req_ipv6 is not None: self.req_ipv6 = rpki.resource_set.resource_set_ipv6(self.req_ipv6) def endElement(self, stack, name, text): """Handle element.""" assert name == "resource_class", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): """Generate element.""" return self.make_elt() class list_resources_elt(base_elt): """ element.""" element_name = "list_resources" attributes = ("type", "self_id", "child_id", "valid_until") def __init__(self): self.resources = [] def startElement(self, stack, name, attrs): """Handle element.""" if name == "resource_class": rc = resource_class_elt() self.resources.append(rc) stack.append(rc) rc.startElement(stack, name, attrs) else: assert name == "list_resources", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def toXML(self): """Generate element.""" elt = self.make_elt() elt.extend([i.toXML() for i in self.resources]) return elt class report_error_elt(base_elt): """ element.""" element_name = "report_error" attributes = ("self_id", "error_code") def startElement(self, stack, name, attrs): """Handle element.""" assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def toXML(self): """Generate element.""" return self.make_elt() ## 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)]) class msg(list): """Left-right PDU.""" version = 1 def startElement(self, stack, name, attrs): """Handle left-right PDU.""" if name == "msg": assert self.version == int(attrs["version"]) else: elt = 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): 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 class sax_handler(rpki.sax_utils.handler): """SAX handler for Left-Right protocol.""" def create_top_level(self, name, attrs): """Top-level PDU for this protocol is .""" assert name == "msg" and attrs["version"] == "1" return msg()