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