RPKI Engine 1.0

x509.py (3869)

Go to the documentation of this file.
00001 """
00002 One X.509 implementation to rule them all...
00003 
00004 ...and in the darkness hide the twisty maze of partially overlapping
00005 X.509 support packages in Python.
00006 
00007 There are several existing packages, none of which do quite what I
00008 need, due to age, lack of documentation, specialization, or lack of
00009 foresight on somebody's part (perhaps mine).  This module attempts to
00010 bring together the functionality I need in a way that hides at least
00011 some of the nasty details.  This involves a lot of format conversion.
00012 
00013 $Id: x509.py 3869 2011-06-14 13:17:13Z melkins $
00014 
00015 
00016 Copyright (C) 2009--2011  Internet Systems Consortium ("ISC")
00017 
00018 Permission to use, copy, modify, and distribute this software for any
00019 purpose with or without fee is hereby granted, provided that the above
00020 copyright notice and this permission notice appear in all copies.
00021 
00022 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00023 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00024 AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00025 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00026 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00027 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00028 PERFORMANCE OF THIS SOFTWARE.
00029 
00030 
00031 Portions copyright (C) 2007--2008  American Registry for Internet Numbers ("ARIN")
00032 
00033 Permission to use, copy, modify, and distribute this software for any
00034 purpose with or without fee is hereby granted, provided that the above
00035 copyright notice and this permission notice appear in all copies.
00036 
00037 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00038 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00039 AND FITNESS.  IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00040 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00041 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00042 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00043 PERFORMANCE OF THIS SOFTWARE.
00044 """
00045 
00046 import rpki.POW, rpki.POW.pkix, base64, lxml.etree, os, subprocess, sys
00047 import email.mime.application, email.utils, mailbox, time
00048 import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial
00049 import rpki.manifest, rpki.roa, rpki.log, rpki.async, rpki.ghostbuster
00050 
00051 def base64_with_linebreaks(der):
00052   """
00053   Encode DER (really, anything) as Base64 text, with linebreaks to
00054   keep the result (sort of) readable.
00055   """
00056   b = base64.b64encode(der)
00057   n = len(b)
00058   return "\n" + "\n".join(b[i : min(i + 64, n)] for i in xrange(0, n, 64)) + "\n"
00059 
00060 def calculate_SKI(public_key_der):
00061   """
00062   Calculate the SKI value given the DER representation of a public
00063   key, which requires first peeling the ASN.1 wrapper off the key.
00064   """
00065   k = rpki.POW.pkix.SubjectPublicKeyInfo()
00066   k.fromString(public_key_der)
00067   d = rpki.POW.Digest(rpki.POW.SHA1_DIGEST)
00068   d.update(k.subjectPublicKey.get())
00069   return d.digest()
00070 
00071 class PEM_converter(object):
00072   """
00073   Convert between DER and PEM encodings for various kinds of ASN.1 data.
00074   """
00075 
00076   def __init__(self, kind):    # "CERTIFICATE", "RSA PRIVATE KEY", ...
00077     """
00078     Initialize PEM_converter.
00079     """
00080     self.b = "-----BEGIN %s-----" % kind
00081     self.e = "-----END %s-----"   % kind
00082 
00083   def looks_like_PEM(self, text):
00084     """
00085     Guess whether text looks like a PEM encoding.
00086     """
00087     b = text.find(self.b)
00088     return b >= 0 and text.find(self.e) > b + len(self.b)
00089 
00090   def to_DER(self, pem):
00091     """
00092     Convert from PEM to DER.
00093     """
00094     lines = [line.strip() for line in pem.splitlines(0)]
00095     while lines and lines.pop(0) != self.b:
00096       pass
00097     while lines and lines.pop(-1) != self.e:
00098       pass
00099     if not lines:
00100       raise rpki.exceptions.EmptyPEM, "Could not find PEM in:\n%s" % pem
00101     return base64.b64decode("".join(lines))
00102 
00103   def to_PEM(self, der):
00104     """
00105     Convert from DER to PEM.
00106     """
00107     return self.b + base64_with_linebreaks(der) + self.e + "\n"
00108 
00109 def _find_xia_uri(extension, name):
00110   """
00111   Find a rsync URI in an SIA or AIA extension.
00112   Returns the URI if found, otherwise None.
00113   """
00114   oid = rpki.oids.name2oid[name]
00115 
00116   # extension may be None if the AIA is not present
00117   if extension:
00118     for method, location in extension:
00119       if method == oid and location[0] == "uri" and location[1].startswith("rsync://"):
00120         return location[1]
00121   return None
00122 
00123 class DER_object(object):
00124   """
00125   Virtual class to hold a generic DER object.
00126   """
00127 
00128   ## Formats supported in this object
00129   formats = ("DER",)
00130 
00131   ## PEM converter for this object
00132   pem_converter = None
00133 
00134   ## Other attributes that self.clear() should whack
00135   other_clear = ()
00136 
00137   ## @var DER
00138   ## DER value of this object
00139 
00140   def empty(self):
00141     """
00142     Test whether this object is empty.
00143     """
00144     return all(getattr(self, a, None) is None for a in self.formats)
00145 
00146   def clear(self):
00147     """
00148     Make this object empty.
00149     """
00150     for a in self.formats + self.other_clear:
00151       setattr(self, a, None)
00152     self.filename = None
00153     self.timestamp = None
00154 
00155   def __init__(self, **kw):
00156     """
00157     Initialize a DER_object.
00158     """
00159     self.clear()
00160     if len(kw):
00161       self.set(**kw)
00162 
00163   def set(self, **kw):
00164     """
00165     Set this object by setting one of its known formats.
00166 
00167     This method only allows one to set one format at a time.
00168     Subsequent calls will clear the object first.  The point of all
00169     this is to let the object's internal converters handle mustering
00170     the object into whatever format you need at the moment.
00171     """
00172 
00173     if len(kw) == 1:
00174       name = kw.keys()[0]
00175       if name in self.formats:
00176         self.clear()
00177         setattr(self, name, kw[name])
00178         return
00179       if name == "PEM":
00180         self.clear()
00181         self.DER = self.pem_converter.to_DER(kw[name])
00182         return
00183       if name == "Base64":
00184         self.clear()
00185         self.DER = base64.b64decode(kw[name])
00186         return
00187       if name == "Auto_update":
00188         self.filename = kw[name]
00189         self.check_auto_update()
00190         return
00191       if name in ("PEM_file", "DER_file", "Auto_file"):
00192         f = open(kw[name], "rb")
00193         value = f.read()
00194         f.close()
00195         if name == "PEM_file" or (name == "Auto_file" and self.pem_converter.looks_like_PEM(value)):
00196           value = self.pem_converter.to_DER(value)
00197         self.clear()
00198         self.DER = value
00199         return
00200     raise rpki.exceptions.DERObjectConversionError, "Can't honor conversion request %r" % (kw,)
00201   
00202   def check_auto_update(self):
00203     """
00204     Check for updates to a DER object that auto-updates from a file.
00205     """
00206     if self.filename is None:
00207       return
00208     filename = self.filename
00209     timestamp = os.stat(self.filename).st_mtime
00210     if self.timestamp is None or self.timestamp < timestamp:
00211       rpki.log.debug("Updating %s, timestamp %s" % (filename, rpki.sundial.datetime.fromtimestamp(timestamp)))
00212       f = open(filename, "rb")
00213       value = f.read()
00214       f.close()
00215       if self.pem_converter.looks_like_PEM(value):
00216         value = self.pem_converter.to_DER(value)
00217       self.clear()
00218       self.DER = value
00219       self.filename = filename
00220       self.timestamp = timestamp
00221 
00222   def check(self):
00223     """
00224     Perform basic checks on a DER object.
00225     """
00226     assert not self.empty()
00227     self.check_auto_update()
00228 
00229   def get_DER(self):
00230     """
00231     Get the DER value of this object.
00232 
00233     Subclasses will almost certainly override this method.
00234     """
00235     self.check()
00236     if self.DER:
00237       return self.DER
00238     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00239 
00240   def get_Base64(self):
00241     """
00242     Get the Base64 encoding of the DER value of this object.
00243     """
00244     return base64_with_linebreaks(self.get_DER())
00245 
00246   def get_PEM(self):
00247     """
00248     Get the PEM representation of this object.
00249     """
00250     return self.pem_converter.to_PEM(self.get_DER())
00251 
00252   def __cmp__(self, other):
00253     """
00254     Compare two DER-encoded objects.
00255     """
00256     if self is None and other is None:
00257       return 0
00258     elif self is None:
00259       return -1
00260     elif other is None:
00261       return 1
00262     else:
00263       return cmp(self.get_DER(), other.get_DER())
00264 
00265   def hSKI(self):
00266     """
00267     Return hexadecimal string representation of SKI for this object.
00268     Only work for subclasses that implement get_SKI().
00269     """
00270     ski = self.get_SKI()
00271     return ":".join(("%02X" % ord(i) for i in ski)) if ski else ""
00272 
00273   def gSKI(self):
00274     """
00275     Calculate g(SKI) for this object.  Only work for subclasses
00276     that implement get_SKI().
00277     """
00278     return base64.urlsafe_b64encode(self.get_SKI()).rstrip("=")
00279 
00280   def hAKI(self):
00281     """
00282     Return hexadecimal string representation of AKI for this
00283     object.  Only work for subclasses that implement get_AKI().
00284     """
00285     aki = self.get_AKI()
00286     return ":".join(("%02X" % ord(i) for i in aki)) if aki else ""
00287 
00288   def gAKI(self):
00289     """
00290     Calculate g(AKI) for this object.  Only work for subclasses
00291     that implement get_AKI().
00292     """
00293     return base64.urlsafe_b64encode(self.get_AKI()).rstrip("=")
00294 
00295   def get_AKI(self):
00296     """
00297     Get the AKI extension from this object.  Only works for subclasses
00298     that support getExtension().
00299     """
00300     aki = (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityKeyIdentifier"]) or ((), 0, None))[2]
00301     return aki[0] if isinstance(aki, tuple) else aki
00302 
00303   def get_SKI(self):
00304     """
00305     Get the SKI extension from this object.  Only works for subclasses
00306     that support getExtension().
00307     """
00308     return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectKeyIdentifier"]) or ((), 0, None))[2]
00309 
00310   def get_SIA(self):
00311     """
00312     Get the SIA extension from this object.  Only works for subclasses
00313     that support getExtension().
00314     """
00315     return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2]
00316 
00317   def get_sia_directory_uri(self):
00318     """
00319     Get SIA directory (id-ad-caRepository) URI from this object.
00320     Only works for subclasses that support getExtension().
00321     """
00322     return _find_xia_uri(self.get_SIA(), "id-ad-caRepository")
00323 
00324   def get_sia_manifest_uri(self):
00325     """
00326     Get SIA manifest (id-ad-rpkiManifest) URI from this object.
00327     Only works for subclasses that support getExtension().
00328     """
00329     return _find_xia_uri(self.get_SIA(), "id-ad-rpkiManifest")
00330 
00331   def get_AIA(self):
00332     """
00333     Get the SIA extension from this object.  Only works for subclasses
00334     that support getExtension().
00335     """
00336     return (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityInfoAccess"]) or ((), 0, None))[2]
00337 
00338   def get_aia_uri(self):
00339     """
00340     Get AIA (id-ad-caIssuers) URI from this object.
00341     Only works for subclasses that support getExtension().
00342     """
00343     return _find_xia_uri(self.get_AIA(), "id-ad-caIssuers")
00344 
00345   def get_basicConstraints(self):
00346     """
00347     Get the basicConstraints extension from this object.  Only works
00348     for subclasses that support getExtension().
00349     """
00350     return (self.get_POWpkix().getExtension(rpki.oids.name2oid["basicConstraints"]) or ((), 0, None))[2]
00351 
00352   def is_CA(self):
00353     """
00354     Return True if and only if object has the basicConstraints
00355     extension and its cA value is true.
00356     """
00357     basicConstraints = self.get_basicConstraints()
00358     return basicConstraints and basicConstraints[0] != 0
00359 
00360   def get_3779resources(self):
00361     """
00362     Get RFC 3779 resources as rpki.resource_set objects.  Only works
00363     for subclasses that support getExtensions().
00364     """
00365     resources = rpki.resource_set.resource_bag.from_rfc3779_tuples(self.get_POWpkix().getExtensions())
00366     try:
00367       resources.valid_until = self.getNotAfter()
00368     except AttributeError:
00369       pass
00370     return resources
00371 
00372   @classmethod
00373   def from_sql(cls, x):
00374     """
00375     Convert from SQL storage format.
00376     """
00377     return cls(DER = x)
00378 
00379   def to_sql(self):
00380     """
00381     Convert to SQL storage format.
00382     """
00383     return self.get_DER()
00384 
00385   def dumpasn1(self):
00386     """
00387     Pretty print an ASN.1 DER object using cryptlib dumpasn1 tool.
00388     Use a temporary file rather than popen4() because dumpasn1 uses
00389     seek() when decoding ASN.1 content nested in OCTET STRING values.
00390     """
00391 
00392     ret = None
00393     fn = "dumpasn1.%d.tmp" % os.getpid()
00394     try:
00395       f = open(fn, "wb")
00396       f.write(self.get_DER())
00397       f.close()
00398       p = subprocess.Popen(("dumpasn1", "-a", fn), stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
00399       ret = "\n".join(x for x in p.communicate()[0].splitlines() if x.startswith(" "))
00400     except Exception, e:
00401       ret = "[Could not run dumpasn1: %s]" % e
00402     finally:
00403       os.unlink(fn)
00404     return ret
00405 
00406 class X509(DER_object):
00407   """
00408   X.509 certificates.
00409 
00410   This class is designed to hold all the different representations of
00411   X.509 certs we're using and convert between them.  X.509 support in
00412   Python a nasty maze of half-cooked stuff (except perhaps for
00413   cryptlib, which is just different).  Users of this module should not
00414   have to care about this implementation nightmare.
00415   """
00416 
00417   formats = ("DER", "POW", "POWpkix")
00418   pem_converter = PEM_converter("CERTIFICATE")
00419   
00420   def get_DER(self):
00421     """
00422     Get the DER value of this certificate.
00423     """
00424     self.check()
00425     if self.DER:
00426       return self.DER
00427     if self.POW:
00428       self.DER = self.POW.derWrite()
00429       return self.get_DER()
00430     if self.POWpkix:
00431       self.DER = self.POWpkix.toString()
00432       return self.get_DER()
00433     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00434 
00435   def get_POW(self):
00436     """
00437     Get the rpki.POW value of this certificate.
00438     """
00439     self.check()
00440     if not self.POW:
00441       self.POW = rpki.POW.derRead(rpki.POW.X509_CERTIFICATE, self.get_DER())
00442     return self.POW
00443 
00444   def get_POWpkix(self):
00445     """
00446     Get the rpki.POW.pkix value of this certificate.
00447     """
00448     self.check()
00449     if not self.POWpkix:
00450       cert = rpki.POW.pkix.Certificate()
00451       cert.fromString(self.get_DER())
00452       self.POWpkix = cert
00453     return self.POWpkix
00454 
00455   def getIssuer(self):
00456     """
00457     Get the issuer of this certificate.
00458     """
00459     return "".join("/%s=%s" % rdn for rdn in self.get_POW().getIssuer())
00460 
00461   def getSubject(self):
00462     """
00463     Get the subject of this certificate.
00464     """
00465     return "".join("/%s=%s" % rdn for rdn in self.get_POW().getSubject())
00466 
00467   def getNotBefore(self):
00468     """
00469     Get the inception time of this certificate.
00470     """
00471     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notBefore.get())
00472 
00473   def getNotAfter(self):
00474     """
00475     Get the expiration time of this certificate.
00476     """
00477     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notAfter.get())
00478 
00479   def getSerial(self):
00480     """
00481     Get the serial number of this certificate.
00482     """
00483     return self.get_POW().getSerial()
00484 
00485   def getPublicKey(self):
00486     """
00487     Extract the public key from this certificate.
00488     """
00489     return RSApublic(DER = self.get_POWpkix().tbs.subjectPublicKeyInfo.toString())
00490 
00491   def expired(self):
00492     """
00493     Test whether this certificate has expired.
00494     """
00495     return self.getNotAfter() <= rpki.sundial.now()
00496 
00497   def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter,
00498             cn = None, resources = None, is_ca = True):
00499     """
00500     Issue a certificate.
00501     """
00502 
00503     now = rpki.sundial.now()
00504     aki = self.get_SKI()
00505     ski = subject_key.get_SKI()
00506 
00507     if cn is None:
00508       cn = "".join(("%02X" % ord(i) for i in ski))
00509 
00510     # if notAfter is None: notAfter = now + rpki.sundial.timedelta(days = 30)
00511 
00512     cert = rpki.POW.pkix.Certificate()
00513     cert.setVersion(2)
00514     cert.setSerial(serial)
00515     cert.setIssuer(self.get_POWpkix().getSubject())
00516     cert.setSubject((((rpki.oids.name2oid["commonName"], ("printableString", cn)),),))
00517     cert.setNotBefore(now.toASN1tuple())
00518     cert.setNotAfter(notAfter.toASN1tuple())
00519     cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
00520 
00521     exts = [ ["subjectKeyIdentifier",   False, ski],
00522              ["authorityKeyIdentifier", False, (aki, (), None)],
00523              ["cRLDistributionPoints",  False, ((("fullName", (("uri", crldp),)), None, ()),)],
00524              ["authorityInfoAccess",    False, ((rpki.oids.name2oid["id-ad-caIssuers"], ("uri", aia)),)],
00525              ["certificatePolicies",    True,  ((rpki.oids.name2oid["id-cp-ipAddr-asNumber"], ()),)] ]
00526 
00527     if is_ca:
00528       exts.append(["basicConstraints",  True,  (1, None)])
00529       exts.append(["keyUsage",          True,  (0, 0, 0, 0, 0, 1, 1)])
00530     else:
00531       exts.append(["keyUsage",          True,  (1,)])
00532 
00533     if sia is not None:
00534       exts.append(["subjectInfoAccess", False, sia])
00535     else:
00536       assert not is_ca
00537 
00538     # This next bit suggests that perhaps .to_rfc3779_tuple() should
00539     # be raising an exception when there are no resources rather than
00540     # returning None.  Maybe refactor later.
00541 
00542     if resources is not None:
00543       r = resources.asn.to_rfc3779_tuple()
00544       if r is not None:
00545         exts.append(["sbgp-autonomousSysNum", True, (r, None)])
00546       r = [x for x in (resources.v4.to_rfc3779_tuple(), resources.v6.to_rfc3779_tuple()) if x is not None]
00547       if r:
00548         exts.append(["sbgp-ipAddrBlock", True, r])
00549 
00550     for x in exts:
00551       x[0] = rpki.oids.name2oid[x[0]]
00552     cert.setExtensions(exts)
00553 
00554     cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
00555 
00556     return X509(POWpkix = cert)
00557 
00558   def cross_certify(self, keypair, source_cert, serial, notAfter, now = None, pathLenConstraint = 0):
00559     """
00560     Issue a certificate with values taking from an existing certificate.
00561     This is used to construct some kinds oF BPKI certificates.
00562     """
00563 
00564     if now is None:
00565       now = rpki.sundial.now()
00566 
00567     assert isinstance(pathLenConstraint, int) and pathLenConstraint >= 0
00568 
00569     cert = rpki.POW.pkix.Certificate()
00570     cert.setVersion(2)
00571     cert.setSerial(serial)
00572     cert.setIssuer(self.get_POWpkix().getSubject())
00573     cert.setSubject(source_cert.get_POWpkix().getSubject())
00574     cert.setNotBefore(now.toASN1tuple())
00575     cert.setNotAfter(notAfter.toASN1tuple())
00576     cert.tbs.subjectPublicKeyInfo.set(
00577       source_cert.get_POWpkix().tbs.subjectPublicKeyInfo.get())
00578     cert.setExtensions((
00579       (rpki.oids.name2oid["subjectKeyIdentifier"  ], False, source_cert.get_SKI()),
00580       (rpki.oids.name2oid["authorityKeyIdentifier"], False, (self.get_SKI(), (), None)),
00581       (rpki.oids.name2oid["basicConstraints"      ], True,  (1, 0))))
00582     cert.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
00583 
00584     return X509(POWpkix = cert)
00585 
00586   @classmethod
00587   def normalize_chain(cls, chain):
00588     """
00589     Normalize a chain of certificates into a tuple of X509 objects.
00590     Given all the glue certificates needed for BPKI cross
00591     certification, it's easiest to allow sloppy arguments to the CMS
00592     validation methods and provide a single method that normalizes the
00593     allowed cases.  So this method allows X509, None, lists, and
00594     tuples, and returns a tuple of X509 objects.
00595     """
00596     if isinstance(chain, cls):
00597       chain = (chain,)
00598     return tuple(x for x in chain if x is not None)
00599 
00600 class PKCS10(DER_object):
00601   """
00602   Class to hold a PKCS #10 request.
00603   """
00604 
00605   formats = ("DER", "POWpkix")
00606   pem_converter = PEM_converter("CERTIFICATE REQUEST")
00607   
00608   def get_DER(self):
00609     """
00610     Get the DER value of this certification request.
00611     """
00612     self.check()
00613     if self.DER:
00614       return self.DER
00615     if self.POWpkix:
00616       self.DER = self.POWpkix.toString()
00617       return self.get_DER()
00618     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00619 
00620   def get_POWpkix(self):
00621     """
00622     Get the rpki.POW.pkix value of this certification request.
00623     """
00624     self.check()
00625     if not self.POWpkix:
00626       req = rpki.POW.pkix.CertificationRequest()
00627       req.fromString(self.get_DER())
00628       self.POWpkix = req
00629     return self.POWpkix
00630 
00631   def getPublicKey(self):
00632     """
00633     Extract the public key from this certification request.
00634     """
00635     return RSApublic(DER = self.get_POWpkix().certificationRequestInfo.subjectPublicKeyInfo.toString())
00636 
00637   def check_valid_rpki(self):
00638     """
00639     Check this certification request to see whether it's a valid
00640     request for an RPKI certificate.  This is broken out of the
00641     up-down protocol code because it's somewhat involved and the
00642     up-down code doesn't need to know the details.
00643 
00644     Throws an exception if the request isn't valid, so if this method
00645     returns at all, the request is ok.
00646     """
00647 
00648     if not self.get_POWpkix().verify():
00649       raise rpki.exceptions.BadPKCS10, "Signature check failed"
00650 
00651     if self.get_POWpkix().certificationRequestInfo.version.get() != 0:
00652       raise rpki.exceptions.BadPKCS10, \
00653             "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version
00654 
00655     if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) \
00656          not in ("sha256WithRSAEncryption", "sha384WithRSAEncryption", "sha512WithRSAEncryption"):
00657       raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm
00658 
00659     exts = self.get_POWpkix().getExtensions()
00660     for oid, critical, value in exts:
00661       if rpki.oids.oid2name.get(oid) not in ("basicConstraints", "keyUsage", "subjectInfoAccess"):
00662         raise rpki.exceptions.BadExtension, "Forbidden extension %s" % oid
00663     req_exts = dict((rpki.oids.oid2name[oid], value) for (oid, critical, value) in exts)
00664 
00665     if "basicConstraints" not in req_exts or not req_exts["basicConstraints"][0]:
00666       raise rpki.exceptions.BadPKCS10, "request for EE cert not allowed here"
00667 
00668     if req_exts["basicConstraints"][1] is not None:
00669       raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length"
00670 
00671     if "keyUsage" in req_exts and (not req_exts["keyUsage"][5] or not req_exts["keyUsage"][6]):
00672       raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints"
00673 
00674     for method, location in req_exts.get("subjectInfoAccess", ()):
00675       if rpki.oids.oid2name.get(method) == "id-ad-caRepository" and \
00676            (location[0] != "uri" or (location[1].startswith("rsync://") and not location[1].endswith("/"))):
00677         raise rpki.exceptions.BadPKCS10, "Certificate request includes bad SIA component: %r" % location
00678 
00679     # This one is an implementation restriction.  I don't yet
00680     # understand what the spec is telling me to do in this case.
00681     assert "subjectInfoAccess" in req_exts, "Can't (yet) handle PKCS #10 without an SIA extension"
00682 
00683   @classmethod
00684   def create_ca(cls, keypair, sia = None):
00685     """
00686     Create a new request for a given keypair, including given SIA value.
00687     """
00688     exts = [["basicConstraints", True, (1, None)],
00689             ["keyUsage",         True, (0, 0, 0, 0, 0, 1, 1)]]
00690     if sia is not None:
00691       exts.append(["subjectInfoAccess", False, sia])
00692     for x in exts:
00693       x[0] = rpki.oids.name2oid[x[0]]
00694     return cls.create(keypair, exts)
00695 
00696   @classmethod
00697   def create(cls, keypair, exts = None):
00698     """
00699     Create a new request for a given keypair, including given extensions.
00700     """
00701     cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI()))
00702     req = rpki.POW.pkix.CertificationRequest()
00703     req.certificationRequestInfo.version.set(0)
00704     req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"],
00705                                                 ("printableString", cn)),),))
00706     if exts is not None:
00707       req.setExtensions(exts)
00708     req.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
00709     return cls(POWpkix = req)
00710 
00711 class RSA(DER_object):
00712   """
00713   Class to hold an RSA key pair.
00714   """
00715 
00716   formats = ("DER", "POW")
00717   pem_converter = PEM_converter("RSA PRIVATE KEY")
00718   
00719   def get_DER(self):
00720     """
00721     Get the DER value of this keypair.
00722     """
00723     self.check()
00724     if self.DER:
00725       return self.DER
00726     if self.POW:
00727       self.DER = self.POW.derWrite(rpki.POW.RSA_PRIVATE_KEY)
00728       return self.get_DER()
00729     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00730 
00731   def get_POW(self):
00732     """
00733     Get the rpki.POW value of this keypair.
00734     """
00735     self.check()
00736     if not self.POW:
00737       self.POW = rpki.POW.derRead(rpki.POW.RSA_PRIVATE_KEY, self.get_DER())
00738     return self.POW
00739 
00740   @classmethod
00741   def generate(cls, keylength = 2048):
00742     """
00743     Generate a new keypair.
00744     """
00745     rpki.log.debug("Generating new %d-bit RSA key" % keylength)
00746     return cls(POW = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, keylength))
00747 
00748   def get_public_DER(self):
00749     """
00750     Get the DER encoding of the public key from this keypair.
00751     """
00752     return self.get_POW().derWrite(rpki.POW.RSA_PUBLIC_KEY)
00753 
00754   def get_SKI(self):
00755     """
00756     Calculate the SKI of this keypair.
00757     """
00758     return calculate_SKI(self.get_public_DER())
00759 
00760   def get_RSApublic(self):
00761     """
00762     Convert the public key of this keypair into a RSApublic object.
00763     """
00764     return RSApublic(DER = self.get_public_DER())
00765 
00766 class RSApublic(DER_object):
00767   """
00768   Class to hold an RSA public key.
00769   """
00770 
00771   formats = ("DER", "POW")
00772   pem_converter = PEM_converter("RSA PUBLIC KEY")
00773   
00774   def get_DER(self):
00775     """
00776     Get the DER value of this public key.
00777     """
00778     self.check()
00779     if self.DER:
00780       return self.DER
00781     if self.POW:
00782       self.DER = self.POW.derWrite(rpki.POW.RSA_PUBLIC_KEY)
00783       return self.get_DER()
00784     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00785 
00786   def get_POW(self):
00787     """
00788     Get the rpki.POW value of this public key.
00789     """
00790     self.check()
00791     if not self.POW:
00792       self.POW = rpki.POW.derRead(rpki.POW.RSA_PUBLIC_KEY, self.get_DER())
00793     return self.POW
00794 
00795   def get_SKI(self):
00796     """
00797     Calculate the SKI of this public key.
00798     """
00799     return calculate_SKI(self.get_DER())
00800 
00801 def POWify_OID(oid):
00802   """
00803   Utility function to convert tuple form of an OID to the
00804   dotted-decimal string form that rpki.POW uses.
00805   """
00806   if isinstance(oid, str):
00807     return POWify_OID(rpki.oids.name2oid[oid])
00808   else:
00809     return ".".join(str(i) for i in oid)
00810 
00811 class CMS_object(DER_object):
00812   """
00813   Class to hold a CMS-wrapped object.
00814 
00815   CMS-wrapped objects are a little different from the other DER_object
00816   types because the signed object is CMS wrapping inner content that's
00817   also ASN.1, and due to our current minimal support for CMS we can't
00818   just handle this as a pretty composite object.  So, for now anyway,
00819   a CMS_object is the outer CMS wrapped object so that the usual DER
00820   and PEM operations do the obvious things, and the inner content is
00821   handle via separate methods.
00822   """
00823 
00824   formats = ("DER", "POW")
00825   other_clear = ("content",)
00826   econtent_oid = POWify_OID("id-data")
00827   pem_converter = PEM_converter("CMS")
00828 
00829   ## @var dump_on_verify_failure
00830   # Set this to True to get dumpasn1 dumps of ASN.1 on CMS verify failures.
00831 
00832   dump_on_verify_failure = True
00833 
00834   ## @var debug_cms_certs
00835   # Set this to True to log a lot of chatter about CMS certificates.
00836 
00837   debug_cms_certs = False
00838 
00839   ## @var require_crls
00840   # Set this to False to make CMS CRLs optional in the cases where we
00841   # would otherwise require them.  Some day this option should go away
00842   # and CRLs should be uncondtionally mandatory in such cases.
00843 
00844   require_crls = False
00845   
00846   ## @var print_on_der_error
00847   # Set this to True to log alleged DER when we have trouble parsing
00848   # it, in case it's really a Perl backtrace or something.
00849 
00850   print_on_der_error = True
00851 
00852   def get_DER(self):
00853     """
00854     Get the DER value of this CMS_object.
00855     """
00856     self.check()
00857     if self.DER:
00858       return self.DER
00859     if self.POW:
00860       self.DER = self.POW.derWrite()
00861       return self.get_DER()
00862     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00863 
00864   def get_POW(self):
00865     """
00866     Get the rpki.POW value of this CMS_object.
00867     """
00868     self.check()
00869     if not self.POW:
00870       self.POW = rpki.POW.derRead(rpki.POW.CMS_MESSAGE, self.get_DER())
00871     return self.POW
00872 
00873   def get_content(self):
00874     """
00875     Get the inner content of this CMS_object.
00876     """
00877     if self.content is None:
00878       raise rpki.exceptions.CMSContentNotSet, "Inner content of CMS object %r is not set" % self
00879     return self.content
00880 
00881   def set_content(self, content):
00882     """
00883     Set the (inner) content of this CMS_object, clearing the wrapper.
00884     """
00885     self.clear()
00886     self.content = content
00887 
00888   def get_signingTime(self):
00889     """
00890     Extract signingTime from CMS signed attributes.
00891     """
00892     return rpki.sundial.datetime.fromGeneralizedTime(self.get_POW().signingTime())
00893 
00894   def verify(self, ta):
00895     """
00896     Verify CMS wrapper and store inner content.
00897     """
00898 
00899     try:
00900       cms = self.get_POW()
00901     except (rpki.async.ExitNow, SystemExit):
00902       raise
00903     except:
00904       if self.print_on_der_error:
00905         rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %r" % self.get_DER())
00906       raise rpki.exceptions.UnparsableCMSDER
00907 
00908     if cms.eContentType() != self.econtent_oid:
00909       raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00910 
00911     certs = [X509(POW = x) for x in cms.certs()]
00912     crls  = [CRL(POW = c) for c in cms.crls()]
00913 
00914     if self.debug_cms_certs:
00915       for x in certs:
00916         rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00917       for c in crls:
00918         rpki.log.debug("Received CMS CRL issuer %r" % (c.getIssuer(),))
00919 
00920     store = rpki.POW.X509Store()
00921 
00922     trusted_ee = None
00923 
00924     for x in X509.normalize_chain(ta):
00925       if self.debug_cms_certs:
00926         rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00927       if not x.is_CA():
00928         assert trusted_ee is None, "Can't have two EE certs in the same validation chain"
00929         trusted_ee = x
00930       store.addTrust(x.get_POW())
00931 
00932     if trusted_ee:
00933       if self.debug_cms_certs:
00934         rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
00935       if certs and (len(certs) > 1 or certs[0].getSubject() != trusted_ee.getSubject() or certs[0].getPublicKey() != trusted_ee.getPublicKey()):
00936         raise rpki.exceptions.UnexpectedCMSCerts, certs
00937       if crls:
00938         raise rpki.exceptions.UnexpectedCMSCRLs, crls
00939     else:
00940       if not certs:
00941         raise rpki.exceptions.MissingCMSEEcert, certs        
00942       if len(certs) > 1 or certs[0].is_CA():
00943         raise rpki.exceptions.UnexpectedCMSCerts, certs
00944       if not crls:
00945         if self.require_crls:
00946           raise rpki.exceptions.MissingCMSCRL, crls
00947         else:
00948           rpki.log.warn("MISSING CMS CRL!  Ignoring per self.require_crls setting")
00949       if len(crls) > 1:
00950         raise rpki.exceptions.UnexpectedCMSCRLs, crls
00951 
00952     try:
00953       content = cms.verify(store)
00954     except (rpki.async.ExitNow, SystemExit):
00955       raise
00956     except:
00957       if self.dump_on_verify_failure:
00958         if True:
00959           dbg = self.dumpasn1()
00960         else:
00961           dbg = cms.pprint()
00962         sys.stderr.write("CMS verification failed, dumping ASN.1 (%d octets):\n%s\n" % (len(self.get_DER()), dbg))
00963       raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
00964 
00965     self.decode(content)
00966     return self.get_content()
00967 
00968   def extract(self):
00969     """
00970     Extract and store inner content from CMS wrapper without verifying
00971     the CMS.
00972 
00973     DANGER WILL ROBINSON!!!
00974 
00975     Do not use this method on unvalidated data.  Use the verify()
00976     method instead.
00977 
00978     If you don't understand this warning, don't use this method.
00979     """
00980 
00981     try:
00982       cms = self.get_POW()
00983     except (rpki.async.ExitNow, SystemExit):
00984       raise
00985     except:
00986       raise rpki.exceptions.UnparsableCMSDER
00987 
00988     if cms.eContentType() != self.econtent_oid:
00989       raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00990 
00991     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)
00992 
00993     self.decode(content)
00994     return self.get_content()
00995 
00996   def sign(self, keypair, certs, crls = None, no_certs = False):
00997     """
00998     Sign and wrap inner content.
00999     """
01000 
01001     rpki.log.trace()
01002 
01003     if isinstance(certs, X509):
01004       cert = certs
01005       certs = ()
01006     else:
01007       cert = certs[0]
01008       certs = certs[1:]
01009 
01010     if crls is None:
01011       crls = ()
01012     elif isinstance(crls, CRL):
01013       crls = (crls,)
01014 
01015     if self.debug_cms_certs:
01016       rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI()))
01017       for i, c in enumerate(certs):
01018         rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI()))
01019 
01020     cms = rpki.POW.CMS()
01021 
01022     cms.sign(cert.get_POW(),
01023              keypair.get_POW(),
01024              self.encode(),
01025              [x.get_POW() for x in certs],
01026              [c.get_POW() for c in crls],
01027              self.econtent_oid,
01028              rpki.POW.CMS_NOCERTS if no_certs else 0)
01029 
01030     self.POW = cms
01031 
01032 class DER_CMS_object(CMS_object):
01033   """
01034   Class to hold CMS objects with DER-based content.
01035   """
01036 
01037   def encode(self):
01038     """
01039     Encode inner content for signing.
01040     """
01041     return self.get_content().toString()
01042 
01043   def decode(self, der):
01044     """
01045     Decode DER and set inner content.
01046     """
01047     obj = self.content_class()
01048     obj.fromString(der)
01049     self.content = obj
01050 
01051 class SignedManifest(DER_CMS_object):
01052   """
01053   Class to hold a signed manifest.
01054   """
01055 
01056   pem_converter = PEM_converter("RPKI MANIFEST")
01057   content_class = rpki.manifest.Manifest
01058   econtent_oid = POWify_OID("id-ct-rpkiManifest")
01059   
01060   def getThisUpdate(self):
01061     """
01062     Get thisUpdate value from this manifest.
01063     """
01064     return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
01065 
01066   def getNextUpdate(self):
01067     """
01068     Get nextUpdate value from this manifest.
01069     """
01070     return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
01071 
01072   @classmethod
01073   def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
01074     """
01075     Build a signed manifest.
01076     """
01077     self = cls()
01078     filelist = []
01079     for name, obj in names_and_objs:
01080       d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
01081       d.update(obj.get_DER())
01082       filelist.append((name.rpartition("/")[2], d.digest()))
01083     filelist.sort(key = lambda x: x[0])
01084     m = rpki.manifest.Manifest()
01085     m.version.set(version)
01086     m.manifestNumber.set(serial)
01087     m.thisUpdate.set(thisUpdate.toGeneralizedTime())
01088     m.nextUpdate.set(nextUpdate.toGeneralizedTime())
01089     m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
01090     m.fileList.set(filelist)
01091     self.set_content(m)
01092     self.sign(keypair, certs)
01093     return self
01094 
01095 class ROA(DER_CMS_object):
01096   """
01097   Class to hold a signed ROA.
01098   """
01099 
01100   pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
01101   content_class = rpki.roa.RouteOriginAttestation
01102   econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
01103 
01104   @classmethod
01105   def build(cls, asn, ipv4, ipv6, keypair, certs, version = 0):
01106     """
01107     Build a ROA.
01108     """
01109     try:
01110       self = cls()
01111       r = rpki.roa.RouteOriginAttestation()
01112       r.version.set(version)
01113       r.asID.set(asn)
01114       r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
01115       self.set_content(r)
01116       self.sign(keypair, certs)
01117       return self
01118     except rpki.POW.pkix.DerError, e:
01119       rpki.log.debug("Encoding error while generating ROA %r: %s" % (self, e))
01120       rpki.log.debug("ROA inner content: %r" % (r.get(),))
01121       raise
01122 
01123 class Ghostbuster(DER_CMS_object):
01124   """
01125   Class to hold a signed Ghostbuster record.
01126   """
01127 
01128   content_class = rpki.ghostbuster.Ghostbuster
01129 
01130   @classmethod
01131   def build(cls, vcard, keypair, certs):
01132       self = cls()
01133       gbr = content_class(vcard)
01134       self.set_content(gbr)
01135       self.sign(keypair, certs)
01136       return self
01137 
01138 class DeadDrop(object):
01139   """
01140   Dead-drop utility for storing copies of CMS messages for debugging or
01141   audit.  At the moment this uses Maildir mailbox format, as it has
01142   approximately the right properties and a number of useful tools for
01143   manipulating it already exist.
01144   """
01145 
01146   def __init__(self, name):
01147     self.maildir = mailbox.Maildir(name, factory = None, create = True)
01148     self.pid = os.getpid()
01149 
01150   def dump(self, obj):
01151     now = time.time()
01152     msg = email.mime.application.MIMEApplication(obj.get_DER(), "x-rpki")
01153     msg["Date"] = email.utils.formatdate(now)
01154     msg["Subject"] = "Process %s dump of %r" % (self.pid, obj)
01155     msg["Message-ID"] = email.utils.make_msgid()
01156     msg["X-RPKI-PID"] = str(self.pid)
01157     msg["X-RPKI-Object"] = repr(obj)
01158     msg["X-RPKI-Timestamp"] = "%f" % now
01159     self.maildir.add(msg)
01160 
01161 class XML_CMS_object(CMS_object):
01162   """
01163   Class to hold CMS-wrapped XML protocol data.
01164   """
01165 
01166   econtent_oid = POWify_OID("id-ct-xml")
01167 
01168   ## @var dump_outbound_cms
01169   # If set, we write all outbound XML-CMS PDUs to disk, for debugging.
01170   # If set, value should be a DeadDrop object.
01171 
01172   dump_outbound_cms = None
01173 
01174   ## @var dump_inbound_cms
01175   # If set, we write all inbound XML-CMS PDUs to disk, for debugging.
01176   # If set, value should be a DeadDrop object.
01177 
01178   dump_inbound_cms = None
01179 
01180   def encode(self):
01181     """
01182     Encode inner content for signing.
01183     """
01184     return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01185 
01186   def decode(self, xml):
01187     """
01188     Decode XML and set inner content.
01189     """
01190     self.content = lxml.etree.fromstring(xml)
01191 
01192   def pretty_print_content(self):
01193     """
01194     Pretty print XML content of this message.
01195     """
01196     return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01197 
01198   def schema_check(self):
01199     """
01200     Handle XML RelaxNG schema check.
01201     """
01202     try:
01203       self.schema.assertValid(self.get_content())
01204     except lxml.etree.DocumentInvalid:
01205       rpki.log.error("PDU failed schema check")
01206       for line in self.pretty_print_content().splitlines():
01207         rpki.log.warn(line)
01208       raise
01209 
01210   def dump_to_disk(self, prefix):
01211     """
01212     Write DER of current message to disk, for debugging.
01213     """
01214     f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
01215     f.write(self.get_DER())
01216     f.close()
01217 
01218   def wrap(self, msg, keypair, certs, crls = None):
01219     """
01220     Wrap an XML PDU in CMS and return its DER encoding.
01221     """
01222     rpki.log.trace()
01223     self.set_content(msg.toXML())
01224     self.schema_check()
01225     self.sign(keypair, certs, crls)
01226     if self.dump_outbound_cms:
01227       self.dump_outbound_cms.dump(self)
01228     return self.get_DER()
01229 
01230   def unwrap(self, ta):
01231     """
01232     Unwrap a CMS-wrapped XML PDU and return Python objects.
01233     """
01234     if self.dump_inbound_cms:
01235       self.dump_inbound_cms.dump(self)
01236     self.verify(ta)
01237     self.schema_check()
01238     return self.saxify(self.get_content())
01239 
01240 class Ghostbuster(CMS_object):
01241   """
01242   Class to hold Ghostbusters record (CMS-wrapped VCard).  This is
01243   quite minimal because we treat the VCard as an opaque byte string
01244   managed by the back-end.
01245   """
01246 
01247   pem_converter = PEM_converter("GHOSTBUSTERS RECORD")
01248   econtent_oid = POWify_OID("id-ct-rpkiGhostbusters")
01249 
01250   def encode(self):
01251     """
01252     Encode inner content for signing.  At the moment we're treating
01253     the VCard as an opaque byte string, so no encoding needed here.
01254     """
01255     return self.get_content()
01256 
01257   def decode(self, vcard):
01258     """
01259     Decode XML and set inner content.  At the moment we're treating
01260     the VCard as an opaque byte string, so no encoding needed here.
01261     """
01262     self.content = vcard
01263 
01264   @classmethod
01265   def build(cls, vcard, keypair, certs):
01266     """
01267     Build a Ghostbuster record.
01268     """
01269     self = cls()
01270     self.set_content(vcard)
01271     self.sign(keypair, certs)
01272     return self
01273 
01274 
01275 class CRL(DER_object):
01276   """
01277   Class to hold a Certificate Revocation List.
01278   """
01279 
01280   formats = ("DER", "POW", "POWpkix")
01281   pem_converter = PEM_converter("X509 CRL")
01282   
01283   def get_DER(self):
01284     """
01285     Get the DER value of this CRL.
01286     """
01287     self.check()
01288     if self.DER:
01289       return self.DER
01290     if self.POW:
01291       self.DER = self.POW.derWrite()
01292       return self.get_DER()
01293     if self.POWpkix:
01294       self.DER = self.POWpkix.toString()
01295       return self.get_DER()
01296     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
01297 
01298   def get_POW(self):
01299     """
01300     Get the rpki.POW value of this CRL.
01301     """
01302     self.check()
01303     if not self.POW:
01304       self.POW = rpki.POW.derRead(rpki.POW.X509_CRL, self.get_DER())
01305     return self.POW
01306 
01307   def get_POWpkix(self):
01308     """
01309     Get the rpki.POW.pkix value of this CRL.
01310     """
01311     self.check()
01312     if not self.POWpkix:
01313       crl = rpki.POW.pkix.CertificateList()
01314       crl.fromString(self.get_DER())
01315       self.POWpkix = crl
01316     return self.POWpkix
01317 
01318   def getThisUpdate(self):
01319     """
01320     Get thisUpdate value from this CRL.
01321     """
01322     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
01323 
01324   def getNextUpdate(self):
01325     """
01326     Get nextUpdate value from this CRL.
01327     """
01328     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
01329 
01330   def getIssuer(self):
01331     """
01332     Get issuer value of this CRL.
01333     """
01334     return "".join("/%s=%s" % rdn for rdn in self.get_POW().getIssuer())
01335 
01336   @classmethod
01337   def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
01338     """
01339     Generate a new CRL.
01340     """
01341     crl = rpki.POW.pkix.CertificateList()
01342     crl.setVersion(version)
01343     crl.setIssuer(issuer.get_POWpkix().getSubject())
01344     crl.setThisUpdate(thisUpdate.toASN1tuple())
01345     crl.setNextUpdate(nextUpdate.toASN1tuple())
01346     if revokedCertificates:
01347       crl.setRevokedCertificates(revokedCertificates)
01348     crl.setExtensions(
01349       ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
01350        (rpki.oids.name2oid["cRLNumber"], False, serial)))
01351     crl.sign(keypair.get_POW(), digestType)
01352     return cls(POWpkix = crl)
 All Classes Namespaces Files Functions Variables