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