""" 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. $Id$ Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ import rpki.resource_set, os subdir = "resource-cert-samples" openssl = "../../openssl/openssl/apps/openssl" keybits = 2048 def main(): """Main program, including the toy database itself.""" db = allocation_db() db.add("ISP1", ipv4 = "192.0.2.1-192.0.2.33", asn = "64533") db.add("ISP2", ipv4 = "192.0.2.44-192.0.2.100") db.add("ISP3", ipv6 = "2001:db8::44-2001:db8::100") db.add("ISP4", ipv6 = "2001:db8::10:0:44/128", asn = "64544") db.add("ISP5a", ipv4 = "10.0.0.0/24", ipv6 = "2001:db8::a00:0/120") db.add("ISP5b", ipv4 = "10.3.0.0/24", ipv6 = "2001:db8::a03:0/120") db.add("ISP5c", asn = "64534-64540") db.add("LIR1", children = ["ISP1", "ISP2"]) db.add("LIR2", children = ["ISP3", "ISP4"]) db.add("LIR3", children = ["ISP5a", "ISP5b", "ISP5c"]) db.add("RIR", children = ["LIR1", "LIR2", "LIR3"]) if not os.path.isdir(subdir): os.mkdir(subdir) for i in db: write_maybe("%s/%s.cnf" % (subdir, i.name), i.cfg_string()) write_maybe("%s/Makefile" % subdir, "# Automatically generated, do not edit.\n" + "".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. This simplifies interactions with "make". """ old_content = None if os.path.isfile(name): f = open(name, "r") old_content = f.read() f.close() if old_content != new_content: print "Writing", name f = open(name, "w") f.write(new_content) f.close() class allocation_db(list): """Class to represent an allocation database.""" def __init__(self): self.allocation_map = {} 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_map = self.allocation_map, **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_map = 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_map[i] for i in children] for child in self.children: assert child.parent is None child.parent = self self.asn = self.summarize("asn", rpki.resource_set.resource_set_as(asn)) self.ipv4 = self.summarize("ipv4", rpki.resource_set.resource_set_ipv4(ipv4)) self.ipv6 = self.summarize("ipv6", rpki.resource_set.resource_set_ipv6(ipv6)) allocation_map[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: seed = seed.union(child.summarize(attrname)) return seed def __str__(self): 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" : "#", "no_asid" : "#", "no_addr" : "#", "parent" : "???", "asid" : "???", "addr" : "???" } if self.parent: keys["no_parent"] = "" keys["parent"] = self.parent.name if self.asn: keys["no_asid"] = "" keys["asid"] = ",".join(["AS:" + str(x) for x in self.asn]) if self.ipv4 or self.ipv6: keys["no_addr"] = "" keys["addr"] = ",".join(["IPv4:" + str(x) for x in self.ipv4] + ["IPv6:" + str(x) for x in self.ipv6]) 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 } if self.parent: keys["signconf"] = "%s.cnf" % self.parent.name keys["signdeps"] = "%s.key" % self.parent.name else: keys["signconf"] = "%s.cnf -selfsign" % self.name keys["signdeps"] = "%s.key" % self.name return makefile_fmt % keys makefile_fmt = '''\ all:: %(self)s.cer %(self)s.key: %(openssl)s genrsa -out $@ %(keybits)d %(self)s.req: %(self)s.key %(self)s.cnf Makefile %(openssl)s req -new -config %(self)s.cnf -key %(self)s.key -out $@ %(self)s.cer: %(self)s.req %(self)s.cnf %(signdeps)s Makefile @test -d %(self)s || mkdir %(self)s @test -f %(self)s/index || touch %(self)s/index @test -f %(self)s/serial || echo 01 >%(self)s/serial %(openssl)s ca -batch -out $@ -in %(self)s.req -extfile %(self)s.cnf -config %(signconf)s show_req:: %(openssl)s req -noout -text -in %(self)s.req -config /dev/null show_cer:: %(openssl)s x509 -noout -text -in %(self)s.cer ''' openssl_cfg_fmt = '''# Automatically generated, do not edit. [ ca ] default_ca = ca_default [ ca_default ] certificate = %(self)s.cer serial = %(self)s/serial private_key = %(self)s.key database = %(self)s/index new_certs_dir = %(self)s name_opt = ca_default cert_opt = ca_default default_days = 365 default_crl_days = 30 default_md = sha256 preserve = no copy_extensions = copy policy = ca_policy_anything unique_subject = no x509_extensions = ca_x509_ext crl_extensions = crl_x509_ext [ ca_policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional givenName = optional surname = optional [ req ] default_bits = %(keybits)d encrypt_key = no distinguished_name = req_dn req_extensions = req_x509_ext prompt = no [ req_dn ] CN = TEST ENTITY %(self)s [ req_x509_ext ] basicConstraints = critical,CA:true subjectKeyIdentifier = hash keyUsage = critical,keyCertSign,cRLSign subjectInfoAccess = 1.3.6.1.5.5.7.48.5;URI:rsync://wombats-r-us.hactrn.net/%(self)s/ %(no_parent)sauthorityInfoAccess = caIssuers;URI:rsync://wombats-r-us.hactrn.net/%(parent)s.cer %(no_asid)ssbgp-autonomousSysNum = critical,%(asid)s %(no_addr)ssbgp-ipAddrBlock = critical,%(addr)s [ ca_x509_ext ] basicConstraints = critical,CA:true %(no_parent)sauthorityKeyIdentifier = keyid:always keyUsage = critical,keyCertSign,cRLSign subjectInfoAccess = 1.3.6.1.5.5.7.48.5;URI:rsync://wombats-r-us.hactrn.net/%(self)s/ %(no_parent)sauthorityInfoAccess = caIssuers;URI:rsync://wombats-r-us.hactrn.net/%(parent)s.cer %(no_asid)ssbgp-autonomousSysNum = critical,%(asid)s %(no_addr)ssbgp-ipAddrBlock = critical,%(addr)s [ crl_x509_ext ] authorityKeyIdentifier = keyid:always ''' main()