diff options
-rw-r--r-- | rpkid/ext/POW.c | 2 | ||||
-rw-r--r-- | rpkid/rpki/ipaddrs.py | 2 | ||||
-rw-r--r-- | rpkid/rpki/resource_set.py | 27 | ||||
-rw-r--r-- | rpkid/rpki/x509.py | 279 |
4 files changed, 193 insertions, 117 deletions
diff --git a/rpkid/ext/POW.c b/rpkid/ext/POW.c index 64bd479f..5e55a526 100644 --- a/rpkid/ext/POW.c +++ b/rpkid/ext/POW.c @@ -7114,7 +7114,7 @@ roa_object_sign(roa_object *self, PyObject *args) ENTERING(roa_object_sign); - if (!PyArg_ParseTuple(args, "O!O!s#|OOsI", + if (!PyArg_ParseTuple(args, "O!O!|OOsI", &POW_X509_Type, &signcert, &POW_Asymmetric_Type, &signkey, &x509_sequence, diff --git a/rpkid/rpki/ipaddrs.py b/rpkid/rpki/ipaddrs.py index a192f92b..9b67d0f0 100644 --- a/rpkid/rpki/ipaddrs.py +++ b/rpkid/rpki/ipaddrs.py @@ -52,6 +52,7 @@ class v4addr(long): """ bits = 32 + ipversion = 4 def __new__(cls, x): """ @@ -91,6 +92,7 @@ class v6addr(long): """ bits = 128 + ipversion = 6 def __new__(cls, x): """ diff --git a/rpkid/rpki/resource_set.py b/rpkid/rpki/resource_set.py index 0bc31ef2..ab18b226 100644 --- a/rpkid/rpki/resource_set.py +++ b/rpkid/rpki/resource_set.py @@ -10,7 +10,7 @@ We also provide some basic set operations (union, intersection, etc). $Id$ -Copyright (C) 2009--2010 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 @@ -39,8 +39,11 @@ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ -import re, math -import rpki.ipaddrs, rpki.oids, rpki.exceptions +import re +import math +import rpki.ipaddrs +import rpki.oids +import rpki.exceptions ## @var inherit_token # Token used to indicate inheritance in read and print syntax. @@ -967,6 +970,14 @@ class roa_prefix(object): return (_long2bs(self.prefix, self.range_type.datum_type.bits, prefixlen = self.prefixlen), None if self.prefixlen == self.max_prefixlen else self.max_prefixlen) + def to_POW_roa_tuple(self): + """ + Convert a resource_range_ip to rpki.POW.ROA.setPrefixes() format. + """ + return (rpki.POW.IPAddress(self.prefix, self.range_type.datum_type.ipversion), + self.prefixlen, + None if self.prefixlen == self.max_prefixlen else self.max_prefixlen) + @classmethod def parse_str(cls, x): """ @@ -1096,6 +1107,16 @@ class roa_prefix_set(list): else: return None + def to_POW_roa_tuple(self): + """ + Convert ROA prefix set to form used by rpki.POW.ROA.setPrefixes(). + """ + if self: + return tuple(a.to_POW_roa_tuple() for a in self) + else: + return None + + class roa_prefix_set_ipv4(roa_prefix_set): """ Set of IPv4 ROA prefixes. diff --git a/rpkid/rpki/x509.py b/rpkid/rpki/x509.py index 2ef60b17..e62ca52c 100644 --- a/rpkid/rpki/x509.py +++ b/rpkid/rpki/x509.py @@ -1183,21 +1183,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. @@ -1257,24 +1249,9 @@ class CMS_object(DER_object): """ self.check() if not self.POW: - self.POW = rpki.POW.CMS.derRead(self.get_DER()) + 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. @@ -1292,18 +1269,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(),)) @@ -1315,43 +1295,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) @@ -1368,8 +1357,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): """ @@ -1392,12 +1380,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): """ @@ -1419,21 +1408,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())) + rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % ( + i, c.getIssuer(), c.getSubject(), c.hSKI())) - cms = rpki.POW.CMS() - - 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): @@ -1443,24 +1428,98 @@ 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. + """ + 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 Wrapped_CMS_object, clearing the wrapper. + """ + 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): """ - return self.get_content().toString() + Extract and store inner content from CMS wrapper without verifying + the CMS. - def decode(self, der): + 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. """ - Decode DER and set inner content. + + 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. """ - obj = self.content_class() - obj.fromString(der) - self.content = obj + + 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): + """ + Class to hold CMS objects with DER-based 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. + """ + + rpki.log.debug("DER_CMS_object._sign()") + rpki.log.debug("self: %r" % self) + rpki.log.debug("self.POW: %r" % self.get_POW()) + rpki.log.debug("cert: %r" % cert) + rpki.log.debug("keypair: %r" % keypair) + rpki.log.debug("certs, crls: %r, %r" % (certs, crls)) + rpki.log.debug("OID: %r" % (self.econtent_oid,)) + rpki.log.debug("flags: %r" % flags) + + try: + self.get_POW().sign(cert, keypair, certs, crls, self.econtent_oid, flags) + except Exception, e: + rpki.log.debug("%r.sign() threw exception %s (%r)" % (self.get_POW(), e, e)) + raise + class SignedManifest(DER_CMS_object): """ @@ -1470,39 +1529,42 @@ 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 rpki.sundial.datetime.fromGeneralizedTime(self.get_POW().getThisUpdate()) def getNextUpdate(self): """ Get nextUpdate value from this manifest. """ - return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get()) + return rpki.sundial.datetime.fromGeneralizedTime(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) + + pow = cls.POW_class() + pow.setVersion(version) + pow.setManifestNumber(serial) + pow.setThisUpdate(thisUpdate.toGeneralizedTime()) + pow.setNextUpdate(nextUpdate.toGeneralizedTime()) + pow.setAlgorithm(POWify_OID(rpki.oids.name2oid["id-sha256"])) + pow.addFiles(filelist) + + self = cls(POW = pow) self.sign(keypair, certs) return self @@ -1514,29 +1576,22 @@ 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 + pow = cls.POW_class() + pow.setVersion(version) + pow.setASID(asn) + pow.setPrefixes(ipv4 = ipv4, ipv6 = ipv6) + self = cls(POW = pow) + self.sign(keypair, certs) + return self def tracking_data(self, uri): """ @@ -1545,23 +1600,21 @@ 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))) + 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: pass return msg @@ -1597,7 +1650,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. """ @@ -1727,7 +1780,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 |