RPKI Engine
1.0
|
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)