aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2007-08-12 03:04:03 +0000
committerRob Austein <sra@hactrn.net>2007-08-12 03:04:03 +0000
commiteba8fc672bf69b608e759daa9d20aa48e1b88b88 (patch)
tree57484cc973a58a5597fb0bbdfd88e837ca01359d
parentc866ab06942baf21404c25128c2858d639fd4092 (diff)
Doc
svn path=/scripts/generate-testrepo.py; revision=868
-rw-r--r--scripts/generate-testrepo.py40
-rwxr-xr-xscripts/irbe-cli.py38
-rw-r--r--scripts/rpki/cms.py25
-rw-r--r--scripts/rpki/https.py27
-rw-r--r--scripts/rpki/ipaddrs.py25
-rw-r--r--scripts/rpki/left_right.py66
-rw-r--r--scripts/rpki/relaxng.py6
-rw-r--r--scripts/rpki/resource_set.py141
-rw-r--r--scripts/rpki/sax_utils.py30
-rw-r--r--scripts/rpki/up_down.py95
-rw-r--r--scripts/rpki/x509.py87
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()