aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/x509.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki/x509.py')
-rw-r--r--rpkid/rpki/x509.py104
1 files changed, 93 insertions, 11 deletions
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"):