aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2011-12-12 21:07:56 +0000
committerRob Austein <sra@hactrn.net>2011-12-12 21:07:56 +0000
commit63676e02d7e58487cb0794659de6602168e36e90 (patch)
tree3c40faec096fe239a14d5a0348a3801523b5bc9c
parent9151b67621f017e63a046b872dee1008dff6da5a (diff)
Checkpoint. Custom IRDB model fields to handle automatic type
conversion. Debug last night's rewrite of BPKI certificate issuance. svn path=/branches/tk100/; revision=4117
-rw-r--r--rpkid/rpki/irdb/models.py119
-rw-r--r--rpkid/rpki/x509.py77
-rw-r--r--rpkid/tests/smoketest.py7
-rw-r--r--scripts/convert-from-entitydb-to-sql.py31
4 files changed, 176 insertions, 58 deletions
diff --git a/rpkid/rpki/irdb/models.py b/rpkid/rpki/irdb/models.py
index a9bd364f..3a898d8d 100644
--- a/rpkid/rpki/irdb/models.py
+++ b/rpkid/rpki/irdb/models.py
@@ -23,6 +23,7 @@ PERFORMANCE OF THIS SOFTWARE.
"""
import django.db.models
+import rpki.x509
###
@@ -49,14 +50,73 @@ class HandleField(django.db.models.CharField):
kwargs["max_length"] = 120
django.db.models.CharField.__init__(self, *args, **kwargs)
-class BinaryField(django.db.models.Field):
+class DERField(django.db.models.Field):
"""
- A raw binary field type for Django models. Yes, I know this is
- wrong, but breaking out all the ASN.1 isn't practical, and encoding
- binary data as Base64 text doesn't seem much of an improvement.
+ A field type for DER objects.
+
+ This is an abstract class, subclasses need to define rpki_type.
"""
- description = "Raw binary data"
+ description = "DER object"
+
+ __metaclass__ = django.db.models.SubfieldBase
+
+ def __init__(self, *args, **kwargs):
+ kwargs["serialize"] = False
+ kwargs["blank"] = True
+ django.db.models.Field.__init__(self, *args, **kwargs)
+
+ def db_type(self, connection):
+ if connection.settings_dict['ENGINE'] == "django.db.backends.posgresql":
+ return "bytea"
+ else:
+ return "BLOB"
+
+ def to_python(self, value):
+ if isinstance(value, self.rpki_type):
+ return value
+ else:
+ assert isinstance(value, str)
+ return self.rpki_type(DER = value)
+
+ def get_prep_value(self, value):
+ if isinstance(value, self.rpki_type):
+ return value.get_DER()
+ elif 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))
+
+class CertificateField(DERField):
+ description = "X.509 certificate"
+ rpki_type = rpki.x509.X509
+
+class RSAKeyField(DERField):
+ description = "RSA keypair"
+ rpki_type = rpki.x509.RSA
+
+class PKCS10Field(DERField):
+ description = "PKCS #10 certificate request"
+ rpki_type = rpki.x509.PKCS10
+
+class SignedReferralField(django.db.models.Field):
+ description = "CMS signed object containing XML"
+
+ # This should be another subclass of DERField, but we don't have a
+ # suitable subclass of XML_CMS_object yet, in part because the XML
+ # schema we'd need to validate is really just a fragment of another
+ # schema. Maybe. Anyway, subclassing DERField here doesn't work
+ # properly yet, so for the moment this is opaque binary data.
+ #
+ # Fix later.
+
+ def __init__(self, *args, **kwargs):
+ kwargs["serialize"] = False
+ kwargs["blank"] = True
+ django.db.models.Field.__init__(self, *args, **kwargs)
def db_type(self, connection):
if connection.settings_dict['ENGINE'] == "django.db.backends.posgresql":
@@ -94,14 +154,12 @@ class CA(django.db.models.Model):
unique_together = ("identity", "purpose")
class Certificate(django.db.models.Model):
- certificate = BinaryField()
+
+ certificate = CertificateField()
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
@@ -115,21 +173,30 @@ class Certificate(django.db.models.Model):
# to issue the certificate. Seems about right. Not awake enough
# to write it now.
+ # This doesn't handle self-certification yet either.
+ # Self-certification may be different enough that we want to move
+ # the certificate and keypair back to the CA object, since we have
+ # to special-case it no matter what we do.
+
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()
+ cer = cacert.certificate
+ key = cacert.private_key
- result = cer.bpki_certify(key, subject_name, subject_key, ca.next_serial,
+ result = cer.bpki_certify(
+ keypair = key,
+ subject_name = subject_name,
+ subject_key = subject_key,
+ serial = ca.next_serial,
+
+ # This needs to be configurable
+ notAfter = rpki.sundial.now() + rpki.sundial.timedelta(days = 60),
- # 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 (at least) per-class, not universal
- pathLenConstraint = 0,
-
- # This is per-class too
- ca = True)
+ # This is per-class too
+ is_ca = True)
self.ca.next_serial += 1
@@ -139,15 +206,14 @@ class Certificate(django.db.models.Model):
class CrossCertification(Certificate):
handle = HandleField()
- ta = BinaryField()
+ ta = CertificateField()
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()
+ return self.certificate.getSubject(), self.certificate.getPublicKey()
class Revocation(django.db.models.Model):
issuer = django.db.models.ForeignKey(CA, related_name = "revocations")
@@ -162,18 +228,15 @@ 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()
+ private_key = RSAKeyField()
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()
+ pkcs10 = PKCS10Field()
class Meta:
unique_together = ("issuer", "handle")
@@ -209,7 +272,7 @@ class Parent(CrossCertification):
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)
+ referral_authorization = SignedReferralField(null = True, blank = True)
class ROARequest(django.db.models.Model):
identity = django.db.models.ForeignKey(Identity, related_name = "roa_requests")
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 2ac9ffca..2011b416 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -327,6 +327,8 @@ class DER_object(object):
return -1
elif other is None:
return 1
+ elif isinstance(other, str):
+ return cmp(self.get_DER(), other)
else:
return cmp(self.get_DER(), other.get_DER())
@@ -623,39 +625,88 @@ class X509(DER_object):
return X509(POWpkix = cert)
- def cross_certify(self, keypair, source_cert, serial, notAfter, now = None, pathLenConstraint = 0):
+ def bpki_cross_certify(self, keypair, source_cert, serial, notAfter,
+ now = None, pathLenConstraint = 0):
"""
- Issue a certificate with values taking from an existing certificate.
- This is used to construct some kinds oF BPKI certificates.
+ Issue a BPKI certificate with values taking from an existing certificate.
"""
- return self.bpki_certify(keypair, source_cert.getSubject(), source_cert.getPublicKey(),
- serial, notAfter, now, pathLenConstraint, ca = True)
+ return self.bpki_certify(
+ keypair = keypair,
+ subject_name = source_cert.getSubject(),
+ subject_key = source_cert.getPublicKey(),
+ serial = serial,
+ notAfter = notAfter,
+ now = now,
+ pathLenConstraint = pathLenConstraint,
+ is_ca = True)
- def bpki_certify(self, keypair, subject_name, subject_key, serial, notAfter,
- now = None, pathLenConstraint = None, ca = False):
+ @classmethod
+ def bpki_self_certify(cls, keypair, subject_name, serial, notAfter,
+ now = None, pathLenConstraint = None):
+ """
+ Issue a self-signed BPKI CA certificate.
+ """
+ return cls._bpki_certify(
+ keypair = keypair,
+ issuer_name = subject_name,
+ subject_name = subject_name,
+ subject_key = keypair.get_RSApublic(),
+ serial = serial,
+ now = now,
+ notAfter = notAfter,
+ pathLenConstraint = pathLenConstraint,
+ is_ca = True)
+
+ def bpki_certify(self, keypair, subject_name, subject_key, serial, notAfter, is_ca,
+ now = None, pathLenConstraint = None):
+ """
+ Issue a normal BPKI certificate.
+ """
+ assert keypair.get_RSApublic() == self.getPublicKey()
+ return self._bpki_certify(
+ keypair = keypair,
+ issuer_name = self.getSubject(),
+ subject_name = subject_name,
+ subject_key = subject_key,
+ serial = serial,
+ now = now,
+ notAfter = notAfter,
+ pathLenConstraint = pathLenConstraint,
+ is_ca = is_ca)
+
+ @classmethod
+ def _bpki_certify(cls, keypair, issuer_name, subject_name, subject_key,
+ serial, now, notAfter, pathLenConstraint, is_ca):
"""
- Issue a BPKI certificate.
+ Issue a BPKI certificate. This internal method does the real
+ work, after one of the wrapper methods has extracted the relevant
+ fields.
"""
if now is None:
now = rpki.sundial.now()
+ issuer_key = keypair.get_RSApublic()
+
+ assert (issuer_key == subject_key) == (issuer_name == subject_name)
+ assert is_ca or issuer_name != subject_name
+ assert is_ca or pathLenConstraint is None
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:
+ if issuer_key != subject_key:
extensions.append(
- (rpki.oids.name2oid["authorityKeyIdentifier"], False, (self.get_SKI(), (), None)))
- if ca:
+ (rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer_key.get_SKI(), (), None)))
+ if is_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.setIssuer(issuer_name.get_POWpkix())
cert.setSubject(subject_name.get_POWpkix())
cert.setNotBefore(now.toASN1tuple())
cert.setNotAfter(notAfter.toASN1tuple())
@@ -663,7 +714,7 @@ class X509(DER_object):
cert.setExtensions(extensions)
cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
- return X509(POWpkix = cert)
+ return cls(POWpkix = cert)
@classmethod
def normalize_chain(cls, chain):
diff --git a/rpkid/tests/smoketest.py b/rpkid/tests/smoketest.py
index 8145a0eb..189f6d6a 100644
--- a/rpkid/tests/smoketest.py
+++ b/rpkid/tests/smoketest.py
@@ -868,7 +868,12 @@ class allocation(object):
except IOError:
serial = 1
- x = parent.cross_certify(keypair, child, serial, notAfter, now)
+ x = parent.bpki_cross_certify(
+ keypair = keypair,
+ source_cert = child,
+ serial = serial,
+ notAfter = notAfter,
+ now = now)
f = open(serial_file, "w")
f.write("%02x\n" % (serial + 1))
diff --git a/scripts/convert-from-entitydb-to-sql.py b/scripts/convert-from-entitydb-to-sql.py
index c8a85620..8885893b 100644
--- a/scripts/convert-from-entitydb-to-sql.py
+++ b/scripts/convert-from-entitydb-to-sql.py
@@ -130,8 +130,8 @@ def get_or_create_KeyedCertificate(issuer, purpose):
rpki.irdb.KeyedCertificate.objects.get_or_create(
issuer = issuer,
purpose = rpki.irdb.KeyedCertificate.purpose_map[purpose],
- certificate = cer.get_DER(),
- private_key = key.get_DER())
+ certificate = cer,
+ private_key = key)
# Load BPKI CA data
@@ -157,16 +157,15 @@ if any(run_flags.itervalues()):
else:
server_ca = None
-# Load BSC certificates and requests
+# Load BSC certificates and requests. Yes, this currently wires in
+# exactly one BSC handle, "bsc". So does the old myrpki code. Ick.
for fn in glob.iglob(os.path.join(bpki, "resources", "bsc.*.cer")):
- cer = rpki.x509.X509(Auto_file = fn)
- req = rpki.x509.X509(Auto_file = fn[:-4] + ".req")
rpki.irdb.BSC.objects.get_or_create(
issuer = resource_ca,
- certificate = cer.get_DER(),
- pkcs10 = req.get_DER())
-
+ handle = "bsc",
+ certificate = rpki.x509.X509(Auto_file = fn),
+ pkcs10 = rpki.x509.PKCS10(Auto_file = fn[:-4] + ".req"))
def xcert_hash(cert):
"""
@@ -243,8 +242,8 @@ for filename in glob.iglob(os.path.join(entitydb, "children", "*.xml")):
child = rpki.irdb.Child.objects.get_or_create(
handle = child_handle,
valid_until = valid_until.to_sql(),
- ta = ta.get_DER(),
- certificate = xcert.get_DER(),
+ ta = ta,
+ certificate = xcert,
issuer = resource_ca)[0]
cur.execute("""
@@ -294,8 +293,8 @@ for filename in glob.iglob(os.path.join(entitydb, "parents", "*.xml")):
handle = parent_handle,
parent_handle = e.get("parent_handle"),
child_handle = e.get("child_handle"),
- ta = ta.get_DER(),
- certificate = xcert.get_DER(),
+ ta = ta,
+ certificate = xcert,
repository_type = rpki.irdb.Parent.repository_type_map[repository_type],
referrer = referrer,
referral_authorization = referral_authorization,
@@ -336,8 +335,8 @@ for filename in glob.iglob(os.path.join(entitydb, "repositories", "*.xml")):
rpki.irdb.Repository.objects.get_or_create(
handle = repository_handle,
client_handle = e.get("client_handle"),
- ta = ta.get_DER(),
- certificate = xcert.get_DER(),
+ ta = ta,
+ certificate = xcert,
service_uri = e.get("service_uri"),
sia_base = e.get("sia_base"),
parent = parent,
@@ -361,8 +360,8 @@ for filename in glob.iglob(os.path.join(entitydb, "pubclients", "*.xml")):
rpki.irdb.Repository.objects.get_or_create(
handle = client_handle,
- ta = ta.get_DER(),
- certificate = xcert.get_DER(),
+ ta = ta,
+ certificate = xcert,
issuer = server_ca)
# Copy over any ROA requests