00001 """
00002 RPKI "publication" protocol.
00003
00004 $Id: publication.py 2481 2009-06-01 05:07:46Z 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(self):
00107 """
00108 Find the config object on which a get or set method should
00109 operate.
00110 """
00111 r = self.sql_fetch(self.gctx, self.config_id)
00112 if r is None:
00113 raise rpki.exceptions.NotFound
00114 return r
00115
00116 class client_elt(control_elt):
00117 """
00118 <client/> element.
00119 """
00120
00121 element_name = "client"
00122 attributes = ("action", "tag", "client_id", "base_uri")
00123 elements = ("bpki_cert", "bpki_glue")
00124
00125 sql_template = rpki.sql.template("client", "client_id", "base_uri", ("bpki_cert", rpki.x509.X509), ("bpki_glue", rpki.x509.X509))
00126
00127 base_uri = None
00128 bpki_cert = None
00129 bpki_glue = None
00130
00131 clear_https_ta_cache = False
00132
00133 def endElement(self, stack, name, text):
00134 """
00135 Handle subelements of <client/> element. These require special
00136 handling because modifying them invalidates the HTTPS trust anchor
00137 cache.
00138 """
00139 control_elt.endElement(self, stack, name, text)
00140 if name in self.elements:
00141 self.clear_https_ta_cache = True
00142
00143 def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb):
00144 """
00145 Extra server actions for client_elt.
00146 """
00147 if self.clear_https_ta_cache:
00148 self.gctx.clear_https_ta_cache()
00149 self.clear_https_ta_cache = False
00150 cb()
00151
00152 def serve_fetch_one(self):
00153 """
00154 Find the client object on which a get, set, or destroy method
00155 should operate.
00156 """
00157 r = self.sql_fetch(self.gctx, self.client_id)
00158 if r is None:
00159 raise rpki.exceptions.NotFound
00160 return r
00161
00162 def serve_fetch_all(self):
00163 """Find client objects on which a list method should operate."""
00164 return self.sql_fetch_all(self.gctx)
00165
00166 def check_allowed_uri(self, uri):
00167 if not uri.startswith(self.base_uri):
00168 raise rpki.exceptions.ForbiddenURI
00169
00170 class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace):
00171 """
00172 Virtual class for publishable objects. These have very similar
00173 syntax, differences lie in underlying datatype and methods. XML
00174 methods are a little different from the pattern used for objects
00175 that support the create/set/get/list/destroy actions, but
00176 publishable objects don't go in SQL either so these classes would be
00177 different in any case.
00178 """
00179
00180 attributes = ("action", "tag", "client_id", "uri")
00181 payload = None
00182
00183 def endElement(self, stack, name, text):
00184 """
00185 Handle a publishable element element.
00186 """
00187 assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00188 if text:
00189 self.payload = self.payload_type(Base64 = text)
00190 stack.pop()
00191
00192 def toXML(self):
00193 """
00194 Generate XML element for publishable object.
00195 """
00196 elt = self.make_elt()
00197 if self.payload:
00198 elt.text = base64.b64encode(self.payload.get_DER())
00199 return elt
00200
00201 def serve_dispatch(self, r_msg, cb, eb):
00202 """
00203 Action dispatch handler.
00204 """
00205 if self.client is None:
00206 raise rpki.exceptions.BadQuery, "Client query received on control channel"
00207 dispatch = { "publish" : self.serve_publish,
00208 "withdraw" : self.serve_withdraw }
00209 if self.action not in dispatch:
00210 raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
00211 self.client.check_allowed_uri(self.uri)
00212 dispatch[self.action]()
00213 r_pdu = self.__class__()
00214 r_pdu.action = self.action
00215 r_pdu.tag = self.tag
00216 r_pdu.uri = self.uri
00217 r_msg.append(r_pdu)
00218 cb()
00219
00220 def serve_publish(self):
00221 """
00222 Publish an object.
00223 """
00224 rpki.log.info("Publishing %s as %s" % (repr(self.payload), repr(self.uri)))
00225 filename = self.uri_to_filename()
00226 dirname = os.path.dirname(filename)
00227 if not os.path.isdir(dirname):
00228 os.makedirs(dirname)
00229 f = open(filename, "wb")
00230 f.write(self.payload.get_DER())
00231 f.close()
00232
00233 def serve_withdraw(self):
00234 """
00235 Withdraw an object.
00236 """
00237 rpki.log.info("Withdrawing %s" % repr(self.uri))
00238 os.remove(self.uri_to_filename())
00239
00240 def uri_to_filename(self):
00241 """
00242 Convert a URI to a local filename.
00243 """
00244 if not self.uri.startswith("rsync://"):
00245 raise rpki.exceptions.BadURISyntax
00246 filename = self.gctx.publication_base + self.uri[len("rsync://"):]
00247 if filename.find("//") >= 0 or filename.find("/../") >= 0 or filename.endswith("/.."):
00248 raise rpki.exceptions.BadURISyntax
00249 return filename
00250
00251 class certificate_elt(publication_object_elt):
00252 """
00253 <certificate/> element.
00254 """
00255
00256 element_name = "certificate"
00257 payload_type = rpki.x509.X509
00258
00259 class crl_elt(publication_object_elt):
00260 """
00261 <crl/> element.
00262 """
00263
00264 element_name = "crl"
00265 payload_type = rpki.x509.CRL
00266
00267 class manifest_elt(publication_object_elt):
00268 """
00269 <manifest/> element.
00270 """
00271
00272 element_name = "manifest"
00273 payload_type = rpki.x509.SignedManifest
00274
00275 class roa_elt(publication_object_elt):
00276 """
00277 <roa/> element.
00278 """
00279
00280 element_name = "roa"
00281 payload_type = rpki.x509.ROA
00282
00283
00284
00285
00286 obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt))
00287
00288 class report_error_elt(rpki.xml_utils.base_elt, publication_namespace):
00289 """
00290 <report_error/> element.
00291 """
00292
00293 element_name = "report_error"
00294 attributes = ("tag", "error_code")
00295
00296 @classmethod
00297 def from_exception(cls, exc):
00298 """
00299 Generate a <report_error/> element from an exception.
00300 """
00301 self = cls()
00302 self.error_code = exc.__class__.__name__
00303 return self
00304
00305 class msg(rpki.xml_utils.msg, publication_namespace):
00306 """
00307 Publication PDU.
00308 """
00309
00310
00311
00312 version = 1
00313
00314
00315
00316 pdus = dict((x.element_name, x)
00317 for x in (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, report_error_elt))
00318
00319 def serve_top_level(self, gctx, client, cb):
00320 """
00321 Serve one msg PDU.
00322 """
00323 if self.type != "query":
00324 raise rpki.exceptions.BadQuery, "Message type is not query"
00325 r_msg = self.__class__()
00326 r_msg.type = "reply"
00327
00328 def loop(iterator, q_pdu):
00329
00330 def fail(e):
00331 rpki.log.error(traceback.format_exc())
00332 r_msg.append(report_error_elt.from_exception(e))
00333 cb(r_msg)
00334
00335 try:
00336 q_pdu.gctx = gctx
00337 q_pdu.client = client
00338 q_pdu.serve_dispatch(r_msg, iterator, fail)
00339 except (rpki.async.ExitNow, SystemExit):
00340 raise
00341 except Exception, edata:
00342 fail(edata)
00343
00344 def done():
00345 cb(r_msg)
00346
00347 rpki.async.iterator(self, loop, done)
00348
00349 class sax_handler(rpki.xml_utils.sax_handler):
00350 """
00351 SAX handler for publication protocol.
00352 """
00353
00354 pdu = msg
00355 name = "msg"
00356 version = "1"
00357
00358 class cms_msg(rpki.x509.XML_CMS_object):
00359 """
00360 Class to hold a CMS-signed publication PDU.
00361 """
00362
00363 encoding = "us-ascii"
00364 schema = rpki.relaxng.publication
00365 saxify = sax_handler.saxify