diff options
-rw-r--r-- | rpkid/rpki/irdb/models.py | 13 | ||||
-rw-r--r-- | rpkid/rpki/rpkic.py | 988 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 6 |
3 files changed, 204 insertions, 803 deletions
diff --git a/rpkid/rpki/irdb/models.py b/rpkid/rpki/irdb/models.py index 93926bd1..30f179b7 100644 --- a/rpkid/rpki/irdb/models.py +++ b/rpkid/rpki/irdb/models.py @@ -61,7 +61,6 @@ class SundialField(django.db.models.DateTimeField): return rpki.sundial.datetime.fromdatetime( django.db.models.DateTimeField.to_python(self, value)) - class DERField(django.db.models.Field): """ A field type for DER objects. @@ -115,17 +114,9 @@ class PKCS10Field(DERField): description = "PKCS #10 certificate request" rpki_type = rpki.x509.PKCS10 -## @todo -# SignedReferral doesn't belong in rpki.irdb, but I haven't yet -# figured out where it does belong. - -class SignedReferral(rpki.x509.XML_CMS_object): - encoding = "us-ascii" - schema = rpki.relaxng.myrpki - class SignedReferralField(DERField): description = "CMS signed object containing XML" - rpki_type = SignedReferral + rpki_type = rpki.x509.SignedReferral ## @var ip_version_map # Custom choice map for IP version enumerations, so we can use the @@ -179,7 +170,7 @@ class Identity(django.db.models.Model): handle = HandleField(unique = True) class CA(django.db.models.Model): - identity = django.db.models.ForeignKey(Identity, related_name = "bpki_certificates") + identity = django.db.models.ForeignKey(Identity) purpose_map = ChoiceMap("resources", "servers") purpose = django.db.models.PositiveSmallIntegerField(choices = purpose_map.choices) certificate = CertificateField() diff --git a/rpkid/rpki/rpkic.py b/rpkid/rpki/rpkic.py index 154cbf1d..4aeeb100 100644 --- a/rpkid/rpki/rpkic.py +++ b/rpkid/rpki/rpkic.py @@ -72,14 +72,18 @@ namespace = "http://www.hactrn.net/uris/rpki/myrpki/" version = "2" namespaceQName = "{" + namespace + "}" +## @var allow_incomplete # Whether to include incomplete entries when rendering to XML. allow_incomplete = False +## @var whine # Whether to whine about incomplete entries while rendering to XML. whine = True +# A whole lot of exceptions + class BadCommandSyntax(Exception): """ Bad command line syntax. @@ -109,428 +113,9 @@ class CantRunRootd(Exception): """ Can't run rootd. """ - -class comma_set(set): - """ - Minor customization of set(), to provide a print syntax. - """ - - def __str__(self): - return ",".join(self) - -class EntityDB(object): - """ - Wrapper for entitydb path lookups and iterations. - """ - - def __init__(self, cfg): - self.dir = cfg.get("entitydb_dir", "entitydb") - self.identity = os.path.join(self.dir, "identity.xml") - - def __call__(self, dirname, filebase = None): - if filebase is None: - return os.path.join(self.dir, dirname) - else: - return os.path.join(self.dir, dirname, filebase + ".xml") - - def iterate(self, dir, base = "*"): - return glob.iglob(os.path.join(self.dir, dir, base + ".xml")) - -# Not certain, but I //think// everything on this page is used only by -# main.configure_resources_main(). - -class roa_request(object): - """ - Representation of a ROA request. - """ - - v4re = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+(-[0-9]+)?$", re.I) - v6re = re.compile("^([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+(-[0-9]+)?$", re.I) - - def __init__(self, asn, group): - self.asn = asn - self.group = group - self.v4 = comma_set() - self.v6 = comma_set() - - def __repr__(self): - s = "<%s asn %s group %s" % (self.__class__.__name__, self.asn, self.group) - if self.v4: - s += " v4 %s" % self.v4 - if self.v6: - s += " v6 %s" % self.v6 - return s + ">" - - def add(self, prefix): - """ - Add one prefix to this ROA request. - """ - - if self.v4re.match(prefix): - self.v4.add(prefix) - elif self.v6re.match(prefix): - self.v6.add(prefix) - else: - raise BadPrefixSyntax, "Bad prefix syntax: %r" % (prefix,) - - def xml(self, e): - """ - Generate XML element represeting representing this ROA request. - """ - - e = SubElement(e, "roa_request", - asn = self.asn, - v4 = str(self.v4), - v6 = str(self.v6)) - e.tail = "\n" - -class roa_requests(dict): - """ - Database of ROA requests. - """ - - def add(self, asn, group, prefix): - """ - Add one <ASN, group, prefix> set to ROA request database. - """ - - key = (asn, group) - if key not in self: - self[key] = roa_request(asn, group) - self[key].add(prefix) - - def xml(self, e): - """ - Render ROA requests as XML elements. - """ - - for r in self.itervalues(): - r.xml(e) - - @classmethod - def from_csv(cls, roa_csv_file): - """ - Parse ROA requests from CSV file. - """ - - self = cls() - # format: p/n-m asn group - for pnm, asn, group in csv_reader(roa_csv_file, columns = 3): - self.add(asn = asn, group = group, prefix = pnm) - return self - -class child(object): - """ - Representation of one child entity. - """ - - v4re = re.compile("^(([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+)|(([0-9]{1,3}\.){3}[0-9]{1,3}-([0-9]{1,3}\.){3}[0-9]{1,3})$", re.I) - v6re = re.compile("^(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+)|(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}-([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4})$", re.I) - - def __init__(self, handle): - self.handle = handle - self.asns = comma_set() - self.v4 = comma_set() - self.v6 = comma_set() - self.validity = None - self.bpki_certificate = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.asns: - s += " asn %s" % self.asns - if self.v4: - s += " v4 %s" % self.v4 - if self.v6: - s += " v6 %s" % self.v6 - if self.validity: - s += " valid %s" % self.validity - if self.bpki_certificate: - s += " cert %s" % self.bpki_certificate - return s + ">" - - def add(self, prefix = None, asn = None, validity = None, bpki_certificate = None): - """ - Add prefix, autonomous system number, validity date, or BPKI - certificate for this child. - """ - - if prefix is not None: - if self.v4re.match(prefix): - self.v4.add(prefix) - elif self.v6re.match(prefix): - self.v6.add(prefix) - else: - raise BadPrefixSyntax, "Bad prefix syntax: %r" % (prefix,) - if asn is not None: - self.asns.add(asn) - if validity is not None: - self.validity = validity - if bpki_certificate is not None: - self.bpki_certificate = bpki_certificate - - def xml(self, e): - """ - Render this child as an XML element. - """ - - complete = self.bpki_certificate and self.validity - if whine and not complete: - print "Incomplete child entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "child", - handle = self.handle, - valid_until = self.validity, - asns = str(self.asns), - v4 = str(self.v4), - v6 = str(self.v6)) - e.tail = "\n" - if self.bpki_certificate: - PEMElement(e, "bpki_certificate", self.bpki_certificate) - -class children(dict): - """ - Database of children. - """ - - def add(self, handle, prefix = None, asn = None, validity = None, bpki_certificate = None): - """ - Add resources to a child, creating the child object if necessary. - """ - - if handle not in self: - self[handle] = child(handle) - self[handle].add(prefix = prefix, asn = asn, validity = validity, bpki_certificate = bpki_certificate) - - def xml(self, e): - """ - Render children database to XML. - """ - - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_entitydb(cls, prefix_csv_file, asn_csv_file, fxcert, entitydb): - """ - Parse child data from entitydb. - """ - - self = cls() - for f in entitydb.iterate("children"): - c = etree_read(f) - self.add(handle = os.path.splitext(os.path.split(f)[-1])[0], - validity = c.get("valid_until"), - bpki_certificate = fxcert(b64 = c.findtext("bpki_child_ta"), - handle = handle, - bpki_type = rpki.irdb.Child)) - # childname p/n - for handle, pn in csv_reader(prefix_csv_file, columns = 2): - self.add(handle = handle, prefix = pn) - # childname asn - for handle, asn in csv_reader(asn_csv_file, columns = 2): - self.add(handle = handle, asn = asn) - return self - -class parent(object): - """ - Representation of one parent entity. - """ - - def __init__(self, handle): - self.handle = handle - self.service_uri = None - self.bpki_cms_certificate = None - self.myhandle = None - self.sia_base = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.myhandle: - s += " myhandle %s" % self.myhandle - if self.service_uri: - s += " uri %s" % self.service_uri - if self.sia_base: - s += " sia %s" % self.sia_base - if self.bpki_cms_certificate: - s += " cms %s" % self.bpki_cms_certificate - return s + ">" - - def add(self, service_uri = None, - bpki_cms_certificate = None, - myhandle = None, - sia_base = None): - """ - Add service URI or BPKI certificates to this parent object. - """ - - if service_uri is not None: - self.service_uri = service_uri - if bpki_cms_certificate is not None: - self.bpki_cms_certificate = bpki_cms_certificate - if myhandle is not None: - self.myhandle = myhandle - if sia_base is not None: - self.sia_base = sia_base - - def xml(self, e): - """ - Render this parent object to XML. - """ - - complete = self.bpki_cms_certificate and self.myhandle and self.service_uri and self.sia_base - if whine and not complete: - print "Incomplete parent entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "parent", - handle = self.handle, - myhandle = self.myhandle, - service_uri = self.service_uri, - sia_base = self.sia_base) - e.tail = "\n" - if self.bpki_cms_certificate: - PEMElement(e, "bpki_cms_certificate", self.bpki_cms_certificate) - -class parents(dict): - """ - Database of parent objects. - """ - - def add(self, handle, - service_uri = None, - bpki_cms_certificate = None, - myhandle = None, - sia_base = None): - """ - Add service URI or certificates to parent object, creating it if necessary. - """ - - if handle not in self: - self[handle] = parent(handle) - self[handle].add(service_uri = service_uri, - bpki_cms_certificate = bpki_cms_certificate, - myhandle = myhandle, - sia_base = sia_base) - - def xml(self, e): - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_entitydb(cls, fxcert, entitydb): - """ - Parse parent data from entitydb. - """ - - self = cls() - for f in entitydb.iterate("parents"): - h = os.path.splitext(os.path.split(f)[-1])[0] - p = etree_read(f) - r = etree_read(f.replace(os.path.sep + "parents" + os.path.sep, - os.path.sep + "repositories" + os.path.sep)) - if r.get("type") == "confirmed": - self.add(handle = h, - service_uri = p.get("service_uri"), - bpki_cms_certificate = fxcert(b64 = p.findtext("bpki_resource_ta"), - handle = h, - bpki_type = rpki.irdb.Parent), - myhandle = p.get("child_handle"), - sia_base = r.get("sia_base")) - elif whine: - print "Parent %s's repository entry in state %s, skipping this parent" % (h, r.get("type")) - return self - -class repository(object): - """ - Representation of one repository entity. - """ - - def __init__(self, handle): - self.handle = handle - self.service_uri = None - self.bpki_certificate = None - - def __repr__(self): - s = "<%s %s" % (self.__class__.__name__, self.handle) - if self.service_uri: - s += " uri %s" % self.service_uri - if self.bpki_certificate: - s += " cert %s" % self.bpki_certificate - return s + ">" - - def add(self, service_uri = None, bpki_certificate = None): - """ - Add service URI or BPKI certificates to this repository object. - """ - - if service_uri is not None: - self.service_uri = service_uri - if bpki_certificate is not None: - self.bpki_certificate = bpki_certificate - - def xml(self, e): - """ - Render this repository object to XML. - """ - - complete = self.bpki_certificate and self.service_uri - if whine and not complete: - print "Incomplete repository entry %s" % self - if complete or allow_incomplete: - e = SubElement(e, "repository", - handle = self.handle, - service_uri = self.service_uri) - e.tail = "\n" - if self.bpki_certificate: - PEMElement(e, "bpki_certificate", self.bpki_certificate) - -class repositories(dict): - """ - Database of repository objects. - """ - - def add(self, handle, - service_uri = None, - bpki_certificate = None): - """ - Add service URI or certificate to repository object, creating it if necessary. - """ - - if handle not in self: - self[handle] = repository(handle) - self[handle].add(service_uri = service_uri, - bpki_certificate = bpki_certificate) - - def xml(self, e): - for c in self.itervalues(): - c.xml(e) - - @classmethod - def from_entitydb(cls, fxcert, entitydb): - """ - Parse repository data from entitydb. - """ - - self = cls() - for f in entitydb.iterate("repositories"): - h = os.path.splitext(os.path.split(f)[-1])[0] - r = etree_read(f) - if r.get("type") == "confirmed": - self.add(handle = h, - service_uri = r.get("service_uri"), - bpki_certificate = fxcert(b64 = r.findtext("bpki_server_ta"), - handle = h, - bpki_type = rpki.irdb.Repository)) - elif whine: - print "Repository %s in state %s, skipping this repository" % (h, r.get("type")) - - return self - - - -def PEMElement(e, tag, obj, **kwargs): +def B64Element(e, tag, obj, **kwargs): """ Create an XML element containing Base64 encoded data taken from a DER object. @@ -601,7 +186,7 @@ class CA(object): """ ee = self.ee("referral") - return rpki.irdb.SignedReferral().wrap( + return rpki.x509.SignedReferral().wrap( msg = elt, keypair = ee.private_key, certs = ee.certificate, @@ -618,7 +203,7 @@ class CA(object): trust anchor. """ - return rpki.irdb.SignedReferral(Base64 = b64).unwrap( + return rpki.x509.SignedReferral(Base64 = b64).unwrap( ta = (ca, self.ca.certificate)) def bsc(self, handle, pkcs10): @@ -637,43 +222,6 @@ class CA(object): handle = handle, pkcs10 = rpki.x509.PKCS10(Base64 = pkcs10))[0] - def fxcert(self, b64, handle, bpki_type, path_restriction = 0): - """ - Write PEM certificate to file, then cross-certify. - - This is the interface that almost everything uses for - cross-certification. - """ - - fn = os.path.join(self.dir, "temp.%s.cer" % os.getpid()) - - try: - self.run_openssl("x509", "-inform", "DER", "-out", fn, stdin = base64.b64decode(b64)) - return self.xcert(fn, handle, path_restriction) - - finally: - if os.path.exists(fn): - os.unlink(fn) - - def xcert(self, cert, handle, path_restriction = 0): - """ - Cross-certify a certificate represented as a PEM file, if we - haven't already. This only works for self-signed certs, due to - limitations of the OpenSSL command line tool, but that suffices - for our purposes. - - Only .fxcert() and a few bits of the rootd setup use this - directly, everthing else calls .fxcert(). - """ - - xcert = "%s/xcert.%s.cer" % (self.dir, self.run_dgst(self.run_openssl( - "x509", "-noout", "-pubkey", "-subject", "-in", cert)).strip()) - - if not os.path.exists(xcert): - self.run_ca("-ss_cert", cert, "-out", xcert, "-extensions", - self.path_restriction[path_restriction]) - return xcert - def etree_write(e, filename, verbose = False, msg = None): @@ -686,22 +234,11 @@ def etree_write(e, filename, verbose = False, msg = None): filename = os.path.realpath(filename) tempname = filename if not filename.startswith("/dev/"): - tempname += ".tmp" + tempname += ".%s.tmp" % os.getpid() if verbose or msg: print "Writing", filename if msg: print msg - e = etree_pre_write(e) - ElementTree(e).write(tempname) - if tempname != filename: - os.rename(tempname, filename) - -def etree_pre_write(e): - """ - Do the namespace frobbing needed on write; broken out of - etree_write() because also needed with ElementToString(). - """ - e = copy.deepcopy(e) e.set("version", version) for i in e.getiterator(): @@ -709,7 +246,9 @@ def etree_pre_write(e): i.tag = namespaceQName + i.tag assert i.tag.startswith(namespaceQName) rpki.relaxng.myrpki.assertValid(e) - return e + ElementTree(e).write(tempname) + if tempname != filename: + os.rename(tempname, filename) def etree_read(filename, verbose = False): """ @@ -720,14 +259,6 @@ def etree_read(filename, verbose = False): if verbose: print "Reading", filename e = ElementTree(file = filename).getroot() - return etree_post_read(e) - -def etree_post_read(e): - """ - Do the namespace frobbing needed on read; broken out of etree_read() - beause also needed by ElementFromString(). - """ - rpki.relaxng.myrpki.assertValid(e) for i in e.getiterator(): if i.tag.startswith(namespaceQName): @@ -738,120 +269,6 @@ def etree_post_read(e): -class IRDB(object): - """ - Front-end to the IRDB. This is broken out from class main so - that other applications (namely, the portal-gui) can reuse it. - """ - - def __init__(self, cfg): - """ - Opens a new connection to the IRDB, using the configuration - information from a rpki.config.parser object. - """ - - from rpki.mysql_import import MySQLdb - - irdbd_cfg = rpki.config.parser(cfg.get("irdbd_conf", cfg.filename), "irdbd") - - self.db = MySQLdb.connect(user = irdbd_cfg.get("sql-username"), - db = irdbd_cfg.get("sql-database"), - passwd = irdbd_cfg.get("sql-password")) - - def update(self, handle, roa_requests, children, ghostbusters=None): - """ - Update the IRDB for a given resource handle. Removes all - existing data and replaces it with that specified in the - argument list. - - The "roa_requests" argument is a sequence of tuples of the form - (asID, v4_addresses, v6_addresses), where "v*_addresses" are - instances of rpki.resource_set.roa_prefix_set_ipv*. - - The "children" argument is a sequence of tuples of the form - (child_handle, asns, v4addrs, v6addrs, valid_until), - where "asns" is an instance of rpki.resource_set.resource_set_asn, - "v*addrs" are instances of rpki.resource_set.resource_set_ipv*, - and "valid_until" is an instance of rpki.sundial.datetime. - - The "ghostbusters" argument is a sequence of tuples of the form - (parent_handle, vcard_string). "parent_handle" may be value None, - in which case the specified vcard object will be used for all - parents. - """ - - cur = self.db.cursor() - - cur.execute( - """ - DELETE - FROM roa_request_prefix - USING roa_request, roa_request_prefix - WHERE roa_request.roa_request_id = roa_request_prefix.roa_request_id AND roa_request.roa_request_handle = %s - """, (handle,)) - - cur.execute("DELETE FROM roa_request WHERE roa_request.roa_request_handle = %s", (handle,)) - - for asID, v4addrs, v6addrs in roa_requests: - assert isinstance(v4addrs, rpki.resource_set.roa_prefix_set_ipv4) - assert isinstance(v6addrs, rpki.resource_set.roa_prefix_set_ipv6) - cur.execute("INSERT roa_request (roa_request_handle, asn) VALUES (%s, %s)", (handle, asID)) - roa_request_id = cur.lastrowid - for version, prefix_set in ((4, v4addrs), (6, v6addrs)): - if prefix_set: - cur.executemany("INSERT roa_request_prefix (roa_request_id, prefix, prefixlen, max_prefixlen, version) VALUES (%s, %s, %s, %s, %s)", - ((roa_request_id, p.prefix, p.prefixlen, p.max_prefixlen, version) for p in prefix_set)) - - cur.execute( - """ - DELETE - FROM registrant_asn - USING registrant, registrant_asn - WHERE registrant.registrant_id = registrant_asn.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute( - """ - DELETE FROM registrant_net USING registrant, registrant_net - WHERE registrant.registrant_id = registrant_net.registrant_id AND registrant.registry_handle = %s - """ , (handle,)) - - cur.execute("DELETE FROM registrant WHERE registrant.registry_handle = %s" , (handle,)) - - for child_handle, asns, ipv4, ipv6, valid_until in children: - cur.execute("INSERT registrant (registrant_handle, registry_handle, registrant_name, valid_until) VALUES (%s, %s, %s, %s)", - (child_handle, handle, child_handle, valid_until.to_sql())) - child_id = cur.lastrowid - if asns: - cur.executemany("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)", - ((a.min, a.max, child_id) for a in asns)) - if ipv4: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)", - ((a.min, a.max, child_id) for a in ipv4)) - if ipv6: - cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)", - ((a.min, a.max, child_id) for a in ipv6)) - - # don't munge the ghostbuster_request table when the arg is None. - # this allows the cli to safely run configure_resources without - # stomping on GBRs created by the portal gui. - if ghostbusters is not None: - cur.execute("DELETE FROM ghostbuster_request WHERE self_handle = %s", (handle,)) - if ghostbusters: - cur.executemany("INSERT INTO ghostbuster_request (self_handle, parent_handle, vcard) VALUES (%s, %s, %s)", - ((handle, parent_handle, vcard) for parent_handle, vcard in ghostbusters)) - - self.db.commit() - - def close(self): - """ - Close the connection to the IRDB. - """ - - self.db.close() - - - class main(rpki.cli.Cmd): prompt = "rpkic> " @@ -893,17 +310,8 @@ class main(rpki.cli.Cmd): self.stdout.write(" " * 4 + line) self.stdout.write("\n") - def entitydb_complete(self, prefix, text, line, begidx, endidx): - """ - Completion helper for entitydb filenames. - """ - - names = [] - for name in self.entitydb.iterate(prefix): - name = os.path.splitext(os.path.basename(name))[0] - if name.startswith(text): - names.append(name) - return names + def irdb_handle_complete(self, klass, text, line, begidx, endidx): + return [obj.handle for obj in klass.objects.all() if obj.handle.startswith(text)] def read_config(self): @@ -938,23 +346,30 @@ class main(rpki.cli.Cmd): import rpki.irdb - self.entitydb = EntityDB(self.cfg) - if self.run_rootd and (not self.run_pubd or not self.run_rpkid): raise CantRunRootd, "Can't run rootd unless also running rpkid and pubd" - self.bpki_resources = CA(self.cfg.get("bpki_resources_directory", "resources")) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers = CA(self.cfg.get("bpki_servers_directory", "servers")) - else: - self.bpki_servers = None - self.default_repository = self.cfg.get("default_repository", "") self.pubd_contact_info = self.cfg.get("pubd_contact_info", "") self.rsync_module = self.cfg.get("publication_rsync_module") self.rsync_server = self.cfg.get("publication_rsync_server") + try: + self.identity = rpki.irdb.Identity.objects.get(handle = self.handle) + except rpki.irdb.Identity.DoesNotExist: + self.identity = None + else: + try: + self.resource_ca = self.identity.ca_set.get(purpose = "resources") + except rpki.irdb.CA.DoesNotExist: + self.resource_ca = None + if self.run_rpkid or self.run_pubd or self.run_rootd: + try: + self.server_ca = self.identity.ca_set(purpose = "servers") + except rpki.irdb.CA.DoesNotExist: + self.server_ca = None + def do_initialize(self, arg): """ @@ -967,37 +382,48 @@ class main(rpki.cli.Cmd): if arg: raise BadCommandSyntax, "This command takes no arguments" - self.bpki_resources.setup(self.cfg.get("bpki_resources_ta_dn", - "/CN=%s BPKI Resource Trust Anchor" % self.handle)) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers.setup(self.cfg.get("bpki_servers_ta_dn", - "/CN=%s BPKI Server Trust Anchor" % self.handle)) + self.identity, created = rpki.irdb.Identity.objects.get_or_create(handle = self.handle) + if created: + print 'Created new identity for "%s"' % self.handle - # Create entitydb directories. + self.resource_ca, created = rpki.irdb.CA.objects.get_or_certify(identity = self.identity, purpose = "resources") + if created: + print "Created new BPKI resource CA" - for i in ("parents", "children", "repositories", "pubclients"): - d = self.entitydb(i) - if not os.path.exists(d): - os.makedirs(d) - - if self.run_rpkid or self.run_pubd or self.run_rootd: + if not self.run_rpkid and not self.run_pubd and not self.run_rootd: + self.server_ca = None + else: + self.server_ca, created = rpki.irdb.CA.objects.get_or_certify(identity = self.identity, purpose = "servers") + if created: + print "Created new BPKI server CA" if self.run_rpkid: - self.bpki_servers.ee("rpkid") - self.bpki_servers.ee("irdbd") + self.irdb.EECertificate.objects.get_or_certify(issuer = self.server_ca, purpose = "rpkid") + self.irdb.EECertificate.objects.get_or_certify(issuer = self.server_ca, purpose = "irdbd") if self.run_pubd: - self.bpki_servers.ee("pubd") + self.irdb.EECertificate.objects.get_or_certify(issuer = self.server_ca, purpose = "pubd") if self.run_rpkid or self.run_pubd: - self.bpki_servers.ee("irbe") + self.irdb.EECertificate.objects.get_or_certify(issuer = self.server_ca, purpose = "irbe") + + ## @todo + # Why do we issue root's EE certificate under our server CA? + # We've "always" done this, but does it make sense now? rootd + # only speaks up-down, so it's really just another resource + # holder. If we just issued it under our resource CA, we + # wouldn't have to cross certify anything to talk to it. Which + # might in itself break something, as it'd be the only parent we + # -didn't- have to cross-certify. Leave alone for now, but + # think about this later. + if self.run_rootd: - self.bpki_servers.ee("rootd") + self.irdb.EECertificate.objects.get_or_certify(issuer = self.server_ca, purpose = "rootd") # Build the identity.xml file. Need to check for existing file so we don't # overwrite? Worry about that later. e = Element("identity", handle = self.handle) - PEMElement(e, "bpki_ta", self.bpki_resources.cer) - etree_write(e, self.entitydb.identity, + B64Element(e, "bpki_ta", self.resource_ca.certificate) + etree_write(e, "identity.xml", msg = None if self.run_rootd else 'This is the "identity" file you will need to send to your parent') # If we're running rootd, construct a fake parent to go with it, @@ -1005,31 +431,28 @@ class main(rpki.cli.Cmd): if self.run_rootd: - e = Element("parent", parent_handle = self.handle, child_handle = self.handle, - service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port"), - valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365))) - PEMElement(e, "bpki_resource_ta", self.bpki_servers.cer) - PEMElement(e, "bpki_child_ta", self.bpki_resources.cer) - SubElement(e, "repository", type = "offer") - etree_write(e, self.entitydb("parents", self.handle)) - - self.bpki_resources.xcert(self.bpki_servers.cer) - - rootd_child_fn = self.cfg.get("child-bpki-cert", None, "rootd") - if not os.path.exists(rootd_child_fn): - os.link(self.bpki_servers.xcert(self.bpki_resources.cer), rootd_child_fn) - - repo_file_name = self.entitydb("repositories", self.handle) + rpki.irdb.Parent.objects.get_or_certify( + issuer = self.resource_ca, + handle = self.handle, + parent_handle = self.handle, + child_handle = self.handle, + ta = self.server_ca.certificate, + service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port"), + repository_type = "offer") + + rpki.irdb.Child.objects.get_or_certify( + issuer = self.server_ca, + handle = self.handle, + ta = self.resource_ca.certificate, + valid_until = self.resource_ca.certificate.getNotAfter()) try: - want_offer = etree_read(repo_file_name).get("type") != "confirmed" - except IOError: - want_offer = True + self.resource_ca.repositories.get(handle = self.handle) - if want_offer: + except rpki.irdb.Repository.DoesNotExist: e = Element("repository", type = "offer", handle = self.handle, parent_handle = self.handle) - PEMElement(e, "bpki_client_ta", self.bpki_resources.cer) - etree_write(e, repo_file_name, + B64Element(e, "bpki_client_ta", self.resource_ca.certificate) + etree_write(e, "rootd_repository_offer.xml", msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository') @@ -1041,38 +464,25 @@ class main(rpki.cli.Cmd): now. In the long run we might want to be more clever about only touching ones that need maintenance, but this will do for a start. + We also reissue CRLs for all CAs. + Most likely this should be run under cron. """ - if self.bpki_servers: - bpkis = (self.bpki_resources, self.bpki_servers) - else: - bpkis = (self.bpki_resources,) - - for bpki in bpkis: - for cer in glob.iglob("%s/*.cer" % bpki.dir): - key = cer[0:-4] + ".key" - req = cer[0:-4] + ".req" - if os.path.exists(key): - print "Regenerating BPKI PKCS #10", req - bpki.run_openssl("x509", "-x509toreq", "-in", cer, "-out", req, "-signkey", key) - print "Clearing BPKI certificate", cer - os.unlink(cer) - if cer == bpki.cer: - assert req == bpki.req - print "Regenerating certificate", cer - bpki.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-in", req, "-out", cer) - - print "Regenerating CRLs" - for bpki in bpkis: - bpki.run_ca("-gencrl", "-out", bpki.crl) - - self.do_initialize(None) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.do_configure_daemons(arg) - else: - self.do_configure_resources(None) + for model in (rpki.irdb.CA, + rpki.irdb.EECertificate, + rpki.irdb.BSC, + rpki.irdb.Child, + rpki.irdb.Parent, + rpki.irdb.Client, + rpki.irdb.Repository): + for obj in model.all(): + print "Regenerating certificate", obj.certificate.getSubject() + obj.avow() + for ca in rpki.irdb.CA.all(): + print "Regenerating CRL for", ca.identity.handle, ca.purpose + ca.generate_crl() def do_configure_child(self, arg): """ @@ -1099,44 +509,32 @@ class main(rpki.cli.Cmd): if child_handle is None: child_handle = c.get("handle") - try: - e = etree_read(self.cfg.get("xml_filename")) - service_uri_base = e.get("service_uri") - - except IOError: - if self.run_rpkid: - service_uri_base = "http://%s:%s/up-down/%s" % (self.cfg.get("rpkid_server_host"), - self.cfg.get("rpkid_server_port"), - self.handle) - else: - service_uri_base = None + service_uri = "http://%s:%s/up-down/%s/%s" % (self.cfg.get("rpkid_server_host"), + self.cfg.get("rpkid_server_port"), + self.handle, child_handle) - if not service_uri_base: - print "Sorry, you can't set up children of a hosted config that itself has not yet been set up" - return + valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) print "Child calls itself %r, we call it %r" % (c.get("handle"), child_handle) - if self.run_rpkid or self.run_pubd or self.run_rootd: - self.bpki_servers.fxcert(b64 = c.findtext("bpki_ta"), - handle = child_handle, - bpki_type = rpki.irdb.Child) + rpki.irdb.Child.objects.get_or_certify( + issuer = self.resource_ca, + handle = child_handle, + ta = rpki.x509.X509(Base64 = c.findtext("bpki_ta")), + valid_until = valid_until.toXMLtime()) e = Element("parent", parent_handle = self.handle, child_handle = child_handle, - service_uri = "%s/%s" % (service_uri_base, child_handle), - valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365))) - - PEMElement(e, "bpki_resource_ta", self.bpki_resources.cer) + service_uri = service_uri, valid_until = valid_until) + B64Element(e, "bpki_resource_ta", self.resource_ca.certificate) SubElement(e, "bpki_child_ta").text = c.findtext("bpki_ta") - repo = None - for f in self.entitydb.iterate("repositories"): - r = etree_read(f) - if r.get("type") == "confirmed": - h = os.path.splitext(os.path.split(f)[-1])[0] - if repo is None or h == self.default_repository: - repo_handle = h - repo = r + try: + repo = self.resource_ca.repositories.get(handle = self.default_repository) + except rpki.irdb.Repository.DoesNotExist: + try: + repo = self.resource_ca.repositories[0] + except rpki.irdb.Repository.DoesNotExist: + repo = None if repo is None: print "Couldn't find any usable repositories, not giving referral" @@ -1154,25 +552,22 @@ class main(rpki.cli.Cmd): SubElement(r, "authorization", referrer = repo.get("client_handle")).text = auth SubElement(r, "contact_info").text = repo.findtext("contact_info") - etree_write(e, self.entitydb("children", child_handle), + etree_write(e, "parent-response-to-%s.xml" % child_handle, msg = "Send this file back to the child you just configured") def do_delete_child(self, arg): """ Delete a child of this RPKI entity. - - This should check that the XML file it's deleting really is a - child, but doesn't, yet. """ try: - os.unlink(self.entitydb("children", arg)) - except OSError: + self.resource_ca.children.get(handle = arg).delete() + except rpki.irdb.Child.DoesNotExist: print "No such child \"%s\"" % arg def complete_delete_child(self, *args): - return self.entitydb_complete("children", *args) + return self.irdb_handle_complete(rpki.irdb.Child, *args) def do_configure_parent(self, arg): @@ -1202,42 +597,55 @@ class main(rpki.cli.Cmd): if parent_handle is None: parent_handle = p.get("parent_handle") - print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle) - print "Parent calls us %r" % p.get("child_handle") + r = p.find("repository") - self.bpki_resources.fxcert(b64 = p.findtext("bpki_resource_ta"), - handle = parent_handle, - bpki_type = rpki.irdb.Parent) + repository_type = "none" + referrer = None + referral_authorization = None - etree_write(p, self.entitydb("parents", parent_handle)) + if r is not None: + repository_type = r.get("type") - r = p.find("repository") + if repository_type == "referral": + a = r.find("authorization") + referrer = a.get("referrer") + referral_authorization = rpki.x509.SignedReferral(Base64 = a.text) - if r is None or r.get("type") not in ("offer", "referral"): - r = Element("repository", type = "none") + print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle) + print "Parent calls us %r" % p.get("child_handle") + rpki.irdb.Parent.get_or_certify( + issuer = self.resource_ca, + handle = parent_handle, + child_handle = p.get("child_handle"), + parent_handle = p.get("parent_handle"), + service_uri = p.get("service_uri"), + ta = rpki.x509.X509(Base64 = p.findtext("bpki_resource_ta")), + repository_type = rpki.irdb.Parent.repository_type_map[repository_type], + referrer = referrer, + referral_authorization = referral_authorization)[0] + + if repository_type == "none": + r = Element("repository", type = "none") r.set("handle", self.handle) r.set("parent_handle", parent_handle) - PEMElement(r, "bpki_client_ta", self.bpki_resources.cer) - etree_write(r, self.entitydb("repositories", parent_handle), + B64Element(r, "bpki_client_ta", self.resource_ca.certificate) + etree_write(r, "repository-request-for-%s.xml" % parent_handle, msg = "This is the file to send to the repository operator") def do_delete_parent(self, arg): """ Delete a parent of this RPKI entity. - - This should check that the XML file it's deleting really is a - parent, but doesn't, yet. """ try: - os.unlink(self.entitydb("parents", arg)) - except OSError: + self.resource_ca.parents.get(handle = arg).delete() + except rpki.irdb.Parent.DoesNotExist: print "No such parent \"%s\"" % arg def complete_delete_parent(self, *args): - return self.entitydb_complete("parents", *args) + return self.irdb_handle_complete(rpki.irdb.Parent, *args) def do_configure_publication_client(self, arg): @@ -1261,19 +669,9 @@ class main(rpki.cli.Cmd): client = etree_read(argv[0]) - # client_handle is a problem here in the new scheme. This code - # can't even figure out what client_handle is supposed to be until - # long after it's gone off checking for cross-certification and so - # forth. In the new scheme, we need to know client_handle in - # order to look up the cross-certification. Chicken, meet egg. - # - # With luck, these convolutions are just a side effect of the - # bizzare way we did this in the old code, but will need - # attention. If you're reading this because the reference to - # client_handle in the .fxcert() call below threw an exception, I - # haven't sorted this mess out yet. - - if sia_base is None and client.get("handle") == self.handle and self.bpki_resources.ca.certificate == rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")): + client_ta = rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")) + + if sia_base is None and client.get("handle") == self.handle and self.bpki_resources.ca.certificate == client_ta: print "This looks like self-hosted publication" sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, self.handle) @@ -1281,28 +679,24 @@ class main(rpki.cli.Cmd): print "This looks like a referral, checking" try: auth = client.find("authorization") - if auth is None: - raise BadXMLMessage, "Malformed referral, couldn't find <auth/> element" - referrer = etree_read(self.entitydb("pubclients", auth.get("referrer").replace("/","."))) - referrer = self.bpki_servers.fxcert(b64 = referrer.findtext("bpki_client_ta")) - referral = self.bpki_servers.cms_xml_verify(auth.text, referrer) - if rpki.x509.X509(Base64 = referral.text) != rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")): + referrer = self.resource_ca.clients.get(handle = auth.get("referrer")) + referral_cms = rpki.x509.SignedReferral(Base64 = auth.text) + referral_xml = referral_cms.unwrap(ta = (referrer.certificate, self.server_ca.certificate)) + if rpki.x509.X509(Base64 = referral_xml.text) != client_ta: raise BadXMLMessage, "Referral trust anchor does not match" sia_base = referral.get("authorized_sia_base") - except IOError: - print "We have no record of client (%s) alleged to have made this referral" % auth.get("referrer") + except rpki.irdb.Client.DoesNotExist: + print "We have no record of the client (%s) alleged to have made this referral" % auth.get("referrer") if sia_base is None and client.get("type") == "offer" and client.get("parent_handle") == self.handle: print "This looks like an offer, client claims to be our child, checking" - client_ta = client.findtext("bpki_client_ta") - if not client_ta: - raise BadXMLMessage, "Malformed offer, couldn't find <bpki_client_ta/> element" - for child in self.entitydb.iterate("children"): - c = etree_read(child) - if rpki.x509.X509(Base64 = c.findtext("bpki_child_ta")) == rpki.x509.X509(Base64 = client_ta): - sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module, - self.handle, client.get("handle")) - break + try: + child = self.resource_ca.children.get(ta = client_ta) + except rpki.irdb.Child.DoesNotExist: + print "Can't find a child matching this client" + else: + sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module, + self.handle, client.get("handle")) # If we still haven't figured out what to do with this client, it # gets a top-level tree of its own, no attempt at nesting. @@ -1321,9 +715,10 @@ class main(rpki.cli.Cmd): print "Client calls itself %r, we call it %r" % (client.get("handle"), client_handle) print "Client says its parent handle is %r" % parent_handle - self.bpki_servers.fxcert(b64 = client.findtext("bpki_client_ta"), - handle = client_handle, - bpki_type = bpki.irdb.Client) + rpki.irdb.Client.get_or_certify( + issuer = self.server_ca, + handle = client_handle, + ta = client_ta) e = Element("repository", type = "confirmed", client_handle = client_handle, @@ -1333,28 +728,25 @@ class main(rpki.cli.Cmd): self.cfg.get("pubd_server_port"), client_handle)) - PEMElement(e, "bpki_server_ta", self.bpki_servers.cer) - SubElement(e, "bpki_client_ta").text = client.findtext("bpki_client_ta") + B64Element(e, "bpki_server_ta", self.server_ca.certificate) + B64Element(e, "bpki_client_ta", client_ta) SubElement(e, "contact_info").text = self.pubd_contact_info - etree_write(e, self.entitydb("pubclients", client_handle.replace("/", ".")), + etree_write(e, "repository-response-to-%s.xml" % client_handle.replace("/", "."), msg = "Send this file back to the publication client you just configured") def do_delete_publication_client(self, arg): """ Delete a publication client of this RPKI entity. - - This should check that the XML file it's deleting really is a - client, but doesn't, yet. """ try: - os.unlink(self.entitydb("pubclients", arg)) - except OSError: + self.resource_ca.clients.get(handle = arg).delete() + except rpki.irdb.Client.DoesNotExist: print "No such client \"%s\"" % arg def complete_delete_publication_client(self, *args): - return self.entitydb_complete("pubclients", *args) + return self.irdb_handle_complete(rpki.irdb.Client, *args) def do_configure_repository(self, arg): @@ -1385,8 +777,21 @@ class main(rpki.cli.Cmd): print "Repository calls us %r" % (r.get("client_handle")) print "Repository response associated with parent_handle %r" % parent_handle - etree_write(r, self.entitydb("repositories", parent_handle)) + try: + parent = self.resource_ca.parents.get(handle = parent_handle) + + except rpki.irdb.Parent.DoesNotExist: + print "Could not find parent %r in our database" % parent_handle + else: + rpki.irdb.Repository.get_or_certify( + issuer = self.resource_ca, + handle = parent_handle, + client_handle = r.get("client_handle"), + service_uri = r.get("service_uri"), + sia_base = r.get("sia_base"), + ta = rpki.x509.X509(Base64 = r.findtext("bpki_server_ta")), + parent = parent) def do_delete_repository(self, arg): """ @@ -1397,12 +802,12 @@ class main(rpki.cli.Cmd): """ try: - os.unlink(self.entitydb("repositories", arg)) - except OSError: + self.resource_ca.repositories.get(handle = arg).delete() + except rpki.irdb.Repository.DoesNotExist: print "No such repository \"%s\"" % arg def complete_delete_repository(self, *args): - return self.entitydb_complete("repositories", *args) + return self.irdb_handle_complete(rpki.irdb.Repository, *args) def renew_children_common(self, arg, plural): @@ -1420,11 +825,11 @@ class main(rpki.cli.Cmd): if plural: if len(argv) != 0: raise BadCommandSyntax, "Unexpected arguments" - children = "*" + children = self.resource_ca.children else: if len(argv) != 1: raise BadCommandSyntax, "Need to specify child handle" - children = argv[0] + children = self.resource_ca.children.filter(handle = argv[0]) if valid_until is None: valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365) @@ -1435,10 +840,9 @@ class main(rpki.cli.Cmd): print "New validity date", valid_until - for f in self.entitydb.iterate("children", children): - c = etree_read(f) - c.set("valid_until", str(valid_until)) - etree_write(c, f) + for child in children: + child.valid_until = valid_until + child.save() def do_renew_child(self, arg): """ @@ -1448,7 +852,7 @@ class main(rpki.cli.Cmd): return self.renew_children_common(arg, False) def complete_renew_child(self, *args): - return self.entitydb_complete("children", *args) + return self.irdb_handle_complete(rpki.irdb.Child, *args) def do_renew_all_children(self, arg): """ @@ -1503,12 +907,12 @@ class main(rpki.cli.Cmd): fxcert = self.bpki_resources.fxcert, entitydb = self.entitydb).xml(e) - PEMElement(e, "bpki_ca_certificate", self.bpki_resources.cer) - PEMElement(e, "bpki_crl", self.bpki_resources.crl) + B64Element(e, "bpki_ca_certificate", self.bpki_resources.cer) + B64Element(e, "bpki_crl", self.bpki_resources.crl) if bsc is not None: - PEMElement(e, "bpki_bsc_certificate", bsc.certificate) - PEMElement(e, "bpki_bsc_pkcs10", bsc.pkcs10) + B64Element(e, "bpki_bsc_certificate", bsc.certificate) + B64Element(e, "bpki_bsc_pkcs10", bsc.pkcs10) etree_write(e, xml_filename, msg = msg) diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index abdd9194..b5300bee 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -47,6 +47,7 @@ import rpki.POW, rpki.POW.pkix, base64, lxml.etree, os, subprocess, sys import email.mime.application, email.utils, mailbox, time import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial import rpki.manifest, rpki.roa, rpki.log, rpki.async, rpki.ghostbuster +import rpki.relaxng def base64_with_linebreaks(der): """ @@ -1415,6 +1416,11 @@ class XML_CMS_object(CMS_object): saxify = None +class SignedReferral(XML_CMS_object): + encoding = "us-ascii" + schema = rpki.relaxng.myrpki + saxify = None + class Ghostbuster(CMS_object): """ Class to hold Ghostbusters record (CMS-wrapped VCard). This is |