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.py876
1 files changed, 492 insertions, 384 deletions
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 92194a96..6f28e6f7 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -13,7 +13,7 @@ some of the nasty details. This involves a lot of format conversion.
$Id$
-Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
+Copyright (C) 2009--2012 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
@@ -43,10 +43,21 @@ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
"""
-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.POW
+import base64
+import lxml.etree
+import os
+import subprocess
+import email.mime.application
+import email.utils
+import mailbox
+import time
+import rpki.exceptions
+import rpki.resource_set
+import rpki.oids
+import rpki.sundial
+import rpki.log
+import rpki.async
import rpki.relaxng
def base64_with_linebreaks(der):
@@ -58,17 +69,6 @@ def base64_with_linebreaks(der):
n = len(b)
return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n"
-def calculate_SKI(public_key_der):
- """
- Calculate the SKI value given the DER representation of a public
- key, which requires first peeling the ASN.1 wrapper off the key.
- """
- k = rpki.POW.pkix.SubjectPublicKeyInfo()
- k.fromString(public_key_der)
- d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
- d.update(k.subjectPublicKey.get())
- return d.digest()
-
class PEM_converter(object):
"""
Convert between DER and PEM encodings for various kinds of ASN.1 data.
@@ -107,6 +107,18 @@ class PEM_converter(object):
"""
return self.b + base64_with_linebreaks(der) + self.e + "\n"
+def first_rsync_uri(xia):
+ """
+ Find first rsync URI in a sequence of AIA or SIA URIs.
+ Returns the URI if found, otherwise None.
+ """
+
+ if xia is not None:
+ for uri in xia:
+ if uri.startswith("rsync://"):
+ return uri
+ return None
+
def _find_xia_uri(extension, name):
"""
Find a rsync URI in an SIA or AIA extension.
@@ -126,22 +138,17 @@ 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:
+ purposes. See RFC 5280 4.1.2.4 for the ASN.1 details. In brief:
- - A DN is a SEQUENCE of RDNs.
+ - A DN is a SEQUENCE OF RDNs.
- - A RDN is a set of AttributeAndValues; in practice, multi-value
+ - 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.
+ - An AttributeAndValue is a SEQUENCE consisting of a 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
@@ -157,37 +164,43 @@ class X501DN(object):
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, unicode)):
- self.dn = (((rpki.oids.name2oid["commonName"], ("printableString", ini)),),)
- 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.safe_oid2name(a[0]), a[1][1])
+ return "".join("/" + "+".join("%s=%s" % (rpki.oids.safe_dotted2name(a[0]), a[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 __repr__(self):
+ return rpki.log.log_repr(self, str(self))
+
+ def _debug(self):
+ if False:
+ import traceback
+ for chunk in traceback.format_stack(limit = 5):
+ for line in chunk.splitlines():
+ rpki.log.debug("== %s" % line)
+ rpki.log.debug("++ %r %r" % (self, self.dn))
+
+ @classmethod
+ def from_cn(cls, s):
+ assert isinstance(s, (str, unicode))
+ self = cls()
+ self.dn = (((rpki.oids.safe_name2dotted("commonName"), s),),)
+ return self
+
+ @classmethod
+ def from_POW(cls, t):
+ assert isinstance(t, tuple)
+ self = cls()
+ self.dn = t
+ return self
def get_POW(self):
- raise NotImplementedError("Sorry, I haven't written the conversion to POW format yet")
+ return self.dn
class DER_object(object):
"""
@@ -368,57 +381,66 @@ class DER_object(object):
Get the AKI extension from this object. Only works for subclasses
that support getExtension().
"""
- aki = (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityKeyIdentifier"]) or ((), 0, None))[2]
- return aki[0] if isinstance(aki, tuple) else aki
+ return self.get_POW().getAKI()
def get_SKI(self):
"""
Get the SKI extension from this object. Only works for subclasses
that support getExtension().
"""
- return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectKeyIdentifier"]) or ((), 0, None))[2]
+ return self.get_POW().getSKI()
def get_SIA(self):
"""
Get the SIA extension from this object. Only works for subclasses
- that support getExtension().
+ that support getSIA().
"""
- return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2]
+ return self.get_POW().getSIA()
def get_sia_directory_uri(self):
"""
Get SIA directory (id-ad-caRepository) URI from this object.
- Only works for subclasses that support getExtension().
+ Only works for subclasses that support getSIA().
"""
- return _find_xia_uri(self.get_SIA(), "id-ad-caRepository")
+ sia = self.get_POW().getSIA()
+ return None if sia is None else first_rsync_uri(sia[0])
def get_sia_manifest_uri(self):
"""
Get SIA manifest (id-ad-rpkiManifest) URI from this object.
- Only works for subclasses that support getExtension().
+ Only works for subclasses that support getSIA().
+ """
+ sia = self.get_POW().getSIA()
+ return None if sia is None else first_rsync_uri(sia[1])
+
+ def get_sia_object_uri(self):
+ """
+ Get SIA object (id-ad-signedObject) URI from this object.
+ Only works for subclasses that support getSIA().
"""
- return _find_xia_uri(self.get_SIA(), "id-ad-rpkiManifest")
+ sia = self.get_POW().getSIA()
+ return None if sia is None else first_rsync_uri(sia[2])
def get_AIA(self):
"""
Get the SIA extension from this object. Only works for subclasses
- that support getExtension().
+ that support getAIA().
"""
- return (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityInfoAccess"]) or ((), 0, None))[2]
+ return self.get_POW().getAIA()
def get_aia_uri(self):
"""
Get AIA (id-ad-caIssuers) URI from this object.
- Only works for subclasses that support getExtension().
+ Only works for subclasses that support getAIA().
"""
- return _find_xia_uri(self.get_AIA(), "id-ad-caIssuers")
+ return first_rsync_uri(self.get_POW().getAIA())
def get_basicConstraints(self):
"""
Get the basicConstraints extension from this object. Only works
for subclasses that support getExtension().
"""
- return (self.get_POWpkix().getExtension(rpki.oids.name2oid["basicConstraints"]) or ((), 0, None))[2]
+ return self.get_POW().getBasicConstraints()
def is_CA(self):
"""
@@ -426,14 +448,13 @@ class DER_object(object):
extension and its cA value is true.
"""
basicConstraints = self.get_basicConstraints()
- return basicConstraints and basicConstraints[0] != 0
+ return basicConstraints is not None and basicConstraints[0]
def get_3779resources(self):
"""
- Get RFC 3779 resources as rpki.resource_set objects. Only works
- for subclasses that support getExtensions().
+ Get RFC 3779 resources as rpki.resource_set objects.
"""
- resources = rpki.resource_set.resource_bag.from_rfc3779_tuples(self.get_POWpkix().getExtensions())
+ resources = rpki.resource_set.resource_bag.from_POW_rfc3779(self.get_POW().getRFC3779())
try:
resources.valid_until = self.getNotAfter()
except AttributeError:
@@ -486,7 +507,7 @@ class DER_object(object):
d.update(self.get_DER())
return "%s %s %s" % (uri, self.creation_timestamp,
"".join(("%02X" % ord(b) for b in d.digest())))
- except:
+ except: # pylint: disable=W0702
return uri
class X509(DER_object):
@@ -500,7 +521,7 @@ class X509(DER_object):
have to care about this implementation nightmare.
"""
- formats = ("DER", "POW", "POWpkix")
+ formats = ("DER", "POW")
pem_converter = PEM_converter("CERTIFICATE")
def get_DER(self):
@@ -513,9 +534,6 @@ class X509(DER_object):
if self.POW:
self.DER = self.POW.derWrite()
return self.get_DER()
- if self.POWpkix:
- self.DER = self.POWpkix.toString()
- return self.get_DER()
raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
def get_POW(self):
@@ -523,44 +541,33 @@ class X509(DER_object):
Get the rpki.POW value of this certificate.
"""
self.check()
- if not self.POW:
- self.POW = rpki.POW.derRead(rpki.POW.X509_CERTIFICATE, self.get_DER())
+ if not self.POW: # pylint: disable=E0203
+ self.POW = rpki.POW.X509.derRead(self.get_DER())
return self.POW
- def get_POWpkix(self):
- """
- Get the rpki.POW.pkix value of this certificate.
- """
- self.check()
- if not self.POWpkix:
- cert = rpki.POW.pkix.Certificate()
- cert.fromString(self.get_DER())
- self.POWpkix = cert
- return self.POWpkix
-
def getIssuer(self):
"""
Get the issuer of this certificate.
"""
- return X501DN(self.get_POWpkix().getIssuer())
+ return X501DN.from_POW(self.get_POW().getIssuer())
def getSubject(self):
"""
Get the subject of this certificate.
"""
- return X501DN(self.get_POWpkix().getSubject())
+ return X501DN.from_POW(self.get_POW().getSubject())
def getNotBefore(self):
"""
Get the inception time of this certificate.
"""
- return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notBefore.get())
+ return self.get_POW().getNotBefore()
def getNotAfter(self):
"""
Get the expiration time of this certificate.
"""
- return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notAfter.get())
+ return self.get_POW().getNotAfter()
def getSerial(self):
"""
@@ -572,7 +579,13 @@ class X509(DER_object):
"""
Extract the public key from this certificate.
"""
- return RSApublic(DER = self.get_POWpkix().tbs.subjectPublicKeyInfo.toString())
+ return RSApublic(POW = self.get_POW().getPublicKey())
+
+ def get_SKI(self):
+ """
+ Get the SKI extension from this object.
+ """
+ return self.get_POW().getSKI()
def expired(self):
"""
@@ -600,7 +613,7 @@ class X509(DER_object):
resources = resources,
is_ca = is_ca,
aki = self.get_SKI(),
- issuer_name = self.get_POWpkix().getSubject())
+ issuer_name = self.getSubject())
@classmethod
@@ -611,6 +624,7 @@ class X509(DER_object):
"""
ski = subject_key.get_SKI()
+
if cn is None:
cn = "".join(("%02X" % ord(i) for i in ski))
@@ -626,11 +640,11 @@ class X509(DER_object):
resources = resources,
is_ca = True,
aki = ski,
- issuer_name = (((rpki.oids.name2oid["commonName"], ("printableString", cn)),),))
+ issuer_name = X501DN.from_cn(cn))
- @staticmethod
- def _issue(keypair, subject_key, serial, sia, aia, crldp, notAfter,
+ @classmethod
+ def _issue(cls, keypair, subject_key, serial, sia, aia, crldp, notAfter,
cn, resources, is_ca, aki, issuer_name):
"""
Common code to issue an RPKI certificate.
@@ -642,58 +656,50 @@ class X509(DER_object):
if cn is None:
cn = "".join(("%02X" % ord(i) for i in ski))
- # if notAfter is None: notAfter = now + rpki.sundial.timedelta(days = 30)
+ cert = rpki.POW.X509()
- cert = rpki.POW.pkix.Certificate()
cert.setVersion(2)
cert.setSerial(serial)
- cert.setIssuer(issuer_name)
- cert.setSubject((((rpki.oids.name2oid["commonName"], ("printableString", cn)),),))
- cert.setNotBefore(now.toASN1tuple())
- cert.setNotAfter(notAfter.toASN1tuple())
- cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
-
- exts = [ ["subjectKeyIdentifier", False, ski],
- ["authorityKeyIdentifier", False, (aki, (), None)],
- ["certificatePolicies", True, ((rpki.oids.name2oid["id-cp-ipAddr-asNumber"], ()),)] ]
-
+ cert.setIssuer(issuer_name.get_POW())
+ cert.setSubject(X501DN.from_cn(cn).get_POW())
+ cert.setNotBefore(now)
+ cert.setNotAfter(notAfter)
+ cert.setPublicKey(subject_key.get_POW())
+ cert.setSKI(ski)
+ cert.setAKI(aki)
+ cert.setCertificatePolicies((POWify_OID("id-cp-ipAddr-asNumber"),))
if crldp is not None:
- exts.append(["cRLDistributionPoints", False, ((("fullName", (("uri", crldp),)), None, ()),)])
+ cert.setCRLDP((crldp,))
if aia is not None:
- exts.append(["authorityInfoAccess", False, ((rpki.oids.name2oid["id-ad-caIssuers"], ("uri", aia)),)])
+ cert.setAIA((aia,))
if is_ca:
- exts.append(["basicConstraints", True, (1, None)])
- exts.append(["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)])
- else:
- exts.append(["keyUsage", True, (1,)])
+ cert.setBasicConstraints(True, None)
+ cert.setKeyUsage(frozenset(("keyCertSign", "cRLSign")))
- if sia is not None:
- exts.append(["subjectInfoAccess", False, sia])
else:
- assert not is_ca
+ cert.setKeyUsage(frozenset(("digitalSignature",)))
- # This next bit suggests that perhaps .to_rfc3779_tuple() should
- # be raising an exception when there are no resources rather than
- # returning None. Maybe refactor later.
+ assert sia is not None or not is_ca
- if resources is not None:
- r = resources.asn.to_rfc3779_tuple()
- if r is not None:
- exts.append(["sbgp-autonomousSysNum", True, (r, None)])
- r = [x for x in (resources.v4.to_rfc3779_tuple(), resources.v6.to_rfc3779_tuple()) if x is not None]
- if r:
- exts.append(["sbgp-ipAddrBlock", True, r])
+ if sia is not None:
+ caRepository, rpkiManifest, signedObject = sia
+ cert.setSIA(
+ (caRepository,) if isinstance(caRepository, str) else caRepository,
+ (rpkiManifest,) if isinstance(rpkiManifest, str) else rpkiManifest,
+ (signedObject,) if isinstance(signedObject, str) else signedObject)
- for x in exts:
- x[0] = rpki.oids.name2oid[x[0]]
- cert.setExtensions(exts)
+ if resources is not None:
+ cert.setRFC3779(
+ asn = ((r.min, r.max) for r in resources.asn),
+ ipv4 = ((rpki.POW.IPAddress(r.min, 4), rpki.POW.IPAddress(r.max, 4)) for r in resources.v4),
+ ipv6 = ((rpki.POW.IPAddress(r.min, 6), rpki.POW.IPAddress(r.max, 6)) for r in resources.v6))
cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
- return X509(POWpkix = cert)
+ return cls(POW = cert)
def bpki_cross_certify(self, keypair, source_cert, serial, notAfter,
now = None, pathLenConstraint = 0):
@@ -764,27 +770,21 @@ class X509(DER_object):
assert pathLenConstraint is None or (isinstance(pathLenConstraint, (int, long)) and
pathLenConstraint >= 0)
- extensions = [
- (rpki.oids.name2oid["subjectKeyIdentifier" ], False, subject_key.get_SKI())]
- if issuer_key != subject_key:
- extensions.append(
- (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 = rpki.POW.X509()
cert.setVersion(2)
cert.setSerial(serial)
- cert.setIssuer(issuer_name.get_POWpkix())
- cert.setSubject(subject_name.get_POWpkix())
- cert.setNotBefore(now.toASN1tuple())
- cert.setNotAfter(notAfter.toASN1tuple())
- cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
- cert.setExtensions(extensions)
+ cert.setIssuer(issuer_name.get_POW())
+ cert.setSubject(subject_name.get_POW())
+ cert.setNotBefore(now)
+ cert.setNotAfter(notAfter)
+ cert.setPublicKey(subject_key.get_POW())
+ cert.setSKI(subject_key.get_POW().calculateSKI())
+ if issuer_key != subject_key:
+ cert.setAKI(issuer_key.get_POW().calculateSKI())
+ if is_ca:
+ cert.setBasicConstraints(True, pathLenConstraint)
cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
-
- return cls(POWpkix = cert)
+ return cls(POW = cert)
@classmethod
def normalize_chain(cls, chain):
@@ -807,15 +807,27 @@ class X509(DER_object):
"""
return self.getNotBefore()
-
class PKCS10(DER_object):
"""
Class to hold a PKCS #10 request.
"""
- formats = ("DER", "POWpkix")
+ formats = ("DER", "POW")
pem_converter = PEM_converter("CERTIFICATE REQUEST")
-
+
+ ## @var expected_ca_keyUsage
+ # KeyUsage extension flags expected for CA requests.
+
+ expected_ca_keyUsage = frozenset(("keyCertSign", "cRLSign"))
+
+ ## @var allowed_extensions
+ # Extensions allowed by RPKI profile.
+
+ allowed_extensions = frozenset(rpki.oids.safe_name2dotted(name)
+ for name in ("basicConstraints",
+ "keyUsage",
+ "subjectInfoAccess"))
+
def get_DER(self):
"""
Get the DER value of this certification request.
@@ -823,33 +835,31 @@ class PKCS10(DER_object):
self.check()
if self.DER:
return self.DER
- if self.POWpkix:
- self.DER = self.POWpkix.toString()
+ if self.POW:
+ self.DER = self.POW.derWrite()
return self.get_DER()
raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
- def get_POWpkix(self):
+ def get_POW(self):
"""
- Get the rpki.POW.pkix value of this certification request.
+ Get the rpki.POW value of this certification request.
"""
self.check()
- if not self.POWpkix:
- req = rpki.POW.pkix.CertificationRequest()
- req.fromString(self.get_DER())
- self.POWpkix = req
- return self.POWpkix
+ if not self.POW: # pylint: disable=E0203
+ self.POW = rpki.POW.PKCS10.derRead(self.get_DER())
+ return self.POW
def getSubject(self):
"""
Extract the subject name from this certification request.
"""
- return X501DN(self.get_POWpkix().certificationRequestInfo.subject.get())
+ return X501DN.from_POW(self.get_POW().getSubject())
def getPublicKey(self):
"""
Extract the public key from this certification request.
"""
- return RSApublic(DER = self.get_POWpkix().certificationRequestInfo.subjectPublicKeyInfo.toString())
+ return RSApublic(POW = self.get_POW().getPublicKey())
def check_valid_rpki(self):
"""
@@ -866,72 +876,129 @@ class PKCS10(DER_object):
RPKI profile only allows EKU for EE certificates.
"""
- if not self.get_POWpkix().verify():
+ if not self.get_POW().verify():
raise rpki.exceptions.BadPKCS10, "Signature check failed"
- if self.get_POWpkix().certificationRequestInfo.version.get() != 0:
- raise rpki.exceptions.BadPKCS10, \
- "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version
+ ver = self.get_POW().getVersion()
- if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) != "sha256WithRSAEncryption":
- raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm
+ if ver != 0:
+ raise rpki.exceptions.BadPKCS10, "Bad version number %s" % ver
- exts = dict((rpki.oids.oid2name.get(oid, oid), value)
- for (oid, critical, value) in self.get_POWpkix().getExtensions())
+ alg = rpki.oids.safe_dotted2name(self.get_POW().getSignatureAlgorithm())
- if any(oid not in ("basicConstraints", "keyUsage", "subjectInfoAccess") for oid in exts):
- raise rpki.exceptions.BadExtension, "Forbidden extension(s) in certificate request"
+ if alg != "sha256WithRSAEncryption":
+ raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % alg
- if "basicConstraints" not in exts or not exts["basicConstraints"][0]:
+ bc = self.get_POW().getBasicConstraints()
+
+ if bc is None or not bc[0]:
raise rpki.exceptions.BadPKCS10, "Request for EE certificate not allowed here"
- if exts["basicConstraints"][1] is not None:
+ if bc[1] is not None:
raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length"
- if "keyUsage" in exts and (not exts["keyUsage"][5] or not exts["keyUsage"][6]):
- raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints"
+ ku = self.get_POW().getKeyUsage()
- sias = dict((rpki.oids.oid2name.get(oid, oid), value[1])
- for oid, value in exts.get("subjectInfoAccess", ())
- if value[0] == "uri" and value[1].startswith("rsync://"))
+ if ku is not None and self.expected_ca_keyUsage != ku:
+ raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints: %r" % ku
- for oid in ("id-ad-caRepository", "id-ad-rpkiManifest"):
- if oid not in sias:
- raise rpki.exceptions.BadPKCS10, "Certificate request is missing SIA %s" % oid
+ if any(oid not in self.allowed_extensions
+ for oid in self.get_POW().getExtensionOIDs()):
+ raise rpki.exceptions.BadExtension, "Forbidden extension(s) in certificate request"
- if not sias["id-ad-caRepository"].endswith("/"):
- raise rpki.exceptions.BadPKCS10, "Certificate request id-ad-caRepository does not end with slash: %r" % sias["id-ad-caRepository"]
+ sias = self.get_POW().getSIA()
- if sias["id-ad-rpkiManifest"].endswith("/"):
- raise rpki.exceptions.BadPKCS10, "Certificate request id-ad-rpkiManifest ends with slash: %r" % sias["id-ad-rpkiManifest"]
+ if sias is None:
+ raise rpki.exceptions.BadPKCS10, "Certificate request is missing SIA extension"
- @classmethod
- def create_ca(cls, keypair, sia = None):
- """
- Create a new request for a given keypair, including given SIA value.
- """
- exts = [["basicConstraints", True, (1, None)],
- ["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)]]
- if sia is not None:
- exts.append(["subjectInfoAccess", False, sia])
- for x in exts:
- x[0] = rpki.oids.name2oid[x[0]]
- return cls.create(keypair, exts)
+ caRepository, rpkiManifest, signedObject = sias
+
+ if signedObject:
+ raise rpki.exceptions.BadPKCS10, "CA certificate request has SIA id-ad-signedObject"
+
+ if not caRepository:
+ raise rpki.exceptions.BadPKCS10, "Certificate request is missing SIA id-ad-caRepository"
+
+ if not any(uri.startswith("rsync://") for uri in caRepository):
+ raise rpki.exceptions.BadPKCS10, "Certificate request SIA id-ad-caRepository contains no rsync URIs"
+
+ if not rpkiManifest:
+ raise rpki.exceptions.BadPKCS10, "Certificate request is missing SIA id-ad-rpkiManifest"
+
+ if not any(uri.startswith("rsync://") for uri in rpkiManifest):
+ raise rpki.exceptions.BadPKCS10, "Certificate request SIA id-ad-rpkiManifest contains no rsync URIs"
+
+ if any(uri.startswith("rsync://") and not uri.endswith("/") for uri in caRepository):
+ raise rpki.exceptions.BadPKCS10, "Certificate request SIA id-ad-caRepository does not end with slash"
+
+ if any(uri.startswith("rsync://") and uri.endswith("/") for uri in rpkiManifest):
+ raise rpki.exceptions.BadPKCS10, "Certificate request SIA id-ad-rpkiManifest ends with slash"
@classmethod
- def create(cls, keypair, exts = None):
+ def create(cls, keypair, exts = None, is_ca = False,
+ caRepository = None, rpkiManifest = None, signedObject = None):
"""
- Create a new request for a given keypair, including given extensions.
+ Create a new request for a given keypair.
"""
+
+ assert exts is None, "Old calling sequence to rpki.x509.PKCS10.create()"
+
cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI()))
- req = rpki.POW.pkix.CertificationRequest()
- req.certificationRequestInfo.version.set(0)
- req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"],
- ("printableString", cn)),),))
- if exts is not None:
- req.setExtensions(exts)
+
+ if isinstance(caRepository, str):
+ caRepository = (caRepository,)
+
+ if isinstance(rpkiManifest, str):
+ rpkiManifest = (rpkiManifest,)
+
+ if isinstance(signedObject, str):
+ signedObject = (signedObject,)
+
+ req = rpki.POW.PKCS10()
+ req.setVersion(0)
+ req.setSubject(X501DN.from_cn(cn).get_POW())
+ req.setPublicKey(keypair.get_POW())
+
+ if is_ca:
+ req.setBasicConstraints(True, None)
+ req.setKeyUsage(cls.expected_ca_keyUsage)
+
+ if caRepository or rpkiManifest or signedObject:
+ req.setSIA(caRepository, rpkiManifest, signedObject)
+
req.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
- return cls(POWpkix = req)
+ return cls(POW = req)
+
+## @var generate_insecure_debug_only_rsa_key
+# Debugging hack to let us save throwaway RSA keys from one debug
+# session to the next. DO NOT USE THIS IN PRODUCTION.
+
+generate_insecure_debug_only_rsa_key = None
+
+class insecure_debug_only_rsa_key_generator(object):
+
+ def __init__(self, filename, keyno = 0):
+ try:
+ try:
+ import gdbm as dbm_du_jour
+ except ImportError:
+ import dbm as dbm_du_jour
+ self.keyno = long(keyno)
+ self.filename = filename
+ self.db = dbm_du_jour.open(filename, "c")
+ except:
+ rpki.log.warn("insecure_debug_only_rsa_key_generator initialization FAILED, hack inoperative")
+ raise
+
+ def __call__(self):
+ k = str(self.keyno)
+ try:
+ v = rpki.POW.Asymmetric.derReadPrivate(self.db[k])
+ except KeyError:
+ v = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, 2048)
+ self.db[k] = v.derWritePrivate()
+ self.keyno += 1
+ return v
class RSA(DER_object):
"""
@@ -949,7 +1016,7 @@ class RSA(DER_object):
if self.DER:
return self.DER
if self.POW:
- self.DER = self.POW.derWrite(rpki.POW.RSA_PRIVATE_KEY)
+ self.DER = self.POW.derWritePrivate()
return self.get_DER()
raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
@@ -958,8 +1025,8 @@ class RSA(DER_object):
Get the rpki.POW value of this keypair.
"""
self.check()
- if not self.POW:
- self.POW = rpki.POW.derRead(rpki.POW.RSA_PRIVATE_KEY, self.get_DER())
+ if not self.POW: # pylint: disable=E0203
+ self.POW = rpki.POW.Asymmetric.derReadPrivate(self.get_DER())
return self.POW
@classmethod
@@ -969,19 +1036,22 @@ class RSA(DER_object):
"""
if not quiet:
rpki.log.debug("Generating new %d-bit RSA key" % keylength)
- return cls(POW = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, keylength))
+ if generate_insecure_debug_only_rsa_key is not None:
+ return cls(POW = generate_insecure_debug_only_rsa_key())
+ else:
+ return cls(POW = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, keylength))
def get_public_DER(self):
"""
Get the DER encoding of the public key from this keypair.
"""
- return self.get_POW().derWrite(rpki.POW.RSA_PUBLIC_KEY)
+ return self.get_POW().derWritePublic()
def get_SKI(self):
"""
Calculate the SKI of this keypair.
"""
- return calculate_SKI(self.get_public_DER())
+ return self.get_POW().calculateSKI()
def get_RSApublic(self):
"""
@@ -1005,7 +1075,7 @@ class RSApublic(DER_object):
if self.DER:
return self.DER
if self.POW:
- self.DER = self.POW.derWrite(rpki.POW.RSA_PUBLIC_KEY)
+ self.DER = self.POW.derWritePublic()
return self.get_DER()
raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
@@ -1014,15 +1084,15 @@ class RSApublic(DER_object):
Get the rpki.POW value of this public key.
"""
self.check()
- if not self.POW:
- self.POW = rpki.POW.derRead(rpki.POW.RSA_PUBLIC_KEY, self.get_DER())
+ if not self.POW: # pylint: disable=E0203
+ self.POW = rpki.POW.Asymmetric.derReadPublic(self.get_DER())
return self.POW
def get_SKI(self):
"""
Calculate the SKI of this public key.
"""
- return calculate_SKI(self.get_DER())
+ return self.get_POW().calculateSKI()
def POWify_OID(oid):
"""
@@ -1036,21 +1106,13 @@ def POWify_OID(oid):
class CMS_object(DER_object):
"""
- Class to hold a CMS-wrapped object.
-
- CMS-wrapped objects are a little different from the other DER_object
- types because the signed object is CMS wrapping inner content that's
- also ASN.1, and due to our current minimal support for CMS we can't
- just handle this as a pretty composite object. So, for now anyway,
- a CMS_object is the outer CMS wrapped object so that the usual DER
- and PEM operations do the obvious things, and the inner content is
- handle via separate methods.
+ Abstract class to hold a CMS object.
"""
formats = ("DER", "POW")
- other_clear = ("content",)
econtent_oid = POWify_OID("id-data")
pem_converter = PEM_converter("CMS")
+ POW_class = rpki.POW.CMS
## @var dump_on_verify_failure
# Set this to True to get dumpasn1 dumps of ASN.1 on CMS verify failures.
@@ -1109,30 +1171,15 @@ class CMS_object(DER_object):
Get the rpki.POW value of this CMS_object.
"""
self.check()
- if not self.POW:
- self.POW = rpki.POW.derRead(rpki.POW.CMS_MESSAGE, self.get_DER())
+ if not self.POW: # pylint: disable=E0203
+ self.POW = self.POW_class.derRead(self.get_DER())
return self.POW
- def get_content(self):
- """
- Get the inner content of this CMS_object.
- """
- if self.content is None:
- raise rpki.exceptions.CMSContentNotSet, "Inner content of CMS object %r is not set" % self
- return self.content
-
- def set_content(self, content):
- """
- Set the (inner) content of this CMS_object, clearing the wrapper.
- """
- self.clear()
- self.content = content
-
def get_signingTime(self):
"""
Extract signingTime from CMS signed attributes.
"""
- return rpki.sundial.datetime.fromGeneralizedTime(self.get_POW().signingTime())
+ return self.get_POW().signingTime()
def verify(self, ta):
"""
@@ -1145,18 +1192,21 @@ class CMS_object(DER_object):
raise
except Exception:
if self.print_on_der_error:
- rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %r" % self.get_DER())
+ rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %r" %
+ self.get_DER())
raise rpki.exceptions.UnparsableCMSDER
if cms.eContentType() != self.econtent_oid:
- raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
+ raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (
+ cms.eContentType(), self.econtent_oid)
certs = [X509(POW = x) for x in cms.certs()]
crls = [CRL(POW = c) for c in cms.crls()]
if self.debug_cms_certs:
for x in certs:
- rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
+ rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (
+ x.getIssuer(), x.getSubject(), x.hSKI()))
for c in crls:
rpki.log.debug("Received CMS CRL issuer %r" % (c.getIssuer(),))
@@ -1168,43 +1218,52 @@ class CMS_object(DER_object):
for x in X509.normalize_chain(ta):
if self.debug_cms_certs:
- rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
+ rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (
+ x.getIssuer(), x.getSubject(), x.hSKI()))
if x.getNotAfter() < now:
- raise rpki.exceptions.TrustedCMSCertHasExpired("Trusted CMS certificate has expired", "%s (%s)" % (x.getSubject(), x.hSKI()))
+ raise rpki.exceptions.TrustedCMSCertHasExpired("Trusted CMS certificate has expired",
+ "%s (%s)" % (x.getSubject(), x.hSKI()))
if not x.is_CA():
if trusted_ee is None:
trusted_ee = x
else:
- raise rpki.exceptions.MultipleCMSEECert("Multiple CMS EE certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in ta if not x.is_CA()))
+ raise rpki.exceptions.MultipleCMSEECert("Multiple CMS EE certificates", *("%s (%s)" % (
+ x.getSubject(), x.hSKI()) for x in ta if not x.is_CA()))
store.addTrust(x.get_POW())
if trusted_ee:
if self.debug_cms_certs:
- rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
+ rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (
+ trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
if len(certs) > 1 or (len(certs) == 1 and
(certs[0].getSubject() != trusted_ee.getSubject() or
certs[0].getPublicKey() != trusted_ee.getPublicKey())):
- raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in certs))
+ raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (
+ x.getSubject(), x.hSKI()) for x in certs))
if crls:
- raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (c.getIssuer(), c.hAKI()) for c in crls))
+ raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (
+ c.getIssuer(), c.hAKI()) for c in crls))
else:
untrusted_ee = [x for x in certs if not x.is_CA()]
if len(untrusted_ee) < 1:
raise rpki.exceptions.MissingCMSEEcert
if len(untrusted_ee) > 1 or (not self.allow_extra_certs and len(certs) > len(untrusted_ee)):
- raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (x.getSubject(), x.hSKI()) for x in certs))
+ raise rpki.exceptions.UnexpectedCMSCerts("Unexpected CMS certificates", *("%s (%s)" % (
+ x.getSubject(), x.hSKI()) for x in certs))
if len(crls) < 1:
if self.require_crls:
raise rpki.exceptions.MissingCMSCRL
else:
rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting")
if len(crls) > 1 and not self.allow_extra_crls:
- raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (c.getIssuer(), c.hAKI()) for c in crls))
+ raise rpki.exceptions.UnexpectedCMSCRLs("Unexpected CRLs", *("%s (%s)" % (
+ c.getIssuer(), c.hAKI()) for c in crls))
for x in certs:
if x.getNotAfter() < now:
- raise rpki.exceptions.CMSCertHasExpired("CMS certificate has expired", "%s (%s)" % (x.getSubject(), x.hSKI()))
+ raise rpki.exceptions.CMSCertHasExpired("CMS certificate has expired", "%s (%s)" % (
+ x.getSubject(), x.hSKI()))
try:
content = cms.verify(store)
@@ -1221,8 +1280,7 @@ class CMS_object(DER_object):
rpki.log.warn(line)
raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
- self.decode(content)
- return self.get_content()
+ return content
def extract(self):
"""
@@ -1245,12 +1303,13 @@ class CMS_object(DER_object):
raise rpki.exceptions.UnparsableCMSDER
if cms.eContentType() != self.econtent_oid:
- raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
+ raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (
+ cms.eContentType(), self.econtent_oid)
- content = cms.verify(rpki.POW.X509Store(), None, rpki.POW.CMS_NOCRL | rpki.POW.CMS_NO_SIGNER_CERT_VERIFY | rpki.POW.CMS_NO_ATTR_VERIFY | rpki.POW.CMS_NO_CONTENT_VERIFY)
+ return cms.verify(rpki.POW.X509Store(), None,
+ (rpki.POW.CMS_NOCRL | rpki.POW.CMS_NO_SIGNER_CERT_VERIFY |
+ rpki.POW.CMS_NO_ATTR_VERIFY | rpki.POW.CMS_NO_CONTENT_VERIFY))
- self.decode(content)
- return self.get_content()
def sign(self, keypair, certs, crls = None, no_certs = False):
"""
@@ -1272,21 +1331,17 @@ class CMS_object(DER_object):
crls = (crls,)
if self.debug_cms_certs:
- rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI()))
+ rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (
+ cert.getIssuer(), cert.getSubject(), cert.hSKI()))
for i, c in enumerate(certs):
- rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI()))
-
- cms = rpki.POW.CMS()
+ rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (
+ i, c.getIssuer(), c.getSubject(), c.hSKI()))
- cms.sign(cert.get_POW(),
- keypair.get_POW(),
- self.encode(),
- [x.get_POW() for x in certs],
- [c.get_POW() for c in crls],
- self.econtent_oid,
- rpki.POW.CMS_NOCERTS if no_certs else 0)
-
- self.POW = cms
+ self._sign(cert.get_POW(),
+ keypair.get_POW(),
+ [x.get_POW() for x in certs],
+ [c.get_POW() for c in crls],
+ rpki.POW.CMS_NOCERTS if no_certs else 0)
@property
def creation_timestamp(self):
@@ -1296,24 +1351,92 @@ class CMS_object(DER_object):
return self.get_signingTime()
-class DER_CMS_object(CMS_object):
+class Wrapped_CMS_object(CMS_object):
"""
- Class to hold CMS objects with DER-based content.
+ Abstract class to hold CMS objects wrapping non-DER content (eg, XML
+ or VCard).
+
+ CMS-wrapped objects are a little different from the other DER_object
+ types because the signed object is CMS wrapping some other kind of
+ inner content. A Wrapped_CMS_object is the outer CMS wrapped object
+ so that the usual DER and PEM operations do the obvious things, and
+ the inner content is handle via separate methods.
"""
- def encode(self):
+ other_clear = ("content",)
+
+ def get_content(self):
"""
- Encode inner content for signing.
+ Get the inner content of this Wrapped_CMS_object.
"""
- return self.get_content().toString()
+ if self.content is None:
+ raise rpki.exceptions.CMSContentNotSet, "Inner content of CMS object %r is not set" % self
+ return self.content
- def decode(self, der):
+ def set_content(self, content):
+ """
+ Set the (inner) content of this Wrapped_CMS_object, clearing the wrapper.
"""
- Decode DER and set inner content.
+ self.clear()
+ self.content = content
+
+ def verify(self, ta):
+ """
+ Verify CMS wrapper and store inner content.
+ """
+
+ self.decode(CMS_object.verify(self, ta))
+ return self.get_content()
+
+ def extract(self):
+ """
+ Extract and store inner content from CMS wrapper without verifying
+ the CMS.
+
+ DANGER WILL ROBINSON!!!
+
+ Do not use this method on unvalidated data. Use the verify()
+ method instead.
+
+ If you don't understand this warning, don't use this method.
"""
- obj = self.content_class()
- obj.fromString(der)
- self.content = obj
+
+ self.decode(CMS_object.extract(self))
+ return self.get_content()
+
+ def _sign(self, cert, keypair, certs, crls, flags):
+ """
+ Internal method to call POW to do CMS signature. This is split
+ out from the .sign() API method to handle differences in how
+ different CMS-based POW classes handle the inner content.
+ """
+
+ cms = self.POW_class()
+ cms.sign(cert, keypair, self.encode(), certs, crls, self.econtent_oid, flags)
+ self.POW = cms
+
+
+class DER_CMS_object(CMS_object):
+ """
+ Abstract class for CMS-based objects with DER-encoded content
+ handled by C-level subclasses of rpki.POW.CMS.
+ """
+
+ def _sign(self, cert, keypair, certs, crls, flags):
+ self.get_POW().sign(cert, keypair, certs, crls, self.econtent_oid, flags)
+
+
+ def extract_if_needed(self):
+ """
+ Extract inner content if needed. See caveats for .extract(), do
+ not use unless you really know what you are doing.
+ """
+
+ try:
+ self.get_POW().getVersion()
+ except rpki.POW.NotVerifiedError:
+ self.extract()
+
class SignedManifest(DER_CMS_object):
"""
@@ -1321,41 +1444,43 @@ class SignedManifest(DER_CMS_object):
"""
pem_converter = PEM_converter("RPKI MANIFEST")
- content_class = rpki.manifest.Manifest
econtent_oid = POWify_OID("id-ct-rpkiManifest")
+ POW_class = rpki.POW.Manifest
def getThisUpdate(self):
"""
Get thisUpdate value from this manifest.
"""
- return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
+ return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this manifest.
"""
- return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
+ return self.get_POW().getNextUpdate()
@classmethod
def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
"""
Build a signed manifest.
"""
- self = cls()
+
filelist = []
for name, obj in names_and_objs:
d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
d.update(obj.get_DER())
filelist.append((name.rpartition("/")[2], d.digest()))
filelist.sort(key = lambda x: x[0])
- m = rpki.manifest.Manifest()
- m.version.set(version)
- m.manifestNumber.set(serial)
- m.thisUpdate.set(thisUpdate.toGeneralizedTime())
- m.nextUpdate.set(nextUpdate.toGeneralizedTime())
- m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
- m.fileList.set(filelist)
- self.set_content(m)
+
+ obj = cls.POW_class()
+ obj.setVersion(version)
+ obj.setManifestNumber(serial)
+ obj.setThisUpdate(thisUpdate)
+ obj.setNextUpdate(nextUpdate)
+ obj.setAlgorithm(POWify_OID(rpki.oids.name2oid["id-sha256"]))
+ obj.addFiles(filelist)
+
+ self = cls(POW = obj)
self.sign(keypair, certs)
return self
@@ -1365,31 +1490,23 @@ class ROA(DER_CMS_object):
"""
pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
- content_class = rpki.roa.RouteOriginAttestation
econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
+ POW_class = rpki.POW.ROA
@classmethod
def build(cls, asn, ipv4, ipv6, keypair, certs, version = 0):
"""
Build a ROA.
"""
- try:
- self = cls()
- r = rpki.roa.RouteOriginAttestation()
- r.version.set(version)
- r.asID.set(asn)
- r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
- self.set_content(r)
- self.sign(keypair, certs)
- return self
- except rpki.POW.pkix.DerError, e:
- rpki.log.debug("Encoding error while generating ROA %r: %s" % (self, e))
- rpki.log.debug("ROA inner content: %r" % (r.get(),))
- raise
-
- _afi_map = dict((cls.resource_set_type.afi, cls)
- for cls in (rpki.resource_set.roa_prefix_set_ipv4,
- rpki.resource_set.roa_prefix_set_ipv6))
+ ipv4 = ipv4.to_POW_roa_tuple() if ipv4 else None
+ ipv6 = ipv6.to_POW_roa_tuple() if ipv6 else None
+ obj = cls.POW_class()
+ obj.setVersion(version)
+ obj.setASID(asn)
+ obj.setPrefixes(ipv4 = ipv4, ipv6 = ipv6)
+ self = cls(POW = obj)
+ self.sign(keypair, certs)
+ return self
def tracking_data(self, uri):
"""
@@ -1398,42 +1515,25 @@ class ROA(DER_CMS_object):
"""
msg = DER_CMS_object.tracking_data(self, uri)
try:
- if self.content is None:
+ try:
+ self.get_POW().getVersion()
+ except rpki.POW.NotVerifiedError:
self.extract()
- roa = self.get_content()
- asn = roa.asID.get()
- prefix_sets = {}
- for fam in roa.ipAddrBlocks:
- afi = fam.addressFamily.get()
- prefix_sets[afi] = prefix_set = self._afi_map[afi]()
- addr_type = prefix_set.resource_set_type.range_type.datum_type
- for addr in fam.addresses:
- prefix = addr.address.get()
- prefixlen = len(prefix)
- prefix = addr_type(rpki.resource_set._bs2long(prefix, addr_type.bits, 0))
- maxprefixlen = addr.maxLength.get()
- prefix_set.append(prefix_set.prefix_type(prefix, prefixlen, maxprefixlen))
- msg = "%s %s %s" % (msg, asn,
- ",".join(str(prefix_sets[i]) for i in sorted(prefix_sets)))
- except:
+ asn = self.get_POW().getASID()
+ text = []
+ for prefixes in self.get_POW().getPrefixes():
+ if prefixes is not None:
+ for prefix, prefixlen, maxprefixlen in prefixes:
+ if maxprefixlen is None or prefixlen == maxprefixlen:
+ text.append("%s/%s" % (prefix, prefixlen))
+ else:
+ text.append("%s/%s-%s" % (prefix, prefixlen, maxprefixlen))
+ text.sort()
+ msg = "%s %s %s" % (msg, asn, ",".join(text))
+ except: # pylint: disable=W0702
pass
return msg
-class Ghostbuster(DER_CMS_object):
- """
- Class to hold a signed Ghostbuster record.
- """
-
- content_class = rpki.ghostbuster.Ghostbuster
-
- @classmethod
- def build(cls, vcard, keypair, certs):
- self = cls()
- gbr = content_class(vcard)
- self.set_content(gbr)
- self.sign(keypair, certs)
- return self
-
class DeadDrop(object):
"""
Dead-drop utility for storing copies of CMS messages for debugging or
@@ -1465,7 +1565,7 @@ class DeadDrop(object):
rpki.log.warn("Could not write to mailbox %s: %e" % (self.name, e))
self.warned = True
-class XML_CMS_object(CMS_object):
+class XML_CMS_object(Wrapped_CMS_object):
"""
Class to hold CMS-wrapped XML protocol data.
"""
@@ -1484,11 +1584,24 @@ class XML_CMS_object(CMS_object):
dump_inbound_cms = None
+ ## @var check_inbound_schema
+ # If set, perform RelaxNG schema check on inbound messages.
+
+ check_inbound_schema = True
+
+ ## @var check_outbound_schema
+ # If set, perform RelaxNG schema check on outbound messages.
+
+ check_outbound_schema = False
+
def encode(self):
"""
Encode inner content for signing.
"""
- return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
+ return lxml.etree.tostring(self.get_content(),
+ pretty_print = True,
+ encoding = self.encoding,
+ xml_declaration = True)
def decode(self, xml):
"""
@@ -1500,7 +1613,10 @@ class XML_CMS_object(CMS_object):
"""
Pretty print XML content of this message.
"""
- return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
+ return lxml.etree.tostring(self.get_content(),
+ pretty_print = True,
+ encoding = self.encoding,
+ xml_declaration = True)
def schema_check(self):
"""
@@ -1531,7 +1647,8 @@ class XML_CMS_object(CMS_object):
self.set_content(msg)
else:
self.set_content(msg.toXML())
- self.schema_check()
+ if self.check_outbound_schema:
+ self.schema_check()
self.sign(keypair, certs, crls)
if self.dump_outbound_cms:
self.dump_outbound_cms.dump(self)
@@ -1544,11 +1661,12 @@ class XML_CMS_object(CMS_object):
if self.dump_inbound_cms:
self.dump_inbound_cms.dump(self)
self.verify(ta)
- self.schema_check()
+ if self.check_inbound_schema:
+ self.schema_check()
if self.saxify is None:
return self.get_content()
else:
- return self.saxify(self.get_content())
+ return self.saxify(self.get_content()) # pylint: disable=E1102
def check_replay(self, timestamp):
"""
@@ -1583,7 +1701,7 @@ class SignedReferral(XML_CMS_object):
schema = rpki.relaxng.myrpki
saxify = None
-class Ghostbuster(CMS_object):
+class Ghostbuster(Wrapped_CMS_object):
"""
Class to hold Ghostbusters record (CMS-wrapped VCard). This is
quite minimal because we treat the VCard as an opaque byte string
@@ -1623,7 +1741,7 @@ class CRL(DER_object):
Class to hold a Certificate Revocation List.
"""
- formats = ("DER", "POW", "POWpkix")
+ formats = ("DER", "POW")
pem_converter = PEM_converter("X509 CRL")
def get_DER(self):
@@ -1636,9 +1754,6 @@ class CRL(DER_object):
if self.POW:
self.DER = self.POW.derWrite()
return self.get_DER()
- if self.POWpkix:
- self.DER = self.POWpkix.toString()
- return self.get_DER()
raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
def get_POW(self):
@@ -1646,56 +1761,49 @@ class CRL(DER_object):
Get the rpki.POW value of this CRL.
"""
self.check()
- if not self.POW:
- self.POW = rpki.POW.derRead(rpki.POW.X509_CRL, self.get_DER())
+ if not self.POW: # pylint: disable=E0203
+ self.POW = rpki.POW.CRL.derRead(self.get_DER())
return self.POW
- def get_POWpkix(self):
- """
- Get the rpki.POW.pkix value of this CRL.
- """
- self.check()
- if not self.POWpkix:
- crl = rpki.POW.pkix.CertificateList()
- crl.fromString(self.get_DER())
- self.POWpkix = crl
- return self.POWpkix
-
def getThisUpdate(self):
"""
Get thisUpdate value from this CRL.
"""
- return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
+ return self.get_POW().getThisUpdate()
def getNextUpdate(self):
"""
Get nextUpdate value from this CRL.
"""
- return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
+ return self.get_POW().getNextUpdate()
def getIssuer(self):
"""
Get issuer value of this CRL.
"""
- return X501DN(self.get_POWpkix().getIssuer())
+ return X501DN.from_POW(self.get_POW().getIssuer())
+
+ def getCRLNumber(self):
+ """
+ Get CRL Number value for this CRL.
+ """
+ return self.get_POW().getCRLNumber()
@classmethod
- def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
+ def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1):
"""
Generate a new CRL.
"""
- crl = rpki.POW.pkix.CertificateList()
+ crl = rpki.POW.CRL()
crl.setVersion(version)
- crl.setIssuer(issuer.get_POWpkix().getSubject())
- crl.setThisUpdate(thisUpdate.toASN1tuple())
- crl.setNextUpdate(nextUpdate.toASN1tuple())
- if revokedCertificates:
- crl.setRevokedCertificates(revokedCertificates)
- crl.setExtensions(
- ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
- (rpki.oids.name2oid["cRLNumber"], False, serial)))
- crl.sign(keypair.get_POW(), digestType)
- return cls(POWpkix = crl)
+ crl.setIssuer(issuer.getSubject().get_POW())
+ crl.setThisUpdate(thisUpdate)
+ crl.setNextUpdate(nextUpdate)
+ crl.setAKI(issuer.get_SKI())
+ crl.setCRLNumber(serial)
+ crl.addRevocations(revokedCertificates)
+ crl.sign(keypair.get_POW())
+ return cls(POW = crl)
@property
def creation_timestamp(self):