aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpkid/rpki/irdb/models.py102
-rw-r--r--rpkid/rpki/x509.py104
-rw-r--r--scripts/convert-from-entitydb-to-sql.py22
3 files changed, 167 insertions, 61 deletions
diff --git a/rpkid/rpki/irdb/models.py b/rpkid/rpki/irdb/models.py
index e10a9122..a9bd364f 100644
--- a/rpkid/rpki/irdb/models.py
+++ b/rpkid/rpki/irdb/models.py
@@ -85,8 +85,6 @@ class CA(django.db.models.Model):
identity = django.db.models.ForeignKey(Identity, related_name = "bpki_certificates")
purpose_map = ChoiceMap("resources", "servers")
purpose = django.db.models.PositiveSmallIntegerField(choices = purpose_map.choices)
- certificate = BinaryField()
- private_key = BinaryField()
next_serial = django.db.models.BigIntegerField(default = 1)
next_crl_number = django.db.models.BigIntegerField(default = 1)
last_crl_update = django.db.models.DateTimeField()
@@ -96,21 +94,60 @@ class CA(django.db.models.Model):
unique_together = ("identity", "purpose")
class Certificate(django.db.models.Model):
- issuer = django.db.models.ForeignKey(CA)
certificate = BinaryField()
- # We used to use multi-table inheritance here, but that turns out
- # not to work so well once we started applying uniqueness
- # constraints. So now we use an abstract base class.
- #
- # This is probably the right approach for data fields, so that we
- # can share custom model methods for things like certificate
- # issuance, but is a bit tricky for foreign keys due to the
- # "related_name" reverse link. See:
- # https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance
+ class Meta:
+ abstract = True
+
+ def get_cert(self):
+ return rpki.x509.X509(DER = self.certificate)
+
+ def generate_certificate(self):
+
+ # This is sort of vaguely the right idea, but most of it probably
+ # ought to be a method of the CA, not the object being certified,
+ # and the rest doesn't yet cover all the different kinds of
+ # certificates we need to support.
+
+ # Perhaps something where each specialized class has its own
+ # generate_certificate() method which is mostly just a wrapper for
+ # a call to the CA object with the right parameters to get the CA
+ # to issue the certificate. Seems about right. Not awake enough
+ # to write it now.
+
+ cacert = self.issuer.keyed_certificates.filter(purpose = KeyedCertificate.purpose_map["ca"])
+ subject_name, subject_key = self.get_certificate_subject()
+ cer = cacert.get_cert()
+ key = cacert.get_key()
+
+ result = cer.bpki_certify(key, subject_name, subject_key, ca.next_serial,
+
+ # This needs to be configurable
+ rpki.sundial.now() + rpki.sundial.timedelta(days = 60),
+
+ # This is (at least) per-class, not universal
+ pathLenConstraint = 0,
+
+ # This is per-class too
+ ca = True)
+
+ self.ca.next_serial += 1
+
+ self.certificate = result
+
+
+
+class CrossCertification(Certificate):
+ handle = HandleField()
+ ta = BinaryField()
class Meta:
abstract = True
+ unique_together = ("issuer", "handle")
+
+ def get_certificate_subject(self):
+ ta = rpki.x509.X509(DER = self.ta)
+ return ta.getSubject(), ta.getPublicKey()
class Revocation(django.db.models.Model):
issuer = django.db.models.ForeignKey(CA, related_name = "revocations")
@@ -121,29 +158,30 @@ class Revocation(django.db.models.Model):
class Meta:
unique_together = ("issuer", "serial")
-class EECertificate(Certificate):
- purpose_map = ChoiceMap("rpkid", "pubd", "irdbd", "irbe", "rootd")
+class KeyedCertificate(Certificate):
+ issuer = django.db.models.ForeignKey(CA, related_name = "keyed_certificates")
+ purpose_map = ChoiceMap("ca", "rpkid", "pubd", "irdbd", "irbe", "rootd")
purpose = django.db.models.PositiveSmallIntegerField(choices = purpose_map.choices)
private_key = BinaryField()
class Meta:
unique_together = ("issuer", "purpose")
+ def get_key(self):
+ return rpki.x509.RSA(DER = self.private_key)
+
class BSC(Certificate):
+ issuer = django.db.models.ForeignKey(CA, related_name = "bscs")
handle = HandleField()
pkcs10 = BinaryField()
class Meta:
unique_together = ("issuer", "handle")
-class Child(Certificate):
- handle = HandleField()
+class Child(CrossCertification):
+ issuer = django.db.models.ForeignKey(CA, related_name = "children")
name = django.db.models.TextField(null = True, blank = True)
valid_until = django.db.models.DateTimeField()
- ta = BinaryField()
-
- class Meta:
- unique_together = ("issuer", "handle")
class ChildASN(django.db.models.Model):
child = django.db.models.ForeignKey(Child, related_name = "asns")
@@ -163,20 +201,16 @@ class ChildNet(django.db.models.Model):
class Meta:
unique_together = ("child", "start_ip", "end_ip", "version")
-class Parent(Certificate):
- handle = HandleField()
+class Parent(CrossCertification):
+ issuer = django.db.models.ForeignKey(CA, related_name = "parents")
parent_handle = HandleField()
child_handle = HandleField()
- ta = BinaryField()
service_uri = django.db.models.CharField(max_length = 255)
repository_type_map = ChoiceMap("none", "offer", "referral")
repository_type = django.db.models.PositiveSmallIntegerField(choices = repository_type_map.choices)
referrer = HandleField(null = True, blank = True)
referral_authorization = BinaryField(null = True, blank = True)
- class Meta:
- unique_together = ("issuer", "handle")
-
class ROARequest(django.db.models.Model):
identity = django.db.models.ForeignKey(Identity, related_name = "roa_requests")
asn = django.db.models.BigIntegerField()
@@ -197,20 +231,12 @@ class GhostbusterRequest(django.db.models.Model):
parent = django.db.models.ForeignKey(Parent, related_name = "ghostbuster_requests", null = True)
vcard = django.db.models.TextField()
-class Repository(Certificate):
- handle = HandleField()
+class Repository(CrossCertification):
+ issuer = django.db.models.ForeignKey(CA, related_name = "repositories")
client_handle = HandleField()
- ta = BinaryField()
service_uri = django.db.models.CharField(max_length = 255)
sia_base = django.db.models.TextField()
parent = django.db.models.OneToOneField(Parent, related_name = "repository")
- class Meta:
- unique_together = ("issuer", "handle")
-
-class Client(Certificate):
- handle = HandleField()
- ta = BinaryField()
-
- class Meta:
- unique_together = ("issuer", "handle")
+class Client(CrossCertification):
+ issuer = django.db.models.ForeignKey(CA, related_name = "clients")
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 58129363..2ac9ffca 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -120,6 +120,74 @@ def _find_xia_uri(extension, name):
return location[1]
return None
+class X501DN(object):
+ """
+ Class to hold an X.501 Distinguished Name.
+
+ This is nothing like a complete implementation, just enough for our
+ purposes. POW has one interface to this, POW.pkix has another. In
+ terms of completeness in the Python representation, the POW.pkix
+ representation is much closer to right, but the whole thing is a
+ horrible mess.
+
+ See RFC 5280 4.1.2.4 for the ASN.1 details. In brief:
+
+ - A DN is a SEQUENCE of RDNs.
+
+ - A RDN is a set of AttributeAndValues; in practice, multi-value
+ RDNs are rare, so an RDN is almost always a set with a single
+ element.
+
+ - An AttributeAndValue is an OID and a value, where a whole bunch
+ of things including both syntax and semantics of the value are
+ determined by the OID.
+
+ - The value is some kind of ASN.1 string; there are far too many
+ encoding options options, most of which are either strongly
+ discouraged or outright forbidden by the PKIX profile, but which
+ persist for historical reasons. The only ones PKIX actually
+ likes are PrintableString and UTF8String, but there are nuances
+ and special cases where some of the others are required.
+
+ The RPKI profile further restricts DNs to a single mandatory
+ CommonName attribute with a single optional SerialNumber attribute
+ (not to be confused with the certificate serial number).
+
+ BPKI certificates should (we hope) follow the general PKIX guideline
+ but the ones we construct ourselves are likely to be relatively
+ simple.
+
+ The main purpose of this class is to hide as much as possible of
+ this mess from code that has to work with these wretched things.
+ """
+
+ def __init__(self, ini = None, **kwargs):
+ assert ini is None or not kwargs
+ if len(kwargs) == 1 and "CN" in kwargs:
+ ini = kwargs.pop("CN")
+ if isinstance(ini, str):
+ self.dn = (((rpki.oids.name2oid["commonName"], ("printableString", cn)),),)
+ elif isinstance(ini, tuple):
+ self.dn = ini
+ elif kwargs:
+ raise NotImplementedError #("Sorry, I haven't implemented keyword arguments yet")
+ elif ini is not None:
+ raise TypeError #("Don't know how to interpret %r as an X.501 DN" % (ini,), ini)
+
+ def __str__(self):
+ return "".join("/" + "+".join("%s=%s" % (rpki.oids.oid2name[a[0]], a[1][1])
+ for a in rdn)
+ for rdn in self.dn)
+
+ def __cmp__(self, other):
+ return cmp(self.dn, other.dn)
+
+ def get_POWpkix(self):
+ return self.dn
+
+ def get_POW(self):
+ raise NotImplementedError #("Sorry, I haven't written the conversion to POW format yet")
+
class DER_object(object):
"""
Virtual class to hold a generic DER object.
@@ -456,13 +524,13 @@ class X509(DER_object):
"""
Get the issuer of this certificate.
"""
- return "".join("/%s=%s" % rdn for rdn in self.get_POW().getIssuer())
+ return X501DN(self.get_POWpkix().getIssuer())
def getSubject(self):
"""
Get the subject of this certificate.
"""
- return "".join("/%s=%s" % rdn for rdn in self.get_POW().getSubject())
+ return X501DN(self.get_POWpkix().getSubject())
def getNotBefore(self):
"""
@@ -560,25 +628,39 @@ class X509(DER_object):
Issue a certificate with values taking from an existing certificate.
This is used to construct some kinds oF BPKI certificates.
"""
+ return self.bpki_certify(keypair, source_cert.getSubject(), source_cert.getPublicKey(),
+ serial, notAfter, now, pathLenConstraint, ca = True)
+
+ def bpki_certify(self, keypair, subject_name, subject_key, serial, notAfter,
+ now = None, pathLenConstraint = None, ca = False):
+ """
+ Issue a BPKI certificate.
+ """
if now is None:
now = rpki.sundial.now()
- assert isinstance(pathLenConstraint, int) and pathLenConstraint >= 0
+ assert pathLenConstraint is None or (isinstance(pathLenConstraint, (int, long)) and
+ pathLenConstraint >= 0)
+
+ extensions = [
+ (rpki.oids.name2oid["subjectKeyIdentifier" ], False, subject_key.get_SKI())]
+ if not ca or self.getSubject() != subject_name or self.getPublicKey() != subject_key:
+ extensions.append(
+ (rpki.oids.name2oid["authorityKeyIdentifier"], False, (self.get_SKI(), (), None)))
+ if ca:
+ extensions.append(
+ (rpki.oids.name2oid["basicConstraints" ], True, (1, pathLenConstraint)))
cert = rpki.POW.pkix.Certificate()
cert.setVersion(2)
cert.setSerial(serial)
cert.setIssuer(self.get_POWpkix().getSubject())
- cert.setSubject(source_cert.get_POWpkix().getSubject())
+ cert.setSubject(subject_name.get_POWpkix())
cert.setNotBefore(now.toASN1tuple())
cert.setNotAfter(notAfter.toASN1tuple())
- cert.tbs.subjectPublicKeyInfo.set(
- source_cert.get_POWpkix().tbs.subjectPublicKeyInfo.get())
- cert.setExtensions((
- (rpki.oids.name2oid["subjectKeyIdentifier" ], False, source_cert.get_SKI()),
- (rpki.oids.name2oid["authorityKeyIdentifier"], False, (self.get_SKI(), (), None)),
- (rpki.oids.name2oid["basicConstraints" ], True, (1, 0))))
+ cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
+ cert.setExtensions(extensions)
cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
return X509(POWpkix = cert)
@@ -1357,7 +1439,7 @@ class CRL(DER_object):
"""
Get issuer value of this CRL.
"""
- return "".join("/%s=%s" % rdn for rdn in self.get_POW().getIssuer())
+ return X501DN(self.get_POWpkix().getIssuer())
@classmethod
def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
diff --git a/scripts/convert-from-entitydb-to-sql.py b/scripts/convert-from-entitydb-to-sql.py
index 9de2edf2..c8a85620 100644
--- a/scripts/convert-from-entitydb-to-sql.py
+++ b/scripts/convert-from-entitydb-to-sql.py
@@ -113,33 +113,30 @@ def read_openssl_serial(filename):
return int(text.strip(), 16)
def get_or_create_CA(purpose):
- cer = rpki.x509.X509(Auto_file = os.path.join(bpki, purpose, "ca.cer"))
- key = rpki.x509.RSA(Auto_file = os.path.join(bpki, purpose, "ca.key"))
crl = rpki.x509.CRL(Auto_file = os.path.join(bpki, purpose, "ca.crl"))
serial = read_openssl_serial(os.path.join(bpki, purpose, "serial"))
crl_number = read_openssl_serial(os.path.join(bpki, purpose, "crl_number"))
return rpki.irdb.CA.objects.get_or_create(identity = identity,
purpose = rpki.irdb.CA.purpose_map[purpose],
- certificate = cer.get_DER(),
- private_key = key.get_DER(),
next_serial = serial,
next_crl_number = crl_number,
last_crl_update = crl.getThisUpdate().to_sql(),
next_crl_update = crl.getNextUpdate().to_sql())[0]
-def get_or_create_EECertificate(issuer, purpose):
+def get_or_create_KeyedCertificate(issuer, purpose):
cer = rpki.x509.X509(Auto_file = os.path.join(bpki, "servers", purpose + ".cer"))
key = rpki.x509.RSA(Auto_file = os.path.join(bpki, "servers", purpose + ".key"))
- rpki.irdb.EECertificate.objects.get_or_create(
+ rpki.irdb.KeyedCertificate.objects.get_or_create(
issuer = issuer,
- purpose = rpki.irdb.EECertificate.purpose_map[purpose],
+ purpose = rpki.irdb.KeyedCertificate.purpose_map[purpose],
certificate = cer.get_DER(),
private_key = key.get_DER())
# Load BPKI CA data
resource_ca = get_or_create_CA("resources")
+get_or_create_KeyedCertificate(resource_ca, "ca")
# Load BPKI server EE certificates and keys
@@ -148,14 +145,15 @@ run_flags = dict((i, cfg.getboolean(i, section = "myrpki"))
if any(run_flags.itervalues()):
server_ca = get_or_create_CA("servers")
- get_or_create_EECertificate(server_ca, "irbe")
+ get_or_create_KeyedCertificate(server_ca, "ca")
+ get_or_create_KeyedCertificate(server_ca, "irbe")
if run_flags["run_rpkid"]:
- get_or_create_EECertificate(server_ca, "rpkid")
- get_or_create_EECertificate(server_ca, "irdbd")
+ get_or_create_KeyedCertificate(server_ca, "rpkid")
+ get_or_create_KeyedCertificate(server_ca, "irdbd")
if run_flags["run_pubd"]:
- get_or_create_EECertificate(server_ca, "pubd")
+ get_or_create_KeyedCertificate(server_ca, "pubd")
if run_flags["run_rootd"]:
- get_or_create_EECertificate(server_ca, "rootd")
+ get_or_create_KeyedCertificate(server_ca, "rootd")
else:
server_ca = None