diff options
author | Rob Austein <sra@hactrn.net> | 2007-08-12 03:04:03 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2007-08-12 03:04:03 +0000 |
commit | eba8fc672bf69b608e759daa9d20aa48e1b88b88 (patch) | |
tree | 57484cc973a58a5597fb0bbdfd88e837ca01359d | |
parent | c866ab06942baf21404c25128c2858d639fd4092 (diff) |
Doc
svn path=/scripts/generate-testrepo.py; revision=868
-rw-r--r-- | scripts/generate-testrepo.py | 40 | ||||
-rwxr-xr-x | scripts/irbe-cli.py | 38 | ||||
-rw-r--r-- | scripts/rpki/cms.py | 25 | ||||
-rw-r--r-- | scripts/rpki/https.py | 27 | ||||
-rw-r--r-- | scripts/rpki/ipaddrs.py | 25 | ||||
-rw-r--r-- | scripts/rpki/left_right.py | 66 | ||||
-rw-r--r-- | scripts/rpki/relaxng.py | 6 | ||||
-rw-r--r-- | scripts/rpki/resource_set.py | 141 | ||||
-rw-r--r-- | scripts/rpki/sax_utils.py | 30 | ||||
-rw-r--r-- | scripts/rpki/up_down.py | 95 | ||||
-rw-r--r-- | scripts/rpki/x509.py | 87 |
11 files changed, 396 insertions, 184 deletions
diff --git a/scripts/generate-testrepo.py b/scripts/generate-testrepo.py index 01126bee..fd5f2bfb 100644 --- a/scripts/generate-testrepo.py +++ b/scripts/generate-testrepo.py @@ -1,5 +1,16 @@ # $Id$ +"""Generate an RPKI test repository. + +This script generates a toy RPKI repository for test purposes. It's +designed to be relatively easy to reconfigure, making it simple to +test whatever is of interest on a given day, without a lot of setup +overhead. + +Outputs are a bunch of config files for the OpenSSL CLI tool and a +makefile to drive everything. +""" + import rpki.resource_set, os subdir = "resource-cert-samples" @@ -7,10 +18,7 @@ openssl = "../../openssl/openssl-0.9.8e/apps/openssl" keybits = 2048 def main(): - """ - Main program, up front to make it easier to find with all the - OpenSSL config and Makefile template text. - """ + """Main program, including the toy database itself.""" db = allocation_db() db.add("ISP1", ipv4="192.0.2.1-192.0.2.33", asn="64533") @@ -36,8 +44,8 @@ def main(): "".join([i.makefile_rules() for i in db])) def write_maybe(name, new_content): - """ - Write a file if and only if its contents have changed. + """Write a file if and only if its contents have changed. + This simplifies interactions with "make". """ old_content = None if os.path.isfile(name): @@ -51,18 +59,33 @@ def write_maybe(name, new_content): f.close() class allocation_db(list): - + """Class to represent an allocation database.""" + def __init__(self): self.allocation_dict = {} def add(self, name, **kw): + """Add a new entry to this allocation database. + All arguments passed through to the allocation constructor. + """ self.insert(0, allocation(name=name, allocation_dict=self.allocation_dict, **kw)) class allocation(object): + """Class representing one entity holding allocated resources. + + In order to simplify configuration, this class automatically + computes the set of resources that this entity must hold in order to + serve both itself and its children. + """ parent = None def __init__(self, name, asn=None, ipv4=None, ipv6=None, children=[], allocation_dict=None): + """Create a new allocation entry. + + This binds the parent attributes of any children, and computes the + transitive closure of the set of resources this entity needs. + """ self.name = name self.children = [allocation_dict[i] for i in children] for child in self.children: @@ -74,6 +97,7 @@ class allocation(object): allocation_dict[name] = self def summarize(self, attrname, seed=None): + """Compute the transitive resource closure for one resource attribute.""" if seed is None: seed = getattr(self, attrname) for child in self.children: @@ -84,6 +108,7 @@ class allocation(object): return "%s\n ASN: %s\n IPv4: %s\n IPv6: %s" % (self.name, self.asn, self.ipv4, self.ipv6) def cfg_string(self): + """Generate the OpenSSL configuration file needed for this entity.""" keys = { "self" : self.name, "keybits" : keybits, "no_parent" : "#", @@ -104,6 +129,7 @@ class allocation(object): return openssl_cfg_fmt % keys def makefile_rules(self): + """Generate the makefile rules needed for this entity.""" keys = { "self" : self.name, "keybits" : keybits, "openssl" : openssl } diff --git a/scripts/irbe-cli.py b/scripts/irbe-cli.py index 9c7c3cdf..8ebb149e 100755 --- a/scripts/irbe-cli.py +++ b/scripts/irbe-cli.py @@ -1,7 +1,9 @@ # $Id$ -""" -Command line program to simulate behavior of the IR back-end. +"""Command line program to simulate behavior of the IR back-end. + +This only handles the control channel. The query back-channel will be +a separate program. """ import glob, rpki.left_right, rpki.relaxng, getopt, sys, lxml.etree, POW, POW.pkix, rpki.cms, rpki.https, xml.sax, lxml.sax @@ -10,10 +12,16 @@ import glob, rpki.left_right, rpki.relaxng, getopt, sys, lxml.etree, POW, POW.pk convert_from_pem = True class command(object): + """Command processor mixin class for left-right protocol objects. + + This class and its derived classes probably should be merged into + the left-right protocol classes, once this stuff is stable. + """ elements = () def getopt(self, argv): + """Parse options for this class.""" opts, args = getopt.getopt(argv, "", [x + "=" for x in self.attributes + self.elements] + [x for x in self.booleans]) for o, a in opts: o = o[2:] @@ -28,18 +36,26 @@ class command(object): return args def process(self, msg, argv): + """Parse options and add the current object into the msg we're building. + + This is a separate method because at one point I needed to + override it. + """ argv = self.getopt(argv) msg.append(self) return argv def handle_action(self, arg): + """Special handler for --action option.""" self.action = arg self.type = "query" def handle_peer_ta(self, arg): + """Special handler for --peer_ta option.""" self.peer_ta = read_cert(arg) def read_cert(filename): + """Read a certificate file from disk.""" f = open(filename, "r") der = f.read() f.close() @@ -50,10 +66,12 @@ def read_cert(filename): return cert class self(command, rpki.left_right.self_elt): + '''"self" command.''' elements = ("extension_preference",) def handle_extension_preference(self, arg): + """--extension_preferences option.""" k,v = arg.split("=", 1) pref = rpki.left_right.extension_preference_elt() pref.name = k @@ -61,29 +79,39 @@ class self(command, rpki.left_right.self_elt): self.prefs.append(pref) class bsc(command, rpki.left_right.bsc_elt): + '''"bsc" command.''' + elements = ('signing_cert',) def handle_signing_cert(self, arg): + """--signing_cert option.""" self.signing_cert.append(read_cert(arg)) class parent(command, rpki.left_right.parent_elt): + '''"parent" command.''' elements = ("peer_ta",) class child(command, rpki.left_right.child_elt): + '''"child" command.''' elements = ("peer_ta",) class repository(command, rpki.left_right.repository_elt): + '''"repository" command.''' elements = ("peer_ta",) class route_origin(command, rpki.left_right.route_origin_elt): + '''"route_origin" command.''' def handle_asn(self, arg): + """Handle autonomous sequence numbers.""" self.asn = long(arg) def handle_ipv4(self, arg): + """Handle IPv4 addresses.""" self.ipv4 = resource_set.resource_set_ipv4(arg) def handle_ipv6(self, arg): + """Handle IPv6 addresses.""" self.ipv6 = resource_set.resource_set_ipv6(arg) dispatch = dict((x.element_name, x) for x in (self, bsc, parent, child, repository, route_origin)) @@ -95,6 +123,12 @@ def usage(): sys.exit(1) def main(): + """Main program. + + This is still a work in progress. At the moment it gets as + transmitting the generated request, but doesn't yet do anything with + responses. + """ rng = rpki.relaxng.RelaxNG("left-right-schema.rng") httpsCerts = rpki.https.CertInfo("Bob") diff --git a/scripts/rpki/cms.py b/scripts/rpki/cms.py index 374f592d..a00d3ba2 100644 --- a/scripts/rpki/cms.py +++ b/scripts/rpki/cms.py @@ -1,8 +1,9 @@ # $Id$ -""" -CMS routines. For the moment these just call the OpenSSL CLI tool, -which is slow and requires disk I/O and likes PEM format. Fix later. +"""CMS routines. + +For the moment these just call the OpenSSL CLI tool, which is slow, +requires disk I/O, and likes PEM format. Fix this later. """ import os, rpki.x509 @@ -10,6 +11,11 @@ import os, rpki.x509 # openssl smime -sign -nodetach -outform DER -signer biz-certs/Alice-EE.cer -certfile biz-certs/Alice-CA.cer -inkey biz-certs/Alice-EE.key -in PLAN -out PLAN.der def encode(xml, key, cert_files): + """Encode a chunk of XML as CMS signed with a specified key and bag of certificates. + + We have to sort the certificates into the correct order before the + OpenSSL CLI tool will accept them. rpki.x509 handles that for us. + """ certs = rpki.x509.X509_chain() certs.load_from_PEM(cert_files) @@ -41,6 +47,13 @@ def encode(xml, key, cert_files): # openssl smime -verify -inform DER -in PLAN.der -CAfile biz-certs/Alice-Root.cer def decode(cms, ta): + """Decode and check the signature of a chunk of CMS. + + Returns the signed text (XML, until proven otherwise) on success. + if OpenSSL CLI tool reports anything other than successful + verification, we raise an exception. + """ + i,o,e = os.popen3(["openssl", "smime", "-verify", "-inform", "DER", "-CAfile", ta]) i.write(cms) i.close() @@ -48,5 +61,7 @@ def decode(cms, ta): o.close() status = e.read() e.close() - assert status == "Verification successful\n", "CMS verification failed: %s" % status - return xml + if status == "Verification successful\n": + return xml + else: + raise RuntimeError, "CMS verification failed: %s" % status diff --git a/scripts/rpki/https.py b/scripts/rpki/https.py index 4be20537..1faf2bc0 100644 --- a/scripts/rpki/https.py +++ b/scripts/rpki/https.py @@ -2,8 +2,7 @@ import httplib, BaseHTTPServer, tlslite.api, glob, rpki.x509 -""" -HTTPS utilities, both client and server. +"""HTTPS utilities, both client and server. At the moment this only knows how to use the PEM certs in my subversion repository; generalizing it would not be hard, but the more @@ -13,11 +12,17 @@ general version should use SQL anyway. rpki_content_type = "application/x-rpki" class CertInfo(object): + """Certificate context. + + This hides a bunch of grotty details about how we store and name + certificates in this test setup. This code will definitely need to + change, soon, but this class keeps most of this rubbish in one + place. + """ cert_dir = "biz-certs/" def __init__(self, myname=None): - if myname is not None: f = open(self.cert_dir + myname + "-EE.key", "r") @@ -34,6 +39,14 @@ class CertInfo(object): self.x509TrustList = trustlist.tlslite_trustList() def client(msg, certInfo, host="localhost", port=4433, url="/"): + """Open client HTTPS connection, send a message, wait for response. + + This function wraps most of what one needs to do to send a message + over HTTPS and get a response. The certificate checking isn't quite + up to snuff; it's better than with the other packages I've found, + but doesn't appear to handle subjectAltName extensions (sigh). + """ + httpc = tlslite.api.HTTPTLSConnection(host=host, port=port, certChain=certInfo.certChain, @@ -46,10 +59,12 @@ def client(msg, certInfo, host="localhost", port=4433, url="/"): return response.read() class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Derived type to supply POST handler.""" rpki_handlers = None # Subclass must bind def do_POST(self): + """POST handler.""" assert self.headers["Content-Type"] == rpki_content_type query_string = self.rfile.read(int(self.headers["Content-Length"])) rcode = None @@ -68,12 +83,14 @@ class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.wfile.write(rtext) class httpServer(tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): + """Derived type to handle TLS aspects of HTTPS.""" rpki_certChain = None rpki_privateKey = None rpki_sessionCache = None def handshake(self, tlsConnection): + """TLS handshake handler.""" assert self.rpki_certChain is not None assert self.rpki_privateKey is not None assert self.rpki_sessionCache is not None @@ -88,9 +105,7 @@ class httpServer(tlslite.api.TLSSocketServerMixIn, BaseHTTPServer.HTTPServer): return False def server(handlers, certInfo, port=4433, host=""): - - # BaseHTTPServer.HTTPServer takes a class, not an instance, so - # binding our handler requires creating a new subclass. Weird. + """Run an HTTPS server and wait (forever) for connections.""" class boundRequestHandler(requestHandler): rpki_handlers = handlers diff --git a/scripts/rpki/ipaddrs.py b/scripts/rpki/ipaddrs.py index 73e404ca..26beb4a1 100644 --- a/scripts/rpki/ipaddrs.py +++ b/scripts/rpki/ipaddrs.py @@ -1,14 +1,28 @@ # $Id$ +"""Classes to represent IP addresses. + +Given some of the other operations we need to perform on them, it's +most convenient to represent IP addresses as Python "long" values. +The classes in this module just wrap suitable read/write syntax around +the underlying "long" type. + +These classes also supply a "bits" attribute for use by other code +built on these classes; for the most part, IPv6 addresses really are +just IPv4 addresses with more bits, so we supply the number of bits +once, here, thus avoiding a lot of duplicate code elsewhere. +""" + import socket, struct class v4addr(long): - """ - IPv4 address. Derived from long, but supports IPv4 print syntax. + """IPv4 address. + + Derived from long, but supports IPv4 print syntax. """ bits = 32 - + def __new__(cls, x): if isinstance(x, str): y = struct.unpack("!I", socket.inet_pton(socket.AF_INET, x)) @@ -19,8 +33,9 @@ class v4addr(long): return socket.inet_ntop(socket.AF_INET, struct.pack("!I", long(self))) class v6addr(long): - """ - IPv6 address. Derived from long, but supports IPv6 print syntax. + """IPv6 address. + + Derived from long, but supports IPv6 print syntax. """ bits = 128 diff --git a/scripts/rpki/left_right.py b/scripts/rpki/left_right.py index 34e2f92d..0f1ba415 100644 --- a/scripts/rpki/left_right.py +++ b/scripts/rpki/left_right.py @@ -7,26 +7,28 @@ xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/" nsmap = { None : xmlns } class base_elt(object): - """ - Base type for left-right message elements. - """ + """Virtual base type for left-right message elements.""" attributes = () booleans = () def startElement(self, stack, name, attrs): + """Default startElement() handler: just process attributes.""" self.read_attrs(attrs) def endElement(self, stack, name, text): + """Default endElement() handler: just pop the stack.""" stack.pop() def read_attrs(self, attrs): + """Template-driven attribute reader.""" for key in self.attributes: setattr(self, key, attrs.get(key, None)) for key in self.booleans: setattr(self, key, attrs.get(key, False)) def make_elt(self): + """XML element constructor.""" elt = lxml.etree.Element("{%s}%s" % (xmlns, self.element_name), nsmap=nsmap) for key in self.attributes: val = getattr(self, key, None) @@ -38,6 +40,7 @@ class base_elt(object): return elt def make_b64elt(self, elt, name, value=None): + """Constructor for Base64-encoded subelement.""" if value is None: value = getattr(self, name, None) if value is not None: @@ -47,32 +50,35 @@ class base_elt(object): lxml.etree.tostring(self.toXML(), pretty_print=True, encoding="us-ascii") def biz_cert(text): + """Parse a DER certificate.""" cert = POW.pkix.Certificate() cert.fromString(base64.b64decode(text)) return cert class extension_preference_elt(base_elt): - """ - Container for extension preferences. - """ + """Container for extension preferences.""" element_name = "extension_preference" attributes = ("name",) def startElement(self, stack, name, attrs): + """Handle <extension_preference/> elements.""" assert name == "extension_preference", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <extension_preference/> elements.""" self.value = text stack.pop() def toXML(self): + """Generate <extension_preference/> elements.""" elt = self.make_elt() elt.text = self.value return elt class self_elt(base_elt): + """<self/> element.""" element_name = "self" attributes = ("action", "type", "self_id") @@ -82,6 +88,7 @@ class self_elt(base_elt): self.prefs = [] def startElement(self, stack, name, attrs): + """Handle <self/> element.""" if name == "extension_preference": pref = extension_preference_elt() self.prefs.append(pref) @@ -92,16 +99,19 @@ class self_elt(base_elt): self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <self/> element.""" assert name == "self", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): + """Generate <self/> element.""" elt = self.make_elt() elt.extend([i.toXML() for i in self.prefs]) return elt class bsc_elt(base_elt): - + """<bsc/> (Business Signing Context) element.""" + element_name = "bsc" attributes = ("action", "type", "self_id", "bsc_id", "key_type", "hash_alg", "key_length") booleans = ("generate_keypair",) @@ -113,11 +123,13 @@ class bsc_elt(base_elt): self.signing_cert = [] def startElement(self, stack, name, attrs): + """Handle <bsc/> element.""" if not name in ("signing_cert", "public_key", "pkcs10_cert_request"): assert name == "bsc", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <bsc/> element.""" if name == "signing_cert": self.signing_cert.append(biz_cert(text)) elif name == "public_key": @@ -129,6 +141,7 @@ class bsc_elt(base_elt): stack.pop() def toXML(self): + """Generate <bsc/> element.""" elt = self.make_elt() for cert in self.signing_cert: self.make_b64elt(elt, "signing_cert", cert.toString()) @@ -137,6 +150,7 @@ class bsc_elt(base_elt): return elt class parent_elt(base_elt): + """<parent/> element.""" element_name = "parent" attributes = ("action", "type", "self_id", "parent_id", "bsc_link", "repository_link", "peer_contact", "sia_base") @@ -145,11 +159,13 @@ class parent_elt(base_elt): peer_ta = None def startElement(self, stack, name, attrs): + """Handle <bsc/> element.""" if name != "peer_ta": assert name == "parent", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <bsc/> element.""" if name == "peer_ta": self.peer_ta = biz_cert(text) else: @@ -157,12 +173,14 @@ class parent_elt(base_elt): stack.pop() def toXML(self): + """Generate <bsc/> element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.toString()) return elt class child_elt(base_elt): + """<child/> element.""" element_name = "child" attributes = ("action", "type", "self_id", "child_id", "bsc_link", "child_db_id") @@ -171,11 +189,13 @@ class child_elt(base_elt): peer_ta = None def startElement(self, stack, name, attrs): + """Handle <child/> element.""" if name != "peer_ta": assert name == "child", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <child/> element.""" if name == "peer_ta": self.peer_ta = biz_cert(text) else: @@ -183,12 +203,14 @@ class child_elt(base_elt): stack.pop() def toXML(self): + """Generate <child/> element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.toString()) return elt class repository_elt(base_elt): + """<repository/> element.""" element_name = "repository" attributes = ("action", "type", "self_id", "repository_id", "bsc_link", "peer_contact") @@ -196,11 +218,13 @@ class repository_elt(base_elt): peer_ta = None def startElement(self, stack, name, attrs): + """Handle <repository/> element.""" if name != "peer_ta": assert name == "repository", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def endElement(self, stack, name, text): + """Handle <repository/> element.""" if name == "peer_ta": self.peer_ta = biz_cert(text) else: @@ -208,18 +232,21 @@ class repository_elt(base_elt): stack.pop() def toXML(self): + """Generate <repository/> element.""" elt = self.make_elt() if self.peer_ta: self.make_b64elt(elt, "peer_ta", self.peer_ta.toString()) return elt class route_origin_elt(base_elt): + """<route_origin/> element.""" element_name = "route_origin" attributes = ("action", "type", "self_id", "route_origin_id", "asn", "ipv4", "ipv6") booleans = ("suppress_publication",) def startElement(self, stack, name, attrs): + """Handle <route_origin/> element.""" assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.asn is not None: @@ -230,18 +257,22 @@ class route_origin_elt(base_elt): self.ipv6 = resource_set.resource_set_ipv6(self.ipv4) def endElement(self, stack, name, text): + """Handle <route_origin/> element.""" assert name == "route_origin", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): + """Generate <route_origin/> element.""" return self.make_elt() class resource_class_elt(base_elt): + """<resource_class/> element.""" element_name = "resource_class" attributes = ("as", "req_as", "ipv4", "req_ipv4", "ipv6", "req_ipv6") def startElement(self, stack, name, attrs): + """Handle <resource_class/> element.""" assert name == "resource_class", "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) if self.as is not None: @@ -258,13 +289,16 @@ class resource_class_elt(base_elt): self.req_ipv6 = resource_set.resource_set_ipv6(self.req_ipv6) def endElement(self, stack, name, text): + """Handle <resource_class/> element.""" assert name == "resource_class", "Unexpected name %s, stack %s" % (name, stack) stack.pop() def toXML(self): + """Generate <resource_class/> element.""" return self.make_elt() class list_resources_elt(base_elt): + """<list_resources/> element.""" element_name = "list_resources" attributes = ("type", "self_id", "child_id", "valid_until") @@ -273,6 +307,7 @@ class list_resources_elt(base_elt): self.resources = [] def startElement(self, stack, name, attrs): + """Handle <list_resources/> element.""" if name == "resource_class": rc = resource_class_elt() self.resources.append(rc) @@ -283,34 +318,38 @@ class list_resources_elt(base_elt): self.read_attrs(attrs) def toXML(self): + """Generate <list_resources/> element.""" elt = self.make_elt() elt.extend([i.toXML() for i in self.resources]) return elt class report_error_elt(base_elt): + """<report_error/> element.""" element_name = "report_error" attributes = ("self_id", "error_code") def startElement(self, stack, name, attrs): + """Handle <report_error/> element.""" assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack) self.read_attrs(attrs) def toXML(self): + """Generate <report_error/> element.""" return self.make_elt() +## Dispatch table of PDUs for this protocol. pdus = dict([(x.element_name, x) for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt, route_origin_elt, list_resources_elt, report_error_elt)]) class msg(list): - """ - Left-right PDU. - """ + """Left-right PDU.""" version = 1 def startElement(self, stack, name, attrs): + """Handle left-right PDU.""" if name == "msg": assert self.version == int(attrs["version"]) else: @@ -320,6 +359,7 @@ class msg(list): elt.startElement(stack, name, attrs) def endElement(self, stack, name, text): + """Handle left-right PDU.""" assert name == "msg", "Unexpected name %s, stack %s" % (name, stack) assert len(stack) == 1 stack.pop() @@ -328,15 +368,15 @@ class msg(list): lxml.etree.tostring(self.toXML(), pretty_print=True, encoding="us-ascii") def toXML(self): + """Generate left-right PDU.""" elt = lxml.etree.Element("{%s}msg" % (xmlns), nsmap=nsmap, version=str(self.version)) elt.extend([i.toXML() for i in self]) return elt class sax_handler(sax_utils.handler): - """ - SAX handler for Left-Right protocol. - """ + """SAX handler for Left-Right protocol.""" def create_top_level(self, name, attrs): + """Top-level PDU for this protocol is <msg/>.""" assert name == "msg" and attrs["version"] == "1" return msg() diff --git a/scripts/rpki/relaxng.py b/scripts/rpki/relaxng.py index 1478b66f..ebf78ecd 100644 --- a/scripts/rpki/relaxng.py +++ b/scripts/rpki/relaxng.py @@ -1,11 +1,11 @@ # $Id$ +"""Trivial wrapper around lxml.etree.RelaxNG.""" + import lxml.etree class RelaxNG(lxml.etree.RelaxNG): - """ - Minor customizations of lxml.etreeRelaxNG. - """ + """Minor customizations of lxml.etreeRelaxNG.""" def __init__(self, filename): """ diff --git a/scripts/rpki/resource_set.py b/scripts/rpki/resource_set.py index c5e71eb6..518a65b9 100644 --- a/scripts/rpki/resource_set.py +++ b/scripts/rpki/resource_set.py @@ -1,11 +1,22 @@ # $Id$ +"""Classes dealing with sets of resources. + +The basic mechanics of a resource set are the same for any of the +resources we handle (ASNs, IPv4 addresses, or IPv6 addresses), so we +can provide the same operations on any of them, even though the +underlying details vary. + +We also provide some basic set operations (union, intersection, etc). +""" + import re, ipaddrs class resource_range(object): - """ - Generic resource range type. Assumes underlying type is some kind of integer. - You probably don't want to use this type directly. + """Generic resource range type. + + Assumes underlying type is some kind of integer. You probably don't + want to use this type directly. """ def __init__(self, min, max): @@ -21,9 +32,9 @@ class resource_range(object): return c class resource_range_as(resource_range): - """ - Range of Autonomous System Numbers. - Denote a single ASN by a range whose min and max values are identical. + """Range of Autonomous System Numbers. + + Denotes a single ASN by a range whose min and max values are identical. """ def __str__(self): @@ -33,10 +44,10 @@ class resource_range_as(resource_range): return str(self.min) + "-" + str(self.max) class resource_range_ip(resource_range): - """ - Range of (generic) IP addresses. Prefixes are converted to ranges - on input, and ranges that can be represented as prefixes are written - as prefixes on output. + """Range of (generic) IP addresses. + + Prefixes are converted to ranges on input, and ranges that can be + represented as prefixes are written as prefixes on output. """ def __str__(self): @@ -51,18 +62,15 @@ class resource_range_ip(resource_range): return str(self.min) + "/" + str(prefixlen) class resource_range_ipv4(resource_range_ip): - """ - Range of IPv4 addresses. - """ + """Range of IPv4 addresses.""" pass class resource_range_ipv6(resource_range_ip): - """ - Range of IPv6 addresses. - """ + """Range of IPv6 addresses.""" pass -def rsplit(rset, that): +def _rsplit(rset, that): + """Split a resource range into two resource ranges.""" this = rset.pop(0) cell_type = type(this.min) assert type(this) is type(that) and type(this.max) is cell_type and type(that.min) is cell_type and type(that.max) is cell_type @@ -75,9 +83,10 @@ def rsplit(rset, that): rset.insert(1, type(this)(cell_type(that.max + 1), this.max)) class resource_set(list): - """ - Generic resource set. List type containing resource ranges. You - probably don't want to use this type directly. + """Generic resource set. + + List type containing resource ranges. You probably don't want to + use this type directly. """ def __init__(self, ini=None): @@ -97,12 +106,13 @@ class resource_set(list): def __str__(self): return ",".join(map(str, self)) - def comm(self, other): - """ - Like comm(1), sort of. Returns a tuple of three resource sets: - resources only in self, resources only in other, and resources in - both. Used (not very efficiently) as the basis for most set - operations on resource sets. + def _comm(self, other): + """Like comm(1), sort of. + + Returns a tuple of three resource sets: resources only in self, + resources only in other, and resources in both. Used (not very + efficiently) as the basis for most set operations on resource + sets. """ assert type(self) is type(other) set1 = self[:] @@ -114,13 +124,13 @@ class resource_set(list): elif set2 and (not set1 or set2[0].max < set1[0].min): only2.append(set2.pop(0)) elif set1[0].min < set2[0].min: - rsplit(set1, set2[0]) + _rsplit(set1, set2[0]) elif set2[0].min < set1[0].min: - rsplit(set2, set1[0]) + _rsplit(set2, set1[0]) elif set1[0].max < set2[0].max: - rsplit(set2, set1[0]) + _rsplit(set2, set1[0]) elif set2[0].max < set1[0].max: - rsplit(set1, set2[0]) + _rsplit(set1, set2[0]) else: assert set1[0].min == set2[0].min and set1[0].max == set2[0].max both.append(set1.pop(0)) @@ -128,9 +138,7 @@ class resource_set(list): return type(self)(only1), type(self)(only2), type(self)(both) def union(self, other): - """ - Set union for resource sets. - """ + """Set union for resource sets.""" assert type(self) is type(other) set1 = self[:] set2 = other[:] @@ -152,28 +160,20 @@ class resource_set(list): return type(self)(result) def intersection(self, other): - """ - Set intersection for resource sets. - """ - return self.comm(other)[2] + """Set intersection for resource sets.""" + return self._comm(other)[2] def difference(self, other): - """ - Set difference for resource sets. - """ - return self.comm(other)[0] + """Set difference for resource sets.""" + return self._comm(other)[0] def symmetric_difference(self, other): - """ - Set symmetric difference (XOR) for resource sets. - """ - com = self.comm(other) + """Set symmetric difference (XOR) for resource sets.""" + com = self._comm(other) return com[0].union(com[1]) def contains(self, item): - """ - Set membership test for resource sets. - """ + """Set membership test for resource sets.""" for i in self: if isinstance(item, type(i)) and i.min <= item.min and i.max >= item.max: return True @@ -184,11 +184,10 @@ class resource_set(list): return False class resource_set_as(resource_set): - """ - ASN resource set. - """ + """ASN resource set.""" def parse_str(self, x): + """Parse AS resource sets from text (eg, XML attributes).""" r = re.match("^([0-9]+)-([0-9]+)$", x) if r: return resource_range_as(long(r.group(1)), long(r.group(2))) @@ -196,6 +195,7 @@ class resource_set_as(resource_set): return resource_range_as(long(x), long(x)) def parse_tuple(self, x): + """Parse AS resource sets from intermediate form generated by ASN.1 decoder.""" assert x[0] == "asIdsOrRanges" # Not handling "inherit" yet for aor in x[1]: if aor[0] == "range": @@ -207,12 +207,13 @@ class resource_set_as(resource_set): self.append(resource_range_as(min, max)) class resource_set_ip(resource_set): - """ - (Generic) IP address resource set. + """(Generic) IP address resource set. + You probably don't want to use this type directly. """ def parse_str(self, x): + """Parse IP address resource sets from text (eg, XML attributes).""" r = re.match("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$", x) if r: return self.range_type(self.addr_type(r.group(1)), self.addr_type(r.group(2))) @@ -227,52 +228,41 @@ class resource_set_ip(resource_set): raise RuntimeError, 'Bad IP resource "%s"' % (x) def parse_tuple(self, x): + """Parse IP address resource sets from intermediate form generated by ASN.1 decoder.""" assert x[0] == "addressesOrRanges" # Not handling "inherit" yet for aor in x[1]: if aor[0] == "addressRange": - min = bs2long(aor[1][0]) << (self.addr_type.bits - len(aor[1][0])) - max = bs2long(aor[1][1]) << (self.addr_type.bits - len(aor[1][1])) + min = _bs2long(aor[1][0]) << (self.addr_type.bits - len(aor[1][0])) + max = _bs2long(aor[1][1]) << (self.addr_type.bits - len(aor[1][1])) mask = (1L << (self.addr_type.bits - len(aor[1][1]))) - 1 else: - min = bs2long(aor[1]) << (self.addr_type.bits - len(aor[1])) + min = _bs2long(aor[1]) << (self.addr_type.bits - len(aor[1])) mask = (1L << (self.addr_type.bits - len(aor[1]))) - 1 assert (min & mask) == 0, "Resource not in canonical form: %s" % (str(x)) max = min | mask self.append(self.range_type(self.addr_type(min), self.addr_type(max))) class resource_set_ipv4(resource_set_ip): - """ - IPv4 address resource set. - """ + """IPv4 address resource set.""" addr_type = ipaddrs.v4addr range_type = resource_range_ipv4 class resource_set_ipv6(resource_set_ip): - """ - IPv6 address resource set. - """ + """IPv6 address resource set.""" addr_type = ipaddrs.v6addr range_type = resource_range_ipv6 -def bs2long(bs): - """ - Convert a bitstring (tuple representation) into a long. - """ - +def _bs2long(bs): + """Convert a bitstring (tuple representation) into a long.""" return reduce(lambda x, y: (x << 1) | y, bs, 0L) def parse_extensions(exts): - """ - Parse RFC 3779 extensions out of the tuple encoding returned by - POW.pkix.cert.getExtensions(). - """ - + """Parse RFC 3779 extensions from intermediate form returned by ASN.1 decoder.""" as = None v4 = None v6 = None - for x in exts: if x[0] == (1, 3, 6, 1, 5, 5, 7, 1, 8): # sbgp-autonomousSysNum assert x[2][1] is None, "RDI not implemented: %s" % (str(x)) @@ -293,13 +283,14 @@ def parse_extensions(exts): if __name__ == "__main__": def test(t, s1, s2): + """Lame unit test.""" print r1 = t(s1) r2 = t(s2) print "x: ", r1 print "y: ", r2 - v1 = r1.comm(r2) - v2 = r2.comm(r1) + v1 = r1._comm(r2) + v2 = r2._comm(r1) assert v1[0] == v2[1] and v1[1] == v2[0] and v1[2] == v2[2] for i in r1: assert r1.contains(i) and r1.contains(i.min) and r1.contains(i.max) for i in r2: assert r2.contains(i) and r2.contains(i.min) and r2.contains(i.max) diff --git a/scripts/rpki/sax_utils.py b/scripts/rpki/sax_utils.py index 1333df1a..acb7a5a9 100644 --- a/scripts/rpki/sax_utils.py +++ b/scripts/rpki/sax_utils.py @@ -3,8 +3,17 @@ import xml.sax class handler(xml.sax.handler.ContentHandler): - """ - SAX handler for RPKI protocols. + """SAX handler for RPKI protocols. + + This class provides some basic amenities for parsing protocol XML of + the kind we use in the RPKI protocols, including whacking all the + protocol element text into US-ASCII, simplifying accumulation of + text fields, and hiding some of the fun relating to XML namespaces. + + General assumption: by the time this parsing code gets invoked, the + XML has already passed RelaxNG validation, so we only have to check + for errors that the schema can't catch, and we don't have to play as + many XML namespace games. """ def __init__(self): @@ -12,15 +21,28 @@ class handler(xml.sax.handler.ContentHandler): self.stack = [] def startElementNS(self, name, qname, attrs): + """Redirect startElementNS() events to startElement().""" return self.startElement(name[1], attrs) def endElementNS(self, name, qname): + """Redirect endElementNS() events to endElement().""" return self.endElement(name[1]) def characters(self, content): + """Accumulate a chuck of element content (text).""" self.text += content def startElement(self, name, attrs): + """Handle startElement() events. + + We maintain a stack of nested elements under construction so that + we can feed events directly to the current element rather than + having to pass them through all the nesting elements. + + If the stack is empty, this event is for the outermost element, so + we call a virtual method to create the corresponding object and + that's the object we'll be returning as our final result. + """ a = dict() for k,v in attrs.items(): if isinstance(k, tuple): @@ -37,6 +59,10 @@ class handler(xml.sax.handler.ContentHandler): self.stack[-1].startElement(self.stack, name, a) def endElement(self, name): + """Handle endElement() events. + + Mostly this means handling any accumulated element text. + """ text = self.text.encode("ascii").strip() self.text = "" self.stack[-1].endElement(self.stack, name, text) diff --git a/scripts/rpki/up_down.py b/scripts/rpki/up_down.py index b66691c5..84a76d32 100644 --- a/scripts/rpki/up_down.py +++ b/scripts/rpki/up_down.py @@ -7,17 +7,28 @@ xmlns="http://www.apnic.net/specs/rescerts/up-down/" nsmap = { None : xmlns } class base_elt(object): - """ - Generic PDU object, just provides some default methods. + """Generic PDU object. + + Virtual class, just provides some default methods. """ def startElement(self, stack, name, attrs): + """Ignore startElement() if there's no specific handler. + + Some elements have no attributes and we only care about their + text content. + """ pass def endElement(self, stack, name, text): + """Ignore endElement() if there's no specific handler. + + If we don't need to do anything else, just pop the stack. + """ stack.pop() def make_elt(self, name, *attrs): + """Construct a element, copying over a set of attributes.""" elt = lxml.etree.Element("{%s}%s" % (xmlns, name), nsmap=nsmap) for key in attrs: val = getattr(self, key, None) @@ -26,17 +37,17 @@ class base_elt(object): return elt def make_b64elt(self, elt, name, value=None): + """Construct a sub-element with Base64 text content.""" if value is None: value = getattr(self, name, None) if value is not None: lxml.etree.SubElement(elt, "{%s}%s" % (xmlns, name), nsmap=nsmap).text = base64.b64encode(value) class certificate_elt(base_elt): - """ - Up-Down protocol representation of an issued certificate. - """ + """Up-Down protocol representation of an issued certificate.""" def startElement(self, stack, name, attrs): + """Handle attributes of <certificate/> element.""" assert name == "certificate", "Unexpected name %s, stack %s" % (name, stack) self.cert_url = attrs["cert_url"] self.req_resource_set_as = resource_set.resource_set_as(attrs.get("req_resource_set_as")) @@ -44,25 +55,26 @@ class certificate_elt(base_elt): self.req_resource_set_ipv6 = resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) def endElement(self, stack, name, text): + """Handle text content of a <certificate/> element.""" assert name == "certificate" self.cert = POW.pkix.Certificate() self.cert.fromString(base64.b64decode(text)) stack.pop() def toXML(self): + """Generate a <certificate/> element.""" elt = self.make_elt("certificate", "cert_url", "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6") elt.text = base64.b64encode(self.cert.toString()) return elt class class_elt(base_elt): - """ - Up-Down protocol representation of a resource class. - """ + """Up-Down protocol representation of a resource class.""" def __init__(self): self.certs = [] def startElement(self, stack, name, attrs): + """Handle <class/> elements and their children.""" if name == "certificate": cert = certificate_elt() self.certs.append(cert) @@ -78,6 +90,7 @@ class class_elt(base_elt): self.resource_set_ipv6 = resource_set.resource_set_ipv6(attrs["resource_set_ipv6"]) def endElement(self, stack, name, text): + """Handle <class/> elements and their children.""" if name == "issuer": self.issuer = POW.pkix.Certificate() self.issuer.fromString(base64.b64decode(text)) @@ -86,28 +99,27 @@ class class_elt(base_elt): stack.pop() def toXML(self): + """Generate a <class/> element.""" elt = self.make_elt("class", "class_name", "cert_url", "resource_set_as", "resource_set_ipv4", "resource_set_ipv6", "suggested_sia_head") elt.extend([i.toXML() for i in self.certs]) self.make_b64elt(elt, "issuer", self.issuer.toString()) return elt class list_pdu(base_elt): - """ - Up-Down protocol "list" PDU. - """ + """Up-Down protocol "list" PDU.""" def toXML(self): + """Generate (empty) payload of "list" PDU.""" return [] class list_response_pdu(base_elt): - """ - Up-Down protocol "list_response" PDU. - """ + """Up-Down protocol "list_response" PDU.""" def __init__(self): self.classes = [] def startElement(self, stack, name, attrs): + """Handle "list_response" PDU.""" assert name == "class", "Unexpected name %s, stack %s" % (name, stack) klass = class_elt() self.classes.append(klass) @@ -115,14 +127,14 @@ class list_response_pdu(base_elt): klass.startElement(stack, name, attrs) def toXML(self): + """Generate payload of "list_response" PDU.""" return [i.toXML() for i in self.classes] class issue_pdu(base_elt): - """ - Up-Down protocol "issue" PDU. - """ + """Up-Down protocol "issue" PDU.""" def startElement(self, stack, name, attrs): + """Handle "issue" PDU.""" assert name == "request", "Unexpected name %s, stack %s" % (name, stack) self.class_name = attrs["class_name"] self.req_resource_set_as = resource_set.resource_set_as(attrs.get("req_resource_set_as")) @@ -130,53 +142,46 @@ class issue_pdu(base_elt): self.req_resource_set_ipv6 = resource_set.resource_set_ipv6(attrs.get("req_resource_set_ipv6")) def endElement(self, stack, name, text): + """Handle "issue" PDU.""" assert name == "request", "Unexpected name %s, stack %s" % (name, stack) self.pkcs10 = base64.b64decode(text) stack.pop() def toXML(self): + """Generate payload of "issue" PDU.""" elt = self.make_elt("request", "class_name", "req_resource_set_as", "req_resource_set_ipv4", "req_resource_set_ipv6") elt.text = base64.b64encode(self.pkcs10) return [elt] class issue_response_pdu(list_response_pdu): - """ - Up-Down protocol "issue_response" PDU. - """ + """Up-Down protocol "issue_response" PDU.""" def toXML(self): + """Generate payload of "issue_response" PDU.""" assert len(self.classes) == 1 return list_response_pdu.toXML(self) class revoke_pdu(base_elt): - """ - Up-Down protocol "revoke" PDU. - """ + """Up-Down protocol "revoke" PDU.""" def startElement(self, stack, name, attrs): + """Handle "revoke" PDU.""" self.class_name = attrs["class_name"] self.ski = attrs["ski"] def toXML(self): + """Generate payload of "revoke" PDU.""" return [self.make_elt("key", "class_name", "ski")] class revoke_response_pdu(revoke_pdu): - """ - Up-Down protocol "revoke_response" PDU. - """ + """Up-Down protocol "revoke_response" PDU.""" pass class error_response_pdu(base_elt): - """ - Up-Down protocol "error_response" PDU. - """ - - def toXML(self): - elt = self.make_elt("status") - elt.text = str(self.status) - return [elt] + """Up-Down protocol "error_response" PDU.""" def endElement(self, stack, name, text): + """Handle "error_response" PDU.""" if name == "status": self.status = int(text) elif name == "last_message_processed": @@ -188,19 +193,30 @@ class error_response_pdu(base_elt): stack.pop() stack[-1].endElement(stack, name, text) + def toXML(self): + """Generate payload of "error_response" PDU.""" + elt = self.make_elt("status") + elt.text = str(self.status) + return [elt] + class message_pdu(base_elt): - """ - Up-Down protocol message wrapper. - """ + """Up-Down protocol message wrapper PDU.""" version = 1 def toXML(self): + """Generate payload of message PDU.""" elt = self.make_elt("message", "version", "sender", "recipient", "type") elt.extend(self.payload.toXML()) return elt def startElement(self, stack, name, attrs): + """Handle message PDU. + + Payload of the <message/> element varies depending on the "type" + attribute, so after some basic checks we have to instantiate the + right class object to handle whatever kind of PDU this is. + """ assert name == "message", "Unexpected name %s, stack %s" % (name, stack) assert self.version == int(attrs["version"]) self.sender = attrs["sender"] @@ -221,9 +237,8 @@ class message_pdu(base_elt): lxml.etree.tostring(self.toXML(), pretty_print=True, encoding="UTF-8") class sax_handler(sax_utils.handler): - """ - SAX handler for Up-Down protocol. - """ + """SAX handler for Up-Down protocol.""" def create_top_level(self, name, attrs): + """Top-level PDU for this protocol is <message/>.""" return message_pdu() diff --git a/scripts/rpki/x509.py b/scripts/rpki/x509.py index b823c5a3..9b9b1c28 100644 --- a/scripts/rpki/x509.py +++ b/scripts/rpki/x509.py @@ -1,8 +1,9 @@ # $Id$ -""" -One X.509 implementation to rule them all and in the darkness hide the -twisty maze of partially overlapping X.509 support packages in Python. +"""One X.509 implementation to rule them all... + +...and in the darkness hide the twisty maze of partially overlapping +X.509 support packages in Python. There are several existing packages, none of which do quite what I need, due to age, lack of documentation, specialization, or lack of @@ -14,15 +15,14 @@ some of the nasty details. This involves a lot of format conversion. import POW, tlslite.api, POW.pkix, base64 class PEM_converter(object): - """ - Convert between DER and PEM encodings for various kinds of ASN.1 data. - """ + """Convert between DER and PEM encodings for various kinds of ASN.1 data.""" def __init__(self, kind): # "CERTIFICATE", "RSA PRIVATE KEY", ... self.b = "-----BEGIN %s-----" % kind self.e = "-----END %s-----" % kind def toDER(self, pem): + """Convert from PEM to DER.""" lines = pem.splitlines(0) while lines and lines.pop(0) != self.b: pass @@ -32,6 +32,7 @@ class PEM_converter(object): return base64.b64decode("".join(lines)) def toPEM(self, der): + """Convert from DER to PEM.""" b64 = base64.b64encode(der) pem = self.b + "\n" while len(b64) > 64: @@ -40,21 +41,21 @@ class PEM_converter(object): return pem + b64 + "\n" + self.e + "\n" class DER_object(object): - """ - Virtual class to hold a generic DER object. - """ + """Virtual class to hold a generic DER object.""" formats = ("DER",) pem_converter = None other_clear = () def empty(self): + """Test whether this object is empty.""" for a in self.formats: if getattr(self, a, None) is not None: return False return True def clear(self): + """Make this object empty.""" for a in self.formats + self.other_clear: setattr(self, a, None) @@ -64,6 +65,13 @@ class DER_object(object): self.set(**kw) def set(self, **kw): + """Set this object by setting one of its known formats. + + This method only allows one to set one format at a time. + Subsequent calls will clear the object first. The point of all + this is to let the object's internal converters handle mustering + the object into whatever format you need at the moment. + """ name = kw.keys()[0] if len(kw) == 1: if name in self.formats: @@ -82,18 +90,27 @@ class DER_object(object): raise TypeError def get_DER(self): + """Get the DER value of this object. + + Subclasses will almost certainly override this method. + """ assert not self.empty() if self.DER: return self.DER raise RuntimeError, "No conversion path to DER available" def get_PEM(self): + """Get the PEM representation of this object.""" return self.pem_converter.toPEM(self.get_DER()) class X509(DER_object): - """ - Class to hold all the different representations of X.509 certs we're - using and convert between them. + """X.509 certificates. + + This class is designed to hold all the different representations of + X.509 certs we're using and convert between them. X.509 support in + Python a nasty maze of half-cooked stuff (except perhaps for + cryptlib, which is just different). Users of this module should not + have to care about this implementation nightmare. """ formats = ("DER", "POW", "POWpkix", "tlslite") @@ -101,6 +118,7 @@ class X509(DER_object): other_clear = ("POW_extensions",) def get_DER(self): + """Get the DER value of this certificate.""" assert not self.empty() if self.DER: return self.DER @@ -113,12 +131,14 @@ class X509(DER_object): raise RuntimeError def get_POW(self): + """Get the POW value of this certificate.""" assert not self.empty() if not self.POW: self.POW = POW.derRead(POW.X509_CERTIFICATE, self.get_DER()) return self.POW def get_POWpkix(self): + """Get the POW.pkix value of this certificate.""" assert not self.empty() if not self.POWpkix: cert = POW.pkix.Certificate() @@ -127,6 +147,7 @@ class X509(DER_object): return self.POWpkix def get_tlslite(self): + """Get the tlslite value of this certificate.""" assert not self.empty() if not self.tlslite: cert = tlslite.api.X509() @@ -135,12 +156,18 @@ class X509(DER_object): return self.tlslite def getIssuer(self): + """Get the issuer of this certificate.""" return self.get_POW().getIssuer() def getSubject(self): + """Get the subject of this certificate.""" return self.get_POW().getSubject() - def get_POW_extensions(self): + def _get_POW_extensions(self): + """Parse extensions from the POW value of this certificate. + + Build a dictionary to ease lookup, and cache the result. + """ if not self.POW_extensions: cert = self.get_POW() exts = {} @@ -151,23 +178,25 @@ class X509(DER_object): return self.POW_extensions def getAKI(self): - return self.get_POW_extensions().get("authorityKeyIdentifier") + """Get the AKI extension from this certificate.""" + return self._get_POW_extensions().get("authorityKeyIdentifier") def getSKI(self): - return self.get_POW_extensions().get("subjectKeyIdentifier") + """Get the SKI extension from this certificate.""" + return self._get_POW_extensions().get("subjectKeyIdentifier") class X509_chain(list): - """ - Collection of certs with sorting and conversion functions - for various packages. + """Collections of certs. + + This class provides sorting and conversion functions for various + packages. """ def chainsort(self): - """ - Sort a bag of certs into a chain, leaf first. Various other - routines want their certs presented in this order. - """ + """Sort a bag of certs into a chain, leaf first. + Various other routines want their certs presented in this order. + """ bag = self[:] issuer_names = [x.getIssuer() for x in bag] subject_map = dict([(x.getSubject(), x) for x in bag]) @@ -177,7 +206,8 @@ class X509_chain(list): cert = subject_map[subject] chain.append(cert) bag.remove(cert) - assert len(chain) == 1 + if len(chain) != 1: + raise RuntimeError, "Certificates in bag don't form a proper chain" while bag: cert = subject_map[chain[-1].getIssuer()] chain.append(cert) @@ -185,29 +215,33 @@ class X509_chain(list): self[:] = chain def tlslite_certChain(self): + """Return a certChain in the format tlslite likes.""" return tlslite.api.X509CertChain([x.get_tlslite() for x in self]) def tlslite_trustList(self): + """Return a trustList in the format tlslite likes.""" return [x.get_tlslite() for x in self] def clear(self): + """Drop all certs from this bag onto the floor.""" self[:] = [] def load_from_PEM(self, files): + """Load a set of certs from a list of PEM files.""" self.extend([X509(PEM_file=f) for f in files]) def load_from_DER(self, files): + """Load a set of certs from a list of DER files.""" self.extend([X509(DER_file=f) for f in files]) class PKCS10_Request(DER_object): - """ - Class to hold a PKCS #10 request. - """ + """Class to hold a PKCS #10 request.""" formats = ("DER", "POWpkix") pem_converter = PEM_converter("CERTIFICATE REQUEST") def get_DER(self): + """Get the DER value of this certification request.""" assert not self.empty() if self.DER: return self.DER @@ -217,6 +251,7 @@ class PKCS10_Request(DER_object): raise RuntimeError def get_POWpkix(self): + """Get the POW.pkix value of this certification request.""" assert not self.empty() if not self.POWpkix: req = POW.pkix.CertificationRequest() |