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