00001 """
00002 RPKI "up-down" protocol.
00003
00004 $Id: up_down.py 2499 2009-06-07 01:04:39Z 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, traceback
00036 import rpki.resource_set, rpki.x509, rpki.exceptions
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 s.find("://") < 0:
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 rpki.log.info("list_pdu.serve_pdu callback")
00248
00249 r_msg.payload = list_response_pdu()
00250
00251 for parent in child.parents():
00252 for ca in parent.cas():
00253 ca_detail = ca.fetch_active()
00254 if not ca_detail:
00255 continue
00256 resources = ca_detail.latest_ca_cert.get_3779resources().intersection(irdb_resources)
00257 if resources.empty():
00258 continue
00259 rc = class_elt()
00260 rc.class_name = str(ca.ca_id)
00261 rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
00262 rc.from_resource_bag(resources)
00263 for child_cert in child.child_certs(ca_detail = ca_detail):
00264 c = certificate_elt()
00265 c.cert_url = multi_uri(child_cert.uri(ca))
00266 c.cert = child_cert.cert
00267 rc.certs.append(c)
00268 rc.issuer = ca_detail.latest_ca_cert
00269 r_msg.payload.classes.append(rc)
00270 callback()
00271
00272 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, handle, errback)
00273
00274 @classmethod
00275 def query(cls, parent, cb, eb):
00276 """Send a "list" query to parent."""
00277 parent.query_up_down(cls(), cb, eb)
00278
00279 class class_response_syntax(base_elt):
00280 """
00281 Syntax for Up-Down protocol "list_response" and "issue_response" PDUs.
00282 """
00283
00284 def __init__(self):
00285 """Initialize class_response_syntax."""
00286 base_elt.__init__(self)
00287 self.classes = []
00288
00289 def startElement(self, stack, name, attrs):
00290 """
00291 Handle "list_response" and "issue_response" PDUs.
00292 """
00293 assert name == "class", "Unexpected name %s, stack %s" % (name, stack)
00294 c = class_elt()
00295 self.classes.append(c)
00296 stack.append(c)
00297 c.startElement(stack, name, attrs)
00298
00299 def toXML(self):
00300 """Generate payload of "list_response" and "issue_response" PDUs."""
00301 return [c.toXML() for c in self.classes]
00302
00303 class list_response_pdu(class_response_syntax):
00304 """
00305 Up-Down protocol "list_response" PDU.
00306 """
00307 pass
00308
00309 class issue_pdu(base_elt):
00310 """
00311 Up-Down protocol "issue" PDU.
00312 """
00313
00314 def startElement(self, stack, name, attrs):
00315 """
00316 Handle "issue" PDU.
00317 """
00318 assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
00319 self.class_name = attrs["class_name"]
00320 self.req_resource_set_as = rpki.resource_set.resource_set_as(attrs.get("req_resource_set_as"))
00321 self.req_resource_set_ipv4 = rpki.resource_set.resource_set_ipv4(attrs.get("req_resource_set_ipv4"))
00322 self.req_resource_set_ipv6 = rpki.resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6"))
00323
00324 def endElement(self, stack, name, text):
00325 """
00326 Handle "issue" PDU.
00327 """
00328 assert name == "request", "Unexpected name %s, stack %s" % (name, stack)
00329 self.pkcs10 = rpki.x509.PKCS10(Base64=text)
00330 stack.pop()
00331
00332 def toXML(self):
00333 """
00334 Generate payload of "issue" PDU.
00335 """
00336 elt = self.make_elt("request", "class_name", "req_resource_set_as",
00337 "req_resource_set_ipv4", "req_resource_set_ipv6")
00338 elt.text = self.pkcs10.get_Base64()
00339 return [elt]
00340
00341 def serve_pdu(self, q_msg, r_msg, child, callback, errback):
00342 """
00343 Serve one issue request PDU.
00344 """
00345
00346
00347
00348
00349 if self.req_resource_set_as or \
00350 self.req_resource_set_ipv4 or \
00351 self.req_resource_set_ipv6:
00352 raise rpki.exceptions.NotImplementedYet, "req_* attributes not implemented yet, sorry"
00353
00354
00355 self.pkcs10.check_valid_rpki()
00356 ca = child.ca_from_class_name(self.class_name)
00357 ca_detail = ca.fetch_active()
00358 if ca_detail is None:
00359 raise rpki.exceptions.NoActiveCA, "No active CA for class %s" % repr(self.class_name)
00360
00361
00362
00363 def got_resources(irdb_resources):
00364
00365 resources = irdb_resources.intersection(ca_detail.latest_ca_cert.get_3779resources())
00366 req_key = self.pkcs10.getPublicKey()
00367 req_sia = self.pkcs10.get_SIA()
00368 child_cert = child.child_certs(ca_detail = ca_detail, ski = req_key.get_SKI(), unique = True)
00369
00370
00371
00372 def got_child_cert(child_cert):
00373
00374 self.gctx.sql.sweep()
00375 assert child_cert and child_cert.sql_in_db
00376 c = certificate_elt()
00377 c.cert_url = multi_uri(child_cert.uri(ca))
00378 c.cert = child_cert.cert
00379 rc = class_elt()
00380 rc.class_name = self.class_name
00381 rc.cert_url = multi_uri(ca_detail.ca_cert_uri)
00382 rc.from_resource_bag(resources)
00383 rc.certs.append(c)
00384 rc.issuer = ca_detail.latest_ca_cert
00385 r_msg.payload = issue_response_pdu()
00386 r_msg.payload.classes.append(rc)
00387 callback()
00388
00389 if child_cert is None:
00390 ca_detail.issue(
00391 ca = ca,
00392 child = child,
00393 subject_key = req_key,
00394 sia = req_sia,
00395 resources = resources,
00396 callback = got_child_cert,
00397 errback = errback)
00398 else:
00399 child_cert.reissue(
00400 ca_detail = ca_detail,
00401 sia = req_sia,
00402 resources = resources,
00403 callback = got_child_cert,
00404 errback = errback)
00405
00406 self.gctx.irdb_query_child_resources(child.self().self_handle, child.child_handle, got_resources, errback)
00407
00408 @classmethod
00409 def query(cls, parent, ca, ca_detail, callback, errback):
00410 """
00411 Send an "issue" request to parent associated with ca.
00412 """
00413 assert ca_detail is not None and ca_detail.state in ("pending", "active")
00414 sia = ((rpki.oids.name2oid["id-ad-caRepository"], ("uri", ca.sia_uri)),
00415 (rpki.oids.name2oid["id-ad-rpkiManifest"], ("uri", ca_detail.manifest_uri(ca))))
00416 self = cls()
00417 self.class_name = ca.parent_resource_class
00418 self.pkcs10 = rpki.x509.PKCS10.create_ca(ca_detail.private_key_id, sia)
00419 parent.query_up_down(self, callback, errback)
00420
00421 class issue_response_pdu(class_response_syntax):
00422 """
00423 Up-Down protocol "issue_response" PDU.
00424 """
00425
00426 def check_response(self):
00427 """
00428 Check whether this looks like a reasonable issue_response PDU.
00429 XML schema should be tighter for this response.
00430 """
00431 if len(self.classes) != 1 or len(self.classes[0].certs) != 1:
00432 raise rpki.exceptions.BadIssueResponse
00433
00434 class revoke_syntax(base_elt):
00435 """
00436 Syntax for Up-Down protocol "revoke" and "revoke_response" PDUs.
00437 """
00438
00439 def startElement(self, stack, name, attrs):
00440 """Handle "revoke" PDU."""
00441 self.class_name = attrs["class_name"]
00442 self.ski = attrs["ski"]
00443
00444 def toXML(self):
00445 """Generate payload of "revoke" PDU."""
00446 return [self.make_elt("key", "class_name", "ski")]
00447
00448 class revoke_pdu(revoke_syntax):
00449 """
00450 Up-Down protocol "revoke" PDU.
00451 """
00452
00453 def get_SKI(self):
00454 """Convert g(SKI) encoding from PDU back to raw SKI."""
00455 return base64.urlsafe_b64decode(self.ski + "=")
00456
00457 def serve_pdu(self, q_msg, r_msg, child, cb, eb):
00458 """
00459 Serve one revoke request PDU.
00460 """
00461
00462 def loop1(iterator1, ca_detail):
00463
00464 def loop2(iterator2, child_cert):
00465 child_cert.revoke(iterator2, eb)
00466
00467 rpki.async.iterator(child.child_certs(ca_detail = ca_detail, ski = self.get_SKI()), loop2, iterator1)
00468
00469 def done():
00470 self.gctx.sql.sweep()
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 rpki.async.iterator(child.ca_from_class_name(self.class_name).ca_details(), loop1, done)
00477
00478 @classmethod
00479 def query(cls, ca_detail, cb, eb):
00480 """
00481 Send a "revoke" request to parent associated with ca_detail.
00482 """
00483 ca = ca_detail.ca()
00484 parent = ca.parent()
00485 self = cls()
00486 self.class_name = ca.parent_resource_class
00487 self.ski = ca_detail.latest_ca_cert.gSKI()
00488 parent.query_up_down(self, cb, eb)
00489
00490 class revoke_response_pdu(revoke_syntax):
00491 """
00492 Up-Down protocol "revoke_response" PDU.
00493 """
00494
00495 pass
00496
00497 class error_response_pdu(base_elt):
00498 """
00499 Up-Down protocol "error_response" PDU.
00500 """
00501
00502 codes = {
00503 1101 : "Already processing request",
00504 1102 : "Version number error",
00505 1103 : "Unrecognised request type",
00506 1201 : "Request - no such resource class",
00507 1202 : "Request - no resources allocated in resource class",
00508 1203 : "Request - badly formed certificate request",
00509 1301 : "Revoke - no such resource class",
00510 1302 : "Revoke - no such key",
00511 2001 : "Internal Server Error - Request not performed" }
00512
00513 exceptions = {
00514 rpki.exceptions.NoActiveCA : 1202 }
00515
00516 def __init__(self, exception = None):
00517 """
00518 Initialize an error_response PDU from an exception object.
00519 """
00520 base_elt.__init__(self)
00521 if exception is not None:
00522 self.status = self.exceptions.get(type(exception), 2001)
00523 self.description = str(exception)
00524
00525 def endElement(self, stack, name, text):
00526 """
00527 Handle "error_response" PDU.
00528 """
00529 if name == "status":
00530 code = int(text)
00531 if code not in self.codes:
00532 raise rpki.exceptions.BadStatusCode, "%s is not a known status code" % code
00533 self.status = code
00534 elif name == "description":
00535 self.description = text
00536 else:
00537 assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
00538 stack.pop()
00539 stack[-1].endElement(stack, name, text)
00540
00541 def toXML(self):
00542 """
00543 Generate payload of "error_response" PDU.
00544 """
00545 assert self.status in self.codes
00546 elt = self.make_elt("status")
00547 elt.text = str(self.status)
00548 payload = [elt]
00549 if self.description:
00550 elt = self.make_elt("description")
00551 elt.text = str(self.description)
00552 elt.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US")
00553 payload.append(elt)
00554 return payload
00555
00556 def check_response(self):
00557 """
00558 Handle an error response. For now, just raise an exception,
00559 perhaps figure out something more clever to do later.
00560 """
00561 raise rpki.exceptions.UpstreamError, self.codes[self.status]
00562
00563 class message_pdu(base_elt):
00564 """
00565 Up-Down protocol message wrapper PDU.
00566 """
00567
00568 version = 1
00569
00570 name2type = {
00571 "list" : list_pdu,
00572 "list_response" : list_response_pdu,
00573 "issue" : issue_pdu,
00574 "issue_response" : issue_response_pdu,
00575 "revoke" : revoke_pdu,
00576 "revoke_response" : revoke_response_pdu,
00577 "error_response" : error_response_pdu }
00578
00579 type2name = dict((v, k) for k, v in name2type.items())
00580
00581 def toXML(self):
00582 """
00583 Generate payload of message PDU.
00584 """
00585 elt = self.make_elt("message", "version", "sender", "recipient", "type")
00586 elt.extend(self.payload.toXML())
00587 return elt
00588
00589 def startElement(self, stack, name, attrs):
00590 """
00591 Handle message PDU.
00592
00593 Payload of the <message/> element varies depending on the "type"
00594 attribute, so after some basic checks we have to instantiate the
00595 right class object to handle whatever kind of PDU this is.
00596 """
00597 assert name == "message", "Unexpected name %s, stack %s" % (name, stack)
00598 assert self.version == int(attrs["version"])
00599 self.sender = attrs["sender"]
00600 self.recipient = attrs["recipient"]
00601 self.type = attrs["type"]
00602 self.payload = self.name2type[attrs["type"]]()
00603 stack.append(self.payload)
00604
00605 def __str__(self):
00606 """Convert a message PDU to a string."""
00607 lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "UTF-8")
00608
00609 def serve_top_level(self, child, callback):
00610 """
00611 Serve one message request PDU.
00612 """
00613
00614 r_msg = message_pdu()
00615 r_msg.sender = self.recipient
00616 r_msg.recipient = self.sender
00617
00618 def done():
00619 r_msg.type = self.type2name[type(r_msg.payload)]
00620 callback(r_msg)
00621
00622 def lose(e):
00623 rpki.log.error(traceback.format_exc())
00624 callback(self.serve_error(e))
00625
00626 try:
00627 self.payload.serve_pdu(self, r_msg, child, done, lose)
00628 except (rpki.async.ExitNow, SystemExit):
00629 raise
00630 except Exception, edata:
00631 lose(edata)
00632
00633 def serve_error(self, exception):
00634 """
00635 Generate an error_response message PDU.
00636 """
00637 r_msg = message_pdu()
00638 r_msg.sender = self.recipient
00639 r_msg.recipient = self.sender
00640 r_msg.payload = error_response_pdu(exception)
00641 r_msg.type = self.type2name[type(r_msg.payload)]
00642 return r_msg
00643
00644 @classmethod
00645 def make_query(cls, payload, sender, recipient):
00646 """
00647 Construct one message PDU.
00648 """
00649 assert not cls.type2name[type(payload)].endswith("_response")
00650 if sender is None:
00651 sender = "tweedledee"
00652 if recipient is None:
00653 recipient = "tweedledum"
00654 self = cls()
00655 self.sender = sender
00656 self.recipient = recipient
00657 self.payload = payload
00658 self.type = self.type2name[type(payload)]
00659 return self
00660
00661 class sax_handler(rpki.xml_utils.sax_handler):
00662 """
00663 SAX handler for Up-Down protocol.
00664 """
00665
00666 pdu = message_pdu
00667 name = "message"
00668 version = "1"
00669
00670 class cms_msg(rpki.x509.XML_CMS_object):
00671 """
00672 Class to hold a CMS-signed up-down PDU.
00673 """
00674
00675 encoding = "UTF-8"
00676 schema = rpki.relaxng.up_down
00677 saxify = sax_handler.saxify