aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpkid/ext/POW.c40
-rw-r--r--rpkid/rpki/exceptions.py5
-rw-r--r--rpkid/rpki/irdb/zookeeper.py40
-rw-r--r--rpkid/rpki/oids.py73
-rw-r--r--rpkid/rpki/x509.py77
5 files changed, 145 insertions, 90 deletions
diff --git a/rpkid/ext/POW.c b/rpkid/ext/POW.c
index 52441581..683427b1 100644
--- a/rpkid/ext/POW.c
+++ b/rpkid/ext/POW.c
@@ -1425,14 +1425,14 @@ extension_set_sia(X509_EXTENSIONS **exts, PyObject *args, PyObject *kwds)
}
static PyObject *
-extension_get_extended_key_usage(X509_EXTENSIONS **exts)
+extension_get_eku(X509_EXTENSIONS **exts)
{
EXTENDED_KEY_USAGE *ext = NULL;
PyObject *result = NULL;
PyObject *oid = NULL;
int i;
- ENTERING(extension_get_extended_key_usage);
+ ENTERING(extension_get_eku);
POW_assert(exts);
@@ -1461,7 +1461,7 @@ extension_get_extended_key_usage(X509_EXTENSIONS **exts)
}
static PyObject *
-extension_set_extended_key_usage(X509_EXTENSIONS **exts, PyObject *args)
+extension_set_eku(X509_EXTENSIONS **exts, PyObject *args)
{
EXTENDED_KEY_USAGE *ext = NULL;
PyObject *iterable = NULL;
@@ -1472,7 +1472,7 @@ extension_set_extended_key_usage(X509_EXTENSIONS **exts, PyObject *args)
const char *txt;
int ok = 0;
- ENTERING(extension_set_extended_key_usage);
+ ENTERING(extension_set_eku);
POW_assert(exts);
@@ -2814,19 +2814,19 @@ x509_object_set_key_usage(x509_object *self, PyObject *args)
return extension_set_key_usage(x509_object_extension_helper(self), args);
}
-static char x509_object_get_extended_key_usage__doc__[] =
+static char x509_object_get_eku__doc__[] =
"Return a FrozenSet of object identifiers representing the\n"
"ExtendedKeyUsage settings for this certificate, or None if\n"
"the certificate has no ExtendedKeyUsage extension.\n"
;
static PyObject *
-x509_object_get_extended_key_usage(x509_object *self)
+x509_object_get_eku(x509_object *self)
{
- return extension_get_extended_key_usage(x509_object_extension_helper(self));
+ return extension_get_eku(x509_object_extension_helper(self));
}
-static char x509_object_set_extended_key_usage__doc__[] =
+static char x509_object_set_eku__doc__[] =
"Set the ExtendedKeyUsage extension for this certificate.\n"
"\n"
"Argument \"iterable\" should be an iterable object which returns one or more\n"
@@ -2838,9 +2838,9 @@ static char x509_object_set_extended_key_usage__doc__[] =
;
static PyObject *
-x509_object_set_extended_key_usage(x509_object *self, PyObject *args)
+x509_object_set_eku(x509_object *self, PyObject *args)
{
- return extension_set_extended_key_usage(x509_object_extension_helper(self), args);
+ return extension_set_eku(x509_object_extension_helper(self), args);
}
static char x509_object_get_rfc3779__doc__[] =
@@ -3720,8 +3720,8 @@ static struct PyMethodDef x509_object_methods[] = {
Define_Method(setAKI, x509_object_set_aki, METH_VARARGS),
Define_Method(getKeyUsage, x509_object_get_key_usage, METH_NOARGS),
Define_Method(setKeyUsage, x509_object_set_key_usage, METH_VARARGS),
- Define_Method(getExtendedKeyUsage, x509_object_get_extended_key_usage, METH_NOARGS),
- Define_Method(setExtendedKeyUsage, x509_object_set_extended_key_usage, METH_VARARGS),
+ Define_Method(getEKU, x509_object_get_eku, METH_NOARGS),
+ Define_Method(setEKU, x509_object_set_eku, METH_VARARGS),
Define_Method(getRFC3779, x509_object_get_rfc3779, METH_NOARGS),
Define_Method(setRFC3779, x509_object_set_rfc3779, METH_KEYWORDS),
Define_Method(getBasicConstraints, x509_object_get_basic_constraints, METH_NOARGS),
@@ -8566,19 +8566,19 @@ pkcs10_object_set_key_usage(pkcs10_object *self, PyObject *args)
return extension_set_key_usage(pkcs10_object_extension_helper(self), args);
}
-static char pkcs10_object_get_extended_key_usage__doc__[] =
+static char pkcs10_object_get_eku__doc__[] =
"Return a FrozenSet of object identifiers representing the\n"
"ExtendedKeyUsage settings for this PKCS #10 requst, or None if\n"
"the request has no ExtendedKeyUsage extension.\n"
;
static PyObject *
-pkcs10_object_get_extended_key_usage(pkcs10_object *self)
+pkcs10_object_get_eku(pkcs10_object *self)
{
- return extension_get_extended_key_usage(pkcs10_object_extension_helper(self));
+ return extension_get_eku(pkcs10_object_extension_helper(self));
}
-static char pkcs10_object_set_extended_key_usage__doc__[] =
+static char pkcs10_object_set_eku__doc__[] =
"Set the ExtendedKeyUsage extension for this PKCS #10 request.\n"
"\n"
"Argument \"iterable\" should be an iterable object which returns one or more\n"
@@ -8590,9 +8590,9 @@ static char pkcs10_object_set_extended_key_usage__doc__[] =
;
static PyObject *
-pkcs10_object_set_extended_key_usage(pkcs10_object *self, PyObject *args)
+pkcs10_object_set_eku(pkcs10_object *self, PyObject *args)
{
- return extension_set_extended_key_usage(pkcs10_object_extension_helper(self), args);
+ return extension_set_eku(pkcs10_object_extension_helper(self), args);
}
static char pkcs10_object_get_basic_constraints__doc__[] =
@@ -8763,8 +8763,8 @@ static struct PyMethodDef pkcs10_object_methods[] = {
Define_Method(pprint, pkcs10_object_pprint, METH_NOARGS),
Define_Method(getKeyUsage, pkcs10_object_get_key_usage, METH_NOARGS),
Define_Method(setKeyUsage, pkcs10_object_set_key_usage, METH_VARARGS),
- Define_Method(getExtendedKeyUsage, pkcs10_object_get_extended_key_usage, METH_NOARGS),
- Define_Method(setExtendedKeyUsage, pkcs10_object_set_extended_key_usage, METH_VARARGS),
+ Define_Method(getEKU, pkcs10_object_get_eku, METH_NOARGS),
+ Define_Method(setEKU, pkcs10_object_set_eku, METH_VARARGS),
Define_Method(getBasicConstraints, pkcs10_object_get_basic_constraints, METH_NOARGS),
Define_Method(setBasicConstraints, pkcs10_object_set_basic_constraints, METH_VARARGS),
Define_Method(getSIA, pkcs10_object_get_sia, METH_NOARGS),
diff --git a/rpkid/rpki/exceptions.py b/rpkid/rpki/exceptions.py
index 7a0eb71c..d8d3774e 100644
--- a/rpkid/rpki/exceptions.py
+++ b/rpkid/rpki/exceptions.py
@@ -360,3 +360,8 @@ class BadAutonomousSystemNumber(RPKI_Exception):
"""
Bad AutonomousSystem number.
"""
+
+class WrongEKU(RPKI_Exception):
+ """
+ Extended Key Usage extension does not match profile.
+ """
diff --git a/rpkid/rpki/irdb/zookeeper.py b/rpkid/rpki/irdb/zookeeper.py
index bb52bddd..d2bd0c75 100644
--- a/rpkid/rpki/irdb/zookeeper.py
+++ b/rpkid/rpki/irdb/zookeeper.py
@@ -1589,7 +1589,7 @@ class Zookeeper(object):
@django.db.transaction.commit_on_success
- def add_ee_certificate_request(self, pkcs10, resources):
+ def add_ee_certificate_request(self, pkcs10, resources, kind = "ee"):
"""
Check a PKCS #10 request to see if it complies with the
specification for a RPKI EE certificate; if it does, add an
@@ -1600,7 +1600,7 @@ class Zookeeper(object):
.load_asns() and .load_prefixes() for other strategies.
"""
- pkcs10.check_valid_rpki(ee = True)
+ pkcs10.check_valid_rpki(kind = kind)
ee_request = self.resource_ca.ee_certificate_requests.create(
pkcs10 = pkcs10,
gski = pkcs10.gSKI(),
@@ -1613,28 +1613,40 @@ class Zookeeper(object):
ee_request.address_ranges.create(start_ip = str(range.min), end_ip = str(range.max), version = 6)
- def add_router_certificate_request(self, pkcs10, asn):
+ def add_router_certificate_request(self, pkcs10, *asns):
"""
Check a PKCS #10 request to see if it complies with the
specification for a router certificate; if it does, create an EE
certificate request for it along with a specified ASN.
"""
- if isinstance(asn, (str, unicode)):
- asn = long(asn)
- if not isinstance(asn, (int, long)) or asn < 0 or asn > 0xFFFFFFFF:
- raise rpki.exceptions.BadAutonomousSystemNumber("Bad AutonomousSystem number: %s" % asn)
+ asns = tuple(long(a) if isinstance(a, (str, unicode)) else a for a in asns)
+
+ if not asns or not all(isinstance(a, (int, long)) and a >= 0 and a <= 0xFFFFFFFF for a in asns):
+ raise rpki.exceptions.BadAutonomousSystemNumber("Bad AutonomousSystem number%s: %s" % (
+ "" if len(asns) == 1 else "s", ", ".join(repr(a) for a in asns)))
# This attempts to enforce draft-ietf-sidr-bgpsec-pki-profiles-06
# section 3.1.1.1, which may be a mistake, too early to tell.
+ #
+ # Upon further reading: this will have to go somewhere else,
+ # because the combination of draft-ietf-sidr-bgpsec-pki-profiles
+ # and RFC 6487 says that the subject-name-to-be can't be in the
+ # PKCS #10, it has to be carried separately like the ASNs. Save
+ # this code, refactor once I figure out where this belongs.
+
cn, sn = pkcs10.getSubject().extract_cn_and_sn()
- if not cn.startswith("ROUTER-") \
- or len(cn) != 7 + 8 \
- or not cn[7:].isalnum() \
- or int(cn[7:], 16) != asn \
- or not sn.isalnum() \
- or len(sn) != 8 \
- or int(sn, 16) > 0xFFFFFFFF:
+ if (not cn.startswith("ROUTER-") or
+ len(cn) != 7 + 8 or
+ not cn[7:].isalnum() or
+ int(cn[7:], 16) not in asns or
+ not sn.isalnum() or
+ len(sn) != 8 or
+ int(sn, 16) > 0xFFFFFFFF):
raise rpki.exceptions.BadX510DN("Subject name doesn't match router profile: %s" % pkcs10.getSubject())
+ eku = pkcs10.getEKU()
+ if eku is None or rpki.oids.name2oid["id-kp-bgpsec-router"] not in eku:
+ raise rpki.exceptions.WrongEKU("Router certificate EKU not present in request")
+
raise NotImplementedError, "Not finished"
diff --git a/rpkid/rpki/oids.py b/rpkid/rpki/oids.py
index 094fa1a2..1acc8035 100644
--- a/rpkid/rpki/oids.py
+++ b/rpkid/rpki/oids.py
@@ -1,41 +1,61 @@
# $Id$
#
-# 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
-# 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.
-#
+# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
#
# 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.
+# copyright notices and this permission notice appear in all copies.
#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ARIN 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.
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN 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.
"""
OID database.
"""
+def defoid(name, *numbers):
+ """
+ Define a new OID, including adding it to a few dictionaries and
+ making an entry for it in the rpki.oids module symbol table, so
+ other code can refer to it as an ordinary symbol.
+ """
+
+ assert all(isinstance(n, (int, long)) for n in numbers)
+
+ dotted = ".".join(str(n) for n in numbers)
+ name_ = name.replace("-", "_")
+
+ assert name_ not in globals()
+
+ global oid2name
+ oid2name[numbers] = name
+
+ globals()[name_] = dotted
+
+ global dotted2name
+ dotted2name[dotted] = name
+
+ global dotted2name_
+ dotted2name_[dotted] = name_
+
+ global name2dotted
+ name2dotted[name] = dotted
+ name2dotted[name_] = dotted
+
+
## @var oid2name
# Mapping table of OIDs to conventional string names.
oid2name = {
+ (1, 2, 840, 10045, 4, 3, 2) : "ecdsa-with-SHA256",
(1, 2, 840, 113549, 1, 1, 11) : "sha256WithRSAEncryption",
(1, 2, 840, 113549, 1, 1, 12) : "sha384WithRSAEncryption",
(1, 2, 840, 113549, 1, 1, 13) : "sha512WithRSAEncryption",
@@ -51,11 +71,12 @@ oid2name = {
(1, 3, 6, 1, 5, 5, 7, 1, 7) : "sbgp-ipAddrBlock",
(1, 3, 6, 1, 5, 5, 7, 1, 8) : "sbgp-autonomousSysNum",
(1, 3, 6, 1, 5, 5, 7, 14, 2) : "id-cp-ipAddr-asNumber",
+ (1, 3, 6, 1, 5, 5, 7, 3, 666) : "id-kp-bgpsec-router", # {id-kp, 666} -- Real value not known yet
+ (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest",
+ (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject",
(1, 3, 6, 1, 5, 5, 7, 48, 2) : "id-ad-caIssuers",
(1, 3, 6, 1, 5, 5, 7, 48, 5) : "id-ad-caRepository",
(1, 3, 6, 1, 5, 5, 7, 48, 9) : "id-ad-signedObjectRepository",
- (1, 3, 6, 1, 5, 5, 7, 48, 10) : "id-ad-rpkiManifest",
- (1, 3, 6, 1, 5, 5, 7, 48, 11) : "id-ad-signedObject",
(2, 16, 840, 1, 101, 3, 4, 2, 1) : "id-sha256",
(2, 5, 29, 14) : "subjectKeyIdentifier",
(2, 5, 29, 15) : "keyUsage",
@@ -65,14 +86,14 @@ oid2name = {
(2, 5, 29, 32) : "certificatePolicies",
(2, 5, 29, 35) : "authorityKeyIdentifier",
(2, 5, 29, 37) : "extendedKeyUsage",
+ (2, 5, 4, 10) : "organizationName",
+ (2, 5, 4, 11) : "organizationalUnitName",
(2, 5, 4, 3) : "commonName",
(2, 5, 4, 5) : "serialNumber",
(2, 5, 4, 6) : "countryName",
(2, 5, 4, 7) : "localityName",
(2, 5, 4, 8) : "stateOrProvinceName",
(2, 5, 4, 9) : "streetAddress",
- (2, 5, 4, 10) : "organizationName",
- (2, 5, 4, 11) : "organizationalUnitName",
}
## @var name2oid
diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py
index 2e09cb35..8d3ea634 100644
--- a/rpkid/rpki/x509.py
+++ b/rpkid/rpki/x509.py
@@ -864,7 +864,8 @@ class PKCS10(DER_object):
allowed_extensions = frozenset(rpki.oids.safe_name2dotted(name)
for name in ("basicConstraints",
"keyUsage",
- "subjectInfoAccess"))
+ "subjectInfoAccess",
+ "extendedKeyUsage"))
def get_DER(self):
"""
@@ -905,7 +906,7 @@ class PKCS10(DER_object):
"""
return self.getPublicKey().get_SKI()
- def check_valid_rpki(self, ee = False):
+ def check_valid_rpki(self, kind = "ca"):
"""
Check this certification request to see whether it's a valid
request for an RPKI certificate. This is broken out of the
@@ -915,13 +916,11 @@ class PKCS10(DER_object):
Throws an exception if the request isn't valid, so if this method
returns at all, the request is ok.
- At the moment, this only allows requests for CA certificates; as a
- direct consequence, it also rejects ExtendedKeyUsage, because the
- RPKI profile only allows EKU for EE certificates.
+ This needs refactoring, as the nested conditionals to handle the
+ different kinds of certificates have gotten rather nasty.
"""
- if ee:
- raise NotImplementedError("Haven't written EE-certificate checking yet, oops")
+ assert kind in ("ca", "ee", "router")
if not self.get_POW().verify():
raise rpki.exceptions.BadPKCS10("PKCS #10 signature check failed")
@@ -933,15 +932,19 @@ class PKCS10(DER_object):
alg = rpki.oids.safe_dotted2name(self.get_POW().getSignatureAlgorithm())
- if alg != "sha256WithRSAEncryption":
+ if alg != ("ecdsa-with-SHA256" if kind == "router" else "sha256WithRSAEncryption"):
raise rpki.exceptions.BadPKCS10("PKCS #10 request has bad signature algorithm %s" % alg)
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 bc[1] is not None:
+ if kind == "ca":
+ if bc is None or not bc[0]:
+ raise rpki.exceptions.BadPKCS10("Request for EE certificate not allowed here")
+ else:
+ if bc is not None and bc[0]:
+ raise rpki.exceptions.BadPKCS10("Request for CA certificate not allowed here")
+
+ if bc is not None and bc[1] is not None:
raise rpki.exceptions.BadPKCS10("PKCS #10 basicConstraints must not specify Path Length")
ku = self.get_POW().getKeyUsage()
@@ -949,37 +952,51 @@ class PKCS10(DER_object):
if ku is not None and self.expected_ca_keyUsage != ku:
raise rpki.exceptions.BadPKCS10("PKCS #10 keyUsage doesn't match basicConstraints: %r" % ku)
+ eku = self.get_POW().getEKU()
+
+ if kind == "ca" and eku is not None:
+ raise rpki.exceptions.BadPKCS10("EKU not allowed in CA certificate PKCS #10")
+ elif kind == "router" and (eku is None or rpki.oids.name2oid["id-kp-bgpsec-router"] not in eku):
+ raise rpki.exceptions.BadPKCS10("EKU required for router certificate PKCS #10")
+
if any(oid not in self.allowed_extensions
for oid in self.get_POW().getExtensionOIDs()):
raise rpki.exceptions.BadExtension("Forbidden extension(s) in PKCS #10 certificate request")
sias = self.get_POW().getSIA()
- if sias is None:
- raise rpki.exceptions.BadPKCS10("PKCS #10 is missing SIA extension")
+ if kind == "router":
- caRepository, rpkiManifest, signedObject = sias
+ if sias is not None:
+ raise rpki.exceptions.BadPKCS10("router certificate PKCS #10 must not contain SIA extension")
- if signedObject:
- raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request has SIA id-ad-signedObject")
+ elif kind == "ca":
- if not caRepository:
- raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request is missing SIA id-ad-caRepository")
+ if sias is None:
+ raise rpki.exceptions.BadPKCS10("PKCS #10 is missing SIA extension")
- if not any(uri.startswith("rsync://") for uri in caRepository):
- raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request SIA id-ad-caRepository contains no rsync URIs")
+ caRepository, rpkiManifest, signedObject = sias
- if not rpkiManifest:
- raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request is missing SIA id-ad-rpkiManifest")
-
- if not any(uri.startswith("rsync://") for uri in rpkiManifest):
- raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request SIA id-ad-rpkiManifest contains no rsync URIs")
+ if signedObject:
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request has SIA id-ad-signedObject")
+
+ if not caRepository:
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request is missing SIA id-ad-caRepository")
+
+ if not any(uri.startswith("rsync://") for uri in caRepository):
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request SIA id-ad-caRepository contains no rsync URIs")
+
+ if not rpkiManifest:
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request is missing SIA id-ad-rpkiManifest")
+
+ if not any(uri.startswith("rsync://") for uri in rpkiManifest):
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA 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("PKCS #10 CA certificate request SIA id-ad-caRepository does not end with slash")
+ if any(uri.startswith("rsync://") and not uri.endswith("/") for uri in caRepository):
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA 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("PKCS #10 CA certificate request SIA id-ad-rpkiManifest ends with slash")
+ if any(uri.startswith("rsync://") and uri.endswith("/") for uri in rpkiManifest):
+ raise rpki.exceptions.BadPKCS10("PKCS #10 CA certificate request SIA id-ad-rpkiManifest ends with slash")
@classmethod
def create(cls, keypair, exts = None, is_ca = False,