diff options
Diffstat (limited to 'rpki/left_right.py')
-rw-r--r-- | rpki/left_right.py | 1291 |
1 files changed, 40 insertions, 1251 deletions
diff --git a/rpki/left_right.py b/rpki/left_right.py index c8b6d19b..02b118c0 100644 --- a/rpki/left_right.py +++ b/rpki/left_right.py @@ -22,1270 +22,59 @@ RPKI "left-right" protocol. """ import logging -import rpki.resource_set + import rpki.x509 -import rpki.sql import rpki.exceptions -import rpki.xml_utils -import rpki.http import rpki.up_down import rpki.relaxng import rpki.sundial import rpki.log import rpki.publication -import rpki.async import rpki.rpkid_tasks -logger = logging.getLogger(__name__) - -## @var enforce_strict_up_down_xml_sender -# Enforce strict checking of XML "sender" field in up-down protocol - -enforce_strict_up_down_xml_sender = False - -class left_right_namespace(object): - """ - XML namespace parameters for left-right protocol. - """ - - xmlns = rpki.relaxng.left_right.xmlns - nsmap = rpki.relaxng.left_right.nsmap - -class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_namespace): - """ - Virtual class for top-level left-right protocol data elements. - """ - - handles = () - - self_id = None - self_handle = None - - @property - @rpki.sql.cache_reference - def self(self): - """ - Fetch self object to which this object links. - """ - return self_elt.sql_fetch(self.gctx, self.self_id) - - @property - @rpki.sql.cache_reference - def bsc(self): - """ - Return BSC object to which this object links. - """ - return bsc_elt.sql_fetch(self.gctx, self.bsc_id) - - def make_reply_clone_hook(self, r_pdu): - """ - Set handles when cloning, including _id -> _handle translation. - """ - if r_pdu.self_handle is None: - r_pdu.self_handle = self.self_handle - for tag, elt in self.handles: - id_name = tag + "_id" - handle_name = tag + "_handle" - if getattr(r_pdu, handle_name, None) is None: - try: - setattr(r_pdu, handle_name, getattr(elt.sql_fetch(self.gctx, getattr(r_pdu, id_name)), handle_name)) - except AttributeError: - continue - - @classmethod - def serve_fetch_handle(cls, gctx, self_id, handle): - """ - Find an object based on its handle. - """ - return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id)) - - def serve_fetch_one_maybe(self): - """ - Find the object on which a get, set, or destroy method should - operate, or which would conflict with a create method. - """ - where = "%s.%s_handle = %%s AND %s.self_id = self.self_id AND self.self_handle = %%s" % ((self.element_name,) * 3) - args = (getattr(self, self.element_name + "_handle"), self.self_handle) - return self.sql_fetch_where1(self.gctx, where, args, "self") - - def serve_fetch_all(self): - """ - Find the objects on which a list method should operate. - """ - where = "%s.self_id = self.self_id and self.self_handle = %%s" % self.element_name - return self.sql_fetch_where(self.gctx, where, (self.self_handle,), "self") - - def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Hook to do _handle => _id translation before saving. - - self is always the object to be saved to SQL. For create - operations, self and q_pdu are be the same object; for set - operations, self is the pre-existing object from SQL and q_pdu is - the set request received from the the IRBE. - """ - for tag, elt in self.handles: - id_name = tag + "_id" - if getattr(self, id_name, None) is None: - x = elt.serve_fetch_handle(self.gctx, self.self_id, getattr(q_pdu, tag + "_handle")) - if x is None: - raise rpki.exceptions.HandleTranslationError("Could not translate %r %s_handle" % (self, tag)) - setattr(self, id_name, getattr(x, id_name)) - cb() - -class self_elt(data_elt): - """ - <self/> element. - """ - - element_name = "self" - attributes = ("action", "tag", "self_handle", "crl_interval", "regen_margin") - elements = ("bpki_cert", "bpki_glue") - booleans = ("rekey", "reissue", "revoke", "run_now", "publish_world_now", "revoke_forgotten", - "clear_replay_protection") - - sql_template = rpki.sql.template( - "self", - "self_id", - "self_handle", - "use_hsm", - "crl_interval", - "regen_margin", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509)) - - handles = () - - use_hsm = False - crl_interval = None - regen_margin = None - bpki_cert = None - bpki_glue = None - cron_tasks = None - - def __repr__(self): - return rpki.log.log_repr(self) - - @property - def bscs(self): - """ - Fetch all BSC objects that link to this self object. - """ - return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def repositories(self): - """ - Fetch all repository objects that link to this self object. - """ - return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def parents(self): - """ - Fetch all parent objects that link to this self object. - """ - return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def children(self): - """ - Fetch all child objects that link to this self object. - """ - return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def roas(self): - """ - Fetch all ROA objects that link to this self object. - """ - return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def ghostbusters(self): - """ - Fetch all Ghostbuster record objects that link to this self object. - """ - return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - @property - def ee_certificates(self): - """ - Fetch all EE certificate objects that link to this self object. - """ - return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for self_elt. - """ - actions = [] - if q_pdu.rekey: - actions.append(self.serve_rekey) - if q_pdu.revoke: - actions.append(self.serve_revoke) - if q_pdu.reissue: - actions.append(self.serve_reissue) - if q_pdu.revoke_forgotten: - actions.append(self.serve_revoke_forgotten) - if q_pdu.publish_world_now: - actions.append(self.serve_publish_world_now) - if q_pdu.run_now: - actions.append(self.serve_run_now) - if q_pdu.clear_replay_protection: - actions.append(self.serve_clear_replay_protection) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this self. - """ - def loop(iterator, parent): - parent.serve_rekey(iterator, eb) - rpki.async.iterator(self.parents, loop, cb) - - def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this self. - """ - def loop(iterator, parent): - parent.serve_revoke(iterator, eb) - rpki.async.iterator(self.parents, loop, cb) - - def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this self. - """ - def loop(iterator, parent): - parent.serve_reissue(iterator, eb) - rpki.async.iterator(self.parents, loop, cb) - - def serve_revoke_forgotten(self, cb, eb): - """ - Handle a left-right revoke_forgotten action for this self. - """ - def loop(iterator, parent): - parent.serve_revoke_forgotten(iterator, eb) - rpki.async.iterator(self.parents, loop, cb) - - def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this self. - """ - def loop(iterator, obj): - obj.serve_clear_replay_protection(iterator, eb) - rpki.async.iterator(self.parents + self.children + self.repositories, loop, cb) - - def serve_destroy_hook(self, cb, eb): - """ - Extra cleanup actions when destroying a self_elt. - """ - def loop(iterator, parent): - parent.delete(iterator) - rpki.async.iterator(self.parents, loop, cb) - - - def serve_publish_world_now(self, cb, eb): - """ - Handle a left-right publish_world_now action for this self. - - The publication stuff needs refactoring, right now publication is - interleaved with local operations in a way that forces far too - many bounces through the task system for any complex update. The - whole thing ought to be rewritten to queue up outgoing publication - PDUs and only send them when we're all done or when we need to - force publication at a particular point in a multi-phase operation. - - Once that reorganization has been done, this method should be - rewritten to reuse the low-level publish() methods that each - object will have...but we're not there yet. So, for now, we just - do this via brute force. Think of it as a trial version to see - whether we've identified everything that needs to be republished - for this operation. - """ - - def loop(iterator, parent): - q_msg = rpki.publication.msg.query() - for ca in parent.cas: - ca_detail = ca.active_ca_detail - if ca_detail is not None: - q_msg.append(rpki.publication.crl_elt.make_publish( - ca_detail.crl_uri, ca_detail.latest_crl)) - q_msg.append(rpki.publication.manifest_elt.make_publish( - ca_detail.manifest_uri, ca_detail.latest_manifest)) - q_msg.extend(rpki.publication.certificate_elt.make_publish( - c.uri, c.cert) for c in ca_detail.child_certs) - q_msg.extend(rpki.publication.roa_elt.make_publish( - r.uri, r.roa) for r in ca_detail.roas if r.roa is not None) - q_msg.extend(rpki.publication.ghostbuster_elt.make_publish( - g.uri, g.ghostbuster) for g in ca_detail.ghostbusters) - parent.repository.call_pubd(iterator, eb, q_msg) - - rpki.async.iterator(self.parents, loop, cb) - - def serve_run_now(self, cb, eb): - """ - Handle a left-right run_now action for this self. - """ - logger.debug("Forced immediate run of periodic actions for self %s[%d]", - self.self_handle, self.self_id) - completion = rpki.rpkid_tasks.CompletionHandler(cb) - self.schedule_cron_tasks(completion) - assert completion.count > 0 - self.gctx.task_run() - - def serve_fetch_one_maybe(self): - """ - Find the self object upon which a get, set, or destroy action - should operate, or which would conflict with a create method. - """ - return self.serve_fetch_handle(self.gctx, None, self.self_handle) - - @classmethod - def serve_fetch_handle(cls, gctx, self_id, self_handle): - """ - Find a self object based on its self_handle. - """ - return cls.sql_fetch_where1(gctx, "self_handle = %s", (self_handle,)) - - def serve_fetch_all(self): - """ - Find the self objects upon which a list action should operate. - This is different from the list action for all other objects, - where list only works within a given self_id context. - """ - return self.sql_fetch_all(self.gctx) - - def schedule_cron_tasks(self, completion): - """ - Schedule periodic tasks. - """ - - if self.cron_tasks is None: - self.cron_tasks = tuple(task(self) for task in rpki.rpkid_tasks.task_classes) - - for task in self.cron_tasks: - self.gctx.task_add(task) - completion.register(task) - - def find_covering_ca_details(self, resources): - """ - Return all active ca_detail_objs for this <self/> which cover a - particular set of resources. - If we expected there to be a large number of ca_detail_objs, we - could add index tables and write fancy SQL query to do this, but - for the expected common case where there are only one or two - active ca_detail_objs per <self/>, it's probably not worth it. In - any case, this is an optimization we can leave for later. - """ - - results = set() - for parent in self.parents: - for ca in parent.cas: - ca_detail = ca.active_ca_detail - if ca_detail is not None and ca_detail.covers(resources): - results.add(ca_detail) - return results - - -class bsc_elt(data_elt): - """ - <bsc/> (Business Signing Context) element. - """ - - element_name = "bsc" - attributes = ("action", "tag", "self_handle", "bsc_handle", "key_type", "hash_alg", "key_length") - elements = ("signing_cert", "signing_cert_crl", "pkcs10_request") - booleans = ("generate_keypair",) - - sql_template = rpki.sql.template( - "bsc", - "bsc_id", - "bsc_handle", - "self_id", - "hash_alg", - ("private_key_id", rpki.x509.RSA), - ("pkcs10_request", rpki.x509.PKCS10), - ("signing_cert", rpki.x509.X509), - ("signing_cert_crl", rpki.x509.CRL)) - - handles = (("self", self_elt),) - - private_key_id = None - pkcs10_request = None - signing_cert = None - signing_cert_crl = None - - def __repr__(self): - return rpki.log.log_repr(self, self.bsc_handle) - - @property - def repositories(self): - """ - Fetch all repository objects that link to this BSC object. - """ - return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) - - @property - def parents(self): - """ - Fetch all parent objects that link to this BSC object. - """ - return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) - - @property - def children(self): - """ - Fetch all child objects that link to this BSC object. - """ - return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) - - def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for bsc_elt -- handle key generation. For - now this only allows RSA with SHA-256. - """ - if q_pdu.generate_keypair: - assert q_pdu.key_type in (None, "rsa") and q_pdu.hash_alg in (None, "sha256") - self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048) - self.pkcs10_request = rpki.x509.PKCS10.create(keypair = self.private_key_id) - r_pdu.pkcs10_request = self.pkcs10_request - data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb) - -class repository_elt(data_elt): - """ - <repository/> element. - """ - - element_name = "repository" - attributes = ("action", "tag", "self_handle", "repository_handle", "bsc_handle", "peer_contact_uri") - elements = ("bpki_cert", "bpki_glue") - booleans = ("clear_replay_protection",) - - sql_template = rpki.sql.template( - "repository", - "repository_id", - "repository_handle", - "self_id", - "bsc_id", - "peer_contact_uri", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509), - ("last_cms_timestamp", rpki.sundial.datetime)) - - handles = (("self", self_elt), - ("bsc", bsc_elt)) - - bpki_cert = None - bpki_glue = None - last_cms_timestamp = None - - def __repr__(self): - return rpki.log.log_repr(self, self.repository_handle) - - @property - def parents(self): - """ - 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 serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for repository_elt. - """ - actions = [] - if q_pdu.clear_replay_protection: - actions.append(self.serve_clear_replay_protection) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this repository. - """ - self.last_cms_timestamp = None - self.sql_mark_dirty() - cb() - - @staticmethod - def default_pubd_handler(pdu): - """ - Default handler for publication response PDUs. - """ - pdu.raise_if_error() - - def call_pubd(self, callback, errback, q_msg, handlers = None): - """ - Send a message to publication daemon and return the response. - - As a convenience, attempting to send an empty message returns - immediate success without sending anything. - - Handlers is a dict of handler functions to process the response - PDUs. If the tag value in the response PDU appears in the dict, - the associated handler is called to process the PDU. If no tag - matches, default_pubd_handler() is called. A handler value of - False suppresses calling of the default handler. - """ - - try: - self.gctx.sql.sweep() - - if not q_msg: - return callback() - - if handlers is None: - handlers = {} - - for q_pdu in q_msg: - logger.info("Sending %s %s to pubd", q_pdu.action, q_pdu.uri) - - bsc = self.bsc - q_der = rpki.publication.cms_msg().wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl) - bpki_ta_path = (self.gctx.bpki_ta, self.self.bpki_cert, self.self.bpki_glue, self.bpki_cert, self.bpki_glue) - - def done(r_der): - try: - logger.debug("Received response from pubd") - r_cms = rpki.publication.cms_msg(DER = r_der) - r_msg = r_cms.unwrap(bpki_ta_path) - r_cms.check_replay_sql(self, self.peer_contact_uri) - for r_pdu in r_msg: - handler = handlers.get(r_pdu.tag, self.default_pubd_handler) - if handler: - logger.debug("Calling pubd handler %r", handler) - handler(r_pdu) - if len(q_msg) != len(r_msg): - raise rpki.exceptions.BadPublicationReply("Wrong number of response PDUs from pubd: sent %r, got %r" % (q_msg, r_msg)) - callback() - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - errback(e) - - logger.debug("Sending request to pubd") - rpki.http.client( - url = self.peer_contact_uri, - msg = q_der, - callback = done, - errback = errback) - - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - errback(e) - -class parent_elt(data_elt): - """ - <parent/> element. - """ - - element_name = "parent" - attributes = ("action", "tag", "self_handle", "parent_handle", "bsc_handle", "repository_handle", - "peer_contact_uri", "sia_base", "sender_name", "recipient_name") - elements = ("bpki_cms_cert", "bpki_cms_glue") - booleans = ("rekey", "reissue", "revoke", "revoke_forgotten", "clear_replay_protection") - - sql_template = rpki.sql.template( - "parent", - "parent_id", - "parent_handle", - "self_id", - "bsc_id", - "repository_id", - "peer_contact_uri", - "sia_base", - "sender_name", - "recipient_name", - ("bpki_cms_cert", rpki.x509.X509), - ("bpki_cms_glue", rpki.x509.X509), - ("last_cms_timestamp", rpki.sundial.datetime)) - - handles = (("self", self_elt), - ("bsc", bsc_elt), - ("repository", repository_elt)) - - bpki_cms_cert = None - bpki_cms_glue = None - last_cms_timestamp = None - - def __repr__(self): - return rpki.log.log_repr(self, self.parent_handle) - - @property - @rpki.sql.cache_reference - def repository(self): - """ - Fetch repository object to which this parent object links. - """ - return repository_elt.sql_fetch(self.gctx, self.repository_id) - - @property - def cas(self): - """ - Fetch all CA objects that link to this parent object. - """ - return rpki.rpkid.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,)) - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for parent_elt. - """ - actions = [] - if q_pdu.rekey: - actions.append(self.serve_rekey) - if q_pdu.revoke: - actions.append(self.serve_revoke) - if q_pdu.reissue: - actions.append(self.serve_reissue) - if q_pdu.revoke_forgotten: - actions.append(self.serve_revoke_forgotten) - if q_pdu.clear_replay_protection: - actions.append(self.serve_clear_replay_protection) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this parent. - """ - def loop(iterator, ca): - ca.rekey(iterator, eb) - rpki.async.iterator(self.cas, loop, cb) - - def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this parent. - """ - def loop(iterator, ca): - ca.revoke(cb = iterator, eb = eb) - rpki.async.iterator(self.cas, loop, cb) - - def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this parent. - """ - def loop(iterator, ca): - ca.reissue(cb = iterator, eb = eb) - rpki.async.iterator(self.cas, loop, cb) - - def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this parent. - """ - self.last_cms_timestamp = None - self.sql_mark_dirty() - cb() - - - def get_skis(self, cb, eb): - """ - Fetch SKIs that this parent thinks we have. In theory this should - agree with our own database, but in practice stuff can happen, so - sometimes we need to know what our parent thinks. - - Result is a dictionary with the resource class name as key and a - set of SKIs as value. - """ - - def done(r_msg): - cb(dict((rc.class_name, set(c.cert.gSKI() for c in rc.certs)) - for rc in r_msg.payload.classes)) - - rpki.up_down.list_pdu.query(self, done, eb) - - - def revoke_skis(self, rc_name, skis_to_revoke, cb, eb): - """ - Revoke a set of SKIs within a particular resource class. - """ - - def loop(iterator, ski): - logger.debug("Asking parent %r to revoke class %r, SKI %s", self, rc_name, ski) - q_pdu = rpki.up_down.revoke_pdu() - q_pdu.class_name = rc_name - q_pdu.ski = ski - self.query_up_down(q_pdu, lambda r_pdu: iterator(), eb) - - rpki.async.iterator(skis_to_revoke, loop, cb) - - - def serve_revoke_forgotten(self, cb, eb): - """ - Handle a left-right revoke_forgotten action for this parent. - - This is a bit fiddly: we have to compare the result of an up-down - list query with what we have locally and identify the SKIs of any - certificates that have gone missing. This should never happen in - ordinary operation, but can arise if we have somehow lost a - private key, in which case there is nothing more we can do with - the issued cert, so we have to clear it. As this really is not - supposed to happen, we don't clear it automatically, instead we - require an explicit trigger. - """ - - def got_skis(skis_from_parent): - - def loop(iterator, item): - rc_name, skis_to_revoke = item - if rc_name in ca_map: - for ca_detail in ca_map[rc_name].issue_response_candidate_ca_details: - skis_to_revoke.discard(ca_detail.latest_ca_cert.gSKI()) - self.revoke_skis(rc_name, skis_to_revoke, iterator, eb) - - ca_map = dict((ca.parent_resource_class, ca) for ca in self.cas) - rpki.async.iterator(skis_from_parent.items(), loop, cb) - - self.get_skis(got_skis, eb) - - - def delete(self, cb, delete_parent = True): - """ - Delete all the CA stuff under this parent, and perhaps the parent - itself. - """ - - def loop(iterator, ca): - self.gctx.checkpoint() - ca.delete(self, iterator) - - def revoke(): - self.gctx.checkpoint() - self.serve_revoke_forgotten(done, fail) - - def fail(e): - logger.warning("Trouble getting parent to revoke certificates, blundering onwards: %s", e) - done() - - def done(): - self.gctx.checkpoint() - self.gctx.sql.sweep() - if delete_parent: - self.sql_delete() - cb() - - rpki.async.iterator(self.cas, loop, revoke) - - - def serve_destroy_hook(self, cb, eb): - """ - Extra server actions when destroying a parent_elt. - """ - - self.delete(cb, delete_parent = False) - - - def query_up_down(self, q_pdu, cb, eb): - """ - Client code for sending one up-down query PDU to this parent. - """ - - bsc = self.bsc - if bsc is None: - raise rpki.exceptions.BSCNotFound("Could not find BSC %s" % self.bsc_id) - - if bsc.signing_cert is None: - raise rpki.exceptions.BSCNotReady("BSC %r[%s] is not yet usable" % (bsc.bsc_handle, bsc.bsc_id)) - - q_msg = rpki.up_down.message_pdu.make_query( - payload = q_pdu, - sender = self.sender_name, - recipient = self.recipient_name) - - q_der = rpki.up_down.cms_msg().wrap(q_msg, bsc.private_key_id, - bsc.signing_cert, - bsc.signing_cert_crl) - - def unwrap(r_der): - try: - r_cms = rpki.up_down.cms_msg(DER = r_der) - r_msg = r_cms.unwrap((self.gctx.bpki_ta, - self.self.bpki_cert, - self.self.bpki_glue, - self.bpki_cms_cert, - self.bpki_cms_glue)) - r_cms.check_replay_sql(self, self.peer_contact_uri) - r_msg.payload.check_response() - except (SystemExit, rpki.async.ExitNow): - raise - except Exception, e: - eb(e) - else: - cb(r_msg) - - rpki.http.client( - msg = q_der, - url = self.peer_contact_uri, - callback = unwrap, - errback = eb, - content_type = rpki.up_down.content_type) - -class child_elt(data_elt): - """ - <child/> element. - """ - - element_name = "child" - attributes = ("action", "tag", "self_handle", "child_handle", "bsc_handle") - elements = ("bpki_cert", "bpki_glue") - booleans = ("reissue", "clear_replay_protection") - - sql_template = rpki.sql.template( - "child", - "child_id", - "child_handle", - "self_id", - "bsc_id", - ("bpki_cert", rpki.x509.X509), - ("bpki_glue", rpki.x509.X509), - ("last_cms_timestamp", rpki.sundial.datetime)) - - handles = (("self", self_elt), - ("bsc", bsc_elt)) - - bpki_cert = None - bpki_glue = None - last_cms_timestamp = None - - def __repr__(self): - return rpki.log.log_repr(self, self.child_handle) - - def fetch_child_certs(self, ca_detail = None, ski = None, unique = False): - """ - Fetch all child_cert objects that link to this child object. - """ - return rpki.rpkid.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique) - - @property - def child_certs(self): - """ - Fetch all child_cert objects that link to this child object. - """ - return self.fetch_child_certs() - - @property - def parents(self): - """ - Fetch all parent objects that link to self object to which this child object links. - """ - return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for child_elt. - """ - actions = [] - if q_pdu.reissue: - actions.append(self.serve_reissue) - if q_pdu.clear_replay_protection: - actions.append(self.serve_clear_replay_protection) - def loop(iterator, action): - action(iterator, eb) - rpki.async.iterator(actions, loop, cb) - - def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this child. - """ - publisher = rpki.rpkid.publication_queue() - for child_cert in self.child_certs: - child_cert.reissue(child_cert.ca_detail, publisher, force = True) - publisher.call_pubd(cb, eb) - - def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this child. - """ - self.last_cms_timestamp = None - self.sql_mark_dirty() - cb() - - def ca_from_class_name(self, 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.rpkid.ca_obj.sql_fetch(self.gctx, long(class_name)) - if ca is None: - raise rpki.exceptions.ClassNameUnknown("Unknown class name %s" % class_name) - parent = ca.parent - if self.self_id != parent.self_id: - raise rpki.exceptions.ClassNameMismatch( - "Class name mismatch: child.self_id = %d, parent.self_id = %d" % ( - self.self_id, parent.self_id)) - return ca - - def serve_destroy_hook(self, cb, eb): - """ - Extra server actions when destroying a child_elt. - """ - publisher = rpki.rpkid.publication_queue() - for child_cert in self.child_certs: - child_cert.revoke(publisher = publisher, - generate_crl_and_manifest = True) - publisher.call_pubd(cb, eb) - - def serve_up_down(self, query, callback): - """ - Outer layer of server handling for one up-down PDU from this child. - """ - - bsc = self.bsc - if bsc is None: - raise rpki.exceptions.BSCNotFound("Could not find BSC %s" % self.bsc_id) - q_cms = rpki.up_down.cms_msg(DER = query) - q_msg = q_cms.unwrap((self.gctx.bpki_ta, - self.self.bpki_cert, - self.self.bpki_glue, - self.bpki_cert, - self.bpki_glue)) - q_cms.check_replay_sql(self, "child", self.child_handle) - q_msg.payload.gctx = self.gctx - if enforce_strict_up_down_xml_sender and q_msg.sender != self.child_handle: - raise rpki.exceptions.BadSender("Unexpected XML sender %s" % q_msg.sender) - self.gctx.sql.sweep() - - def done(r_msg): - # - # 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. - # - reply = rpki.up_down.cms_msg().wrap(r_msg, bsc.private_key_id, - bsc.signing_cert, bsc.signing_cert_crl) - callback(reply) - - try: - q_msg.serve_top_level(self, done) - except (rpki.async.ExitNow, SystemExit): - raise - except rpki.exceptions.NoActiveCA, data: - done(q_msg.serve_error(data)) - except Exception, e: - logger.exception("Unhandled exception serving up-down request from %r", self) - done(q_msg.serve_error(e)) - -class list_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_resources/> element. - """ - - element_name = "list_resources" - attributes = ("self_handle", "tag", "child_handle", "valid_until", "asn", "ipv4", "ipv6") - valid_until = None - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.child_handle, self.asn, self.ipv4, self.ipv6) - - def startElement(self, stack, name, attrs): - """ - 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): - self.valid_until = rpki.sundial.datetime.fromXMLtime(self.valid_until) - if self.asn is not None: - self.asn = rpki.resource_set.resource_set_as(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.ipv6) - - def toXML(self): - """ - 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()) - return elt - -class list_roa_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_roa_requests/> element. - """ - - element_name = "list_roa_requests" - attributes = ("self_handle", "tag", "asn", "ipv4", "ipv6") - - def startElement(self, stack, name, attrs): - """ - Handle <list_roa_requests/> element. This requires special handling - due to the data types of some of the attributes. - """ - assert name == "list_roa_requests", "Unexpected name %s, stack %s" % (name, stack) - self.read_attrs(attrs) - if self.ipv4 is not None: - self.ipv4 = rpki.resource_set.roa_prefix_set_ipv4(self.ipv4) - if self.ipv6 is not None: - self.ipv6 = rpki.resource_set.roa_prefix_set_ipv6(self.ipv6) - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.asn, self.ipv4, self.ipv6) - -class list_ghostbuster_requests_elt(rpki.xml_utils.text_elt, left_right_namespace): - """ - <list_ghostbuster_requests/> element. - """ - - element_name = "list_ghostbuster_requests" - attributes = ("self_handle", "tag", "parent_handle") - text_attribute = "vcard" - - vcard = None - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.parent_handle) - -class list_ee_certificate_requests_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_ee_certificate_requests/> element. - """ - - element_name = "list_ee_certificate_requests" - attributes = ("self_handle", "tag", "gski", "valid_until", "asn", "ipv4", "ipv6", "cn", "sn", "eku") - elements = ("pkcs10",) - - pkcs10 = None - valid_until = None - eku = None - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.gski, self.cn, self.sn, self.asn, self.ipv4, self.ipv6) - - def startElement(self, stack, name, attrs): - """ - Handle <list_ee_certificate_requests/> element. This requires special - handling due to the data types of some of the attributes. - """ - if name not in self.elements: - assert name == self.element_name, "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.asn is not None: - self.asn = rpki.resource_set.resource_set_as(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.ipv6) - if self.eku is not None: - self.eku = self.eku.split(",") - - def endElement(self, stack, name, text): - """ - Handle <pkcs10/> sub-element. - """ - assert len(self.elements) == 1 - if name == self.elements[0]: - self.pkcs10 = rpki.x509.PKCS10(Base64 = text) - else: - assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) - stack.pop() - - def toXML(self): - """ - Generate <list_ee_certificate_requests/> element. This requires special - handling due to the data types of some of the attributes. - """ - if isinstance(self.eku, (tuple, list)): - self.eku = ",".join(self.eku) - elt = self.make_elt() - for i in self.elements: - self.make_b64elt(elt, i, getattr(self, i, None)) - if isinstance(self.valid_until, int): - elt.set("valid_until", self.valid_until.toXMLtime()) - return elt - -class list_published_objects_elt(rpki.xml_utils.text_elt, left_right_namespace): - """ - <list_published_objects/> element. - """ - - element_name = "list_published_objects" - attributes = ("self_handle", "tag", "uri", "child_handle") - text_attribute = "obj" - - obj = None - child_handle = None - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.child_handle, self.uri) - - def serve_dispatch(self, r_msg, cb, eb): - """ - Handle a <list_published_objects/> query. The method name is a - misnomer here, there's no action attribute and no dispatch, we - just dump every published object for the specified <self/> and return. - """ - for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: - for ca in parent.cas: - ca_detail = ca.active_ca_detail - if ca_detail is not None: - r_msg.append(self.make_reply(ca_detail.crl_uri, ca_detail.latest_crl)) - r_msg.append(self.make_reply(ca_detail.manifest_uri, ca_detail.latest_manifest)) - r_msg.extend(self.make_reply(c.uri, c.cert, c.child.child_handle) - for c in ca_detail.child_certs) - r_msg.extend(self.make_reply(r.uri, r.roa) - for r in ca_detail.roas if r.roa is not None) - r_msg.extend(self.make_reply(g.uri, g.ghostbuster) - for g in ca_detail.ghostbusters) - r_msg.extend(self.make_reply(c.uri, c.cert) - for c in ca_detail.ee_certificates) - cb() - - def make_reply(self, uri, obj, child_handle = None): - """ - Generate one reply PDU. - """ - r_pdu = self.make_pdu(tag = self.tag, self_handle = self.self_handle, - uri = uri, child_handle = child_handle) - r_pdu.obj = obj.get_Base64() - return r_pdu - -class list_received_resources_elt(rpki.xml_utils.base_elt, left_right_namespace): - """ - <list_received_resources/> element. - """ - - element_name = "list_received_resources" - attributes = ("self_handle", "tag", "parent_handle", - "notBefore", "notAfter", "uri", "sia_uri", "aia_uri", "asn", "ipv4", "ipv6") - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.parent_handle, self.uri, self.notAfter) - - def serve_dispatch(self, r_msg, cb, eb): - """ - Handle a <list_received_resources/> query. The method name is a - misnomer here, there's no action attribute and no dispatch, we - just dump a bunch of data about every certificate issued to us by - one of our parents, then return. - """ - for parent in self_elt.serve_fetch_handle(self.gctx, None, self.self_handle).parents: - for ca in parent.cas: - ca_detail = ca.active_ca_detail - if ca_detail is not None and ca_detail.latest_ca_cert is not None: - r_msg.append(self.make_reply(parent.parent_handle, ca_detail.ca_cert_uri, ca_detail.latest_ca_cert)) - cb() - - def make_reply(self, parent_handle, uri, cert): - """ - Generate one reply PDU. - """ - resources = cert.get_3779resources() - return self.make_pdu( - tag = self.tag, - self_handle = self.self_handle, - parent_handle = parent_handle, - notBefore = str(cert.getNotBefore()), - notAfter = str(cert.getNotAfter()), - uri = uri, - sia_uri = cert.get_sia_directory_uri(), - aia_uri = cert.get_aia_uri(), - asn = resources.asn, - ipv4 = resources.v4, - ipv6 = resources.v6) - -class report_error_elt(rpki.xml_utils.text_elt, left_right_namespace): - """ - <report_error/> element. - """ - - element_name = "report_error" - attributes = ("tag", "self_handle", "error_code") - text_attribute = "error_text" - - error_text = None - - def __repr__(self): - return rpki.log.log_repr(self, self.self_handle, self.error_code) - - @classmethod - def from_exception(cls, e, self_handle = None, tag = None): - """ - Generate a <report_error/> element from an exception. - """ - self = cls() - self.self_handle = self_handle - self.tag = tag - self.error_code = e.__class__.__name__ - self.error_text = str(e) - return self - -class msg(rpki.xml_utils.msg, left_right_namespace): - """ - Left-right PDU. - """ +logger = logging.getLogger(__name__) - ## @var version - # Protocol version - version = int(rpki.relaxng.left_right.version) +xmlns = rpki.relaxng.left_right.xmlns +nsmap = rpki.relaxng.left_right.nsmap +version = rpki.relaxng.left_right.version + +tag_bpki_cert = xmlns + "bpki_cert" +tag_bpki_glue = xmlns + "bpki_glue" +tag_bsc = xmlns + "bsc" +tag_child = xmlns + "child" +tag_list_ee_certificate_requests = xmlns + "list_ee_certificate_requests" +tag_list_ghostbuster_requests = xmlns + "list_ghostbuster_requests" +tag_list_published_objects = xmlns + "list_published_objects" +tag_list_received_resources = xmlns + "list_received_resources" +tag_list_resources = xmlns + "list_resources" +tag_list_roa_requests = xmlns + "list_roa_requests" +tag_msg = xmlns + "msg" +tag_parent = xmlns + "parent" +tag_pkcs10 = xmlns + "pkcs10" +tag_pkcs10_request = xmlns + "pkcs10_request" +tag_report_error = xmlns + "report_error" +tag_repository = xmlns + "repository" +tag_rpki_root_cert = xmlns + "rpki_root_cert" +tag_tenant = xmlns + "tenant" +tag_signing_cert = xmlns + "signing_cert" +tag_signing_cert_crl = xmlns + "signing_cert_crl" + +## @var content_type +# Content type to use when sending left-right queries +content_type = "application/x-rpki" + +## @var allowed_content_types +# Content types we consider acceptable for incoming left-right +# queries. + +allowed_content_types = (content_type,) - ## @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, list_resources_elt, - list_roa_requests_elt, list_ghostbuster_requests_elt, - list_ee_certificate_requests_elt, - list_published_objects_elt, - list_received_resources_elt, report_error_elt)) - def serve_top_level(self, gctx, cb): +class cms_msg(rpki.x509.XML_CMS_object): """ - Serve one msg PDU. + CMS-signed left-right PDU. """ - r_msg = self.__class__.reply() - - def loop(iterator, q_pdu): - - def fail(e): - if not isinstance(e, rpki.exceptions.NotFound): - logger.exception("Unhandled exception serving left-right PDU %r", q_pdu) - r_msg.append(report_error_elt.from_exception( - e, self_handle = q_pdu.self_handle, tag = q_pdu.tag)) - cb(r_msg) - - try: - q_pdu.gctx = gctx - q_pdu.serve_dispatch(r_msg, iterator, fail) - except (rpki.async.ExitNow, SystemExit): - raise - except Exception, e: - fail(e) - - def done(): - cb(r_msg) - - rpki.async.iterator(self, loop, done) - -class sax_handler(rpki.xml_utils.sax_handler): - """ - SAX handler for Left-Right protocol. - """ - - pdu = msg - name = "msg" - version = rpki.relaxng.left_right.version - -class cms_msg(rpki.x509.XML_CMS_object): - """ - Class to hold a CMS-signed left-right PDU. - """ - - encoding = "us-ascii" - schema = rpki.relaxng.left_right - saxify = sax_handler.saxify + encoding = "us-ascii" + schema = rpki.relaxng.left_right |