aboutsummaryrefslogtreecommitdiff
path: root/rpkid
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid')
-rw-r--r--rpkid/rpki/csv_utils.py100
-rw-r--r--rpkid/rpki/irdb/models.py84
-rw-r--r--rpkid/rpki/rpkic.py553
-rw-r--r--rpkid/rpki/x509.py12
-rw-r--r--rpkid/rpkic.py6
5 files changed, 390 insertions, 365 deletions
diff --git a/rpkid/rpki/csv_utils.py b/rpkid/rpki/csv_utils.py
new file mode 100644
index 00000000..f7eed414
--- /dev/null
+++ b/rpkid/rpki/csv_utils.py
@@ -0,0 +1,100 @@
+"""
+CSV utilities, moved here from myrpki.py.
+
+$Id$
+
+Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+
+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 ISC DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL ISC 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 csv
+import os
+
+class BadCSVSyntax(Exception):
+ """
+ Bad CSV syntax.
+ """
+
+class csv_reader(object):
+ """
+ Reader for tab-delimited text that's (slightly) friendlier than the
+ stock Python csv module (which isn't intended for direct use by
+ humans anyway, and neither was this package originally, but that
+ seems to be the way that it has evolved...).
+
+ Columns parameter specifies how many columns users of the reader
+ expect to see; lines with fewer columns will be padded with None
+ values.
+
+ Original API design for this class courtesy of Warren Kumari, but
+ don't blame him if you don't like what I did with his ideas.
+ """
+
+ def __init__(self, filename, columns = None, min_columns = None, comment_characters = "#;"):
+ assert columns is None or isinstance(columns, int)
+ assert min_columns is None or isinstance(min_columns, int)
+ if columns is not None and min_columns is None:
+ min_columns = columns
+ self.filename = filename
+ self.columns = columns
+ self.min_columns = min_columns
+ self.comment_characters = comment_characters
+ self.file = open(filename, "r")
+
+ def __iter__(self):
+ line_number = 0
+ for line in self.file:
+ line_number += 1
+ line = line.strip()
+ if not line or line[0] in self.comment_characters:
+ continue
+ fields = line.split()
+ if self.min_columns is not None and len(fields) < self.min_columns:
+ raise BadCSVSyntax, "%s:%d: Not enough columns in line %r" % (self.filename, line_number, line)
+ if self.columns is not None and len(fields) > self.columns:
+ raise BadCSVSyntax, "%s:%d: Too many columns in line %r" % (self.filename, line_number, line)
+ if self.columns is not None and len(fields) < self.columns:
+ fields += tuple(None for i in xrange(self.columns - len(fields)))
+ yield fields
+
+class csv_writer(object):
+ """
+ Writer object for tab delimited text. We just use the stock CSV
+ module in excel-tab mode for this.
+
+ If "renmwo" is set (default), the file will be written to
+ a temporary name and renamed to the real filename after closing.
+ """
+
+ def __init__(self, filename, renmwo = True):
+ self.filename = filename
+ self.renmwo = "%s.~renmwo%d~" % (filename, os.getpid()) if renmwo else filename
+ self.file = open(self.renmwo, "w")
+ self.writer = csv.writer(self.file, dialect = csv.get_dialect("excel-tab"))
+
+ def close(self):
+ """
+ Close this writer.
+ """
+ if self.file is not None:
+ self.file.close()
+ self.file = None
+ if self.filename != self.renmwo:
+ os.rename(self.renmwo, self.filename)
+
+ def __getattr__(self, attr):
+ """
+ Fake inheritance from whatever object csv.writer deigns to give us.
+ """
+ return getattr(self.writer, attr)
diff --git a/rpkid/rpki/irdb/models.py b/rpkid/rpki/irdb/models.py
index e05f3f03..93926bd1 100644
--- a/rpkid/rpki/irdb/models.py
+++ b/rpkid/rpki/irdb/models.py
@@ -76,6 +76,7 @@ class DERField(django.db.models.Field):
def __init__(self, *args, **kwargs):
kwargs["serialize"] = False
kwargs["blank"] = True
+ kwargs["default"] = None
django.db.models.Field.__init__(self, *args, **kwargs)
def db_type(self, connection):
@@ -85,22 +86,18 @@ class DERField(django.db.models.Field):
return "BLOB"
def to_python(self, value):
- if value is None or isinstance(value, self.rpki_type):
- return value
- else:
- assert isinstance(value, str)
+ assert value is None or isinstance(value, (self.rpki_type, str))
+ if isinstance(value, str):
return self.rpki_type(DER = value)
+ else:
+ return value
def get_prep_value(self, value):
+ assert value is None or isinstance(value, (self.rpki_type, str))
if isinstance(value, self.rpki_type):
return value.get_DER()
- elif value is None or isinstance(value, str):
- return value
else:
- import sys
- sys.stderr.write(
- "get_prep_value got %r, expected string or %r\n" % (type(value), self.rpki_type))
- assert isinstance(value, (self.rpki_type, str))
+ return value
class CertificateField(DERField):
description = "X.509 certificate"
@@ -118,6 +115,10 @@ 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
@@ -138,6 +139,40 @@ ip_version_map = { "IPv4" : 4, "IPv6" : 6 }
#
ip_version_choices = tuple((y, x) for (x, y) in ip_version_map.iteritems())
+class CertificateManager(django.db.models.Manager):
+
+ def get_or_certify(self, **kwargs):
+ """
+ Sort of like .get_or_create(), but for models containing
+ certificates which need to be generated based on other fields.
+
+ Takes keyword arguments like .get(), checks for existing object.
+ If none, creates a new one; if found an existing object but some
+ of the non-key fields don't match, updates the existing object.
+ Runs certification method for new or updated objects. Returns a
+ tuple consisting of the object and a boolean indicating whether
+ anything has changed.
+
+ This is a somewhat weird use of .Meta.unique_together, but fits
+ with the way this application use unique indices and foreign keys.
+ """
+
+ try:
+ obj = self.get(**dict((k, kwargs[k]) for k in self.model.Meta.unique_together))
+
+ except self.model.DoesNotExist:
+ obj = self.model(**kwargs)
+
+ else:
+ if all(getattr(obj, k) == kwargs[k] for k in kwargs):
+ return obj, False
+ for k in kwargs:
+ setattr(obj, k, kwargs[k])
+
+ obj.avow()
+ obj.save()
+ return obj, True
+
###
class Identity(django.db.models.Model):
@@ -160,6 +195,8 @@ class CA(django.db.models.Model):
last_crl_update = django.db.models.DateTimeField()
next_crl_update = django.db.models.DateTimeField()
+ objects = CertificateManager()
+
class Meta:
unique_together = ("identity", "purpose")
@@ -167,7 +204,9 @@ class CA(django.db.models.Model):
ca_certificate_lifetime = rpki.sundial.timedelta(days = 3652)
crl_interval = rpki.sundial.timedelta(days = 1)
- def self_certify(self):
+ def avow(self):
+ if self.private_key is None:
+ self.private_key = rpki.x509.RSA.generate()
subject_name = rpki.x509.X501DN("%s BPKI %s CA" % (
self.identity.handle, self.get_purpose_display()))
now = rpki.sundial.now()
@@ -179,6 +218,7 @@ class CA(django.db.models.Model):
now = now,
notAfter = notAfter)
self.serial += 1
+ self.generate_crl()
return self.certificate
def certify(self, subject_name, subject_key, validity_interval, is_ca, pathLenConstraint = None):
@@ -208,18 +248,22 @@ class CA(django.db.models.Model):
def generate_crl(self):
now = rpki.sundial.now()
self.revocations.filter(expires__lt = now).delete()
- revoked_certificates = [(r.serial, rpki.sundial.datetime.fromdatetime(r.revoked).toASN1tuple(), ())
- for r in self.revocations]
+ revoked = [(r.serial, rpki.sundial.datetime.fromdatetime(r.revoked).toASN1tuple(), ())
+ for r in self.revocations]
self.latest_crl = rpki.x509.CRL.generate(
keypair = self.private_key,
issuer = self.certificate.getSubject(),
+ serial = self.next_crl_number,
thisUpdate = now,
nextUpdate = now + self.crl_interval,
- revoked_certificates = revoked_certificates)
-
+ revoked_certificates = revoked)
+ self.last_crl_update = now
+ self.next_crl_update = now + self.crl_interval
+ self.next_crl_number += 1
class Certificate(django.db.models.Model):
certificate = CertificateField()
+ objects = CertificateManager()
default_interval = rpki.sundial.timedelta(days = 60)
@@ -238,7 +282,7 @@ class CrossCertification(Certificate):
abstract = True
unique_together = ("issuer", "handle")
- def generate_certificate(self):
+ def avow(self):
self.certificate = self.issuer.certify(
subject_name = self.ta.getSubject(),
subject_key = self.ta.getPublicKey(),
@@ -258,14 +302,16 @@ class Revocation(django.db.models.Model):
class EECertificate(Certificate):
issuer = django.db.models.ForeignKey(CA, related_name = "ee_certificates")
- purpose_map = ChoiceMap("rpkid", "pubd", "irdbd", "irbe", "rootd")
+ purpose_map = ChoiceMap("rpkid", "pubd", "irdbd", "irbe", "rootd", "referral")
purpose = django.db.models.PositiveSmallIntegerField(choices = purpose_map.choices)
private_key = RSAKeyField()
class Meta:
unique_together = ("issuer", "purpose")
- def generate_certificate(self):
+ def avow(self):
+ if self.private_key is None:
+ self.private_key = rpki.x509.RSA.generate()
subject_name = rpki.x509.X501DN("%s BPKI %s EE" % (
self.issuer.identity.handle, self.get_purpose_display()))
self.certificate = self.issuer.certify(
@@ -282,7 +328,7 @@ class BSC(Certificate):
class Meta:
unique_together = ("issuer", "handle")
- def generate_certificate(self):
+ def avow(self):
self.certificate = self.issuer.certify(
subject_name = self.pkcs10.getSubject(),
subject_key = self.pkcs10.getPublicKey(),
diff --git a/rpkid/rpki/rpkic.py b/rpkid/rpki/rpkic.py
index 54427ebb..154cbf1d 100644
--- a/rpkid/rpki/rpkic.py
+++ b/rpkid/rpki/rpkic.py
@@ -62,6 +62,8 @@ from lxml.etree import (Element, SubElement, ElementTree,
fromstring as ElementFromString,
tostring as ElementToString)
+from rpki.csv_utils import (csv_reader, csv_writer, BadCSVSyntax)
+
# Our XML namespace and protocol version.
@@ -93,11 +95,6 @@ class CouldntTalkToDaemon(Exception):
Couldn't talk to daemon.
"""
-class BadCSVSyntax(Exception):
- """
- Bad CSV syntax.
- """
-
class BadXMLMessage(Exception):
"""
Bad XML message.
@@ -139,6 +136,11 @@ class EntityDB(object):
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.
@@ -165,6 +167,7 @@ class roa_request(object):
"""
Add one prefix to this ROA request.
"""
+
if self.v4re.match(prefix):
self.v4.add(prefix)
elif self.v6re.match(prefix):
@@ -176,6 +179,7 @@ class roa_request(object):
"""
Generate XML element represeting representing this ROA request.
"""
+
e = SubElement(e, "roa_request",
asn = self.asn,
v4 = str(self.v4),
@@ -191,6 +195,7 @@ class roa_requests(dict):
"""
Add one <ASN, group, prefix> set to ROA request database.
"""
+
key = (asn, group)
if key not in self:
self[key] = roa_request(asn, group)
@@ -200,6 +205,7 @@ class roa_requests(dict):
"""
Render ROA requests as XML elements.
"""
+
for r in self.itervalues():
r.xml(e)
@@ -208,6 +214,7 @@ class roa_requests(dict):
"""
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):
@@ -249,6 +256,7 @@ class child(object):
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)
@@ -267,6 +275,7 @@ class child(object):
"""
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
@@ -290,6 +299,7 @@ class children(dict):
"""
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)
@@ -298,6 +308,7 @@ class children(dict):
"""
Render children database to XML.
"""
+
for c in self.itervalues():
c.xml(e)
@@ -306,12 +317,15 @@ class children(dict):
"""
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(c.findtext("bpki_child_ta")))
+ 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)
@@ -351,6 +365,7 @@ class parent(object):
"""
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:
@@ -364,6 +379,7 @@ class parent(object):
"""
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
@@ -390,6 +406,7 @@ class parents(dict):
"""
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,
@@ -406,6 +423,7 @@ class parents(dict):
"""
Parse parent data from entitydb.
"""
+
self = cls()
for f in entitydb.iterate("parents"):
h = os.path.splitext(os.path.split(f)[-1])[0]
@@ -415,7 +433,9 @@ class parents(dict):
if r.get("type") == "confirmed":
self.add(handle = h,
service_uri = p.get("service_uri"),
- bpki_cms_certificate = fxcert(p.findtext("bpki_resource_ta")),
+ 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:
@@ -444,6 +464,7 @@ class repository(object):
"""
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:
@@ -453,6 +474,7 @@ class repository(object):
"""
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
@@ -475,6 +497,7 @@ class repositories(dict):
"""
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,
@@ -489,6 +512,7 @@ class repositories(dict):
"""
Parse repository data from entitydb.
"""
+
self = cls()
for f in entitydb.iterate("repositories"):
h = os.path.splitext(os.path.split(f)[-1])[0]
@@ -496,355 +520,161 @@ class repositories(dict):
if r.get("type") == "confirmed":
self.add(handle = h,
service_uri = r.get("service_uri"),
- bpki_certificate = fxcert(r.findtext("bpki_server_ta")))
+ 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
-class csv_reader(object):
- """
- Reader for tab-delimited text that's (slightly) friendlier than the
- stock Python csv module (which isn't intended for direct use by
- humans anyway, and neither was this package originally, but that
- seems to be the way that it has evolved...).
-
- Columns parameter specifies how many columns users of the reader
- expect to see; lines with fewer columns will be padded with None
- values.
-
- Original API design for this class courtesy of Warren Kumari, but
- don't blame him if you don't like what I did with his ideas.
- """
-
- def __init__(self, filename, columns = None, min_columns = None, comment_characters = "#;"):
- assert columns is None or isinstance(columns, int)
- assert min_columns is None or isinstance(min_columns, int)
- if columns is not None and min_columns is None:
- min_columns = columns
- self.filename = filename
- self.columns = columns
- self.min_columns = min_columns
- self.comment_characters = comment_characters
- self.file = open(filename, "r")
-
- def __iter__(self):
- line_number = 0
- for line in self.file:
- line_number += 1
- line = line.strip()
- if not line or line[0] in self.comment_characters:
- continue
- fields = line.split()
- if self.min_columns is not None and len(fields) < self.min_columns:
- raise BadCSVSyntax, "%s:%d: Not enough columns in line %r" % (self.filename, line_number, line)
- if self.columns is not None and len(fields) > self.columns:
- raise BadCSVSyntax, "%s:%d: Too many columns in line %r" % (self.filename, line_number, line)
- if self.columns is not None and len(fields) < self.columns:
- fields += tuple(None for i in xrange(self.columns - len(fields)))
- yield fields
-
-class csv_writer(object):
- """
- Writer object for tab delimited text. We just use the stock CSV
- module in excel-tab mode for this.
-
- If "renmwo" is set (default), the file will be written to
- a temporary name and renamed to the real filename after closing.
- """
-
- def __init__(self, filename, renmwo = True):
- self.filename = filename
- self.renmwo = "%s.~renmwo%d~" % (filename, os.getpid()) if renmwo else filename
- self.file = open(self.renmwo, "w")
- self.writer = csv.writer(self.file, dialect = csv.get_dialect("excel-tab"))
-
- def close(self):
- """
- Close this writer.
- """
- if self.file is not None:
- self.file.close()
- self.file = None
- if self.filename != self.renmwo:
- os.rename(self.renmwo, self.filename)
-
- def __getattr__(self, attr):
- """
- Fake inheritance from whatever object csv.writer deigns to give us.
- """
- return getattr(self.writer, attr)
+
-def PEMBase64(filename):
- """
- Extract Base64 encoded data from a PEM file.
- """
- lines = open(filename).readlines()
- while lines:
- if lines.pop(0).startswith("-----BEGIN "):
- break
- while lines:
- if lines.pop(-1).startswith("-----END "):
- break
- return "".join(lines)
-
-def PEMElement(e, tag, filename, **kwargs):
+def PEMElement(e, tag, obj, **kwargs):
"""
Create an XML element containing Base64 encoded data taken from a
- PEM file.
+ DER object.
"""
+
if e.text is None:
e.text = "\n"
se = SubElement(e, tag, **kwargs)
- se.text = "\n" + PEMBase64(filename)
+ se.text = "\n" + obj.get_Base64()
se.tail = "\n"
return se
+
+
class CA(object):
"""
Representation of one certification authority.
- """
- # Mapping of path restriction values we use to OpenSSL config file
- # section names.
+ This used to be a big complicated wrapper around the awfulness of
+ running the OpenSSL command line tool in a subprocess.
- path_restriction = { 0 : "ca_x509_ext_xcert0",
- 1 : "ca_x509_ext_xcert1" }
+ With the new Django-model-based IRDB, this is mostly just a wrapper
+ around the IRDB objects and their associated crypto methods.
- def __init__(self, cfg_file, dir):
- self.cfg = cfg_file
- self.dir = dir
- self.cer = dir + "/ca.cer"
- self.key = dir + "/ca.key"
- self.req = dir + "/ca.req"
- self.crl = dir + "/ca.crl"
- self.index = dir + "/index"
- self.serial = dir + "/serial"
- self.crlnum = dir + "/crl_number"
-
- cfg = rpki.config.parser(cfg_file, "myrpki")
- self.openssl = cfg.get("openssl", "openssl")
-
- self.env = { "PATH" : os.environ["PATH"],
- "BPKI_DIRECTORY" : dir,
- "RANDFILE" : ".OpenSSL.whines.unless.I.set.this",
- "OPENSSL_CONF" : cfg_file }
-
- def run_openssl(self, *cmd, **kwargs):
- """
- Run an OpenSSL command, suppresses stderr unless OpenSSL returns
- failure, and returns stdout.
- """
- stdin = kwargs.pop("stdin", None)
- env = self.env.copy()
- env.update(kwargs)
- cmd = (self.openssl,) + cmd
- p = subprocess.Popen(cmd, env = env, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- stdout, stderr = p.communicate(stdin)
- if p.wait() != 0:
- sys.stderr.write("OpenSSL command failed: " + stderr + "\n")
- raise subprocess.CalledProcessError(returncode = p.returncode, cmd = cmd)
- return stdout
-
- def run_ca(self, *args):
- """
- Run OpenSSL "ca" command with common initial arguments.
- """
- self.run_openssl("ca", "-batch", "-config", self.cfg, *args)
-
- def run_req(self, key_file, req_file, log_key = sys.stdout):
- """
- Run OpenSSL "genrsa" and "req" commands.
- """
- if not os.path.exists(key_file):
- if log_key:
- log_key.write("Generating 2048-bit RSA key %s\n" % os.path.realpath(key_file))
- self.run_openssl("genrsa", "-out", key_file, "2048")
- if not os.path.exists(req_file):
- self.run_openssl("req", "-new", "-sha256", "-config", self.cfg, "-key", key_file, "-out", req_file)
+ For the moment we keep the BPKI directories, because we need some
+ place to write cert and key files for the daemons. Not yet sure
+ quite where this is going in the long run.
+ """
- def run_dgst(self, input, algorithm = "md5"):
- """
- Run OpenSSL "dgst" command, return cleaned-up result.
- """
- hash = self.run_openssl("dgst", "-" + algorithm, stdin = input)
- #
- # Twits just couldn't leave well enough alone, grr.
- hash = "".join(hash.split())
- if hash.startswith("(stdin)="):
- hash = hash[len("(stdin)="):]
- return hash
-
- @staticmethod
- def touch_file(filename, content = None):
- """
- Create dumb little text files expected by OpenSSL "ca" utility.
- """
- if not os.path.exists(filename):
- f = open(filename, "w")
- if content is not None:
- f.write(content)
- f.close()
+ def __init__(self, dir, identity, purpose):
+ self.dir = dir
+ self.cer = dir + "/ca.cer"
+ self.crl = dir + "/ca.crl"
+ self.identiy = identity
+ self.purpose = purpose
+ self.ca = None
def setup(self, ca_name):
"""
- Set up this CA. ca_name is an X.509 distinguished name in
- /tag=val/tag=val format.
+ Set up this CA. I no longer remember why this is not part of
+ __init__(). Maybe it will come back to me
"""
- modified = False
-
- if not os.path.exists(self.dir):
- os.makedirs(self.dir)
- self.touch_file(self.index)
- self.touch_file(self.serial, "01\n")
- self.touch_file(self.crlnum, "01\n")
-
- self.run_req(key_file = self.key, req_file = self.req)
+ self.ca = rpki.irdb.CA.objects.get_or_certify(
+ identity = self.identity,
+ purpose = self.purpose)[0]
- if not os.path.exists(self.cer):
- modified = True
- self.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-subj", ca_name, "-in", self.req, "-out", self.cer)
+ f = open(self.cer, "w")
+ f.write(self.ca.certificate.get_PEM())
+ f.close()
- if not os.path.exists(self.crl):
- modified = True
- self.run_ca("-gencrl", "-out", self.crl)
+ f = open(self.crl, "w")
+ f.write(self.ca.latest_crl.get_PEM())
+ f.close()
- return modified
-
- def ee(self, ee_name, base_name):
+ def ee(self, purpose):
"""
Issue an end-enity certificate.
"""
- key_file = "%s/%s.key" % (self.dir, base_name)
- req_file = "%s/%s.req" % (self.dir, base_name)
- cer_file = "%s/%s.cer" % (self.dir, base_name)
- self.run_req(key_file = key_file, req_file = req_file)
- if not os.path.exists(cer_file):
- self.run_ca("-extensions", "ca_x509_ext_ee", "-subj", ee_name, "-in", req_file, "-out", cer_file)
- return True
- else:
- return False
- def cms_xml_sign(self, ee_name, base_name, elt):
+ return rpki.irdb.EECertificate.objects.get_or_certify(
+ issuer = self.ca,
+ purpose = purpose)[0]
+
+ def cms_xml_sign(self, elt):
"""
Sign an XML object with CMS, return Base64 text.
"""
- self.ee(ee_name, base_name)
- return base64.b64encode(self.run_openssl(
- "cms", "-sign", "-binary", "-outform", "DER",
- "-keyid", "-md", "sha256", "-nodetach", "-nosmimecap",
- "-econtent_type", ".".join(str(i) for i in rpki.oids.name2oid["id-ct-xml"]),
- "-inkey", "%s/%s.key" % (self.dir, base_name),
- "-signer", "%s/%s.cer" % (self.dir, base_name),
- stdin = ElementToString(etree_pre_write(elt))))
+
+ ee = self.ee("referral")
+ return rpki.irdb.SignedReferral().wrap(
+ msg = elt,
+ keypair = ee.private_key,
+ certs = ee.certificate,
+ crls = self.ca.latest_crl)
def cms_xml_verify(self, b64, ca):
"""
Attempt to verify and extract XML from a Base64-encoded signed CMS
- object. CA is the filename of a certificate that we expect to be
- the issuer of the EE certificate bundled with the CMS, and must
- previously have been cross-certified under our trust anchor.
- """
- # In theory, we should be able to use the -certfile parameter to
- # pass in the CA certificate, but in practice, I have never gotten
- # this to work, either with the command line tool or in the
- # OpenSSL C API. Dunno why. Passing both TA and CA via -CAfile
- # does work, so we do that, using a temporary file, sigh.
- CAfile = os.path.join(self.dir, "temp.%s.pem" % os.getpid())
- try:
- f = open(CAfile, "w")
- f.write(open(self.cer).read())
- f.write(open(ca).read())
- f.close()
- return etree_post_read(ElementFromString(self.run_openssl(
- "cms", "-verify", "-inform", "DER", "-CAfile", CAfile,
- stdin = base64.b64decode(b64))))
- finally:
- if os.path.exists(CAfile):
- os.unlink(CAfile)
+ object.
- def bsc(self, pkcs10):
- """
- Issue BSC certificate, if we have a PKCS #10 request for it.
+ ca is a rpki.x509.X509 CA certificate which we expect to be the
+ issuer of the EE certificate bundled with the CMS; this CA
+ certificate must previously have been cross-certified under our
+ trust anchor.
"""
- if pkcs10 is None:
- return None, None
-
- pkcs10 = base64.b64decode(pkcs10)
+ return rpki.irdb.SignedReferral(Base64 = b64).unwrap(
+ ta = (ca, self.ca.certificate))
- hash = self.run_dgst(pkcs10)
+ def bsc(self, handle, pkcs10):
+ """
+ Issue BSC certificate, if we have a PKCS #10 request for it.
- req_file = "%s/bsc.%s.req" % (self.dir, hash)
- cer_file = "%s/bsc.%s.cer" % (self.dir, hash)
+ Returns IRDB BSC object, or None if we don't have one and can't
+ make one because we don't have the PKCS #10 yet.
+ """
- if not os.path.exists(cer_file):
- self.run_openssl("req", "-inform", "DER", "-out", req_file, stdin = pkcs10)
- self.run_ca("-extensions", "ca_x509_ext_ee", "-in", req_file, "-out", cer_file)
+ if pkcs10 is None:
+ return None
- return req_file, cer_file
+ return rpki.irdb.BSC.objects.get_or_certify(
+ issuer = self.ca,
+ handle = handle,
+ pkcs10 = rpki.x509.PKCS10(Base64 = pkcs10))[0]
- def fxcert(self, b64, filename = None, path_restriction = 0):
+ def fxcert(self, b64, handle, bpki_type, path_restriction = 0):
"""
Write PEM certificate to file, then cross-certify.
- """
- fn = os.path.join(self.dir, filename or "temp.%s.cer" % os.getpid())
- der = base64.b64decode(b64)
- if False:
- try:
- text = self.run_openssl("x509", "-inform", "DER", "-noout",
- "-issuer", "-subject", stdin = der)
- except:
- text = ""
- print "fxcert():", self.dir, filename, text
- try:
- self.run_openssl("x509", "-inform", "DER", "-out", fn,
- stdin = der)
- return self.xcert(fn, path_restriction)
- finally:
- if not filename and os.path.exists(fn):
- os.unlink(fn)
- def xcert_filename(self, cert):
+ This is the interface that almost everything uses for
+ cross-certification.
"""
- Generate filename for a cross-certification.
- Extracts public key and subject name from PEM file and hash it so
- we can use the result as a tag for cross-certifying this cert.
- """
+ fn = os.path.join(self.dir, "temp.%s.cer" % os.getpid())
- if cert and os.path.exists(cert):
- return "%s/xcert.%s.cer" % (self.dir, self.run_dgst(self.run_openssl(
- "x509", "-noout", "-pubkey", "-subject", "-in", cert)).strip())
- else:
- return None
+ try:
+ self.run_openssl("x509", "-inform", "DER", "-out", fn, stdin = base64.b64decode(b64))
+ return self.xcert(fn, handle, path_restriction)
- def xcert(self, cert, path_restriction = 0):
+ 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 = self.xcert_filename(cert)
+ 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])
+ self.run_ca("-ss_cert", cert, "-out", xcert, "-extensions",
+ self.path_restriction[path_restriction])
return xcert
- def xcert_revoke(self, cert):
- """
- Revoke a cross-certification and regenerate CRL.
- """
-
- xcert = self.xcert_filename(cert)
- if xcert:
- self.run_ca("-revoke", xcert)
- self.run_ca("-gencrl", "-out", self.crl)
-
-def etree_validate(e):
- rpki.relaxng.myrpki.assertValid(e)
+
def etree_write(e, filename, verbose = False, msg = None):
"""
@@ -852,6 +682,7 @@ def etree_write(e, filename, verbose = False, msg = None):
I still miss SYSCAL(RENMWO).
"""
+
filename = os.path.realpath(filename)
tempname = filename
if not filename.startswith("/dev/"):
@@ -870,13 +701,14 @@ 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():
if i.tag[0] != "{":
i.tag = namespaceQName + i.tag
assert i.tag.startswith(namespaceQName)
- etree_validate(e)
+ rpki.relaxng.myrpki.assertValid(e)
return e
def etree_read(filename, verbose = False):
@@ -884,6 +716,7 @@ def etree_read(filename, verbose = False):
Read an etree from a file, verifying then stripping XML namespace
cruft.
"""
+
if verbose:
print "Reading", filename
e = ElementTree(file = filename).getroot()
@@ -894,7 +727,8 @@ def etree_post_read(e):
Do the namespace frobbing needed on read; broken out of etree_read()
beause also needed by ElementFromString().
"""
- etree_validate(e)
+
+ rpki.relaxng.myrpki.assertValid(e)
for i in e.getiterator():
if i.tag.startswith(namespaceQName):
i.tag = i.tag[len(namespaceQName):]
@@ -902,12 +736,6 @@ def etree_post_read(e):
raise BadXMLMessage, "XML tag %r is not in namespace %r" % (i.tag, namespace)
return e
-def b64_equal(thing1, thing2):
- """
- Compare two Base64-encoded values for equality.
- """
- return "".join(thing1.split()) == "".join(thing2.split())
-
class IRDB(object):
@@ -915,6 +743,7 @@ 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
@@ -1018,6 +847,7 @@ class IRDB(object):
"""
Close the connection to the IRDB.
"""
+
self.db.close()
@@ -1058,6 +888,7 @@ class main(rpki.cli.Cmd):
do this using the textwrap module, but for now something simple
and crude will suffice.
"""
+
for line in __doc__.splitlines(True):
self.stdout.write(" " * 4 + line)
self.stdout.write("\n")
@@ -1066,6 +897,7 @@ class main(rpki.cli.Cmd):
"""
Completion helper for entitydb filenames.
"""
+
names = []
for name in self.entitydb.iterate(prefix):
name = os.path.splitext(os.path.basename(name))[0]
@@ -1075,6 +907,12 @@ class main(rpki.cli.Cmd):
def read_config(self):
+ # For reasons I don't understand, importing this at the global
+ # level isn't working properly today. Importing it here works
+ # fine. WTF?
+
+ import rpki.config
+
self.cfg = rpki.config.parser(self.cfg_file, "myrpki")
self.histfile = self.cfg.get("history_file", ".rpkic_history")
@@ -1082,14 +920,32 @@ class main(rpki.cli.Cmd):
self.run_rpkid = self.cfg.getboolean("run_rpkid")
self.run_pubd = self.cfg.getboolean("run_pubd")
self.run_rootd = self.cfg.getboolean("run_rootd")
+
+ from django.conf import settings
+
+ irdbd_section = "irdbd"
+
+ settings.configure(
+ DATABASES = { "default" : {
+ "ENGINE" : "django.db.backends.mysql",
+ "NAME" : self.cfg.get("sql-database", section = irdbd_section),
+ "USER" : self.cfg.get("sql-username", section = irdbd_section),
+ "PASSWORD" : self.cfg.get("sql-password", section = irdbd_section),
+ "HOST" : "",
+ "PORT" : "" }},
+ INSTALLED_APPS = ("rpki.irdb",),
+ )
+
+ 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.filename, self.cfg.get("bpki_resources_directory"))
+ 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.filename, self.cfg.get("bpki_servers_directory"))
+ self.bpki_servers = CA(self.cfg.get("bpki_servers_directory", "servers"))
else:
self.bpki_servers = None
@@ -1127,19 +983,14 @@ class main(rpki.cli.Cmd):
if self.run_rpkid or self.run_pubd or self.run_rootd:
if self.run_rpkid:
- self.bpki_servers.ee(self.cfg.get("bpki_rpkid_ee_dn",
- "/CN=%s rpkid server certificate" % self.handle), "rpkid")
- self.bpki_servers.ee(self.cfg.get("bpki_irdbd_ee_dn",
- "/CN=%s irdbd server certificate" % self.handle), "irdbd")
+ self.bpki_servers.ee("rpkid")
+ self.bpki_servers.ee("irdbd")
if self.run_pubd:
- self.bpki_servers.ee(self.cfg.get("bpki_pubd_ee_dn",
- "/CN=%s pubd server certificate" % self.handle), "pubd")
+ self.bpki_servers.ee("pubd")
if self.run_rpkid or self.run_pubd:
- self.bpki_servers.ee(self.cfg.get("bpki_irbe_ee_dn",
- "/CN=%s irbe client certificate" % self.handle), "irbe")
+ self.bpki_servers.ee("irbe")
if self.run_rootd:
- self.bpki_servers.ee(self.cfg.get("bpki_rootd_ee_dn",
- "/CN=%s rootd server certificate" % self.handle), "rootd")
+ self.bpki_servers.ee("rootd")
# Build the identity.xml file. Need to check for existing file so we don't
# overwrite? Worry about that later.
@@ -1267,7 +1118,9 @@ class main(rpki.cli.Cmd):
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(c.findtext("bpki_ta"))
+ self.bpki_servers.fxcert(b64 = c.findtext("bpki_ta"),
+ handle = child_handle,
+ bpki_type = rpki.irdb.Child)
e = Element("parent", parent_handle = self.handle, child_handle = child_handle,
service_uri = "%s/%s" % (service_uri_base, child_handle),
@@ -1295,8 +1148,7 @@ class main(rpki.cli.Cmd):
proposed_sia_base = repo.get("sia_base") + child_handle + "/"
r = Element("referral", authorized_sia_base = proposed_sia_base)
r.text = c.findtext("bpki_ta")
- auth = self.bpki_resources.cms_xml_sign(
- "/CN=%s Publication Referral" % self.handle, "referral", r)
+ auth = self.bpki_resources.cms_xml_sign(r)
r = SubElement(e, "repository", type = "referral")
SubElement(r, "authorization", referrer = repo.get("client_handle")).text = auth
@@ -1353,7 +1205,9 @@ class main(rpki.cli.Cmd):
print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle)
print "Parent calls us %r" % p.get("child_handle")
- self.bpki_resources.fxcert(p.findtext("bpki_resource_ta"))
+ self.bpki_resources.fxcert(b64 = p.findtext("bpki_resource_ta"),
+ handle = parent_handle,
+ bpki_type = rpki.irdb.Parent)
etree_write(p, self.entitydb("parents", parent_handle))
@@ -1407,7 +1261,19 @@ class main(rpki.cli.Cmd):
client = etree_read(argv[0])
- if sia_base is None and client.get("handle") == self.handle and b64_equal(PEMBase64(self.bpki_resources.cer), client.findtext("bpki_client_ta")):
+ # 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")):
print "This looks like self-hosted publication"
sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, self.handle)
@@ -1418,9 +1284,9 @@ class main(rpki.cli.Cmd):
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(referrer.findtext("bpki_client_ta"))
+ referrer = self.bpki_servers.fxcert(b64 = referrer.findtext("bpki_client_ta"))
referral = self.bpki_servers.cms_xml_verify(auth.text, referrer)
- if not b64_equal(referral.text, client.findtext("bpki_client_ta")):
+ if rpki.x509.X509(Base64 = referral.text) != rpki.x509.X509(Base64 = client.findtext("bpki_client_ta")):
raise BadXMLMessage, "Referral trust anchor does not match"
sia_base = referral.get("authorized_sia_base")
except IOError:
@@ -1433,7 +1299,7 @@ class main(rpki.cli.Cmd):
raise BadXMLMessage, "Malformed offer, couldn't find <bpki_client_ta/> element"
for child in self.entitydb.iterate("children"):
c = etree_read(child)
- if b64_equal(c.findtext("bpki_child_ta"), client_ta):
+ 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
@@ -1455,7 +1321,9 @@ 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(client.findtext("bpki_client_ta"))
+ self.bpki_servers.fxcert(b64 = client.findtext("bpki_client_ta"),
+ handle = client_handle,
+ bpki_type = bpki.irdb.Client)
e = Element("repository", type = "confirmed",
client_handle = client_handle,
@@ -1576,6 +1444,7 @@ class main(rpki.cli.Cmd):
"""
Update validity period for one child entity.
"""
+
return self.renew_children_common(arg, False)
def complete_renew_child(self, *args):
@@ -1585,6 +1454,7 @@ class main(rpki.cli.Cmd):
"""
Update validity period for all child entities.
"""
+
return self.renew_children_common(arg, True)
@@ -1606,10 +1476,10 @@ class main(rpki.cli.Cmd):
try:
e = etree_read(xml_filename)
- bsc_req, bsc_cer = self.bpki_resources.bsc(e.findtext("bpki_bsc_pkcs10"))
+ bsc = self.bpki_resources.bsc(e.findtext("bpki_bsc_pkcs10"))
service_uri = e.get("service_uri")
except IOError:
- bsc_req, bsc_cer = None, None
+ bsc = None
service_uri = None
e = Element("myrpki", handle = self.handle)
@@ -1636,11 +1506,9 @@ class main(rpki.cli.Cmd):
PEMElement(e, "bpki_ca_certificate", self.bpki_resources.cer)
PEMElement(e, "bpki_crl", self.bpki_resources.crl)
- if bsc_cer:
- PEMElement(e, "bpki_bsc_certificate", bsc_cer)
-
- if bsc_req:
- PEMElement(e, "bpki_bsc_pkcs10", bsc_req)
+ if bsc is not None:
+ PEMElement(e, "bpki_bsc_certificate", bsc.certificate)
+ PEMElement(e, "bpki_bsc_pkcs10", bsc.pkcs10)
etree_write(e, xml_filename, msg = msg)
@@ -1765,17 +1633,19 @@ class main(rpki.cli.Cmd):
# Update IRDB with parsed resource and roa-request data.
- roa_requests = [(
- x.get('asn'),
- rpki.resource_set.roa_prefix_set_ipv4(x.get("v4")),
- rpki.resource_set.roa_prefix_set_ipv6(x.get("v6"))) for x in tree.getiterator("roa_request")]
+ roa_requests = [
+ (x.get('asn'),
+ rpki.resource_set.roa_prefix_set_ipv4(x.get("v4")),
+ rpki.resource_set.roa_prefix_set_ipv6(x.get("v6")))
+ for x in tree.getiterator("roa_request") ]
- children = [(
- x.get("handle"),
- rpki.resource_set.resource_set_as(x.get("asns")),
- rpki.resource_set.resource_set_ipv4(x.get("v4")),
- rpki.resource_set.resource_set_ipv6(x.get("v6")),
- rpki.sundial.datetime.fromXMLtime(x.get("valid_until"))) for x in tree.getiterator("child")]
+ children = [
+ (x.get("handle"),
+ rpki.resource_set.resource_set_as(x.get("asns")),
+ rpki.resource_set.resource_set_ipv4(x.get("v4")),
+ rpki.resource_set.resource_set_ipv6(x.get("v6")),
+ rpki.sundial.datetime.fromXMLtime(x.get("valid_until")))
+ for x in tree.getiterator("child") ]
# ghostbusters are ignored for now
irdb.update(handle, roa_requests, children)
@@ -1787,9 +1657,9 @@ class main(rpki.cli.Cmd):
print "Nothing else I can do without a trust anchor for the entity I'm hosting."
continue
- rpkid_xcert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(b64 = hosted_cacert.get_Base64(),
- #filename = handle + ".cacert.cer",
- path_restriction = 1))
+ rpkid_xcert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(
+ b64 = hosted_cacert.get_Base64(),
+ path_restriction = 1))
# See what rpkid and pubd already have on file for this entity.
@@ -1962,7 +1832,10 @@ class main(rpki.cli.Cmd):
client_handle = c.get("client_handle")
client_base_uri = c.get("sia_base")
- client_bpki_cert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(c.findtext("bpki_client_ta")))
+ client_bpki_cert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(
+ b64 = c.findtext("bpki_client_ta"),
+ handle = client_handle,
+ bpki_type = rpki.irdb.Client))
client_pdu = client_pdus.pop(client_handle, None)
if (client_pdu is None or
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 29470c31..abdd9194 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -1385,7 +1385,10 @@ class XML_CMS_object(CMS_object):
Wrap an XML PDU in CMS and return its DER encoding.
"""
rpki.log.trace()
- self.set_content(msg.toXML())
+ if self.saxify is None:
+ self.set_content(msg)
+ else:
+ self.set_content(msg.toXML())
self.schema_check()
self.sign(keypair, certs, crls)
if self.dump_outbound_cms:
@@ -1400,14 +1403,17 @@ class XML_CMS_object(CMS_object):
self.dump_inbound_cms.dump(self)
self.verify(ta)
self.schema_check()
- return self.saxify(self.get_content())
+ if self.saxify is None:
+ return self.get_content()
+ else:
+ return self.saxify(self.get_content())
## @var saxify
# SAX handler hook. Subclasses can set this to a SAX handler, in
# which case .unwrap() will call it and return the result.
# Otherwise, .unwrap() just returns a verified element tree.
- saxify = staticmethod(lambda x: x)
+ saxify = None
class Ghostbuster(CMS_object):
"""
diff --git a/rpkid/rpkic.py b/rpkid/rpkic.py
index d8f68627..6ef3a67b 100644
--- a/rpkid/rpkic.py
+++ b/rpkid/rpkic.py
@@ -1,7 +1,7 @@
"""
$Id$
-Copyright (C) 2010 Internet Systems Consortium ("ISC")
+Copyright (C) 2010-2011 Internet Systems Consortium ("ISC")
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,5 +17,5 @@ PERFORMANCE OF THIS SOFTWARE.
"""
if __name__ == "__main__":
- import rpki.myrpki
- rpki.myrpki.main()
+ import rpki.rpkic
+ rpki.rpkic.main()