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 2481 2009-06-01 05:07:46Z sra $
00014
00015
00016 Copyright (C) 2009 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 POW, tlslite.api, POW.pkix, base64, lxml.etree, os
00047 import rpki.exceptions, rpki.resource_set, rpki.oids, rpki.sundial
00048 import rpki.manifest, rpki.roa, rpki.log, rpki.async
00049
00050 def calculate_SKI(public_key_der):
00051 """
00052 Calculate the SKI value given the DER representation of a public
00053 key, which requires first peeling the ASN.1 wrapper off the key.
00054 """
00055 k = POW.pkix.SubjectPublicKeyInfo()
00056 k.fromString(public_key_der)
00057 d = POW.Digest(POW.SHA1_DIGEST)
00058 d.update(k.subjectPublicKey.get())
00059 return d.digest()
00060
00061 class PEM_converter(object):
00062 """
00063 Convert between DER and PEM encodings for various kinds of ASN.1 data.
00064 """
00065
00066 def __init__(self, kind):
00067 """
00068 Initialize PEM_converter.
00069 """
00070 self.b = "-----BEGIN %s-----" % kind
00071 self.e = "-----END %s-----" % kind
00072
00073 def looks_like_PEM(self, text):
00074 """
00075 Guess whether text looks like a PEM encoding.
00076 """
00077 b = text.find(self.b)
00078 return b >= 0 and text.find(self.e) > b + len(self.b)
00079
00080 def to_DER(self, pem):
00081 """
00082 Convert from PEM to DER.
00083 """
00084 lines = [line.strip() for line in pem.splitlines(0)]
00085 while lines and lines.pop(0) != self.b:
00086 pass
00087 while lines and lines.pop(-1) != self.e:
00088 pass
00089 if not lines:
00090 raise rpki.exceptions.EmptyPEM, "Could not find PEM in:\n%s" % pem
00091 return base64.b64decode("".join(lines))
00092
00093 def to_PEM(self, der):
00094 """
00095 Convert from DER to PEM.
00096 """
00097 b64 = base64.b64encode(der)
00098 pem = self.b + "\n"
00099 while len(b64) > 64:
00100 pem += b64[0:64] + "\n"
00101 b64 = b64[64:]
00102 return pem + b64 + "\n" + self.e + "\n"
00103
00104 class DER_object(object):
00105 """
00106 Virtual class to hold a generic DER object.
00107 """
00108
00109
00110 formats = ("DER",)
00111
00112
00113 pem_converter = None
00114
00115
00116 other_clear = ()
00117
00118
00119
00120
00121 def empty(self):
00122 """
00123 Test whether this object is empty.
00124 """
00125 for a in self.formats:
00126 if getattr(self, a, None) is not None:
00127 return False
00128 return True
00129
00130 def clear(self):
00131 """
00132 Make this object empty.
00133 """
00134 for a in self.formats + self.other_clear:
00135 setattr(self, a, None)
00136
00137 def __init__(self, **kw):
00138 """
00139 Initialize a DER_object.
00140 """
00141 self.clear()
00142 if len(kw):
00143 self.set(**kw)
00144
00145 def set(self, **kw):
00146 """
00147 Set this object by setting one of its known formats.
00148
00149 This method only allows one to set one format at a time.
00150 Subsequent calls will clear the object first. The point of all
00151 this is to let the object's internal converters handle mustering
00152 the object into whatever format you need at the moment.
00153 """
00154
00155 if len(kw) == 1:
00156 name = kw.keys()[0]
00157 if name in self.formats:
00158 self.clear()
00159 setattr(self, name, kw[name])
00160 return
00161 if name == "PEM":
00162 self.clear()
00163 self.DER = self.pem_converter.to_DER(kw[name])
00164 return
00165 if name == "Base64":
00166 self.clear()
00167 self.DER = base64.b64decode(kw[name])
00168 return
00169 if name in ("PEM_file", "DER_file", "Auto_file"):
00170 f = open(kw[name], "rb")
00171 value = f.read()
00172 f.close()
00173 if name == "PEM_file" or (name == "Auto_file" and self.pem_converter.looks_like_PEM(value)):
00174 value = self.pem_converter.to_DER(value)
00175 self.clear()
00176 self.DER = value
00177 return
00178 raise rpki.exceptions.DERObjectConversionError, "Can't honor conversion request %s" % repr(kw)
00179
00180 def get_DER(self):
00181 """
00182 Get the DER value of this object.
00183
00184 Subclasses will almost certainly override this method.
00185 """
00186 assert not self.empty()
00187 if self.DER:
00188 return self.DER
00189 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00190
00191 def get_Base64(self):
00192 """Get the Base64 encoding of the DER value of this object."""
00193 return base64.b64encode(self.get_DER())
00194
00195 def get_PEM(self):
00196 """Get the PEM representation of this object."""
00197 return self.pem_converter.to_PEM(self.get_DER())
00198
00199 def __cmp__(self, other):
00200 """Compare two DER-encoded objects."""
00201 return cmp(self.get_DER(), other.get_DER())
00202
00203 def hSKI(self):
00204 """
00205 Return hexadecimal string representation of SKI for this object.
00206 Only work for subclasses that implement get_SKI().
00207 """
00208 ski = self.get_SKI()
00209 return ":".join(("%02X" % ord(i) for i in ski)) if ski else ""
00210
00211 def gSKI(self):
00212 """
00213 Calculate g(SKI) for this object. Only work for subclasses
00214 that implement get_SKI().
00215 """
00216 return base64.urlsafe_b64encode(self.get_SKI()).rstrip("=")
00217
00218 def hAKI(self):
00219 """
00220 Return hexadecimal string representation of AKI for this
00221 object. Only work for subclasses that implement get_AKI().
00222 """
00223 aki = self.get_AKI()
00224 return ":".join(("%02X" % ord(i) for i in aki)) if aki else ""
00225
00226 def gAKI(self):
00227 """
00228 Calculate g(AKI) for this object. Only work for subclasses
00229 that implement get_AKI().
00230 """
00231 return base64.urlsafe_b64encode(self.get_AKI()).rstrip("=")
00232
00233 def get_AKI(self):
00234 """
00235 Get the AKI extension from this object. Only works for subclasses
00236 that support getExtension().
00237 """
00238 aki = (self.get_POWpkix().getExtension(rpki.oids.name2oid["authorityKeyIdentifier"]) or ((), 0, None))[2]
00239 return aki[0] if isinstance(aki, tuple) else aki
00240
00241 def get_SKI(self):
00242 """
00243 Get the SKI extension from this object. Only works for subclasses
00244 that support getExtension().
00245 """
00246 return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectKeyIdentifier"]) or ((), 0, None))[2]
00247
00248 def get_SIA(self):
00249 """
00250 Get the SIA extension from this object. Only works for subclasses
00251 that support getExtension().
00252 """
00253 return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2]
00254
00255 def get_AIA(self):
00256 """
00257 Get the SIA extension from this object. Only works for subclasses
00258 that support getExtension().
00259 """
00260 return (self.get_POWpkix().getExtension(rpki.oids.name2oid["subjectInfoAccess"]) or ((), 0, None))[2]
00261
00262 def get_basicConstraints(self):
00263 """
00264 Get the basicConstraints extension from this object. Only works
00265 for subclasses that support getExtension().
00266 """
00267 return (self.get_POWpkix().getExtension(rpki.oids.name2oid["basicConstraints"]) or ((), 0, None))[2]
00268
00269 def is_CA(self):
00270 """
00271 Return True if and only if object has the basicConstraints
00272 extension and its cA value is true.
00273 """
00274 basicConstraints = self.get_basicConstraints()
00275 return basicConstraints and basicConstraints[0] != 0
00276
00277 def get_3779resources(self):
00278 """
00279 Get RFC 3779 resources as rpki.resource_set objects. Only works
00280 for subclasses that support getExtensions().
00281 """
00282 resources = rpki.resource_set.resource_bag.from_rfc3779_tuples(self.get_POWpkix().getExtensions())
00283 try:
00284 resources.valid_until = self.getNotAfter()
00285 except AttributeError:
00286 pass
00287 return resources
00288
00289 @classmethod
00290 def from_sql(cls, x):
00291 """Convert from SQL storage format."""
00292 return cls(DER = x)
00293
00294 def to_sql(self):
00295 """Convert to SQL storage format."""
00296 return self.get_DER()
00297
00298 def dumpasn1(self):
00299 """
00300 Pretty print an ASN.1 DER object using cryptlib dumpasn1 tool.
00301 Use a temporary file rather than popen4() because dumpasn1 uses
00302 seek() when decoding ASN.1 content nested in OCTET STRING values.
00303 """
00304
00305 ret = None
00306 fn = "dumpasn1.tmp"
00307 try:
00308 f = open(fn, "wb")
00309 f.write(self.get_DER())
00310 f.close()
00311 f = os.popen("dumpasn1 2>&1 -a " + fn)
00312 ret = "\n".join(x for x in f.read().splitlines() if x.startswith(" "))
00313 f.close()
00314 finally:
00315 os.unlink(fn)
00316 return ret
00317
00318 class X509(DER_object):
00319 """
00320 X.509 certificates.
00321
00322 This class is designed to hold all the different representations of
00323 X.509 certs we're using and convert between them. X.509 support in
00324 Python a nasty maze of half-cooked stuff (except perhaps for
00325 cryptlib, which is just different). Users of this module should not
00326 have to care about this implementation nightmare.
00327 """
00328
00329 formats = ("DER", "POW", "POWpkix", "tlslite")
00330 pem_converter = PEM_converter("CERTIFICATE")
00331
00332 def get_DER(self):
00333 """
00334 Get the DER value of this certificate.
00335 """
00336 assert not self.empty()
00337 if self.DER:
00338 return self.DER
00339 if self.POW:
00340 self.DER = self.POW.derWrite()
00341 return self.get_DER()
00342 if self.POWpkix:
00343 self.DER = self.POWpkix.toString()
00344 return self.get_DER()
00345 if self.tlslite:
00346 der = self.tlslite.writeBytes()
00347 if not isinstance(der, str):
00348 der = der.tostring()
00349 self.DER = der
00350 return self.get_DER()
00351 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00352
00353 def get_POW(self):
00354 """
00355 Get the POW value of this certificate.
00356 """
00357 assert not self.empty()
00358 if not self.POW:
00359 self.POW = POW.derRead(POW.X509_CERTIFICATE, self.get_DER())
00360 return self.POW
00361
00362 def get_POWpkix(self):
00363 """
00364 Get the POW.pkix value of this certificate.
00365 """
00366 assert not self.empty()
00367 if not self.POWpkix:
00368 cert = POW.pkix.Certificate()
00369 cert.fromString(self.get_DER())
00370 self.POWpkix = cert
00371 return self.POWpkix
00372
00373 def get_tlslite(self):
00374 """
00375 Get the tlslite value of this certificate.
00376 """
00377 assert not self.empty()
00378 if not self.tlslite:
00379 cert = tlslite.api.X509()
00380 cert.parseBinary(self.get_DER())
00381 self.tlslite = cert
00382 return self.tlslite
00383
00384 def getIssuer(self):
00385 """Get the issuer of this certificate."""
00386 return self.get_POW().getIssuer()
00387
00388 def getSubject(self):
00389 """Get the subject of this certificate."""
00390 return self.get_POW().getSubject()
00391
00392 def getNotBefore(self):
00393 """Get the inception time of this certificate."""
00394 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notBefore.get())
00395
00396 def getNotAfter(self):
00397 """Get the expiration time of this certificate."""
00398 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notAfter.get())
00399
00400 def getSerial(self):
00401 """Get the serial number of this certificate."""
00402 return self.get_POW().getSerial()
00403
00404 def getPublicKey(self):
00405 """Extract the public key from this certificate."""
00406 return RSApublic(DER = self.get_POWpkix().tbs.subjectPublicKeyInfo.toString())
00407
00408 def expired(self):
00409 """Test whether this certificate has expired."""
00410 return self.getNotAfter() <= rpki.sundial.now()
00411
00412 def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter,
00413 cn = None, resources = None, is_ca = True):
00414 """
00415 Issue a certificate.
00416 """
00417
00418 now = rpki.sundial.now()
00419 aki = self.get_SKI()
00420 ski = subject_key.get_SKI()
00421
00422 if cn is None:
00423 cn = "".join(("%02X" % ord(i) for i in ski))
00424
00425
00426
00427 cert = POW.pkix.Certificate()
00428 cert.setVersion(2)
00429 cert.setSerial(serial)
00430 cert.setIssuer(self.get_POWpkix().getSubject())
00431 cert.setSubject((((rpki.oids.name2oid["commonName"], ("printableString", cn)),),))
00432 cert.setNotBefore(now.toASN1tuple())
00433 cert.setNotAfter(notAfter.toASN1tuple())
00434 cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
00435
00436 exts = [ ["subjectKeyIdentifier", False, ski],
00437 ["authorityKeyIdentifier", False, (aki, (), None)],
00438 ["cRLDistributionPoints", False, ((("fullName", (("uri", crldp),)), None, ()),)],
00439 ["authorityInfoAccess", False, ((rpki.oids.name2oid["id-ad-caIssuers"], ("uri", aia)),)],
00440 ["certificatePolicies", True, ((rpki.oids.name2oid["id-cp-ipAddr-asNumber"], ()),)] ]
00441
00442 if is_ca:
00443 exts.append(["basicConstraints", True, (1, None)])
00444 exts.append(["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)])
00445 else:
00446 exts.append(["keyUsage", True, (1,)])
00447
00448 if sia is not None:
00449 exts.append(["subjectInfoAccess", False, sia])
00450 else:
00451 assert not is_ca
00452
00453 if resources is not None and resources.asn:
00454 exts.append(["sbgp-autonomousSysNum", True, (resources.asn.to_rfc3779_tuple(), None)])
00455
00456 if resources is not None and (resources.v4 or resources.v6):
00457 exts.append(["sbgp-ipAddrBlock", True, [x for x in (resources.v4.to_rfc3779_tuple(), resources.v6.to_rfc3779_tuple()) if x is not None]])
00458
00459 for x in exts:
00460 x[0] = rpki.oids.name2oid[x[0]]
00461 cert.setExtensions(exts)
00462
00463 cert.sign(keypair.get_POW(), POW.SHA256_DIGEST)
00464
00465 return X509(POWpkix = cert)
00466
00467 @classmethod
00468 def normalize_chain(cls, chain):
00469 """
00470 Normalize a chain of certificates into a tuple of X509 objects.
00471 Given all the glue certificates needed for BPKI cross
00472 certification, it's easiest to allow sloppy arguments to the HTTPS
00473 and CMS validation methods and provide a single method that
00474 normalizes the allowed cases. So this method allows X509, None,
00475 lists, and tuples, and returns a tuple of X509 objects.
00476 """
00477 if isinstance(chain, cls):
00478 chain = (chain,)
00479 return tuple(x for x in chain if x is not None)
00480
00481 class PKCS10(DER_object):
00482 """
00483 Class to hold a PKCS #10 request.
00484 """
00485
00486 formats = ("DER", "POWpkix")
00487 pem_converter = PEM_converter("CERTIFICATE REQUEST")
00488
00489 def get_DER(self):
00490 """
00491 Get the DER value of this certification request.
00492 """
00493 assert not self.empty()
00494 if self.DER:
00495 return self.DER
00496 if self.POWpkix:
00497 self.DER = self.POWpkix.toString()
00498 return self.get_DER()
00499 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00500
00501 def get_POWpkix(self):
00502 """
00503 Get the POW.pkix value of this certification request.
00504 """
00505 assert not self.empty()
00506 if not self.POWpkix:
00507 req = POW.pkix.CertificationRequest()
00508 req.fromString(self.get_DER())
00509 self.POWpkix = req
00510 return self.POWpkix
00511
00512 def getPublicKey(self):
00513 """Extract the public key from this certification request."""
00514 return RSApublic(DER = self.get_POWpkix().certificationRequestInfo.subjectPublicKeyInfo.toString())
00515
00516 def check_valid_rpki(self):
00517 """
00518 Check this certification request to see whether it's a valid
00519 request for an RPKI certificate. This is broken out of the
00520 up-down protocol code because it's somewhat involved and the
00521 up-down code doesn't need to know the details.
00522
00523 Throws an exception if the request isn't valid, so if this method
00524 returns at all, the request is ok.
00525 """
00526
00527 if not self.get_POWpkix().verify():
00528 raise rpki.exceptions.BadPKCS10, "Signature check failed"
00529
00530 if self.get_POWpkix().certificationRequestInfo.version.get() != 0:
00531 raise rpki.exceptions.BadPKCS10, \
00532 "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version
00533
00534 if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) \
00535 not in ("sha256WithRSAEncryption", "sha384WithRSAEncryption", "sha512WithRSAEncryption"):
00536 raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm
00537
00538 exts = self.get_POWpkix().getExtensions()
00539 for oid, critical, value in exts:
00540 if rpki.oids.oid2name.get(oid) not in ("basicConstraints", "keyUsage", "subjectInfoAccess"):
00541 raise rpki.exceptions.BadExtension, "Forbidden extension %s" % oid
00542 req_exts = dict((rpki.oids.oid2name[oid], value) for (oid, critical, value) in exts)
00543
00544 if "basicConstraints" not in req_exts or not req_exts["basicConstraints"][0]:
00545 raise rpki.exceptions.BadPKCS10, "request for EE cert not allowed here"
00546
00547 if req_exts["basicConstraints"][1] is not None:
00548 raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length"
00549
00550 if "keyUsage" in req_exts and (not req_exts["keyUsage"][5] or not req_exts["keyUsage"][6]):
00551 raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints"
00552
00553 for method, location in req_exts.get("subjectInfoAccess", ()):
00554 if rpki.oids.oid2name.get(method) == "id-ad-caRepository" and \
00555 (location[0] != "uri" or (location[1].startswith("rsync://") and not location[1].endswith("/"))):
00556 raise rpki.exceptions.BadPKCS10, "Certificate request includes bad SIA component: %s" % repr(location)
00557
00558
00559
00560 assert "subjectInfoAccess" in req_exts, "Can't (yet) handle PKCS #10 without an SIA extension"
00561
00562 @classmethod
00563 def create_ca(cls, keypair, sia = None):
00564 """
00565 Create a new request for a given keypair, including given SIA value.
00566 """
00567 exts = [["basicConstraints", True, (1, None)],
00568 ["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)]]
00569 if sia is not None:
00570 exts.append(["subjectInfoAccess", False, sia])
00571 for x in exts:
00572 x[0] = rpki.oids.name2oid[x[0]]
00573 return cls.create(keypair, exts)
00574
00575 @classmethod
00576 def create(cls, keypair, exts = None):
00577 """
00578 Create a new request for a given keypair, including given extensions.
00579 """
00580 cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI()))
00581 req = POW.pkix.CertificationRequest()
00582 req.certificationRequestInfo.version.set(0)
00583 req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"],
00584 ("printableString", cn)),),))
00585 if exts is not None:
00586 req.setExtensions(exts)
00587 req.sign(keypair.get_POW(), POW.SHA256_DIGEST)
00588 return cls(POWpkix = req)
00589
00590 class RSA(DER_object):
00591 """
00592 Class to hold an RSA key pair.
00593 """
00594
00595 formats = ("DER", "POW", "tlslite")
00596 pem_converter = PEM_converter("RSA PRIVATE KEY")
00597
00598 def get_DER(self):
00599 """
00600 Get the DER value of this keypair.
00601 """
00602 assert not self.empty()
00603 if self.DER:
00604 return self.DER
00605 if self.POW:
00606 self.DER = self.POW.derWrite(POW.RSA_PRIVATE_KEY)
00607 return self.get_DER()
00608 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00609
00610 def get_POW(self):
00611 """
00612 Get the POW value of this keypair.
00613 """
00614 assert not self.empty()
00615 if not self.POW:
00616 self.POW = POW.derRead(POW.RSA_PRIVATE_KEY, self.get_DER())
00617 return self.POW
00618
00619 def get_tlslite(self):
00620 """
00621 Get the tlslite value of this keypair.
00622 """
00623 assert not self.empty()
00624 if not self.tlslite:
00625 self.tlslite = tlslite.api.parsePEMKey(self.get_PEM(), private=True)
00626 return self.tlslite
00627
00628 @classmethod
00629 def generate(cls, keylength = 2048):
00630 """
00631 Generate a new keypair.
00632 """
00633 rpki.log.debug("Generating new %d-bit RSA key" % keylength)
00634 return cls(POW = POW.Asymmetric(POW.RSA_CIPHER, keylength))
00635
00636 def get_public_DER(self):
00637 """Get the DER encoding of the public key from this keypair."""
00638 return self.get_POW().derWrite(POW.RSA_PUBLIC_KEY)
00639
00640 def get_SKI(self):
00641 """Calculate the SKI of this keypair."""
00642 return calculate_SKI(self.get_public_DER())
00643
00644 def get_RSApublic(self):
00645 """Convert the public key of this keypair into a RSApublic object."""
00646 return RSApublic(DER = self.get_public_DER())
00647
00648 class RSApublic(DER_object):
00649 """
00650 Class to hold an RSA public key.
00651 """
00652
00653 formats = ("DER", "POW")
00654 pem_converter = PEM_converter("RSA PUBLIC KEY")
00655
00656 def get_DER(self):
00657 """
00658 Get the DER value of this public key.
00659 """
00660 assert not self.empty()
00661 if self.DER:
00662 return self.DER
00663 if self.POW:
00664 self.DER = self.POW.derWrite(POW.RSA_PUBLIC_KEY)
00665 return self.get_DER()
00666 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00667
00668 def get_POW(self):
00669 """
00670 Get the POW value of this public key.
00671 """
00672 assert not self.empty()
00673 if not self.POW:
00674 self.POW = POW.derRead(POW.RSA_PUBLIC_KEY, self.get_DER())
00675 return self.POW
00676
00677 def get_SKI(self):
00678 """Calculate the SKI of this public key."""
00679 return calculate_SKI(self.get_DER())
00680
00681 def POWify_OID(oid):
00682 """
00683 Utility function to convert tuple form of an OID to the
00684 dotted-decimal string form that POW uses.
00685 """
00686 if isinstance(oid, str):
00687 return POWify_OID(rpki.oids.name2oid[oid])
00688 else:
00689 return ".".join(str(i) for i in oid)
00690
00691 class CMS_object(DER_object):
00692 """
00693 Class to hold a CMS-wrapped object.
00694
00695 CMS-wrapped objects are a little different from the other DER_object
00696 types because the signed object is CMS wrapping inner content that's
00697 also ASN.1, and due to our current minimal support for CMS we can't
00698 just handle this as a pretty composite object. So, for now anyway,
00699 a CMS_object is the outer CMS wrapped object so that the usual DER
00700 and PEM operations do the obvious things, and the inner content is
00701 handle via separate methods.
00702 """
00703
00704 formats = ("DER", "POW")
00705 other_clear = ("content",)
00706 econtent_oid = POWify_OID("id-data")
00707 pem_converter = PEM_converter("CMS")
00708
00709
00710
00711
00712 dump_on_verify_failure = True
00713
00714
00715
00716
00717 debug_cms_certs = False
00718
00719
00720
00721
00722
00723
00724 require_crls = False
00725
00726
00727
00728
00729
00730 print_on_der_error = True
00731
00732 def get_DER(self):
00733 """
00734 Get the DER value of this CMS_object.
00735 """
00736 assert not self.empty()
00737 if self.DER:
00738 return self.DER
00739 if self.POW:
00740 self.DER = self.POW.derWrite()
00741 return self.get_DER()
00742 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00743
00744 def get_POW(self):
00745 """
00746 Get the POW value of this CMS_object.
00747 """
00748 assert not self.empty()
00749 if not self.POW:
00750 self.POW = POW.derRead(POW.CMS_MESSAGE, self.get_DER())
00751 return self.POW
00752
00753 def get_content(self):
00754 """
00755 Get the inner content of this CMS_object.
00756 """
00757 assert self.content is not None
00758 return self.content
00759
00760 def set_content(self, content):
00761 """
00762 Set the (inner) content of this CMS_object, clearing the wrapper.
00763 """
00764 self.clear()
00765 self.content = content
00766
00767 def verify(self, ta):
00768 """
00769 Verify CMS wrapper and store inner content.
00770 """
00771
00772 try:
00773 cms = self.get_POW()
00774 except (rpki.async.ExitNow, SystemExit):
00775 raise
00776 except:
00777 if self.print_on_der_error:
00778 rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %s"
00779 % repr(self.get_DER()))
00780 raise rpki.exceptions.UnparsableCMSDER
00781
00782 if cms.eContentType() != self.econtent_oid:
00783 raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00784
00785 certs = [X509(POW = x) for x in cms.certs()]
00786 crls = [CRL(POW = c) for c in cms.crls()]
00787
00788 if self.debug_cms_certs:
00789 for x in certs:
00790 rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00791 for c in crls:
00792 rpki.log.debug("Received CMS CRL issuer %s" % repr(c.getIssuer()))
00793
00794 store = POW.X509Store()
00795
00796 trusted_ee = None
00797
00798 for x in X509.normalize_chain(ta):
00799 if self.debug_cms_certs:
00800 rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00801 if not x.is_CA():
00802 assert trusted_ee is None, "Can't have two EE certs in the same validation chain"
00803 trusted_ee = x
00804 store.addTrust(x.get_POW())
00805
00806 if trusted_ee:
00807 if self.debug_cms_certs:
00808 rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
00809 if certs and (len(certs) > 1 or certs[0] != trusted_ee):
00810 raise rpki.exceptions.UnexpectedCMSCerts, certs
00811 if crls:
00812 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00813 else:
00814 if not certs:
00815 raise rpki.exceptions.MissingCMSEEcert, certs
00816 if len(certs) > 1 or certs[0].is_CA():
00817 raise rpki.exceptions.UnexpectedCMSCerts, certs
00818 if not crls:
00819 if self.require_crls:
00820 raise rpki.exceptions.MissingCMSCRL, crls
00821 else:
00822 rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting")
00823 if len(crls) > 1:
00824 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00825
00826 try:
00827 content = cms.verify(store)
00828 except (rpki.async.ExitNow, SystemExit):
00829 raise
00830 except:
00831 if self.dump_on_verify_failure:
00832 if True:
00833 dbg = self.dumpasn1()
00834 else:
00835 dbg = cms.pprint()
00836 print "CMS verification failed, dumping ASN.1 (%d octets):\n%s" % (len(self.get_DER()), dbg)
00837 raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
00838
00839 self.decode(content)
00840 return self.get_content()
00841
00842 def extract(self):
00843 """
00844 Extract and store inner content from CMS wrapper without verifying
00845 the CMS.
00846
00847 DANGER WILL ROBINSON!!!
00848
00849 Do not use this method on unvalidated data. Use the verify()
00850 method instead.
00851
00852 If you don't understand this warning, don't use this method.
00853 """
00854
00855 try:
00856 cms = self.get_POW()
00857 except (rpki.async.ExitNow, SystemExit):
00858 raise
00859 except:
00860 raise rpki.exceptions.UnparsableCMSDER
00861
00862 if cms.eContentType() != self.econtent_oid:
00863 raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00864
00865 content = cms.verify(POW.X509Store(), None, POW.CMS_NOCRL | POW.CMS_NO_SIGNER_CERT_VERIFY | POW.CMS_NO_ATTR_VERIFY | POW.CMS_NO_CONTENT_VERIFY)
00866
00867 self.decode(content)
00868 return self.get_content()
00869
00870 def sign(self, keypair, certs, crls = None, no_certs = False):
00871 """
00872 Sign and wrap inner content.
00873 """
00874
00875 rpki.log.trace()
00876
00877 if isinstance(certs, X509):
00878 cert = certs
00879 certs = ()
00880 else:
00881 cert = certs[0]
00882 certs = certs[1:]
00883
00884 if crls is None:
00885 crls = ()
00886 elif isinstance(crls, CRL):
00887 crls = (crls,)
00888
00889 if self.debug_cms_certs:
00890 rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI()))
00891 for i, c in enumerate(certs):
00892 rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI()))
00893
00894 cms = POW.CMS()
00895
00896 cms.sign(cert.get_POW(),
00897 keypair.get_POW(),
00898 self.encode(),
00899 [x.get_POW() for x in certs],
00900 [c.get_POW() for c in crls],
00901 self.econtent_oid,
00902 POW.CMS_NOCERTS if no_certs else 0)
00903
00904 self.POW = cms
00905
00906 class DER_CMS_object(CMS_object):
00907 """
00908 Class to hold CMS objects with DER-based content.
00909 """
00910
00911 def encode(self):
00912 """Encode inner content for signing."""
00913 return self.get_content().toString()
00914
00915 def decode(self, der):
00916 """
00917 Decode DER and set inner content.
00918 """
00919 obj = self.content_class()
00920 obj.fromString(der)
00921 self.content = obj
00922
00923 class SignedManifest(DER_CMS_object):
00924 """
00925 Class to hold a signed manifest.
00926 """
00927
00928 pem_converter = PEM_converter("RPKI MANIFEST")
00929 content_class = rpki.manifest.Manifest
00930 econtent_oid = POWify_OID("id-ct-rpkiManifest")
00931
00932 def getThisUpdate(self):
00933 """Get thisUpdate value from this manifest."""
00934 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
00935
00936 def getNextUpdate(self):
00937 """Get nextUpdate value from this manifest."""
00938 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
00939
00940 @classmethod
00941 def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
00942 """
00943 Build a signed manifest.
00944 """
00945 self = cls()
00946 filelist = []
00947 for name, obj in names_and_objs:
00948 d = POW.Digest(POW.SHA256_DIGEST)
00949 d.update(obj.get_DER())
00950 filelist.append((name.rpartition("/")[2], d.digest()))
00951 filelist.sort(key = lambda x: x[0])
00952 m = rpki.manifest.Manifest()
00953 m.version.set(version)
00954 m.manifestNumber.set(serial)
00955 m.thisUpdate.set(thisUpdate.toGeneralizedTime())
00956 m.nextUpdate.set(nextUpdate.toGeneralizedTime())
00957 m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
00958 m.fileList.set(filelist)
00959 self.set_content(m)
00960 self.sign(keypair, certs)
00961 return self
00962
00963 class ROA(DER_CMS_object):
00964 """
00965 Class to hold a signed ROA.
00966 """
00967
00968 pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
00969 content_class = rpki.roa.RouteOriginAttestation
00970 econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
00971
00972 @classmethod
00973 def build(cls, as_number, ipv4, ipv6, keypair, certs, version = 0):
00974 """
00975 Build a ROA.
00976 """
00977 self = cls()
00978 r = rpki.roa.RouteOriginAttestation()
00979 r.version.set(version)
00980 r.asID.set(as_number)
00981 r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
00982 self.set_content(r)
00983 self.sign(keypair, certs)
00984 return self
00985
00986 class XML_CMS_object(CMS_object):
00987 """
00988 Class to hold CMS-wrapped XML protocol data.
00989 """
00990
00991 econtent_oid = POWify_OID("id-ct-xml")
00992
00993
00994
00995
00996
00997
00998 dump_outbound_cms = None
00999
01000
01001
01002
01003
01004
01005 dump_inbound_cms = None
01006
01007 def encode(self):
01008 """Encode inner content for signing."""
01009 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01010
01011 def decode(self, xml):
01012 """Decode XML and set inner content."""
01013 self.content = lxml.etree.fromstring(xml)
01014
01015 def pretty_print_content(self):
01016 """Pretty print XML content of this message."""
01017 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
01018
01019 def schema_check(self):
01020 """
01021 Handle XML RelaxNG schema check.
01022 """
01023 try:
01024 self.schema.assertValid(self.get_content())
01025 except lxml.etree.DocumentInvalid:
01026 rpki.log.error("PDU failed schema check: " + self.pretty_print_content())
01027 raise
01028
01029 def dump_to_disk(self, prefix):
01030 """
01031 Write DER of current message to disk, for debugging.
01032 """
01033 f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
01034 f.write(self.get_DER())
01035 f.close()
01036
01037 @classmethod
01038 def wrap(cls, msg, keypair, certs, crls = None, pretty_print = False):
01039 """
01040 Build a CMS-wrapped XML PDU and return its DER encoding.
01041 """
01042 rpki.log.trace()
01043 self = cls()
01044 self.set_content(msg.toXML())
01045 self.schema_check()
01046 self.sign(keypair, certs, crls)
01047 if self.dump_outbound_cms:
01048 self.dump_to_disk(self.dump_outbound_cms)
01049 if pretty_print:
01050 return self.get_DER(), self.pretty_print_content()
01051 else:
01052 return self.get_DER()
01053
01054 @classmethod
01055 def unwrap(cls, der, ta, pretty_print = False):
01056 """
01057 Unwrap a CMS-wrapped XML PDU and return Python objects.
01058 """
01059 self = cls(DER = der)
01060 if self.dump_inbound_cms:
01061 self.dump_to_disk(self.dump_inbound_cms)
01062 self.verify(ta)
01063 self.schema_check()
01064 msg = self.saxify(self.get_content())
01065 if pretty_print:
01066 return msg, self.pretty_print_content()
01067 else:
01068 return msg
01069
01070 class CRL(DER_object):
01071 """
01072 Class to hold a Certificate Revocation List.
01073 """
01074
01075 formats = ("DER", "POW", "POWpkix")
01076 pem_converter = PEM_converter("X509 CRL")
01077
01078 def get_DER(self):
01079 """
01080 Get the DER value of this CRL.
01081 """
01082 assert not self.empty()
01083 if self.DER:
01084 return self.DER
01085 if self.POW:
01086 self.DER = self.POW.derWrite()
01087 return self.get_DER()
01088 if self.POWpkix:
01089 self.DER = self.POWpkix.toString()
01090 return self.get_DER()
01091 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
01092
01093 def get_POW(self):
01094 """
01095 Get the POW value of this CRL.
01096 """
01097 assert not self.empty()
01098 if not self.POW:
01099 self.POW = POW.derRead(POW.X509_CRL, self.get_DER())
01100 return self.POW
01101
01102 def get_POWpkix(self):
01103 """
01104 Get the POW.pkix value of this CRL.
01105 """
01106 assert not self.empty()
01107 if not self.POWpkix:
01108 crl = POW.pkix.CertificateList()
01109 crl.fromString(self.get_DER())
01110 self.POWpkix = crl
01111 return self.POWpkix
01112
01113 def getThisUpdate(self):
01114 """Get thisUpdate value from this CRL."""
01115 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
01116
01117 def getNextUpdate(self):
01118 """Get nextUpdate value from this CRL."""
01119 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
01120
01121 def getIssuer(self):
01122 """Get issuer value of this CRL."""
01123 return self.get_POW().getIssuer()
01124
01125 @classmethod
01126 def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
01127 """
01128 Generate a new CRL.
01129 """
01130 crl = POW.pkix.CertificateList()
01131 crl.setVersion(version)
01132 crl.setIssuer(issuer.get_POWpkix().getSubject())
01133 crl.setThisUpdate(thisUpdate.toASN1tuple())
01134 crl.setNextUpdate(nextUpdate.toASN1tuple())
01135 if revokedCertificates:
01136 crl.setRevokedCertificates(revokedCertificates)
01137 crl.setExtensions(
01138 ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
01139 (rpki.oids.name2oid["cRLNumber"], False, serial)))
01140 crl.sign(keypair.get_POW(), digestType)
01141 return cls(POWpkix = crl)