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