00001 """
00002 RPKI "up-down" protocol.
00003
00004 $Id: up_down.py 2935 2010-01-07 17:23:05Z 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, lxml.etree
00036 import rpki.resource_set, rpki.x509, rpki.exceptions, rpki.log
00037 import rpki.xml_utils, rpki.relaxng
00038
00039 xmlns = "http://www.apnic.net/specs/rescerts/up-down/"
00040
00041 nsmap = { None : xmlns }
00042
00043 class base_elt(object):
00044 """
00045 Generic PDU object.
00046
00047 Virtual class, just provides some default methods.
00048 """
00049
00050 def startElement(self, stack, name, attrs):
00051 """
00052 Ignore startElement() if there's no specific handler.
00053
00054 Some elements have no attributes and we only care about their
00055 text content.
00056 """
00057 pass
00058
00059 def endElement(self, stack, name, text):
00060 """
00061 Ignore endElement() if there's no specific handler.
00062
00063 If we don't need to do anything else, just pop the stack.
00064 """
00065 stack.pop()
00066
00067 def make_elt(self, name, *attrs):
00068 """
00069 Construct a element, copying over a set of attributes.
00070 """
00071 elt = lxml.etree.Element("{%s}%s" % (xmlns, name), nsmap=nsmap)
00072 for key in attrs:
00073 val = getattr(self, key, None)
00074 if val is not None:
00075 elt.set(key, str(val))
00076 return elt
00077
00078 def make_b64elt(self, elt, name, value=None):
00079 """
00080 Construct a sub-element with Base64 text content.
00081 """
00082 if value is None:
00083 value = getattr(self, name, None)
00084 if value is not None:
00085 lxml.etree.SubElement(elt, "{%s}%s" % (xmlns, name), nsmap=nsmap).text = base64.b64encode(value)
00086
00087 def serve_pdu(self, q_msg, r_msg, child, callback, errback):
00088 """Default PDU handler to catch unexpected types."""
00089 raise rpki.exceptions.BadQuery, "Unexpected query type %s" % q_msg.type
00090
00091 def check_response(self):
00092 """Placeholder for response checking."""
00093 pass
00094
00095 class multi_uri(list):
00096 """
00097 Container for a set of URIs.
00098 """
00099
00100 def __init__(self, ini):
00101 """
00102 Initialize a set of URIs, which includes basic some syntax checking.
00103 """
00104 list.__init__(self)
00105 if isinstance(ini, (list, tuple)):
00106 self[:] = ini
00107 elif isinstance(ini, str):
00108 self[:] = ini.split(",")
00109 for s in self:
00110 if s.strip() != s or "://" not in s:
00111 raise rpki.exceptions.BadURISyntax, "Bad URI \"%s\"" % s
00112 else:
00113 raise TypeError
00114
00115 def __str__(self):
00116 """Convert a multi_uri back to a string representation."""
00117 return ",".join(self)
00118
00119 def rsync(self):
00120 """
00121 Find first rsync://... URI in self.
00122 """
00123 for s in self:
00124 if s.startswith("rsync://"):
00125 return s
00126 return None
00127
00128 class certificate_elt(base_elt):
00129 """
00130 Up-Down protocol representation of an issued certificate.
00131 """
00132
00133 def startElement(self, stack, name, attrs):
00134 """
00135 Handle attributes of <certificate/> element.
00136 """
00137 assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
00138 self.cert_url = multi_uri(attrs["cert_url"])
00139 self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
00140 self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4"))
00141 self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6"))
00142
00143 def endElement(self, stack, name, text):
00144 """
00145 Handle text content of a <certificate/> element.
00146 """
00147 assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack)
00148 self.cert = rpki.x509.X509(Base64=text)
00149 stack.pop()
00150
00151 def toXML(self):
00152 """
00153 Generate a <certificate/> element.
00154 """
00155 elt = self.make_elt("certificate", "cert_url",
00156 "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6")
00157 elt.text = self.cert.get_Base64()
00158 return elt
00159
00160 class class_elt(base_elt):
00161 """
00162 Up-Down protocol representation of a resource class.
00163 """
00164
00165 issuer = None
00166
00167 def __init__(self):
00168 """Initialize class_elt."""
00169 base_elt.__init__(self)
00170 self.certs = []
00171
00172 def startElement(self, stack, name, attrs):
00173 """
00174 Handle <class/> elements and their children.
00175 """
00176 if name == "certificate":
00177 cert = certificate_elt()
00178 self.certs.append(cert)
00179 stack.append(cert)
00180 cert.startElement(stack, name, attrs)
00181 elif name != "issuer":
00182 assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
00183 self.class_name = attrs["class_name"]
00184 self.cert_url = multi_uri(attrs["cert_url"])
00185 self.suggested_sia_head = attrs.get("suggested_sia_head")
00186 self.resource_set_as = rpki.resource_set.resource_set_as(attrs["resource_set_as"])
00187 self.resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs["resource_set_ipv4"])
00188 self.resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs["resource_set_ipv6"])
00189 self.resource_set_notafter = rpki.sundial.datetime.fromXMLtime(attrs.get("resource_set_notafter"))
00190
00191 def endElement(self, stack, name, text):
00192 """
00193 Handle <class/> elements and their children.
00194 """
00195 if name == "issuer":
00196 self.issuer = rpki.x509.X509(Base64=text)
00197 else:
00198 assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
00199 stack.pop()
00200
00201 def toXML(self):
00202 """
00203 Generate a <class/> element.
00204 """
00205 elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as",
00206 "resource_set_ipv4", "resource_set_ipv6",
00207 "resource_set_notafter", "suggested_sia_head")
00208 elt.extend([i.toXML() for i in self.certs])
00209 if self.issuer is not None:
00210 self.make_b64elt(elt, "issuer", self.issuer.get_DER())
00211 return elt
00212
00213 def to_resource_bag(self):
00214 """
00215 Build a resource_bag from from this <class/> element.
00216 """
00217 return rpki.resource_set.resource_bag(self.resource_set_as,
00218 self.resource_set_ipv4,
00219 self.resource_set_ipv6,
00220 self.resource_set_notafter)
00221
00222 def from_resource_bag(self, bag):
00223 """
00224 Set resources of this class element from a resource_bag.
00225 """
00226 self.resource_set_as = bag.asn
00227 self.resource_set_ipv4 = bag.v4
00228 self.resource_set_ipv6 = bag.v6
00229 self.resource_set_notafter = bag.valid_until
00230
00231 class list_pdu(base_elt):
00232 """
00233 Up-Down protocol "list" PDU.
00234 """
00235
00236 def toXML(self):
00237 """Generate (empty) payload of "list" PDU."""
00238 return []
00239
00240 def serve_pdu(self, q_msg, r_msg, child, callback, errback):
00241 """
00242 Serve one "list" PDU.
00243 """
00244
00245 def handle(irdb_resources):
00246
00247 r_msg.payload = list_response_pdu()
00248
00249 for parent in child.parents():
00250 for ca in parent.cas():
00251 ca_detail = ca.fetch_active()
00252 if not ca_detail:
00253 continue
00254 resources = ca_detail.latest_ca_cert.get_3779resources().intersection(irdb_resources)
00255 if resources.empty():
00256 continue
00257 rc = class_elt()
00258 rc.class_name = str(ca.ca_id)
00259 rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
00260 rc.from_resource_bag(resources)
00261 for child_cert in child.child_certs(ca_detail = ca_detail):
00262 c = certificate_elt()
00263 c.cert_url = multi_uri(child_cert.uri(ca))
00264 c.cert = child_cert.cert
00265 rc.certs.append(c)
00266 rc.issuer = ca_detail.latest_ca_cert
00267 r_msg.payload.classes.append(rc)
00268 callback()
00269
00270 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, handle, errback)
00271
00272 @classmethod
00273 def query(cls, parent, cb, eb):
00274 """
00275 Send a "list" query to parent.
00276 """
00277 try:
00278 parent.query_up_down(cls(), cb, eb)
00279 except (rpki.async.ExitNow, SystemExit):
00280 raise
00281 except Exception, e:
00282 eb(e)
00283
00284 class class_response_syntax(base_elt):
00285 """
00286 Syntax for Up-Down protocol "list_response" and "issue_response" PDUs.
00287 """
00288
00289 def __init__(self):
00290 """
00291 Initialize class_response_syntax.
00292 """
00293 base_elt.__init__(self)
00294 self.classes = []
00295
00296 def startElement(self, stack, name, attrs):
00297 """
00298 Handle "list_response" and "issue_response" PDUs.
00299 """
00300 assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
00301 c = class_elt()
00302 self.classes.append(c)
00303 stack.append(c)
00304 c.startElement(stack, name, attrs)
00305
00306 def toXML(self):
00307 """Generate payload of "list_response" and "issue_response" PDUs."""
00308 return [c.toXML() for c in self.classes]
00309
00310 class list_response_pdu(class_response_syntax):
00311 """
00312 Up-Down protocol "list_response" PDU.
00313 """
00314 pass
00315
00316 class issue_pdu(base_elt):
00317 """
00318 Up-Down protocol "issue" PDU.
00319 """
00320
00321 def startElement(self, stack, name, attrs):
00322 """
00323 Handle "issue" PDU.
00324 """
00325 assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
00326 self.class_name = attrs["class_name"]
00327 self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
00328 self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4"))
00329 self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6"))
00330
00331 def endElement(self, stack, name, text):
00332 """
00333 Handle "issue" PDU.
00334 """
00335 assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
00336 self.pkcs10 = rpki.x509.PKCS10(Base64=text)
00337 stack.pop()
00338
00339 def toXML(self):
00340 """
00341 Generate payload of "issue" PDU.
00342 """
00343 elt = self.make_elt("request", "class_name", "req_resource_set_as",
00344 "req_resource_set_ipv4", "req_resource_set_ipv6")
00345 elt.text = self.pkcs10.get_Base64()
00346 return [elt]
00347
00348 def serve_pdu(self, q_msg, r_msg, child, callback, errback):
00349 """
00350 Serve one issue request PDU.
00351 """
00352
00353
00354
00355
00356 if self.req_resource_set_as or \
00357 self.req_resource_set_ipv4 or \
00358 self.req_resource_set_ipv6:
00359 raise rpki.exceptions.NotImplementedYet, "req_* attributes not implemented yet, sorry"
00360
00361
00362 self.pkcs10.check_valid_rpki()
00363 ca = child.ca_from_class_name(self.class_name)
00364 ca_detail = ca.fetch_active()
00365 if ca_detail is None:
00366 raise rpki.exceptions.NoActiveCA, "No active CA for class %r" % self.class_name
00367
00368
00369
00370 def got_resources(irdb_resources):
00371
00372 resources = irdb_resources.intersection(ca_detail.latest_ca_cert.get_3779resources())
00373 req_key = self.pkcs10.getPublicKey()
00374 req_sia = self.pkcs10.get_SIA()
00375 child_cert = child.child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True)
00376
00377
00378
00379 publisher = rpki.rpki_engine.publication_queue()
00380
00381 if child_cert is None:
00382 child_cert = ca_detail.issue(
00383 ca = ca,
00384 child = child,
00385 subject_key = req_key,
00386 sia = req_sia,
00387 resources = resources,
00388 publisher = publisher)
00389 else:
00390 child_cert = child_cert.reissue(
00391 ca_detail = ca_detail,
00392 sia = req_sia,
00393 resources = resources,
00394 publisher = publisher)
00395
00396 def done():
00397 c = certificate_elt()
00398 c.cert_url = multi_uri(child_cert.uri(ca))
00399 c.cert = child_cert.cert
00400 rc = class_elt()
00401 rc.class_name = self.class_name
00402 rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
00403 rc.from_resource_bag(resources)
00404 rc.certs.append(c)
00405 rc.issuer = ca_detail.latest_ca_cert
00406 r_msg.payload = issue_response_pdu()
00407 r_msg.payload.classes.append(rc)
00408 callback()
00409
00410 self.gctx.sql.sweep()
00411 assert child_cert and child_cert.sql_in_db
00412 publisher.call_pubd(done, errback)
00413
00414 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, errback)
00415
00416 @classmethod
00417 def query(cls, parent, ca, ca_detail, callback, errback):
00418 """
00419 Send an "issue" request to parent associated with ca.
00420 """
00421 assert ca_detail is not None and ca_detail.state in ("pending", "active")
00422 sia = ((rpki.oids.name2oid["id-ad-caRepository"], ("uri", ca.sia_uri)),
00423 (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", ca_detail.manifest_uri(ca))))
00424 self = cls()
00425 self.class_name = ca.parent_resource_class
00426 self.pkcs10 = rpki.x509.PKCS10.create_ca(ca_detail.private_key_id, sia)
00427 parent.query_up_down(self, callback, errback)
00428
00429 class issue_response_pdu(class_response_syntax):
00430 """
00431 Up-Down protocol "issue_response" PDU.
00432 """
00433
00434 def check_response(self):
00435 """
00436 Check whether this looks like a reasonable issue_response PDU.
00437 XML schema should be tighter for this response.
00438 """
00439 if len(self.classes) != 1 or len(self.classes[0].certs) != 1:
00440 raise rpki.exceptions.BadIssueResponse
00441
00442 class revoke_syntax(base_elt):
00443 """
00444 Syntax for Up-Down protocol "revoke" and "revoke_response" PDUs.
00445 """
00446
00447 def startElement(self, stack, name, attrs):
00448 """Handle "revoke" PDU."""
00449 self.class_name = attrs["class_name"]
00450 self.ski = attrs["ski"]
00451
00452 def toXML(self):
00453 """Generate payload of "revoke" PDU."""
00454 return [self.make_elt("key", "class_name", "ski")]
00455
00456 class revoke_pdu(revoke_syntax):
00457 """
00458 Up-Down protocol "revoke" PDU.
00459 """
00460
00461 def get_SKI(self):
00462 """Convert g(SKI) encoding from PDU back to raw SKI."""
00463 return base64.urlsafe_b64decode(self.ski + "=")
00464
00465 def serve_pdu(self, q_msg, r_msg, child, cb, eb):
00466 """
00467 Serve one revoke request PDU.
00468 """
00469
00470 def done():
00471 r_msg.payload = revoke_response_pdu()
00472 r_msg.payload.class_name = self.class_name
00473 r_msg.payload.ski = self.ski
00474 cb()
00475
00476 ca = child.ca_from_class_name(self.class_name)
00477 publisher = rpki.rpki_engine.publication_queue()
00478 for ca_detail in ca.ca_details():
00479 for child_cert in child.child_certs(ca_detail = ca_detail, ski = self.get_SKI()):
00480 child_cert.revoke(publisher = publisher)
00481 self.gctx.sql.sweep()
00482 publisher.call_pubd(done, eb)
00483
00484 @classmethod
00485 def query(cls, ca, gski, cb, eb):
00486 """
00487 Send a "revoke" request for certificate(s) named by gski to parent associated with ca.
00488 """
00489 parent = ca.parent()
00490 self = cls()
00491 self.class_name = ca.parent_resource_class
00492 self.ski = gski
00493 parent.query_up_down(self, cb, eb)
00494
00495 class revoke_response_pdu(revoke_syntax):
00496 """
00497 Up-Down protocol "revoke_response" PDU.
00498 """
00499
00500 pass
00501
00502 class error_response_pdu(base_elt):
00503 """
00504 Up-Down protocol "error_response" PDU.
00505 """
00506
00507 codes = {
00508 1101 : "Already processing request",
00509 1102 : "Version number error",
00510 1103 : "Unrecognised request type",
00511 1201 : "Request - no such resource class",
00512 1202 : "Request - no resources allocated in resource class",
00513 1203 : "Request - badly formed certificate request",
00514 1301 : "Revoke - no such resource class",
00515 1302 : "Revoke - no such key",
00516 2001 : "Internal Server Error - Request not performed" }
00517
00518 exceptions = {
00519 rpki.exceptions.NoActiveCA : 1202 }
00520
00521 def __init__(self, exception = None):
00522 """
00523 Initialize an error_response PDU from an exception object.
00524 """
00525 base_elt.__init__(self)
00526 if exception is not None:
00527 self.status = self.exceptions.get(type(exception), 2001)
00528 self.description = str(exception)
00529
00530 def endElement(self, stack, name, text):
00531 """
00532 Handle "error_response" PDU.
00533 """
00534 if name == "status":
00535 code = int(text)
00536 if code not in self.codes:
00537 raise rpki.exceptions.BadStatusCode, "%s is not a known status code" % code
00538 self.status = code
00539 elif name == "description":
00540 self.description = text
00541 else:
00542 assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
00543 stack.pop()
00544 stack[-1].endElement(stack, name, text)
00545
00546 def toXML(self):
00547 """
00548 Generate payload of "error_response" PDU.
00549 """
00550 assert self.status in self.codes
00551 elt = self.make_elt("status")
00552 elt.text = str(self.status)
00553 payload = [elt]
00554 if self.description:
00555 elt = self.make_elt("description")
00556 elt.text = str(self.description)
00557 elt.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US")
00558 payload.append(elt)
00559 return payload
00560
00561 def check_response(self):
00562 """
00563 Handle an error response. For now, just raise an exception,
00564 perhaps figure out something more clever to do later.
00565 """
00566 raise rpki.exceptions.UpstreamError, self.codes[self.status]
00567
00568 class message_pdu(base_elt):
00569 """
00570 Up-Down protocol message wrapper PDU.
00571 """
00572
00573 version = 1
00574
00575 name2type = {
00576 "list" : list_pdu,
00577 "list_response" : list_response_pdu,
00578 "issue" : issue_pdu,
00579 "issue_response" : issue_response_pdu,
00580 "revoke" : revoke_pdu,
00581 "revoke_response" : revoke_response_pdu,
00582 "error_response" : error_response_pdu }
00583
00584 type2name = dict((v, k) for k, v in name2type.items())
00585
00586 def toXML(self):
00587 """
00588 Generate payload of message PDU.
00589 """
00590 elt = self.make_elt("message", "version", "sender", "recipient", "type")
00591 elt.extend(self.payload.toXML())
00592 return elt
00593
00594 def startElement(self, stack, name, attrs):
00595 """
00596 Handle message PDU.
00597
00598 Payload of the <message/> element varies depending on the "type"
00599 attribute, so after some basic checks we have to instantiate the
00600 right class object to handle whatever kind of PDU this is.
00601 """
00602 assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
00603 assert self.version == int(attrs["version"])
00604 self.sender = attrs["sender"]
00605 self.recipient = attrs["recipient"]
00606 self.type = attrs["type"]
00607 self.payload = self.name2type[attrs["type"]]()
00608 stack.append(self.payload)
00609
00610 def __str__(self):
00611 """Convert a message PDU to a string."""
00612 lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8")
00613
00614 def serve_top_level(self, child, callback):
00615 """
00616 Serve one message request PDU.
00617 """
00618
00619 r_msg = message_pdu()
00620 r_msg.sender = self.recipient
00621 r_msg.recipient = self.sender
00622
00623 def done():
00624 r_msg.type = self.type2name[type(r_msg.payload)]
00625 callback(r_msg)
00626
00627 def lose(e):
00628 rpki.log.traceback()
00629 callback(self.serve_error(e))
00630
00631 try:
00632 self.log_query(child)
00633 self.payload.serve_pdu(self, r_msg, child, done, lose)
00634 except (rpki.async.ExitNow, SystemExit):
00635 raise
00636 except Exception, e:
00637 lose(e)
00638
00639 def log_query(self, child):
00640 """
00641 Log query we're handling. Separate method so rootd can override.
00642 """
00643 rpki.log.info("Serving %s query from child %s" % (self.type, child.child_handle))
00644
00645 def serve_error(self, exception):
00646 """
00647 Generate an error_response message PDU.
00648 """
00649 r_msg = message_pdu()
00650 r_msg.sender = self.recipient
00651 r_msg.recipient = self.sender
00652 r_msg.payload = error_response_pdu(exception)
00653 r_msg.type = self.type2name[type(r_msg.payload)]
00654 return r_msg
00655
00656 @classmethod
00657 def make_query(cls, payload, sender, recipient):
00658 """
00659 Construct one message PDU.
00660 """
00661 assert not cls.type2name[type(payload)].endswith("_response")
00662 if sender is None:
00663 sender = "tweedledee"
00664 if recipient is None:
00665 recipient = "tweedledum"
00666 self = cls()
00667 self.sender = sender
00668 self.recipient = recipient
00669 self.payload = payload
00670 self.type = self.type2name[type(payload)]
00671 return self
00672
00673 class sax_handler(rpki.xml_utils.sax_handler):
00674 """
00675 SAX handler for Up-Down protocol.
00676 """
00677
00678 pdu = message_pdu
00679 name = "message"
00680 version = "1"
00681
00682 class cms_msg(rpki.x509.XML_CMS_object):
00683 """
00684 Class to hold a CMS-signed up-down PDU.
00685 """
00686
00687 encoding = "UTF-8"
00688 schema = rpki.relaxng.up_down
00689 saxify = sax_handler.saxify