00001 """
00002 RPKI "publication" protocol.
00003
00004 $Id: publication.py 2493 2009-06-04 18:30:40Z sra $
00005
00006 Copyright (C) 2009 Internet Systems Consortium ("ISC")
00007
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019
00020 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00021
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034
00035 import base64, os, traceback
00036 import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00037 import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
00038
00039 class publication_namespace(object):
00040 """
00041 XML namespace parameters for publication protocol.
00042 """
00043
00044 xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/"
00045 nsmap = { None : xmlns }
00046
00047 class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_namespace):
00048 """
00049 Virtual class for control channel objects.
00050 """
00051
00052 def serve_dispatch(self, r_msg, cb, eb):
00053 """
00054 Action dispatch handler. This needs special handling because we
00055 need to make sure that this PDU arrived via the control channel.
00056 """
00057 if self.client is not None:
00058 raise rpki.exceptions.BadQuery, "Control query received on client channel"
00059 rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb)
00060
00061 class config_elt(control_elt):
00062 """
00063 <config/> element. This is a little weird because there should
00064 never be more than one row in the SQL config table, but we have to
00065 put the BPKI CRL somewhere and SQL is the least bad place available.
00066
00067 So we reuse a lot of the SQL machinery, but we nail config_id at 1,
00068 we don't expose it in the XML protocol, and we only support the get
00069 and set actions.
00070 """
00071
00072 attributes = ("action", "tag")
00073 element_name = "config"
00074 elements = ("bpki_crl",)
00075
00076 sql_template = rpki.sql.template("config", "config_id", ("bpki_crl", rpki.x509.CRL))
00077
00078 wired_in_config_id = 1
00079
00080 def startElement(self, stack, name, attrs):
00081 """
00082 StartElement() handler for config object. This requires special
00083 handling because of the weird way we treat config_id.
00084 """
00085 control_elt.startElement(self, stack, name, attrs)
00086 self.config_id = self.wired_in_config_id
00087
00088 @classmethod
00089 def fetch(cls, gctx):
00090 """
00091 Fetch the config object from SQL. This requires special handling
00092 because of the weird way we treat config_id.
00093 """
00094 return cls.sql_fetch(gctx, cls.wired_in_config_id)
00095
00096 def serve_set(self, r_msg, cb, eb):
00097 """
00098 Handle a set action. This requires special handling because
00099 config doesn't support the create method.
00100 """
00101 if self.sql_fetch(self.gctx, self.config_id) is None:
00102 control_elt.serve_create(self, r_msg, cb, eb)
00103 else:
00104 control_elt.serve_set(self, r_msg, cb, eb)
00105
00106 def serve_fetch_one_maybe(self):
00107 """
00108 Find the config object on which a get or set method should
00109 operate.
00110 """
00111 return self.sql_fetch(self.gctx, self.config_id)
00112
00113 class client_elt(control_elt):
00114 """
00115 <client/> element.
00116 """
00117
00118 element_name = "client"
00119 attributes = ("action", "tag", "client_handle", "base_uri")
00120 elements = ("bpki_cert", "bpki_glue")
00121
00122 sql_template = rpki.sql.template("client", "client_id", "client_handle", "base_uri", ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00123
00124 base_uri = None
00125 bpki_cert = None
00126 bpki_glue = None
00127
00128 clear_https_ta_cache = False
00129
00130 def endElement(self, stack, name, text):
00131 """
00132 Handle subelements of <client/> element. These require special
00133 handling because modifying them invalidates the HTTPS trust anchor
00134 cache.
00135 """
00136 control_elt.endElement(self, stack, name, text)
00137 if name in self.elements:
00138 self.clear_https_ta_cache = True
00139
00140 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00141 """
00142 Extra server actions for client_elt.
00143 """
00144 if self.clear_https_ta_cache:
00145 self.gctx.clear_https_ta_cache()
00146 self.clear_https_ta_cache = False
00147 cb()
00148
00149 def serve_fetch_one_maybe(self):
00150 """
00151 Find the client object on which a get, set, or destroy method
00152 should operate, or which would conflict with a create method.
00153 """
00154 return self.sql_fetch_where1(self.gctx, "client_handle = %s", self.client_handle)
00155
00156 def serve_fetch_all(self):
00157 """Find client objects on which a list method should operate."""
00158 return self.sql_fetch_all(self.gctx)
00159
00160 def check_allowed_uri(self, uri):
00161 if not uri.startswith(self.base_uri):
00162 raise rpki.exceptions.ForbiddenURI
00163
00164 class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace):
00165 """
00166 Virtual class for publishable objects. These have very similar
00167 syntax, differences lie in underlying datatype and methods. XML
00168 methods are a little different from the pattern used for objects
00169 that support the create/set/get/list/destroy actions, but
00170 publishable objects don't go in SQL either so these classes would be
00171 different in any case.
00172 """
00173
00174 attributes = ("action", "tag", "client_handle", "uri")
00175 payload = None
00176
00177 def endElement(self, stack, name, text):
00178 """
00179 Handle a publishable element element.
00180 """
00181 assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00182 if text:
00183 self.payload = self.payload_type(Base64 = text)
00184 stack.pop()
00185
00186 def toXML(self):
00187 """
00188 Generate XML element for publishable object.
00189 """
00190 elt = self.make_elt()
00191 if self.payload:
00192 elt.text = base64.b64encode(self.payload.get_DER())
00193 return elt
00194
00195 def serve_dispatch(self, r_msg, cb, eb):
00196 """
00197 Action dispatch handler.
00198 """
00199 if self.client is None:
00200 raise rpki.exceptions.BadQuery, "Client query received on control channel"
00201 dispatch = { "publish" : self.serve_publish,
00202 "withdraw" : self.serve_withdraw }
00203 if self.action not in dispatch:
00204 raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
00205 self.client.check_allowed_uri(self.uri)
00206 dispatch[self.action]()
00207 r_pdu = self.__class__()
00208 r_pdu.action = self.action
00209 r_pdu.tag = self.tag
00210 r_pdu.uri = self.uri
00211 r_msg.append(r_pdu)
00212 cb()
00213
00214 def serve_publish(self):
00215 """
00216 Publish an object.
00217 """
00218 rpki.log.info("Publishing %s as %s" % (repr(self.payload), repr(self.uri)))
00219 filename = self.uri_to_filename()
00220 dirname = os.path.dirname(filename)
00221 if not os.path.isdir(dirname):
00222 os.makedirs(dirname)
00223 f = open(filename, "wb")
00224 f.write(self.payload.get_DER())
00225 f.close()
00226
00227 def serve_withdraw(self):
00228 """
00229 Withdraw an object.
00230 """
00231 rpki.log.info("Withdrawing %s" % repr(self.uri))
00232 os.remove(self.uri_to_filename())
00233
00234 def uri_to_filename(self):
00235 """
00236 Convert a URI to a local filename.
00237 """
00238 if not self.uri.startswith("rsync://"):
00239 raise rpki.exceptions.BadURISyntax
00240 filename = self.gctx.publication_base + self.uri[len("rsync://"):]
00241 if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."):
00242 raise rpki.exceptions.BadURISyntax
00243 return filename
00244
00245 class certificate_elt(publication_object_elt):
00246 """
00247 <certificate/> element.
00248 """
00249
00250 element_name = "certificate"
00251 payload_type = rpki.x509.X509
00252
00253 class crl_elt(publication_object_elt):
00254 """
00255 <crl/> element.
00256 """
00257
00258 element_name = "crl"
00259 payload_type = rpki.x509.CRL
00260
00261 class manifest_elt(publication_object_elt):
00262 """
00263 <manifest/> element.
00264 """
00265
00266 element_name = "manifest"
00267 payload_type = rpki.x509.SignedManifest
00268
00269 class roa_elt(publication_object_elt):
00270 """
00271 <roa/> element.
00272 """
00273
00274 element_name = "roa"
00275 payload_type = rpki.x509.ROA
00276
00277
00278
00279
00280 obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt))
00281
00282 class report_error_elt(rpki.xml_utils.base_elt, publication_namespace):
00283 """
00284 <report_error/> element.
00285 """
00286
00287 element_name = "report_error"
00288 attributes = ("tag", "error_code")
00289
00290 @classmethod
00291 def from_exception(cls, exc):
00292 """
00293 Generate a <report_error/> element from an exception.
00294 """
00295 self = cls()
00296 self.error_code = exc.__class__.__name__
00297 return self
00298
00299 class msg(rpki.xml_utils.msg, publication_namespace):
00300 """
00301 Publication PDU.
00302 """
00303
00304
00305
00306 version = 1
00307
00308
00309
00310 pdus = dict((x.element_name, x)
00311 for x in (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt))
00312
00313 def serve_top_level(self, gctx, client, cb):
00314 """
00315 Serve one msg PDU.
00316 """
00317 if self.type != "query":
00318 raise rpki.exceptions.BadQuery, "Message type is not query"
00319 r_msg = self.__class__()
00320 r_msg.type = "reply"
00321
00322 def loop(iterator, q_pdu):
00323
00324 def fail(e):
00325 rpki.log.error(traceback.format_exc())
00326 r_msg.append(report_error_elt.from_exception(e))
00327 cb(r_msg)
00328
00329 try:
00330 q_pdu.gctx = gctx
00331 q_pdu.client = client
00332 q_pdu.serve_dispatch(r_msg, iterator, fail)
00333 except (rpki.async.ExitNow, SystemExit):
00334 raise
00335 except Exception, edata:
00336 fail(edata)
00337
00338 def done():
00339 cb(r_msg)
00340
00341 rpki.async.iterator(self, loop, done)
00342
00343 class sax_handler(rpki.xml_utils.sax_handler):
00344 """
00345 SAX handler for publication protocol.
00346 """
00347
00348 pdu = msg
00349 name = "msg"
00350 version = "1"
00351
00352 class cms_msg(rpki.x509.XML_CMS_object):
00353 """
00354 Class to hold a CMS-signed publication PDU.
00355 """
00356
00357 encoding = "us-ascii"
00358 schema = rpki.relaxng.publication
00359 saxify = sax_handler.saxify