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