RPKI Engine  1.0
x509.py (4028)
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 4028 2011-10-07 23:42:24Z sra $
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     At the moment, this only allows requests for CA certificates; as a
00648     direct consequence, it also rejects ExtendedKeyUsage, because the
00649     RPKI profile only allows EKU for EE certificates.
00650     """
00651 
00652     if not self.get_POWpkix().verify():
00653       raise rpki.exceptions.BadPKCS10, "Signature check failed"
00654 
00655     if self.get_POWpkix().certificationRequestInfo.version.get() != 0:
00656       raise rpki.exceptions.BadPKCS10, \
00657             "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version
00658 
00659     if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) != "sha256WithRSAEncryption":
00660       raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm
00661 
00662     exts = dict((rpki.oids.oid2name.get(oid, oid), value)
00663                 for (oid, critical, value) in self.get_POWpkix().getExtensions())
00664 
00665     if any(oid not in ("basicConstraints", "keyUsage", "subjectInfoAccess") for oid in exts):
00666       raise rpki.exceptions.BadExtension, "Forbidden extension(s) in certificate request"
00667 
00668     if "basicConstraints" not in exts or not exts["basicConstraints"][0]:
00669       raise rpki.exceptions.BadPKCS10, "Request for EE certificate not allowed here"
00670 
00671     if exts["basicConstraints"][1] is not None:
00672       raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length"
00673 
00674     if "keyUsage" in exts and (not exts["keyUsage"][5] or not exts["keyUsage"][6]):
00675       raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints"
00676 
00677     sias = dict((rpki.oids.oid2name.get(oid, oid), value[1])
00678                 for oid, value in exts.get("subjectInfoAccess", ())
00679                 if value[0] == "uri" and value[1].startswith("rsync://"))
00680 
00681     for oid in ("id-ad-caRepository", "id-ad-rpkiManifest"):
00682       if oid not in sias:
00683         raise rpki.exceptions.BadPKCS10, "Certificate request is missing SIA %s" % oid
00684 
00685     if not sias["id-ad-caRepository"].endswith("/"):
00686       raise rpki.exceptions.BadPKCS10, "Certificate request id-ad-caRepository does not end with slash: %r" % sias["id-ad-caRepository"]
00687 
00688     if sias["id-ad-rpkiManifest"].endswith("/"):
00689       raise rpki.exceptions.BadPKCS10, "Certificate request id-ad-rpkiManifest ends with slash: %r" % sias["id-ad-rpkiManifest"]
00690 
00691   @classmethod
00692   def create_ca(cls, keypair, sia = None):
00693     """
00694     Create a new request for a given keypair, including given SIA value.
00695     """
00696     exts = [["basicConstraints", True, (1, None)],
00697             ["keyUsage",         True, (0, 0, 0, 0, 0, 1, 1)]]
00698     if sia is not None:
00699       exts.append(["subjectInfoAccess", False, sia])
00700     for x in exts:
00701       x[0] = rpki.oids.name2oid[x[0]]
00702     return cls.create(keypair, exts)
00703 
00704   @classmethod
00705   def create(cls, keypair, exts = None):
00706     """
00707     Create a new request for a given keypair, including given extensions.
00708     """
00709     cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI()))
00710     req = rpki.POW.pkix.CertificationRequest()
00711     req.certificationRequestInfo.version.set(0)
00712     req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"],
00713                                                 ("printableString", cn)),),))
00714     if exts is not None:
00715       req.setExtensions(exts)
00716     req.sign(keypair.get_POW(), rpki.POW.SHA256_DIGEST)
00717     return cls(POWpkix = req)
00718 
00719 class RSA(DER_object):
00720   """
00721   Class to hold an RSA key pair.
00722   """
00723 
00724   formats = ("DER", "POW")
00725   pem_converter = PEM_converter("RSA PRIVATE KEY")
00726   
00727   def get_DER(self):
00728     """
00729     Get the DER value of this keypair.
00730     """
00731     self.check()
00732     if self.DER:
00733       return self.DER
00734     if self.POW:
00735       self.DER = self.POW.derWrite(rpki.POW.RSA_PRIVATE_KEY)
00736       return self.get_DER()
00737     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00738 
00739   def get_POW(self):
00740     """
00741     Get the rpki.POW value of this keypair.
00742     """
00743     self.check()
00744     if not self.POW:
00745       self.POW = rpki.POW.derRead(rpki.POW.RSA_PRIVATE_KEY, self.get_DER())
00746     return self.POW
00747 
00748   @classmethod
00749   def generate(cls, keylength = 2048):
00750     """
00751     Generate a new keypair.
00752     """
00753     rpki.log.debug("Generating new %d-bit RSA key" % keylength)
00754     return cls(POW = rpki.POW.Asymmetric(rpki.POW.RSA_CIPHER, keylength))
00755 
00756   def get_public_DER(self):
00757     """
00758     Get the DER encoding of the public key from this keypair.
00759     """
00760     return self.get_POW().derWrite(rpki.POW.RSA_PUBLIC_KEY)
00761 
00762   def get_SKI(self):
00763     """
00764     Calculate the SKI of this keypair.
00765     """
00766     return calculate_SKI(self.get_public_DER())
00767 
00768   def get_RSApublic(self):
00769     """
00770     Convert the public key of this keypair into a RSApublic object.
00771     """
00772     return RSApublic(DER = self.get_public_DER())
00773 
00774 class RSApublic(DER_object):
00775   """
00776   Class to hold an RSA public key.
00777   """
00778 
00779   formats = ("DER", "POW")
00780   pem_converter = PEM_converter("RSA PUBLIC KEY")
00781   
00782   def get_DER(self):
00783     """
00784     Get the DER value of this public key.
00785     """
00786     self.check()
00787     if self.DER:
00788       return self.DER
00789     if self.POW:
00790       self.DER = self.POW.derWrite(rpki.POW.RSA_PUBLIC_KEY)
00791       return self.get_DER()
00792     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00793 
00794   def get_POW(self):
00795     """
00796     Get the rpki.POW value of this public key.
00797     """
00798     self.check()
00799     if not self.POW:
00800       self.POW = rpki.POW.derRead(rpki.POW.RSA_PUBLIC_KEY, self.get_DER())
00801     return self.POW
00802 
00803   def get_SKI(self):
00804     """
00805     Calculate the SKI of this public key.
00806     """
00807     return calculate_SKI(self.get_DER())
00808 
00809 def POWify_OID(oid):
00810   """
00811   Utility function to convert tuple form of an OID to the
00812   dotted-decimal string form that rpki.POW uses.
00813   """
00814   if isinstance(oid, str):
00815     return POWify_OID(rpki.oids.name2oid[oid])
00816   else:
00817     return ".".join(str(i) for i in oid)
00818 
00819 class CMS_object(DER_object):
00820   """
00821   Class to hold a CMS-wrapped object.
00822 
00823   CMS-wrapped objects are a little different from the other DER_object
00824   types because the signed object is CMS wrapping inner content that's
00825   also ASN.1, and due to our current minimal support for CMS we can't
00826   just handle this as a pretty composite object.  So, for now anyway,
00827   a CMS_object is the outer CMS wrapped object so that the usual DER
00828   and PEM operations do the obvious things, and the inner content is
00829   handle via separate methods.
00830   """
00831 
00832   formats = ("DER", "POW")
00833   other_clear = ("content",)
00834   econtent_oid = POWify_OID("id-data")
00835   pem_converter = PEM_converter("CMS")
00836 
00837   ## @var dump_on_verify_failure
00838   # Set this to True to get dumpasn1 dumps of ASN.1 on CMS verify failures.
00839 
00840   dump_on_verify_failure = True
00841 
00842   ## @var debug_cms_certs
00843   # Set this to True to log a lot of chatter about CMS certificates.
00844 
00845   debug_cms_certs = False
00846 
00847   ## @var dump_using_dumpasn1
00848   # Set this to use external dumpasn1 program, which is prettier and
00849   # more informative than OpenSSL's CMS text dump, but which won't
00850   # work if the dumpasn1 program isn't installed.
00851 
00852   dump_using_dumpasn1 = False
00853 
00854   ## @var require_crls
00855   # Set this to False to make CMS CRLs optional in the cases where we
00856   # would otherwise require them.  Some day this option should go away
00857   # and CRLs should be uncondtionally mandatory in such cases.
00858 
00859   require_crls = False
00860   
00861   ## @var print_on_der_error
00862   # Set this to True to log alleged DER when we have trouble parsing
00863   # it, in case it's really a Perl backtrace or something.
00864 
00865   print_on_der_error = True
00866 
00867   def get_DER(self):
00868     """
00869     Get the DER value of this CMS_object.
00870     """
00871     self.check()
00872     if self.DER:
00873       return self.DER
00874     if self.POW:
00875       self.DER = self.POW.derWrite()
00876       return self.get_DER()
00877     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00878 
00879   def get_POW(self):
00880     """
00881     Get the rpki.POW value of this CMS_object.
00882     """
00883     self.check()
00884     if not self.POW:
00885       self.POW = rpki.POW.derRead(rpki.POW.CMS_MESSAGE, self.get_DER())
00886     return self.POW
00887 
00888   def get_content(self):
00889     """
00890     Get the inner content of this CMS_object.
00891     """
00892     if self.content is None:
00893       raise rpki.exceptions.CMSContentNotSet, "Inner content of CMS object %r is not set" % self
00894     return self.content
00895 
00896   def set_content(self, content):
00897     """
00898     Set the (inner) content of this CMS_object, clearing the wrapper.
00899     """
00900     self.clear()
00901     self.content = content
00902 
00903   def get_signingTime(self):
00904     """
00905     Extract signingTime from CMS signed attributes.
00906     """
00907     return rpki.sundial.datetime.fromGeneralizedTime(self.get_POW().signingTime())
00908 
00909   def verify(self, ta):
00910     """
00911     Verify CMS wrapper and store inner content.
00912     """
00913 
00914     try:
00915       cms = self.get_POW()
00916     except (rpki.async.ExitNow, SystemExit):
00917       raise
00918     except Exception:
00919       if self.print_on_der_error:
00920         rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %r" % self.get_DER())
00921       raise rpki.exceptions.UnparsableCMSDER
00922 
00923     if cms.eContentType() != self.econtent_oid:
00924       raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00925 
00926     certs = [X509(POW = x) for x in cms.certs()]
00927     crls  = [CRL(POW = c) for c in cms.crls()]
00928 
00929     if self.debug_cms_certs:
00930       for x in certs:
00931         rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00932       for c in crls:
00933         rpki.log.debug("Received CMS CRL issuer %r" % (c.getIssuer(),))
00934 
00935     store = rpki.POW.X509Store()
00936 
00937     now = rpki.sundial.now()
00938 
00939     trusted_ee = None
00940 
00941     for x in X509.normalize_chain(ta):
00942       if self.debug_cms_certs:
00943         rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00944       if x.getNotAfter() < now:
00945         raise rpki.exceptions.TrustedCMSCertHasExpired
00946       if not x.is_CA():
00947         if trusted_ee is not None:
00948           raise rpki.exceptions.MultipleCMSEECert
00949         trusted_ee = x
00950       store.addTrust(x.get_POW())
00951 
00952     if trusted_ee:
00953       if self.debug_cms_certs:
00954         rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
00955       if certs and (len(certs) > 1 or certs[0].getSubject() != trusted_ee.getSubject() or certs[0].getPublicKey() != trusted_ee.getPublicKey()):
00956         raise rpki.exceptions.UnexpectedCMSCerts # , certs
00957       if crls:
00958         raise rpki.exceptions.UnexpectedCMSCRLs # , crls
00959     else:
00960       if not certs:
00961         raise rpki.exceptions.MissingCMSEEcert # , certs
00962       if len(certs) > 1 or certs[0].is_CA():
00963         raise rpki.exceptions.UnexpectedCMSCerts # , certs
00964       if not crls:
00965         if self.require_crls:
00966           raise rpki.exceptions.MissingCMSCRL # , crls
00967         else:
00968           rpki.log.warn("MISSING CMS CRL!  Ignoring per self.require_crls setting")
00969       if len(crls) > 1:
00970         raise rpki.exceptions.UnexpectedCMSCRLs # , crls
00971 
00972     for x in certs:
00973       if x.getNotAfter() < now:
00974         raise rpki.exceptions.CMSCertHasExpired # , x
00975 
00976     try:
00977       content = cms.verify(store)
00978     except (rpki.async.ExitNow, SystemExit):
00979       raise
00980     except Exception:
00981       if self.dump_on_verify_failure:
00982         if self.dump_using_dumpasn1:
00983           dbg = self.dumpasn1()
00984         else:
00985           dbg = cms.pprint()
00986         rpki.log.warn("CMS verification failed, dumping ASN.1 (%d octets):" % len(self.get_DER()))
00987         for line in dbg.splitlines():
00988           rpki.log.warn(line)
00989       raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
00990 
00991     self.decode(content)
00992     return self.get_content()
00993 
00994   def extract(self):
00995     """
00996     Extract and store inner content from CMS wrapper without verifying
00997     the CMS.
00998 
00999     DANGER WILL ROBINSON!!!
01000 
01001     Do not use this method on unvalidated data.  Use the verify()
01002     method instead.
01003 
01004     If you don't understand this warning, don't use this method.
01005     """
01006 
01007     try:
01008       cms = self.get_POW()
01009     except (rpki.async.ExitNow, SystemExit):
01010       raise
01011     except Exception:
01012       raise rpki.exceptions.UnparsableCMSDER
01013 
01014     if cms.eContentType() != self.econtent_oid:
01015       raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
01016 
01017     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)
01018 
01019     self.decode(content)
01020     return self.get_content()
01021 
01022   def sign(self, keypair, certs, crls = None, no_certs = False):
01023     """
01024     Sign and wrap inner content.
01025     """
01026 
01027     rpki.log.trace()
01028 
01029     if isinstance(certs, X509):
01030       cert = certs
01031       certs = ()
01032     else:
01033       cert = certs[0]
01034       certs = certs[1:]
01035 
01036     if crls is None:
01037       crls = ()
01038     elif isinstance(crls, CRL):
01039       crls = (crls,)
01040 
01041     if self.debug_cms_certs:
01042       rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI()))
01043       for i, c in enumerate(certs):
01044         rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI()))
01045 
01046     cms = rpki.POW.CMS()
01047 
01048     cms.sign(cert.get_POW(),
01049              keypair.get_POW(),
01050              self.encode(),
01051              [x.get_POW() for x in certs],
01052              [c.get_POW() for c in crls],
01053              self.econtent_oid,
01054              rpki.POW.CMS_NOCERTS if no_certs else 0)
01055 
01056     self.POW = cms
01057 
01058 class DER_CMS_object(CMS_object):
01059   """
01060   Class to hold CMS objects with DER-based content.
01061   """
01062 
01063   def encode(self):
01064     """
01065     Encode inner content for signing.
01066     """
01067     return self.get_content().toString()
01068 
01069   def decode(self, der):
01070     """
01071     Decode DER and set inner content.
01072     """
01073     obj = self.content_class()
01074     obj.fromString(der)
01075     self.content = obj
01076 
01077 class SignedManifest(DER_CMS_object):
01078   """
01079   Class to hold a signed manifest.
01080   """
01081 
01082   pem_converter = PEM_converter("RPKI MANIFEST")
01083   content_class = rpki.manifest.Manifest
01084   econtent_oid = POWify_OID("id-ct-rpkiManifest")
01085   
01086   def getThisUpdate(self):
01087     """
01088     Get thisUpdate value from this manifest.
01089     """
01090     return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
01091 
01092   def getNextUpdate(self):
01093     """
01094     Get nextUpdate value from this manifest.
01095     """
01096     return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
01097 
01098   @classmethod
01099   def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
01100     """
01101     Build a signed manifest.
01102     """
01103     self = cls()
01104     filelist = []
01105     for name, obj in names_and_objs:
01106       d = rpki.POW.Digest(rpki.POW.SHA256_DIGEST)
01107       d.update(obj.get_DER())
01108       filelist.append((name.rpartition("/")[2], d.digest()))
01109     filelist.sort(key = lambda x: x[0])
01110     m = rpki.manifest.Manifest()
01111     m.version.set(version)
01112     m.manifestNumber.set(serial)
01113     m.thisUpdate.set(thisUpdate.toGeneralizedTime())
01114     m.nextUpdate.set(nextUpdate.toGeneralizedTime())
01115     m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
01116     m.fileList.set(filelist)
01117     self.set_content(m)
01118     self.sign(keypair, certs)
01119     return self
01120 
01121 class ROA(DER_CMS_object):
01122   """
01123   Class to hold a signed ROA.
01124   """
01125 
01126   pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
01127   content_class = rpki.roa.RouteOriginAttestation
01128   econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
01129 
01130   @classmethod
01131   def build(cls, asn, ipv4, ipv6, keypair, certs, version = 0):
01132     """
01133     Build a ROA.
01134     """
01135     try:
01136       self = cls()
01137       r = rpki.roa.RouteOriginAttestation()
01138       r.version.set(version)
01139       r.asID.set(asn)
01140       r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
01141       self.set_content(r)
01142       self.sign(keypair, certs)
01143       return self
01144     except rpki.POW.pkix.DerError, e:
01145       rpki.log.debug("Encoding error while generating ROA %r: %s" % (self, e))
01146       rpki.log.debug("ROA inner content: %r" % (r.get(),))
01147       raise
01148 
01149 class Ghostbuster(DER_CMS_object):
01150   """
01151   Class to hold a signed Ghostbuster record.
01152   """
01153 
01154   content_class = rpki.ghostbuster.Ghostbuster
01155 
01156   @classmethod
01157   def build(cls, vcard, keypair, certs):
01158       self = cls()
01159       gbr = content_class(vcard)
01160       self.set_content(gbr)
01161       self.sign(keypair, certs)
01162       return self
01163 
01164 class DeadDrop(object):
01165   """
01166   Dead-drop utility for storing copies of CMS messages for debugging or
01167   audit.  At the moment this uses Maildir mailbox format, as it has
01168   approximately the right properties and a number of useful tools for
01169   manipulating it already exist.
01170   """
01171 
01172   def __init__(self, name):
01173     self.maildir = mailbox.Maildir(name, factory = None, create = True)
01174     self.pid = os.getpid()
01175 
01176   def dump(self, obj):
01177     now = time.time()
01178     msg = email.mime.application.MIMEApplication(obj.get_DER(), "x-rpki")
01179     msg["Date"] = email.utils.formatdate(now)
01180     msg["Subject"] = "Process %s dump of %r" % (self.pid, obj)
01181     msg["Message-ID"] = email.utils.make_msgid()
01182     msg["X-RPKI-PID"] = str(self.pid)
01183     msg["X-RPKI-Object"] = repr(obj)
01184     msg["X-RPKI-Timestamp"] = "%f" % now
01185     self.maildir.add(msg)
01186 
01187 class XML_CMS_object(CMS_object):
01188   """
01189   Class to hold CMS-wrapped XML protocol data.
01190   """
01191 
01192   econtent_oid = POWify_OID("id-ct-xml")
01193 
01194   ## @var dump_outbound_cms
01195   # If set, we write all outbound XML-CMS PDUs to disk, for debugging.
01196   # If set, value should be a DeadDrop object.
01197 
01198   dump_outbound_cms = None
01199 
01200   ## @var dump_inbound_cms
01201   # If set, we write all inbound XML-CMS PDUs to disk, for debugging.
01202   # If set, value should be a DeadDrop object.
01203 
01204   dump_inbound_cms = None
01205 
01206   def encode(self):
01207     """
01208     Encode inner content for signing.
01209     """
01210     return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01211 
01212   def decode(self, xml):
01213     """
01214     Decode XML and set inner content.
01215     """
01216     self.content = lxml.etree.fromstring(xml)
01217 
01218   def pretty_print_content(self):
01219     """
01220     Pretty print XML content of this message.
01221     """
01222     return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01223 
01224   def schema_check(self):
01225     """
01226     Handle XML RelaxNG schema check.
01227     """
01228     try:
01229       self.schema.assertValid(self.get_content())
01230     except lxml.etree.DocumentInvalid:
01231       rpki.log.error("PDU failed schema check")
01232       for line in self.pretty_print_content().splitlines():
01233         rpki.log.warn(line)
01234       raise
01235 
01236   def dump_to_disk(self, prefix):
01237     """
01238     Write DER of current message to disk, for debugging.
01239     """
01240     f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
01241     f.write(self.get_DER())
01242     f.close()
01243 
01244   def wrap(self, msg, keypair, certs, crls = None):
01245     """
01246     Wrap an XML PDU in CMS and return its DER encoding.
01247     """
01248     rpki.log.trace()
01249     self.set_content(msg.toXML())
01250     self.schema_check()
01251     self.sign(keypair, certs, crls)
01252     if self.dump_outbound_cms:
01253       self.dump_outbound_cms.dump(self)
01254     return self.get_DER()
01255 
01256   def unwrap(self, ta):
01257     """
01258     Unwrap a CMS-wrapped XML PDU and return Python objects.
01259     """
01260     if self.dump_inbound_cms:
01261       self.dump_inbound_cms.dump(self)
01262     self.verify(ta)
01263     self.schema_check()
01264     return self.saxify(self.get_content())
01265 
01266 class Ghostbuster(CMS_object):
01267   """
01268   Class to hold Ghostbusters record (CMS-wrapped VCard).  This is
01269   quite minimal because we treat the VCard as an opaque byte string
01270   managed by the back-end.
01271   """
01272 
01273   pem_converter = PEM_converter("GHOSTBUSTERS RECORD")
01274   econtent_oid = POWify_OID("id-ct-rpkiGhostbusters")
01275 
01276   def encode(self):
01277     """
01278     Encode inner content for signing.  At the moment we're treating
01279     the VCard as an opaque byte string, so no encoding needed here.
01280     """
01281     return self.get_content()
01282 
01283   def decode(self, vcard):
01284     """
01285     Decode XML and set inner content.  At the moment we're treating
01286     the VCard as an opaque byte string, so no encoding needed here.
01287     """
01288     self.content = vcard
01289 
01290   @classmethod
01291   def build(cls, vcard, keypair, certs):
01292     """
01293     Build a Ghostbuster record.
01294     """
01295     self = cls()
01296     self.set_content(vcard)
01297     self.sign(keypair, certs)
01298     return self
01299 
01300 
01301 class CRL(DER_object):
01302   """
01303   Class to hold a Certificate Revocation List.
01304   """
01305 
01306   formats = ("DER", "POW", "POWpkix")
01307   pem_converter = PEM_converter("X509 CRL")
01308   
01309   def get_DER(self):
01310     """
01311     Get the DER value of this CRL.
01312     """
01313     self.check()
01314     if self.DER:
01315       return self.DER
01316     if self.POW:
01317       self.DER = self.POW.derWrite()
01318       return self.get_DER()
01319     if self.POWpkix:
01320       self.DER = self.POWpkix.toString()
01321       return self.get_DER()
01322     raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
01323 
01324   def get_POW(self):
01325     """
01326     Get the rpki.POW value of this CRL.
01327     """
01328     self.check()
01329     if not self.POW:
01330       self.POW = rpki.POW.derRead(rpki.POW.X509_CRL, self.get_DER())
01331     return self.POW
01332 
01333   def get_POWpkix(self):
01334     """
01335     Get the rpki.POW.pkix value of this CRL.
01336     """
01337     self.check()
01338     if not self.POWpkix:
01339       crl = rpki.POW.pkix.CertificateList()
01340       crl.fromString(self.get_DER())
01341       self.POWpkix = crl
01342     return self.POWpkix
01343 
01344   def getThisUpdate(self):
01345     """
01346     Get thisUpdate value from this CRL.
01347     """
01348     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
01349 
01350   def getNextUpdate(self):
01351     """
01352     Get nextUpdate value from this CRL.
01353     """
01354     return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
01355 
01356   def getIssuer(self):
01357     """
01358     Get issuer value of this CRL.
01359     """
01360     return "".join("/%s=%s" % rdn for rdn in self.get_POW().getIssuer())
01361 
01362   @classmethod
01363   def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
01364     """
01365     Generate a new CRL.
01366     """
01367     crl = rpki.POW.pkix.CertificateList()
01368     crl.setVersion(version)
01369     crl.setIssuer(issuer.get_POWpkix().getSubject())
01370     crl.setThisUpdate(thisUpdate.toASN1tuple())
01371     crl.setNextUpdate(nextUpdate.toASN1tuple())
01372     if revokedCertificates:
01373       crl.setRevokedCertificates(revokedCertificates)
01374     crl.setExtensions(
01375       ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
01376        (rpki.oids.name2oid["cRLNumber"], False, serial)))
01377     crl.sign(keypair.get_POW(), digestType)
01378     return cls(POWpkix = crl)
 All Classes Namespaces Files Functions Variables Properties