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