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 2510 2009-06-09 20:25:16Z 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
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")
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 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00346
00347 def get_POW(self):
00348 """
00349 Get the POW value of this certificate.
00350 """
00351 assert not self.empty()
00352 if not self.POW:
00353 self.POW = POW.derRead(POW.X509_CERTIFICATE, self.get_DER())
00354 return self.POW
00355
00356 def get_POWpkix(self):
00357 """
00358 Get the POW.pkix value of this certificate.
00359 """
00360 assert not self.empty()
00361 if not self.POWpkix:
00362 cert = POW.pkix.Certificate()
00363 cert.fromString(self.get_DER())
00364 self.POWpkix = cert
00365 return self.POWpkix
00366
00367 def getIssuer(self):
00368 """Get the issuer of this certificate."""
00369 return self.get_POW().getIssuer()
00370
00371 def getSubject(self):
00372 """Get the subject of this certificate."""
00373 return self.get_POW().getSubject()
00374
00375 def getNotBefore(self):
00376 """Get the inception time of this certificate."""
00377 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notBefore.get())
00378
00379 def getNotAfter(self):
00380 """Get the expiration time of this certificate."""
00381 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().tbs.validity.notAfter.get())
00382
00383 def getSerial(self):
00384 """Get the serial number of this certificate."""
00385 return self.get_POW().getSerial()
00386
00387 def getPublicKey(self):
00388 """Extract the public key from this certificate."""
00389 return RSApublic(DER = self.get_POWpkix().tbs.subjectPublicKeyInfo.toString())
00390
00391 def expired(self):
00392 """Test whether this certificate has expired."""
00393 return self.getNotAfter() <= rpki.sundial.now()
00394
00395 def issue(self, keypair, subject_key, serial, sia, aia, crldp, notAfter,
00396 cn = None, resources = None, is_ca = True):
00397 """
00398 Issue a certificate.
00399 """
00400
00401 now = rpki.sundial.now()
00402 aki = self.get_SKI()
00403 ski = subject_key.get_SKI()
00404
00405 if cn is None:
00406 cn = "".join(("%02X" % ord(i) for i in ski))
00407
00408
00409
00410 cert = POW.pkix.Certificate()
00411 cert.setVersion(2)
00412 cert.setSerial(serial)
00413 cert.setIssuer(self.get_POWpkix().getSubject())
00414 cert.setSubject((((rpki.oids.name2oid["commonName"], ("printableString", cn)),),))
00415 cert.setNotBefore(now.toASN1tuple())
00416 cert.setNotAfter(notAfter.toASN1tuple())
00417 cert.tbs.subjectPublicKeyInfo.fromString(subject_key.get_DER())
00418
00419 exts = [ ["subjectKeyIdentifier", False, ski],
00420 ["authorityKeyIdentifier", False, (aki, (), None)],
00421 ["cRLDistributionPoints", False, ((("fullName", (("uri", crldp),)), None, ()),)],
00422 ["authorityInfoAccess", False, ((rpki.oids.name2oid["id-ad-caIssuers"], ("uri", aia)),)],
00423 ["certificatePolicies", True, ((rpki.oids.name2oid["id-cp-ipAddr-asNumber"], ()),)] ]
00424
00425 if is_ca:
00426 exts.append(["basicConstraints", True, (1, None)])
00427 exts.append(["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)])
00428 else:
00429 exts.append(["keyUsage", True, (1,)])
00430
00431 if sia is not None:
00432 exts.append(["subjectInfoAccess", False, sia])
00433 else:
00434 assert not is_ca
00435
00436 if resources is not None and resources.asn:
00437 exts.append(["sbgp-autonomousSysNum", True, (resources.asn.to_rfc3779_tuple(), None)])
00438
00439 if resources is not None and (resources.v4 or resources.v6):
00440 exts.append(["sbgp-ipAddrBlock", True, [x for x in (resources.v4.to_rfc3779_tuple(), resources.v6.to_rfc3779_tuple()) if x is not None]])
00441
00442 for x in exts:
00443 x[0] = rpki.oids.name2oid[x[0]]
00444 cert.setExtensions(exts)
00445
00446 cert.sign(keypair.get_POW(), POW.SHA256_DIGEST)
00447
00448 return X509(POWpkix = cert)
00449
00450 @classmethod
00451 def normalize_chain(cls, chain):
00452 """
00453 Normalize a chain of certificates into a tuple of X509 objects.
00454 Given all the glue certificates needed for BPKI cross
00455 certification, it's easiest to allow sloppy arguments to the HTTPS
00456 and CMS validation methods and provide a single method that
00457 normalizes the allowed cases. So this method allows X509, None,
00458 lists, and tuples, and returns a tuple of X509 objects.
00459 """
00460 if isinstance(chain, cls):
00461 chain = (chain,)
00462 return tuple(x for x in chain if x is not None)
00463
00464 class PKCS10(DER_object):
00465 """
00466 Class to hold a PKCS #10 request.
00467 """
00468
00469 formats = ("DER", "POWpkix")
00470 pem_converter = PEM_converter("CERTIFICATE REQUEST")
00471
00472 def get_DER(self):
00473 """
00474 Get the DER value of this certification request.
00475 """
00476 assert not self.empty()
00477 if self.DER:
00478 return self.DER
00479 if self.POWpkix:
00480 self.DER = self.POWpkix.toString()
00481 return self.get_DER()
00482 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00483
00484 def get_POWpkix(self):
00485 """
00486 Get the POW.pkix value of this certification request.
00487 """
00488 assert not self.empty()
00489 if not self.POWpkix:
00490 req = POW.pkix.CertificationRequest()
00491 req.fromString(self.get_DER())
00492 self.POWpkix = req
00493 return self.POWpkix
00494
00495 def getPublicKey(self):
00496 """Extract the public key from this certification request."""
00497 return RSApublic(DER = self.get_POWpkix().certificationRequestInfo.subjectPublicKeyInfo.toString())
00498
00499 def check_valid_rpki(self):
00500 """
00501 Check this certification request to see whether it's a valid
00502 request for an RPKI certificate. This is broken out of the
00503 up-down protocol code because it's somewhat involved and the
00504 up-down code doesn't need to know the details.
00505
00506 Throws an exception if the request isn't valid, so if this method
00507 returns at all, the request is ok.
00508 """
00509
00510 if not self.get_POWpkix().verify():
00511 raise rpki.exceptions.BadPKCS10, "Signature check failed"
00512
00513 if self.get_POWpkix().certificationRequestInfo.version.get() != 0:
00514 raise rpki.exceptions.BadPKCS10, \
00515 "Bad version number %s" % self.get_POWpkix().certificationRequestInfo.version
00516
00517 if rpki.oids.oid2name.get(self.get_POWpkix().signatureAlgorithm.algorithm.get()) \
00518 not in ("sha256WithRSAEncryption", "sha384WithRSAEncryption", "sha512WithRSAEncryption"):
00519 raise rpki.exceptions.BadPKCS10, "Bad signature algorithm %s" % self.get_POWpkix().signatureAlgorithm
00520
00521 exts = self.get_POWpkix().getExtensions()
00522 for oid, critical, value in exts:
00523 if rpki.oids.oid2name.get(oid) not in ("basicConstraints", "keyUsage", "subjectInfoAccess"):
00524 raise rpki.exceptions.BadExtension, "Forbidden extension %s" % oid
00525 req_exts = dict((rpki.oids.oid2name[oid], value) for (oid, critical, value) in exts)
00526
00527 if "basicConstraints" not in req_exts or not req_exts["basicConstraints"][0]:
00528 raise rpki.exceptions.BadPKCS10, "request for EE cert not allowed here"
00529
00530 if req_exts["basicConstraints"][1] is not None:
00531 raise rpki.exceptions.BadPKCS10, "basicConstraints must not specify Path Length"
00532
00533 if "keyUsage" in req_exts and (not req_exts["keyUsage"][5] or not req_exts["keyUsage"][6]):
00534 raise rpki.exceptions.BadPKCS10, "keyUsage doesn't match basicConstraints"
00535
00536 for method, location in req_exts.get("subjectInfoAccess", ()):
00537 if rpki.oids.oid2name.get(method) == "id-ad-caRepository" and \
00538 (location[0] != "uri" or (location[1].startswith("rsync://") and not location[1].endswith("/"))):
00539 raise rpki.exceptions.BadPKCS10, "Certificate request includes bad SIA component: %s" % repr(location)
00540
00541
00542
00543 assert "subjectInfoAccess" in req_exts, "Can't (yet) handle PKCS #10 without an SIA extension"
00544
00545 @classmethod
00546 def create_ca(cls, keypair, sia = None):
00547 """
00548 Create a new request for a given keypair, including given SIA value.
00549 """
00550 exts = [["basicConstraints", True, (1, None)],
00551 ["keyUsage", True, (0, 0, 0, 0, 0, 1, 1)]]
00552 if sia is not None:
00553 exts.append(["subjectInfoAccess", False, sia])
00554 for x in exts:
00555 x[0] = rpki.oids.name2oid[x[0]]
00556 return cls.create(keypair, exts)
00557
00558 @classmethod
00559 def create(cls, keypair, exts = None):
00560 """
00561 Create a new request for a given keypair, including given extensions.
00562 """
00563 cn = "".join(("%02X" % ord(i) for i in keypair.get_SKI()))
00564 req = POW.pkix.CertificationRequest()
00565 req.certificationRequestInfo.version.set(0)
00566 req.certificationRequestInfo.subject.set((((rpki.oids.name2oid["commonName"],
00567 ("printableString", cn)),),))
00568 if exts is not None:
00569 req.setExtensions(exts)
00570 req.sign(keypair.get_POW(), POW.SHA256_DIGEST)
00571 return cls(POWpkix = req)
00572
00573 class RSA(DER_object):
00574 """
00575 Class to hold an RSA key pair.
00576 """
00577
00578 formats = ("DER", "POW")
00579 pem_converter = PEM_converter("RSA PRIVATE KEY")
00580
00581 def get_DER(self):
00582 """
00583 Get the DER value of this keypair.
00584 """
00585 assert not self.empty()
00586 if self.DER:
00587 return self.DER
00588 if self.POW:
00589 self.DER = self.POW.derWrite(POW.RSA_PRIVATE_KEY)
00590 return self.get_DER()
00591 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00592
00593 def get_POW(self):
00594 """
00595 Get the POW value of this keypair.
00596 """
00597 assert not self.empty()
00598 if not self.POW:
00599 self.POW = POW.derRead(POW.RSA_PRIVATE_KEY, self.get_DER())
00600 return self.POW
00601
00602 @classmethod
00603 def generate(cls, keylength = 2048):
00604 """
00605 Generate a new keypair.
00606 """
00607 rpki.log.debug("Generating new %d-bit RSA key" % keylength)
00608 return cls(POW = POW.Asymmetric(POW.RSA_CIPHER, keylength))
00609
00610 def get_public_DER(self):
00611 """Get the DER encoding of the public key from this keypair."""
00612 return self.get_POW().derWrite(POW.RSA_PUBLIC_KEY)
00613
00614 def get_SKI(self):
00615 """Calculate the SKI of this keypair."""
00616 return calculate_SKI(self.get_public_DER())
00617
00618 def get_RSApublic(self):
00619 """Convert the public key of this keypair into a RSApublic object."""
00620 return RSApublic(DER = self.get_public_DER())
00621
00622 class RSApublic(DER_object):
00623 """
00624 Class to hold an RSA public key.
00625 """
00626
00627 formats = ("DER", "POW")
00628 pem_converter = PEM_converter("RSA PUBLIC KEY")
00629
00630 def get_DER(self):
00631 """
00632 Get the DER value of this public key.
00633 """
00634 assert not self.empty()
00635 if self.DER:
00636 return self.DER
00637 if self.POW:
00638 self.DER = self.POW.derWrite(POW.RSA_PUBLIC_KEY)
00639 return self.get_DER()
00640 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00641
00642 def get_POW(self):
00643 """
00644 Get the POW value of this public key.
00645 """
00646 assert not self.empty()
00647 if not self.POW:
00648 self.POW = POW.derRead(POW.RSA_PUBLIC_KEY, self.get_DER())
00649 return self.POW
00650
00651 def get_SKI(self):
00652 """Calculate the SKI of this public key."""
00653 return calculate_SKI(self.get_DER())
00654
00655 def POWify_OID(oid):
00656 """
00657 Utility function to convert tuple form of an OID to the
00658 dotted-decimal string form that POW uses.
00659 """
00660 if isinstance(oid, str):
00661 return POWify_OID(rpki.oids.name2oid[oid])
00662 else:
00663 return ".".join(str(i) for i in oid)
00664
00665 class CMS_object(DER_object):
00666 """
00667 Class to hold a CMS-wrapped object.
00668
00669 CMS-wrapped objects are a little different from the other DER_object
00670 types because the signed object is CMS wrapping inner content that's
00671 also ASN.1, and due to our current minimal support for CMS we can't
00672 just handle this as a pretty composite object. So, for now anyway,
00673 a CMS_object is the outer CMS wrapped object so that the usual DER
00674 and PEM operations do the obvious things, and the inner content is
00675 handle via separate methods.
00676 """
00677
00678 formats = ("DER", "POW")
00679 other_clear = ("content",)
00680 econtent_oid = POWify_OID("id-data")
00681 pem_converter = PEM_converter("CMS")
00682
00683
00684
00685
00686 dump_on_verify_failure = True
00687
00688
00689
00690
00691 debug_cms_certs = False
00692
00693
00694
00695
00696
00697
00698 require_crls = False
00699
00700
00701
00702
00703
00704 print_on_der_error = True
00705
00706 def get_DER(self):
00707 """
00708 Get the DER value of this CMS_object.
00709 """
00710 assert not self.empty()
00711 if self.DER:
00712 return self.DER
00713 if self.POW:
00714 self.DER = self.POW.derWrite()
00715 return self.get_DER()
00716 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00717
00718 def get_POW(self):
00719 """
00720 Get the POW value of this CMS_object.
00721 """
00722 assert not self.empty()
00723 if not self.POW:
00724 self.POW = POW.derRead(POW.CMS_MESSAGE, self.get_DER())
00725 return self.POW
00726
00727 def get_content(self):
00728 """
00729 Get the inner content of this CMS_object.
00730 """
00731 assert self.content is not None
00732 return self.content
00733
00734 def set_content(self, content):
00735 """
00736 Set the (inner) content of this CMS_object, clearing the wrapper.
00737 """
00738 self.clear()
00739 self.content = content
00740
00741 def verify(self, ta):
00742 """
00743 Verify CMS wrapper and store inner content.
00744 """
00745
00746 try:
00747 cms = self.get_POW()
00748 except (rpki.async.ExitNow, SystemExit):
00749 raise
00750 except:
00751 if self.print_on_der_error:
00752 rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %s"
00753 % repr(self.get_DER()))
00754 raise rpki.exceptions.UnparsableCMSDER
00755
00756 if cms.eContentType() != self.econtent_oid:
00757 raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00758
00759 certs = [X509(POW = x) for x in cms.certs()]
00760 crls = [CRL(POW = c) for c in cms.crls()]
00761
00762 if self.debug_cms_certs:
00763 for x in certs:
00764 rpki.log.debug("Received CMS cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00765 for c in crls:
00766 rpki.log.debug("Received CMS CRL issuer %s" % repr(c.getIssuer()))
00767
00768 store = POW.X509Store()
00769
00770 trusted_ee = None
00771
00772 for x in X509.normalize_chain(ta):
00773 if self.debug_cms_certs:
00774 rpki.log.debug("CMS trusted cert issuer %s subject %s SKI %s" % (x.getIssuer(), x.getSubject(), x.hSKI()))
00775 if not x.is_CA():
00776 assert trusted_ee is None, "Can't have two EE certs in the same validation chain"
00777 trusted_ee = x
00778 store.addTrust(x.get_POW())
00779
00780 if trusted_ee:
00781 if self.debug_cms_certs:
00782 rpki.log.debug("Trusted CMS EE cert issuer %s subject %s SKI %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject(), trusted_ee.hSKI()))
00783 if certs and (len(certs) > 1 or certs[0] != trusted_ee):
00784 raise rpki.exceptions.UnexpectedCMSCerts, certs
00785 if crls:
00786 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00787 else:
00788 if not certs:
00789 raise rpki.exceptions.MissingCMSEEcert, certs
00790 if len(certs) > 1 or certs[0].is_CA():
00791 raise rpki.exceptions.UnexpectedCMSCerts, certs
00792 if not crls:
00793 if self.require_crls:
00794 raise rpki.exceptions.MissingCMSCRL, crls
00795 else:
00796 rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting")
00797 if len(crls) > 1:
00798 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00799
00800 try:
00801 content = cms.verify(store)
00802 except (rpki.async.ExitNow, SystemExit):
00803 raise
00804 except:
00805 if self.dump_on_verify_failure:
00806 if True:
00807 dbg = self.dumpasn1()
00808 else:
00809 dbg = cms.pprint()
00810 print "CMS verification failed, dumping ASN.1 (%d octets):\n%s" % (len(self.get_DER()), dbg)
00811 raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
00812
00813 self.decode(content)
00814 return self.get_content()
00815
00816 def extract(self):
00817 """
00818 Extract and store inner content from CMS wrapper without verifying
00819 the CMS.
00820
00821 DANGER WILL ROBINSON!!!
00822
00823 Do not use this method on unvalidated data. Use the verify()
00824 method instead.
00825
00826 If you don't understand this warning, don't use this method.
00827 """
00828
00829 try:
00830 cms = self.get_POW()
00831 except (rpki.async.ExitNow, SystemExit):
00832 raise
00833 except:
00834 raise rpki.exceptions.UnparsableCMSDER
00835
00836 if cms.eContentType() != self.econtent_oid:
00837 raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00838
00839 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)
00840
00841 self.decode(content)
00842 return self.get_content()
00843
00844 def sign(self, keypair, certs, crls = None, no_certs = False):
00845 """
00846 Sign and wrap inner content.
00847 """
00848
00849 rpki.log.trace()
00850
00851 if isinstance(certs, X509):
00852 cert = certs
00853 certs = ()
00854 else:
00855 cert = certs[0]
00856 certs = certs[1:]
00857
00858 if crls is None:
00859 crls = ()
00860 elif isinstance(crls, CRL):
00861 crls = (crls,)
00862
00863 if self.debug_cms_certs:
00864 rpki.log.debug("Signing with cert issuer %s subject %s SKI %s" % (cert.getIssuer(), cert.getSubject(), cert.hSKI()))
00865 for i, c in enumerate(certs):
00866 rpki.log.debug("Additional cert %d issuer %s subject %s SKI %s" % (i, c.getIssuer(), c.getSubject(), c.hSKI()))
00867
00868 cms = POW.CMS()
00869
00870 cms.sign(cert.get_POW(),
00871 keypair.get_POW(),
00872 self.encode(),
00873 [x.get_POW() for x in certs],
00874 [c.get_POW() for c in crls],
00875 self.econtent_oid,
00876 POW.CMS_NOCERTS if no_certs else 0)
00877
00878 self.POW = cms
00879
00880 class DER_CMS_object(CMS_object):
00881 """
00882 Class to hold CMS objects with DER-based content.
00883 """
00884
00885 def encode(self):
00886 """Encode inner content for signing."""
00887 return self.get_content().toString()
00888
00889 def decode(self, der):
00890 """
00891 Decode DER and set inner content.
00892 """
00893 obj = self.content_class()
00894 obj.fromString(der)
00895 self.content = obj
00896
00897 class SignedManifest(DER_CMS_object):
00898 """
00899 Class to hold a signed manifest.
00900 """
00901
00902 pem_converter = PEM_converter("RPKI MANIFEST")
00903 content_class = rpki.manifest.Manifest
00904 econtent_oid = POWify_OID("id-ct-rpkiManifest")
00905
00906 def getThisUpdate(self):
00907 """Get thisUpdate value from this manifest."""
00908 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
00909
00910 def getNextUpdate(self):
00911 """Get nextUpdate value from this manifest."""
00912 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
00913
00914 @classmethod
00915 def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
00916 """
00917 Build a signed manifest.
00918 """
00919 self = cls()
00920 filelist = []
00921 for name, obj in names_and_objs:
00922 d = POW.Digest(POW.SHA256_DIGEST)
00923 d.update(obj.get_DER())
00924 filelist.append((name.rpartition("/")[2], d.digest()))
00925 filelist.sort(key = lambda x: x[0])
00926 m = rpki.manifest.Manifest()
00927 m.version.set(version)
00928 m.manifestNumber.set(serial)
00929 m.thisUpdate.set(thisUpdate.toGeneralizedTime())
00930 m.nextUpdate.set(nextUpdate.toGeneralizedTime())
00931 m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
00932 m.fileList.set(filelist)
00933 self.set_content(m)
00934 self.sign(keypair, certs)
00935 return self
00936
00937 class ROA(DER_CMS_object):
00938 """
00939 Class to hold a signed ROA.
00940 """
00941
00942 pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
00943 content_class = rpki.roa.RouteOriginAttestation
00944 econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
00945
00946 @classmethod
00947 def build(cls, asn, ipv4, ipv6, keypair, certs, version = 0):
00948 """
00949 Build a ROA.
00950 """
00951 self = cls()
00952 r = rpki.roa.RouteOriginAttestation()
00953 r.version.set(version)
00954 r.asID.set(asn)
00955 r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
00956 self.set_content(r)
00957 self.sign(keypair, certs)
00958 return self
00959
00960 class XML_CMS_object(CMS_object):
00961 """
00962 Class to hold CMS-wrapped XML protocol data.
00963 """
00964
00965 econtent_oid = POWify_OID("id-ct-xml")
00966
00967
00968
00969
00970
00971
00972 dump_outbound_cms = None
00973
00974
00975
00976
00977
00978
00979 dump_inbound_cms = None
00980
00981 def encode(self):
00982 """Encode inner content for signing."""
00983 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
00984
00985 def decode(self, xml):
00986 """Decode XML and set inner content."""
00987 self.content = lxml.etree.fromstring(xml)
00988
00989 def pretty_print_content(self):
00990 """Pretty print XML content of this message."""
00991 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
00992
00993 def schema_check(self):
00994 """
00995 Handle XML RelaxNG schema check.
00996 """
00997 try:
00998 self.schema.assertValid(self.get_content())
00999 except lxml.etree.DocumentInvalid:
01000 rpki.log.error("PDU failed schema check: " + self.pretty_print_content())
01001 raise
01002
01003 def dump_to_disk(self, prefix):
01004 """
01005 Write DER of current message to disk, for debugging.
01006 """
01007 f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
01008 f.write(self.get_DER())
01009 f.close()
01010
01011 @classmethod
01012 def wrap(cls, msg, keypair, certs, crls = None, pretty_print = False):
01013 """
01014 Build a CMS-wrapped XML PDU and return its DER encoding.
01015 """
01016 rpki.log.trace()
01017 self = cls()
01018 self.set_content(msg.toXML())
01019 self.schema_check()
01020 self.sign(keypair, certs, crls)
01021 if self.dump_outbound_cms:
01022 self.dump_to_disk(self.dump_outbound_cms)
01023 if pretty_print:
01024 return self.get_DER(), self.pretty_print_content()
01025 else:
01026 return self.get_DER()
01027
01028 @classmethod
01029 def unwrap(cls, der, ta, pretty_print = False):
01030 """
01031 Unwrap a CMS-wrapped XML PDU and return Python objects.
01032 """
01033 self = cls(DER = der)
01034 if self.dump_inbound_cms:
01035 self.dump_to_disk(self.dump_inbound_cms)
01036 self.verify(ta)
01037 self.schema_check()
01038 msg = self.saxify(self.get_content())
01039 if pretty_print:
01040 return msg, self.pretty_print_content()
01041 else:
01042 return msg
01043
01044 class CRL(DER_object):
01045 """
01046 Class to hold a Certificate Revocation List.
01047 """
01048
01049 formats = ("DER", "POW", "POWpkix")
01050 pem_converter = PEM_converter("X509 CRL")
01051
01052 def get_DER(self):
01053 """
01054 Get the DER value of this CRL.
01055 """
01056 assert not self.empty()
01057 if self.DER:
01058 return self.DER
01059 if self.POW:
01060 self.DER = self.POW.derWrite()
01061 return self.get_DER()
01062 if self.POWpkix:
01063 self.DER = self.POWpkix.toString()
01064 return self.get_DER()
01065 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
01066
01067 def get_POW(self):
01068 """
01069 Get the POW value of this CRL.
01070 """
01071 assert not self.empty()
01072 if not self.POW:
01073 self.POW = POW.derRead(POW.X509_CRL, self.get_DER())
01074 return self.POW
01075
01076 def get_POWpkix(self):
01077 """
01078 Get the POW.pkix value of this CRL.
01079 """
01080 assert not self.empty()
01081 if not self.POWpkix:
01082 crl = POW.pkix.CertificateList()
01083 crl.fromString(self.get_DER())
01084 self.POWpkix = crl
01085 return self.POWpkix
01086
01087 def getThisUpdate(self):
01088 """Get thisUpdate value from this CRL."""
01089 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
01090
01091 def getNextUpdate(self):
01092 """Get nextUpdate value from this CRL."""
01093 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
01094
01095 def getIssuer(self):
01096 """Get issuer value of this CRL."""
01097 return self.get_POW().getIssuer()
01098
01099 @classmethod
01100 def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
01101 """
01102 Generate a new CRL.
01103 """
01104 crl = POW.pkix.CertificateList()
01105 crl.setVersion(version)
01106 crl.setIssuer(issuer.get_POWpkix().getSubject())
01107 crl.setThisUpdate(thisUpdate.toASN1tuple())
01108 crl.setNextUpdate(nextUpdate.toASN1tuple())
01109 if revokedCertificates:
01110 crl.setRevokedCertificates(revokedCertificates)
01111 crl.setExtensions(
01112 ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
01113 (rpki.oids.name2oid["cRLNumber"], False, serial)))
01114 crl.sign(keypair.get_POW(), digestType)
01115 return cls(POWpkix = crl)