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 1916 2008-06-21 19:44:45Z 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 @classmethod
00531 def generate(cls, keylength = 2048):
00532 """Generate a new keypair."""
00533 return cls(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 pem_converter = PEM_converter("CMS")
00599
00600
00601
00602
00603 dump_on_verify_failure = True
00604
00605
00606
00607
00608 debug_cms_certs = False
00609
00610
00611
00612
00613
00614
00615 require_crls = False
00616
00617
00618
00619
00620
00621 print_on_der_error = True
00622
00623 def get_DER(self):
00624 """Get the DER value of this CMS_object."""
00625 assert not self.empty()
00626 if self.DER:
00627 return self.DER
00628 if self.POW:
00629 self.DER = self.POW.derWrite()
00630 return self.get_DER()
00631 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00632
00633 def get_POW(self):
00634 """Get the POW value of this CMS_object."""
00635 assert not self.empty()
00636 if not self.POW:
00637 self.POW = POW.derRead(POW.CMS_MESSAGE, self.get_DER())
00638 return self.POW
00639
00640 def get_content(self):
00641 """Get the inner content of this CMS_object."""
00642 assert self.content is not None
00643 return self.content
00644
00645 def set_content(self, content):
00646 """Set the (inner) content of this CMS_object, clearing the wrapper."""
00647 self.clear()
00648 self.content = content
00649
00650 def verify(self, ta):
00651 """Verify CMS wrapper and store inner content."""
00652
00653 try:
00654 cms = self.get_POW()
00655 except:
00656 if self.print_on_der_error:
00657 rpki.log.debug("Problem parsing DER CMS message, might not really be DER: %s"
00658 % repr(self.get_DER()))
00659 raise rpki.exceptions.UnparsableCMSDER
00660
00661 if cms.eContentType() != self.econtent_oid:
00662 raise rpki.exceptions.WrongEContentType, "Got CMS eContentType %s, expected %s" % (cms.eContentType(), self.econtent_oid)
00663
00664 certs = [X509(POW = x) for x in cms.certs()]
00665 crls = [CRL(POW = c) for c in cms.crls()]
00666
00667 if self.debug_cms_certs:
00668 for x in certs:
00669 rpki.log.debug("Received CMS cert issuer %s subject %s" % (x.getIssuer(), x.getSubject()))
00670 for c in crls:
00671 rpki.log.debug("Received CMS CRL issuer %s" % c.getIssuer())
00672
00673 store = POW.X509Store()
00674
00675 trusted_ee = None
00676
00677 for x in X509.normalize_chain(ta):
00678 if self.debug_cms_certs:
00679 rpki.log.debug("CMS trusted cert issuer %s subject %s" % (x.getIssuer(), x.getSubject()))
00680 if not x.is_CA():
00681 assert trusted_ee is None, "Can't have two EE certs in the same validation chain"
00682 trusted_ee = x
00683 store.addTrust(x.get_POW())
00684
00685 if trusted_ee:
00686 if self.debug_cms_certs:
00687 rpki.log.debug("Trusted CMS EE cert issuer %s subject %s" % (trusted_ee.getIssuer(), trusted_ee.getSubject()))
00688 if certs and (len(certs) > 1 or certs[0] != trusted_ee):
00689 raise rpki.exceptions.UnexpectedCMSCerts, certs
00690 if crls:
00691 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00692 else:
00693 if not certs:
00694 raise rpki.exceptions.MissingCMSEEcert, certs
00695 if len(certs) > 1 or certs[0].is_CA():
00696 raise rpki.exceptions.UnexpectedCMSCerts, certs
00697 if not crls:
00698 if self.require_crls:
00699 raise rpki.exceptions.MissingCMSCRL, crls
00700 else:
00701 rpki.log.warn("MISSING CMS CRL! Ignoring per self.require_crls setting")
00702 if len(crls) > 1:
00703 raise rpki.exceptions.UnexpectedCMSCRLs, crls
00704
00705 try:
00706 content = cms.verify(store)
00707 except:
00708 if self.dump_on_verify_failure:
00709 if True:
00710 dbg = self.dumpasn1()
00711 else:
00712 dbg = cms.pprint()
00713 print "CMS verification failed, dumping ASN.1 (%d octets):\n%s" % (len(self.get_DER()), dbg)
00714 raise rpki.exceptions.CMSVerificationFailed, "CMS verification failed"
00715
00716 self.decode(content)
00717 return self.get_content()
00718
00719 def sign(self, keypair, certs, crls = None, no_certs = False):
00720 """Sign and wrap inner content."""
00721
00722 rpki.log.trace()
00723
00724 if isinstance(certs, X509):
00725 cert = certs
00726 certs = ()
00727 else:
00728 cert = certs[0]
00729 certs = certs[1:]
00730
00731 if crls is None:
00732 crls = ()
00733 elif isinstance(crls, CRL):
00734 crls = (crls,)
00735
00736 cms = POW.CMS()
00737
00738 cms.sign(cert.get_POW(),
00739 keypair.get_POW(),
00740 self.encode(),
00741 [x.get_POW() for x in certs],
00742 [c.get_POW() for c in crls],
00743 self.econtent_oid,
00744 POW.CMS_NOCERTS if no_certs else 0)
00745
00746 self.POW = cms
00747
00748 class DER_CMS_object(CMS_object):
00749 """Class to hold CMS objects with DER-based content."""
00750
00751 def encode(self):
00752 """Encode inner content for signing."""
00753 return self.get_content().toString()
00754
00755 def decode(self, der):
00756 """Decode DER and set inner content."""
00757 obj = self.content_class()
00758 obj.fromString(der)
00759 self.content = obj
00760
00761 class SignedManifest(DER_CMS_object):
00762 """Class to hold a signed manifest."""
00763
00764 pem_converter = PEM_converter("RPKI MANIFEST")
00765 content_class = rpki.manifest.Manifest
00766 econtent_oid = POWify_OID("id-ct-rpkiManifest")
00767
00768 def getThisUpdate(self):
00769 """Get thisUpdate value from this manifest."""
00770 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().thisUpdate.get())
00771
00772 def getNextUpdate(self):
00773 """Get nextUpdate value from this manifest."""
00774 return rpki.sundial.datetime.fromGeneralizedTime(self.get_content().nextUpdate.get())
00775
00776 @classmethod
00777 def build(cls, serial, thisUpdate, nextUpdate, names_and_objs, keypair, certs, version = 0):
00778 """Build a signed manifest."""
00779 self = cls()
00780 filelist = []
00781 for name, obj in names_and_objs:
00782 d = POW.Digest(POW.SHA256_DIGEST)
00783 d.update(obj.get_DER())
00784 filelist.append((name.rpartition("/")[2], d.digest()))
00785 filelist.sort(key = lambda x: x[0])
00786 m = rpki.manifest.Manifest()
00787 m.version.set(version)
00788 m.manifestNumber.set(serial)
00789 m.thisUpdate.set(thisUpdate.toGeneralizedTime())
00790 m.nextUpdate.set(nextUpdate.toGeneralizedTime())
00791 m.fileHashAlg.set(rpki.oids.name2oid["id-sha256"])
00792 m.fileList.set(filelist)
00793 self.set_content(m)
00794 self.sign(keypair, certs)
00795 return self
00796
00797 class ROA(DER_CMS_object):
00798 """Class to hold a signed ROA."""
00799
00800 pem_converter = PEM_converter("ROUTE ORIGIN ATTESTATION")
00801 content_class = rpki.roa.RouteOriginAttestation
00802 econtent_oid = POWify_OID("id-ct-routeOriginAttestation")
00803
00804 @classmethod
00805 def build(cls, as_number, ipv4, ipv6, keypair, certs, version = 0):
00806 """Build a ROA."""
00807 self = cls()
00808 r = rpki.roa.RouteOriginAttestation()
00809 r.version.set(version)
00810 r.asID.set(as_number)
00811 r.ipAddrBlocks.set((a.to_roa_tuple() for a in (ipv4, ipv6) if a))
00812 self.set_content(r)
00813 self.sign(keypair, certs)
00814 return self
00815
00816 class XML_CMS_object(CMS_object):
00817 """Class to hold CMS-wrapped XML protocol data."""
00818
00819 econtent_oid = POWify_OID("id-ct-xml")
00820
00821
00822
00823
00824
00825
00826 dump_outbound_cms = None
00827
00828
00829
00830
00831
00832
00833 dump_inbound_cms = None
00834
00835 def encode(self):
00836 """Encode inner content for signing."""
00837 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
00838
00839 def decode(self, xml):
00840 """Decode XML and set inner content."""
00841 self.content = lxml.etree.fromstring(xml)
00842
00843 def pretty_print_content(self):
00844 """Pretty print XML content of this message."""
00845 return lxml.etree.tostring(self.get_content(), pretty_print = True, encoding = self.encoding, xml_declaration = True)
00846
00847 def schema_check(self):
00848 """Handle XML RelaxNG schema check."""
00849 try:
00850 self.schema.assertValid(self.get_content())
00851 except lxml.etree.DocumentInvalid:
00852 rpki.log.error("PDU failed schema check: " + self.pretty_print_content())
00853 raise
00854
00855 def dump_to_disk(self, prefix):
00856 """Write DER of current message to disk, for debugging."""
00857 f = open(prefix + rpki.sundial.now().isoformat() + "Z.cms", "wb")
00858 f.write(self.get_DER())
00859 f.close()
00860
00861 @classmethod
00862 def wrap(cls, msg, keypair, certs, crls = None, pretty_print = False):
00863 """Build a CMS-wrapped XML PDU and return its DER encoding."""
00864 rpki.log.trace()
00865 self = cls()
00866 self.set_content(msg.toXML())
00867 self.schema_check()
00868 self.sign(keypair, certs, crls)
00869 if self.dump_outbound_cms:
00870 self.dump_to_disk(self.dump_outbound_cms)
00871 if pretty_print:
00872 return self.get_DER(), self.pretty_print_content()
00873 else:
00874 return self.get_DER()
00875
00876 @classmethod
00877 def unwrap(cls, der, ta, pretty_print = False):
00878 """Unwrap a CMS-wrapped XML PDU and return Python objects."""
00879 self = cls(DER = der)
00880 if self.dump_inbound_cms:
00881 self.dump_to_disk(self.dump_inbound_cms)
00882 self.verify(ta)
00883 self.schema_check()
00884 msg = self.saxify(self.get_content())
00885 if pretty_print:
00886 return msg, self.pretty_print_content()
00887 else:
00888 return msg
00889
00890 class CRL(DER_object):
00891 """Class to hold a Certificate Revocation List."""
00892
00893 formats = ("DER", "POW", "POWpkix")
00894 pem_converter = PEM_converter("X509 CRL")
00895
00896 def get_DER(self):
00897 """Get the DER value of this CRL."""
00898 assert not self.empty()
00899 if self.DER:
00900 return self.DER
00901 if self.POW:
00902 self.DER = self.POW.derWrite()
00903 return self.get_DER()
00904 if self.POWpkix:
00905 self.DER = self.POWpkix.toString()
00906 return self.get_DER()
00907 raise rpki.exceptions.DERObjectConversionError, "No conversion path to DER available"
00908
00909 def get_POW(self):
00910 """Get the POW value of this CRL."""
00911 assert not self.empty()
00912 if not self.POW:
00913 self.POW = POW.derRead(POW.X509_CRL, self.get_DER())
00914 return self.POW
00915
00916 def get_POWpkix(self):
00917 """Get the POW.pkix value of this CRL."""
00918 assert not self.empty()
00919 if not self.POWpkix:
00920 crl = POW.pkix.CertificateList()
00921 crl.fromString(self.get_DER())
00922 self.POWpkix = crl
00923 return self.POWpkix
00924
00925 def getThisUpdate(self):
00926 """Get thisUpdate value from this CRL."""
00927 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getThisUpdate())
00928
00929 def getNextUpdate(self):
00930 """Get nextUpdate value from this CRL."""
00931 return rpki.sundial.datetime.fromASN1tuple(self.get_POWpkix().getNextUpdate())
00932
00933 def getIssuer(self):
00934 """Get issuer value of this CRL."""
00935 return self.get_POW().getIssuer()
00936
00937 @classmethod
00938 def generate(cls, keypair, issuer, serial, thisUpdate, nextUpdate, revokedCertificates, version = 1, digestType = "sha256WithRSAEncryption"):
00939 crl = POW.pkix.CertificateList()
00940 crl.setVersion(version)
00941 crl.setIssuer(issuer.get_POWpkix().getSubject())
00942 crl.setThisUpdate(thisUpdate.toASN1tuple())
00943 crl.setNextUpdate(nextUpdate.toASN1tuple())
00944 if revokedCertificates:
00945 crl.setRevokedCertificates(revokedCertificates)
00946 crl.setExtensions(
00947 ((rpki.oids.name2oid["authorityKeyIdentifier"], False, (issuer.get_SKI(), (), None)),
00948 (rpki.oids.name2oid["cRLNumber"], False, serial)))
00949 crl.sign(keypair.get_POW(), digestType)
00950 return cls(POWpkix = crl)