pax_global_header00006660000000000000000000000064132172451500014512gustar00rootroot0000000000000052 comment=74aceb2305048906a5059b972ed3c5456e250ec9 cursive-0.2.1/000077500000000000000000000000001321724515000131725ustar00rootroot00000000000000cursive-0.2.1/.gitignore000066400000000000000000000000661321724515000151640ustar00rootroot00000000000000*.egg* *.pyc .tox .testrepository/ releasenotes/build cursive-0.2.1/.gitreview000066400000000000000000000001141321724515000151740ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/cursive.git cursive-0.2.1/.testr.conf000066400000000000000000000005411321724515000152600ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./cursive/tests} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list cursive-0.2.1/CONTRIBUTING.rst000066400000000000000000000012131321724515000156300ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/cursive cursive-0.2.1/HACKING.rst000066400000000000000000000004331321724515000147700ustar00rootroot00000000000000cursive Style Commandments =============================================== Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ cursive Specific Commandments ----------------------------- - Copyright notices for organizations should not be included. cursive-0.2.1/LICENSE000066400000000000000000000236371321724515000142120ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. cursive-0.2.1/MANIFEST.in000066400000000000000000000001361321724515000147300ustar00rootroot00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pyc cursive-0.2.1/README.rst000066400000000000000000000011301321724515000146540ustar00rootroot00000000000000=============================== cursive =============================== Cursive implements OpenStack-specific validation of digital signatures. As OpenStack continues to mature, robust security controls become increasingly critical. The cursive project contains code extracted from various OpenStack projects for verifying digital signatures. Additional capabilities will be added to this project in support of various security features. * Free software: Apache license * Source: http://git.openstack.org/cgit/openstack/cursive * Bugs: http://bugs.launchpad.net/cursive Features -------- * TODO cursive-0.2.1/babel.cfg000066400000000000000000000000211321724515000147110ustar00rootroot00000000000000[python: **.py] cursive-0.2.1/cursive/000077500000000000000000000000001321724515000146525ustar00rootroot00000000000000cursive-0.2.1/cursive/__init__.py000066400000000000000000000012271321724515000167650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'cursive').version_string() cursive-0.2.1/cursive/certificate_utils.py000066400000000000000000000346641321724515000207430ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Support certificate validation.""" from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import rsa from cryptography import x509, exceptions as cryptography_exceptions from oslo_log import log as logging from oslo_utils import timeutils from cursive import exception from cursive import signature_utils LOG = logging.getLogger(__name__) def is_within_valid_dates(certificate): """Determine if the certificate is outside its valid date range. :param certificate: the cryptography certificate object :return: False if the certificate valid time range does not include now, True otherwise. """ # Get now in UTC, since certificate returns times in UTC now = timeutils.utcnow() # Confirm the certificate valid time range includes now if now < certificate.not_valid_before: return False elif now > certificate.not_valid_after: return False return True def is_issuer(issuing_certificate, issued_certificate): """Determine if the issuing cert is the parent of the issued cert. Determine if the issuing certificate is the parent of the issued certificate by: * conducting subject and issuer name matching, and * verifying the signature of the issued certificate with the issuing certificate's public key :param issuing_certificate: the cryptography certificate object that is the potential parent of the issued certificate :param issued_certificate: the cryptography certificate object that is the potential child of the issuing certificate :return: True if the issuing certificate is the parent of the issued certificate, False otherwise. """ if (issuing_certificate is None) or (issued_certificate is None): return False elif issuing_certificate.subject != issued_certificate.issuer: return False else: try: verify_certificate_signature( issuing_certificate, issued_certificate ) except cryptography_exceptions.InvalidSignature: # If verification fails, an exception is expected. return False return True def can_sign_certificates(certificate, certificate_uuid=''): """Determine if the certificate can sign other certificates. :param certificate: the cryptography certificate object :param certificate_uuid: the uuid of the certificate :return: False if the certificate cannot sign other certificates, True otherwise. """ try: basic_constraints = certificate.extensions.get_extension_for_oid( x509.oid.ExtensionOID.BASIC_CONSTRAINTS ).value except x509.extensions.ExtensionNotFound: LOG.debug( "Certificate '%s' does not have a basic constraints extension.", certificate_uuid) return False try: key_usage = certificate.extensions.get_extension_for_oid( x509.oid.ExtensionOID.KEY_USAGE ).value except x509.extensions.ExtensionNotFound: LOG.debug( "Certificate '%s' does not have a key usage extension.", certificate_uuid) return False if basic_constraints.ca and key_usage.key_cert_sign: return True if not basic_constraints.ca: LOG.debug( "Certificate '%s' is not marked as a CA in its basic constraints " "extension.", certificate_uuid) if not key_usage.key_cert_sign: LOG.debug( "Certificate '%s' is not marked for verifying certificate " "signatures in its key usage extension.", certificate_uuid) return False def verify_certificate_signature(signing_certificate, certificate): """Verify that the certificate was signed correctly. :param signing_certificate: the cryptography certificate object used to sign the certificate :param certificate: the cryptography certificate object that was signed by the signing certificate :raises: cryptography.exceptions.InvalidSignature if certificate signature verification fails. """ signature_hash_algorithm = certificate.signature_hash_algorithm signature_bytes = certificate.signature signer_public_key = signing_certificate.public_key() if isinstance(signer_public_key, rsa.RSAPublicKey): verifier = signer_public_key.verifier( signature_bytes, padding.PKCS1v15(), signature_hash_algorithm ) elif isinstance(signer_public_key, ec.EllipticCurvePublicKey): verifier = signer_public_key.verifier( signature_bytes, ec.ECDSA(signature_hash_algorithm) ) else: verifier = signer_public_key.verifier( signature_bytes, signature_hash_algorithm ) verifier.update(certificate.tbs_certificate_bytes) verifier.verify() def verify_certificate(context, certificate_uuid, trusted_certificate_uuids, enforce_valid_dates=True, enforce_signing_extensions=True, enforce_path_length=True): """Validate a certificate against a set of trusted certificates. From the key manager, load the set of trusted certificates and the certificate to validate. Store the trusted certificates in a certificate verification context. Use the context to verify that the certificate is cryptographically linked to at least one of the trusted certificates. :param context: the user context for authentication :param certificate_uuid: the uuid of a certificate to validate, stored in the key manager :param trusted_certificate_uuids: a list containing the uuids of trusted certificates stored in the key manager :param enforce_valid_dates: a boolean indicating whether date checking should be enforced during certificate verification, defaults to True :param enforce_signing_extensions: a boolean indicating whether extension checking should be enforced during certificate verification, defaults to True :param enforce_path_length: a boolean indicating whether path length constraints should be enforced during certificate verification, defaults to True :raises: SignatureVerificationError if the certificate verification fails for any reason. """ trusted_certificates = list() for uuid in trusted_certificate_uuids: try: trusted_certificates.append( (uuid, signature_utils.get_certificate(context, uuid)) ) except exception.SignatureVerificationError: LOG.warning("Skipping trusted certificate: %(id)s" % {'id': uuid}) certificate = signature_utils.get_certificate(context, certificate_uuid) certificate_context = CertificateVerificationContext( trusted_certificates, enforce_valid_dates=enforce_valid_dates, enforce_signing_extensions=enforce_signing_extensions, enforce_path_length=enforce_path_length ) certificate_context.update(certificate) certificate_context.verify() class CertificateVerificationContext(object): """A collection of signing certificates. A collection of signing certificates that may be used to verify the signatures of other certificates. """ def __init__(self, certificate_tuples, enforce_valid_dates=True, enforce_signing_extensions=True, enforce_path_length=True): self._signing_certificates = [] for certificate_tuple in certificate_tuples: certificate_uuid, certificate = certificate_tuple if not isinstance(certificate, x509.Certificate): LOG.error( "A signing certificate must be an x509.Certificate object." ) continue if enforce_valid_dates: if not is_within_valid_dates(certificate): LOG.warning( "Certificate '%s' is outside its valid date range and " "cannot be used as a signing certificate.", certificate_uuid) continue if enforce_signing_extensions: if not can_sign_certificates(certificate, certificate_uuid): LOG.warning( "Certificate '%s' is not configured to act as a " "signing certificate. It will not be used as a " "signing certificate.", certificate_uuid) continue self._signing_certificates.append(certificate_tuple) self._signed_certificate = None self._enforce_valid_dates = enforce_valid_dates self._enforce_path_length = enforce_path_length def update(self, certificate): """Process the certificate to be verified. Raises an exception if the certificate is invalid. Stores it otherwise. :param certificate: the cryptography certificate to be verified :raises: SignatureVerificationError if the certificate is not of the right type or if it is outside its valid date range. """ if not isinstance(certificate, x509.Certificate): raise exception.SignatureVerificationError( "The certificate must be an x509.Certificate object." ) if self._enforce_valid_dates: if not is_within_valid_dates(certificate): raise exception.SignatureVerificationError( "The certificate is outside its valid date range." ) self._signed_certificate = certificate def verify(self): """Locate the certificate's signing certificate and verify it. Locate the certificate's signing certificate in the context certificate cache, using both subject/issuer name matching and signature verification. If the certificate is self-signed, verify that it is also located in the context's certificate cache. Construct the certificate chain from certificates in the context certificate cache. Verify that the signing certificate can have a sufficient number of child certificates to support the chain. :raises: SignatureVerificationError if certificate validation fails for any reason, including mismatched signatures or a failure to find the required signing certificate. """ signed_certificate = self._signed_certificate certificate_chain = [('base', signed_certificate)] # Build the certificate chain. while True: signing_certificate_tuple = None # Search for the signing certificate for certificate_tuple in self._signing_certificates: _, candidate = certificate_tuple if is_issuer(candidate, signed_certificate): signing_certificate_tuple = certificate_tuple break # If a valid signing certificate is found, prepare to find the # next link in the certificate chain. Otherwise, raise an error. if signing_certificate_tuple: # If the certificate is self-signed, the root of the # certificate chain has been found. Otherwise, repeat the # verification process using the newly found signing # certificate. if signed_certificate == signing_certificate_tuple[1]: break else: certificate_chain.insert(0, signing_certificate_tuple) signed_certificate = signing_certificate_tuple[1] else: uuid = certificate_chain[0][0] raise exception.SignatureVerificationError( "Certificate chain building failed. Could not locate the " "signing certificate for %s in the set of trusted " "certificates." % "the base certificate" if uuid == 'base' else "certificate '%s'" % uuid ) if self._enforce_path_length: # Verify that each certificate's path length constraint allows # for it to support the rest of the certificate chain. for i in range(len(certificate_chain)): certificate = certificate_chain[i][1] # No need to check the last certificate in the chain. if certificate == certificate_chain[-1][1]: break try: constraints = certificate.extensions.get_extension_for_oid( x509.oid.ExtensionOID.BASIC_CONSTRAINTS ).value except x509.extensions.ExtensionNotFound: raise exception.SignatureVerificationError( "Certificate validation failed. The signing " "certificate '%s' does not have a basic constraints " "extension." % certificate_chain[i][0] ) # Path length only applies to non-self-issued intermediate # certificates. Do not include the current or end certificates # when computing path length. chain_length = len(certificate_chain[i:]) chain_length = (chain_length - 2) if chain_length > 2 else 0 if constraints.path_length < chain_length: raise exception.SignatureVerificationError( "Certificate validation failed. The signing " "certificate '%s' is not configured to support " "certificate chains of sufficient " "length." % certificate_chain[i][0] ) cursive-0.2.1/cursive/exception.py000066400000000000000000000033011321724515000172170ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Cursive base exception handling""" from cursive.i18n import _ class CursiveException(Exception): """Base Cursive Exception To correctly use this class, inherit from it and define a 'msg_fmt' property. That msg_fmt will get printf'd with the keyword arguments provided to the constructor. """ msg_fmt = _("An unknown exception occurred.") headers = {} safe = False def __init__(self, message=None, **kwargs): self.kwargs = kwargs if not message: try: message = self.msg_fmt % kwargs except Exception: # at least get the core message out if something happened message = self.msg_fmt self.message = message super(CursiveException, self).__init__(message) def format_message(self): # NOTE(dane-fichter): use the first argument to the python Exception # object which should be our full CursiveException message return self.args[0] class SignatureVerificationError(CursiveException): msg_fmt = _("Signature verification for the image " "failed: %(reason)s.") cursive-0.2.1/cursive/i18n.py000066400000000000000000000021661321724515000160100ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See http://docs.openstack.org/developer/oslo.i18n/usage.html . """ import oslo_i18n DOMAIN = 'cursive' _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # Translators for log levels. # # The abbreviated names are meant to reflect the usual use of a short # name like '_'. The "L" is for "log" and the other letter comes from # the level. _LI = _translators.log_info _LW = _translators.log_warning _LE = _translators.log_error _LC = _translators.log_critical cursive-0.2.1/cursive/signature_utils.py000066400000000000000000000312221321724515000204450ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Support signature verification.""" import binascii from castellan.common.exception import KeyManagerError from castellan.common.exception import ManagedObjectNotFoundError from castellan import key_manager from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import hashes from cryptography import x509 from oslo_log import log as logging from oslo_serialization import base64 from oslo_utils import encodeutils from cursive import exception from cursive.i18n import _, _LE LOG = logging.getLogger(__name__) HASH_METHODS = { 'SHA-224': hashes.SHA224(), 'SHA-256': hashes.SHA256(), 'SHA-384': hashes.SHA384(), 'SHA-512': hashes.SHA512(), } # Currently supported signature key types # RSA Options RSA_PSS = 'RSA-PSS' # DSA Options DSA = 'DSA' # ECC curves -- note that only those with key sizes >=384 are included # Note also that some of these may not be supported by the cryptography backend ECC_CURVES = ( ec.SECT571K1(), ec.SECT409K1(), ec.SECT571R1(), ec.SECT409R1(), ec.SECP521R1(), ec.SECP384R1(), ) # These are the currently supported certificate formats X_509 = 'X.509' CERTIFICATE_FORMATS = { X_509, } # These are the currently supported MGF formats, used for RSA-PSS signatures MASK_GEN_ALGORITHMS = { 'MGF1': padding.MGF1, } # Required image property names (SIGNATURE, HASH_METHOD, KEY_TYPE, CERT_UUID) = ( 'img_signature', 'img_signature_hash_method', 'img_signature_key_type', 'img_signature_certificate_uuid' ) class SignatureKeyType(object): REGISTERED_TYPES = {} def __init__(self, name, public_key_type, create_verifier): self.name = name self.public_key_type = public_key_type self.create_verifier = create_verifier @classmethod def register(cls, name, public_key_type, create_verifier): """Register a signature key type. :param name: the name of the signature key type :param public_key_type: e.g. RSAPublicKey, DSAPublicKey, etc. :param create_verifier: a function to create a verifier for this type """ cls.REGISTERED_TYPES[name] = cls(name, public_key_type, create_verifier) @classmethod def lookup(cls, name): """Look up the signature key type. :param name: the name of the signature key type :returns: the SignatureKeyType object :raises: SignatureVerificationError if signature key type is invalid """ if name not in cls.REGISTERED_TYPES: raise exception.SignatureVerificationError( reason=_('Invalid signature key type: %s') % name) return cls.REGISTERED_TYPES[name] # each key type will require its own verifier def create_verifier_for_pss(signature, hash_method, public_key): """Create the verifier to use when the key type is RSA-PSS. :param signature: the decoded signature to use :param hash_method: the hash method to use, as a cryptography object :param public_key: the public key to use, as a cryptography object :raises: SignatureVerificationError if the RSA-PSS specific properties are invalid :returns: the verifier to use to verify the signature for RSA-PSS """ # default to MGF1 mgf = padding.MGF1(hash_method) # default to max salt length salt_length = padding.PSS.MAX_LENGTH # return the verifier return public_key.verifier( signature, padding.PSS(mgf=mgf, salt_length=salt_length), hash_method ) def create_verifier_for_ecc(signature, hash_method, public_key): """Create the verifier to use when the key type is ECC_*. :param signature: the decoded signature to use :param hash_method: the hash method to use, as a cryptography object :param public_key: the public key to use, as a cryptography object :returns: the verifier to use to verify the signature for ECC_*. """ # return the verifier return public_key.verifier( signature, ec.ECDSA(hash_method) ) def create_verifier_for_dsa(signature, hash_method, public_key): """Create the verifier to use when the key type is DSA :param signature: the decoded signature to use :param hash_method: the hash method to use, as a cryptography object :param public_key: the public key to use, as a cryptography object :returns: the verifier to use to verify the signature for DSA """ # return the verifier return public_key.verifier( signature, hash_method ) SignatureKeyType.register(RSA_PSS, rsa.RSAPublicKey, create_verifier_for_pss) SignatureKeyType.register(DSA, dsa.DSAPublicKey, create_verifier_for_dsa) # Register the elliptic curves which are supported by the backend for curve in ECC_CURVES: if default_backend().elliptic_curve_supported(curve): SignatureKeyType.register('ECC_' + curve.name.upper(), ec.EllipticCurvePublicKey, create_verifier_for_ecc) def should_create_verifier(image_properties): """Determine whether a verifier should be created. Using the image properties, determine whether existing properties indicate that signature verification should be done. :param image_properties: the key-value properties about the image :return: True, if signature metadata properties exist, False otherwise """ return (image_properties is not None and CERT_UUID in image_properties and HASH_METHOD in image_properties and SIGNATURE in image_properties and KEY_TYPE in image_properties) def get_verifier(context, img_signature_certificate_uuid, img_signature_hash_method, img_signature, img_signature_key_type): """Instantiate signature properties and use them to create a verifier. :param context: the user context for authentication :param img_signature_certificate_uuid: uuid of signing certificate stored in key manager :param img_signature_hash_method: string denoting hash method used to compute signature :param img_signature: string of base64 encoding of signature :param img_signature_key_type: string denoting type of keypair used to compute signature :returns: instance of cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext :raises: SignatureVerificationError if we fail to build the verifier """ image_meta_props = {'img_signature_uuid': img_signature_certificate_uuid, 'img_signature_hash_method': img_signature_hash_method, 'img_signature': img_signature, 'img_signature_key_type': img_signature_key_type} for key in image_meta_props.keys(): if image_meta_props[key] is None: raise exception.SignatureVerificationError( reason=_('Required image properties for signature verification' ' do not exist. Cannot verify signature. Missing' ' property: %s') % key) signature = get_signature(img_signature) hash_method = get_hash_method(img_signature_hash_method) signature_key_type = SignatureKeyType.lookup(img_signature_key_type) public_key = get_public_key(context, img_signature_certificate_uuid, signature_key_type) # create the verifier based on the signature key type verifier = signature_key_type.create_verifier(signature, hash_method, public_key) if verifier: return verifier else: # Error creating the verifier raise exception.SignatureVerificationError( reason=_('Error occurred while creating the verifier')) def get_signature(signature_data): """Decode the signature data and returns the signature. :param signature_data: the base64-encoded signature data :returns: the decoded signature :raises: SignatureVerificationError if the signature data is malformatted """ try: signature = base64.decode_as_bytes(signature_data) except (TypeError, binascii.Error): raise exception.SignatureVerificationError( reason=_('The signature data was not properly ' 'encoded using base64')) return signature def get_hash_method(hash_method_name): """Verify the hash method name and create the hash method. :param hash_method_name: the name of the hash method to retrieve :returns: the hash method, a cryptography object :raises: SignatureVerificationError if the hash method name is invalid """ if hash_method_name not in HASH_METHODS: raise exception.SignatureVerificationError( reason=_('Invalid signature hash method: %s') % hash_method_name) return HASH_METHODS[hash_method_name] def get_public_key(context, signature_certificate_uuid, signature_key_type): """Create the public key object from a retrieved certificate. :param context: the user context for authentication :param signature_certificate_uuid: the uuid to use to retrieve the certificate :param signature_key_type: a SignatureKeyType object :returns: the public key cryptography object :raises: SignatureVerificationError if public key format is invalid """ certificate = get_certificate(context, signature_certificate_uuid) # Note that this public key could either be # RSAPublicKey, DSAPublicKey, or EllipticCurvePublicKey public_key = certificate.public_key() # Confirm the type is of the type expected based on the signature key type if not isinstance(public_key, signature_key_type.public_key_type): raise exception.SignatureVerificationError( reason=_('Invalid public key type for signature key type: %s') % signature_key_type.name) return public_key def get_certificate(context, signature_certificate_uuid): """Create the certificate object from the retrieved certificate data. :param context: the user context for authentication :param signature_certificate_uuid: the uuid to use to retrieve the certificate :returns: the certificate cryptography object :raises: SignatureVerificationError if the retrieval fails or the format is invalid """ keymgr_api = key_manager.API() try: # The certificate retrieved here is a castellan certificate object cert = keymgr_api.get(context, signature_certificate_uuid) except ManagedObjectNotFoundError as e: raise exception.SignatureVerificationError( reason=_('Certificate not found with ID: %s') % signature_certificate_uuid) except KeyManagerError as e: # The problem encountered may be backend-specific, since castellan # can use different backends. Rather than importing all possible # backends here, the generic "Exception" is used. msg = (_LE("Unable to retrieve certificate with ID %(id)s: %(e)s") % {'id': signature_certificate_uuid, 'e': encodeutils.exception_to_unicode(e)}) LOG.error(msg) raise exception.SignatureVerificationError( reason=_('Unable to retrieve certificate with ID: %s') % signature_certificate_uuid) if cert.format not in CERTIFICATE_FORMATS: raise exception.SignatureVerificationError( reason=_('Invalid certificate format: %s') % cert.format) if cert.format == X_509: # castellan always encodes certificates in DER format cert_data = cert.get_encoded() certificate = x509.load_der_x509_certificate(cert_data, default_backend()) return certificate cursive-0.2.1/cursive/tests/000077500000000000000000000000001321724515000160145ustar00rootroot00000000000000cursive-0.2.1/cursive/tests/__init__.py000066400000000000000000000000001321724515000201130ustar00rootroot00000000000000cursive-0.2.1/cursive/tests/base.py000066400000000000000000000013331321724515000173000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" cursive-0.2.1/cursive/tests/unit/000077500000000000000000000000001321724515000167735ustar00rootroot00000000000000cursive-0.2.1/cursive/tests/unit/__init__.py000066400000000000000000000013411321724515000211030ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ :mod:`cursive.tests.unit` -- Cursive Unittests ===================================================== .. automodule:: cursive.tests.unit :platform: Unix """ cursive-0.2.1/cursive/tests/unit/data/000077500000000000000000000000001321724515000177045ustar00rootroot00000000000000cursive-0.2.1/cursive/tests/unit/data/child_cert.pem000066400000000000000000000112141321724515000225060ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 10 (0xa) Signature Algorithm: sha256WithRSAEncryption Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Parent Validity Not Before: Oct 3 18:02:45 2017 GMT Not After : Oct 1 18:02:45 2027 GMT Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Child Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:b2:45:31:e4:99:12:1f:0f:c3:5b:78:98:39:a5: d0:da:c6:9f:38:23:09:df:fd:35:b6:95:b6:37:5d: b6:49:f2:a5:f1:62:75:62:41:09:9d:36:e5:53:c8: 82:1a:5c:9d:2a:fd:03:9c:a9:00:6d:28:b3:29:bb: cf:f3:eb:0f:5c:c9:81:8d:69:e1:04:f7:9a:1c:09: 33:ab:54:c1:ac:0c:d7:d1:11:79:6c:6f:c0:2b:54: 9e:c2:86:85:05:a3:e4:70:06:84:42:eb:8b:c0:0e: 3a:73:16:cd:13:79:a5:43:e6:89:8b:c3:7f:6b:04: cd:7f:34:6b:4a:47:65:c3:4a:6a:d3:ea:8e:57:34: 5d:39:18:fc:d0:8e:e4:f6:ff:74:86:a0:98:06:67: 40:0c:8f:a6:5e:46:9d:ed:b9:25:99:7c:4c:62:b8: 19:ae:12:1e:33:0b:d3:43:b9:3c:bc:5a:f3:6b:c6: a9:1c:c1:ce:99:1f:64:b7:a3:8d:ed:c8:3e:95:75: 19:e5:ce:51:f1:11:f1:c0:58:76:87:ee:42:12:a4: ff:8e:c6:e8:42:3d:b4:df:c7:be:a6:c7:ea:6c:88: 04:4b:d3:f3:9b:7f:d4:db:87:21:55:36:2e:3c:1c: c9:21:4a:2f:7f:51:f0:08:d7:21:ea:75:c4:e2:78: 91:9d Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B X509v3 Authority Key Identifier: keyid:7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49 X509v3 Basic Constraints: CA:TRUE, pathlen:0 X509v3 Key Usage: Certificate Sign, CRL Sign X509v3 Subject Alternative Name: DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com Netscape Comment: OpenSSL Generated Certificate Signature Algorithm: sha256WithRSAEncryption 10:46:2e:1e:37:b8:10:4a:8c:e3:76:7c:05:57:76:34:05:0b: e2:ed:b3:1b:28:20:2b:56:9b:2d:59:70:e5:4e:5e:ce:a8:11: d5:c1:9b:e7:c8:0e:61:2b:63:ae:d2:1b:ec:cf:75:31:d0:4f: 35:86:c2:51:22:64:c3:07:a7:c4:6b:13:57:cc:e5:d9:86:8d: b4:73:45:c5:ca:48:b7:b6:02:1e:c7:de:71:c6:5f:2a:64:7d: b5:5b:16:9a:27:7d:5f:3c:8a:5e:95:38:7f:c0:7e:d4:39:3f: 36:60:7d:7d:8e:9f:72:06:d4:69:7a:e5:45:3f:e2:c9:eb:7f: 5f:74:1a:6b:6c:b8:a1:08:05:d9:25:ee:d4:97:db:5a:72:1f: 4a:06:a9:86:76:41:58:34:0b:5a:39:be:65:ec:26:b1:13:41: 6b:86:58:fa:2e:cd:ab:06:d2:59:0e:bb:e4:44:2c:de:21:d1: 8c:9c:93:a5:d5:ae:fc:af:37:b0:91:1f:46:61:28:b9:a5:c8: b4:3c:28:33:b1:d9:ca:49:53:fe:14:80:82:de:06:c1:ab:21: e7:44:76:04:d8:85:b4:60:72:30:7a:28:b7:6f:4d:9e:52:70: 21:df:4e:71:aa:01:d6:ba:fa:4b:4a:61:75:9c:57:67:a6:b2: e7:ab:24:6c -----BEGIN CERTIFICATE----- MIID9jCCAt6gAwIBAgIBCjANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEN MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDEUMBIG A1UEAwwLVGVzdCBQYXJlbnQwHhcNMTcxMDAzMTgwMjQ1WhcNMjcxMDAxMTgwMjQ1 WjBPMQswCQYDVQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDEN MAsGA1UECgwEVGVzdDETMBEGA1UEAwwKVGVzdCBDaGlsZDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBALJFMeSZEh8Pw1t4mDml0NrGnzgjCd/9NbaVtjdd tknypfFidWJBCZ025VPIghpcnSr9A5ypAG0osym7z/PrD1zJgY1p4QT3mhwJM6tU wawM19EReWxvwCtUnsKGhQWj5HAGhELri8AOOnMWzRN5pUPmiYvDf2sEzX80a0pH ZcNKatPqjlc0XTkY/NCO5Pb/dIagmAZnQAyPpl5Gne25JZl8TGK4Ga4SHjML00O5 PLxa82vGqRzBzpkfZLejje3IPpV1GeXOUfER8cBYdofuQhKk/47G6EI9tN/HvqbH 6myIBEvT85t/1NuHIVU2LjwcySFKL39R8AjXIep1xOJ4kZ0CAwEAAaOB2zCB2DAd BgNVHQ4EFgQUwgPq/H5wfzQhwb4zDorgfsaiIRswHwYDVR0jBBgwFoAUer59CVpf XN7Mgho7/qjtyroWWEkwDwYDVR0TBAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwSgYD VR0RBEMwQYILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbYIQbWFpbC5leGFt cGxlLmNvbYIPZnRwLmV4YW1wbGUuY29tMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAEEYuHje4 EEqM43Z8BVd2NAUL4u2zGyggK1abLVlw5U5ezqgR1cGb58gOYStjrtIb7M91MdBP NYbCUSJkwwenxGsTV8zl2YaNtHNFxcpIt7YCHsfeccZfKmR9tVsWmid9XzyKXpU4 f8B+1Dk/NmB9fY6fcgbUaXrlRT/iyet/X3Qaa2y4oQgF2SXu1JfbWnIfSgaphnZB WDQLWjm+ZewmsRNBa4ZY+i7NqwbSWQ675EQs3iHRjJyTpdWu/K83sJEfRmEouaXI tDwoM7HZyklT/hSAgt4Gwash50R2BNiFtGByMHoot29NnlJwId9OcaoB1rr6S0ph dZxXZ6ay56skbA== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/grandchild_cert.pem000066400000000000000000000112131321724515000235210ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 11 (0xb) Signature Algorithm: sha256WithRSAEncryption Issuer: C=US, ST=Test, L=Test, O=Test, CN=Test Child Validity Not Before: Oct 3 18:09:07 2017 GMT Not After : Oct 1 18:09:07 2027 GMT Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Grandchild Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:bd:3c:4b:2a:e8:03:5d:07:ae:94:f4:19:ed:00: 21:20:dd:1c:54:f1:dc:44:d8:bf:66:b4:bf:ce:21: 7b:bf:b4:15:7b:b3:4f:0e:d5:ef:fa:f1:31:ab:2a: 22:78:72:20:7d:ce:58:c3:45:0d:2f:5c:23:7c:87: 07:bf:ee:8c:8c:9f:ae:31:70:19:61:dc:92:b5:8f: fb:36:16:1c:08:d4:2c:c0:0c:86:e0:ee:a8:31:20: 21:16:41:b2:78:bc:88:a8:ef:4c:3a:34:4f:a0:08: 25:e7:35:e8:bc:66:d3:c3:b5:2a:05:34:91:b0:d0: ae:02:f2:a1:58:22:af:43:42:d8:40:82:0c:e3:26: 72:22:06:d2:b1:13:87:04:83:70:f6:b0:99:39:bf: 79:26:f6:e2:ff:24:c3:72:48:9f:68:0a:c1:c9:aa: b1:a8:b4:f7:cf:44:38:4a:77:bf:56:20:fa:7e:08: 75:26:04:fb:5e:d5:4f:ff:b8:45:1f:80:12:fb:7e: 61:7e:52:f0:dc:71:ee:72:91:27:fa:60:93:96:e5: 78:1d:d9:fd:5a:b8:00:b9:97:46:12:b5:2a:93:0e: c3:1b:30:6e:b2:67:5d:c5:ca:40:3f:36:0c:7c:4f: d4:48:e0:1f:32:a9:28:0c:37:35:7c:5d:42:f5:cb: 54:b9 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 4F:6C:A8:1F:80:F0:A6:EE:41:85:B9:A2:3F:EC:3A:B2:93:B4:0E:86 X509v3 Authority Key Identifier: keyid:C2:03:EA:FC:7E:70:7F:34:21:C1:BE:33:0E:8A:E0:7E:C6:A2:21:1B X509v3 Basic Constraints: CA:FALSE X509v3 Key Usage: Digital Signature, Key Encipherment X509v3 Subject Alternative Name: DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com Netscape Comment: OpenSSL Generated Certificate Signature Algorithm: sha256WithRSAEncryption ae:a7:62:9e:f6:b7:e3:02:84:0f:fe:c6:7c:c1:0b:74:8e:95: c3:2e:e9:5f:c0:8b:fc:79:45:53:5c:34:9d:b0:de:e6:cf:ed: 52:4c:3f:6a:3f:e9:8d:a3:58:d4:ae:4d:31:30:57:d5:31:f9: a2:ed:82:e2:ae:1a:65:a5:ab:de:64:35:c9:0b:d1:86:b0:83: 57:8a:e4:ca:21:d5:9a:79:5b:44:42:ff:52:9a:51:b6:f4:6e: f1:da:dd:3b:ca:12:cb:4c:e5:9f:a5:12:4f:13:99:85:79:c8: 00:3b:2c:25:7f:02:07:a3:4e:59:0b:4d:8e:f8:43:08:a9:91: 30:0a:17:1c:ff:91:c0:16:d5:c0:1e:ec:a5:24:c8:cc:f0:2c: 0e:30:b9:bb:34:11:83:e7:4d:02:e4:2d:2a:90:98:eb:d8:ae: 7b:2f:19:31:db:63:fc:0c:0b:47:f5:8f:7b:cf:99:0b:30:91: a6:44:19:51:7f:15:4f:ab:8c:08:e2:bd:91:42:e4:e7:88:8e: c0:ea:fd:09:ac:96:c6:14:ef:0e:7d:75:6a:05:b0:b5:4d:43: 60:62:31:85:61:cb:c3:0f:81:24:d6:de:10:42:54:ff:c0:63: 95:40:3d:89:52:f9:00:2a:a5:74:1c:b1:42:be:a1:2f:de:90: cb:d5:a7:3d -----BEGIN CERTIFICATE----- MIID9DCCAtygAwIBAgIBCzANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJVUzEN MAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwEVGVzdDETMBEG A1UEAwwKVGVzdCBDaGlsZDAeFw0xNzEwMDMxODA5MDdaFw0yNzEwMDExODA5MDda MFQxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MQ0w CwYDVQQKDARUZXN0MRgwFgYDVQQDDA9UZXN0IEdyYW5kY2hpbGQwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9PEsq6ANdB66U9BntACEg3RxU8dxE2L9m tL/OIXu/tBV7s08O1e/68TGrKiJ4ciB9zljDRQ0vXCN8hwe/7oyMn64xcBlh3JK1 j/s2FhwI1CzADIbg7qgxICEWQbJ4vIio70w6NE+gCCXnNei8ZtPDtSoFNJGw0K4C 8qFYIq9DQthAggzjJnIiBtKxE4cEg3D2sJk5v3km9uL/JMNySJ9oCsHJqrGotPfP RDhKd79WIPp+CHUmBPte1U//uEUfgBL7fmF+UvDcce5ykSf6YJOW5Xgd2f1auAC5 l0YStSqTDsMbMG6yZ13FykA/Ngx8T9RI4B8yqSgMNzV8XUL1y1S5AgMBAAGjgdUw gdIwHQYDVR0OBBYEFE9sqB+A8KbuQYW5oj/sOrKTtA6GMB8GA1UdIwQYMBaAFMID 6vx+cH80IcG+Mw6K4H7GoiEbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMEoGA1Ud EQRDMEGCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22CEG1haWwuZXhhbXBs ZS5jb22CD2Z0cC5leGFtcGxlLmNvbTAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAK6nYp72t+MC hA/+xnzBC3SOlcMu6V/Ai/x5RVNcNJ2w3ubP7VJMP2o/6Y2jWNSuTTEwV9Ux+aLt guKuGmWlq95kNckL0Yawg1eK5Moh1Zp5W0RC/1KaUbb0bvHa3TvKEstM5Z+lEk8T mYV5yAA7LCV/AgejTlkLTY74QwipkTAKFxz/kcAW1cAe7KUkyMzwLA4wubs0EYPn TQLkLSqQmOvYrnsvGTHbY/wMC0f1j3vPmQswkaZEGVF/FU+rjAjivZFC5OeIjsDq /QmslsYU7w59dWoFsLVNQ2BiMYVhy8MPgSTW3hBCVP/AY5VAPYlS+QAqpXQcsUK+ oS/ekMvVpz0= -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/grandparent_cert.pem000066400000000000000000000041061321724515000237320ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF7jCCA9agAwIBAgIJANHiL5B0pUVmMA0GCSqGSIb3DQEBCwUAMIGDMQswCQYD VQQGEwJVUzENMAsGA1UECAwEVGVzdDENMAsGA1UEBwwEVGVzdDENMAsGA1UECgwE VGVzdDENMAsGA1UECwwEVGVzdDEZMBcGA1UEAwwQVGVzdCBHcmFuZHBhcmVudDEd MBsGCSqGSIb3DQEJARYOZ3BAZXhhbXBsZS5jb20wHhcNMTcxMDAzMTc0NzMyWhcN MTcxMTAyMTc0NzMyWjCBgzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTAL BgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxGTAXBgNV BAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG9w0BCQEWDmdwQGV4YW1wbGUu Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn4m1O+fffNTSnGE5 MPac07jjMNrKHEjARS4aM222C8wCPiXXrs1diTQlvtxrLFOzc0gtCH6xVl59Zcis H7kBf8oV9wNIHcfy2BmGkP7Wv68p3UsIF1IzGSHPyKJWG+l/xNexuXFaVG9y+siu 5Z3bx9DMBPFfXalxwRGoS0fyBOG/tXlqicf/aojF1U3UolML58URQqQ7IvGjEq22 iqAfduEwLlLb99iJ8uiFgO6Rl/hwxvy9gmrGWJGHpHQKJ2Dx37Zc8MMcAJ5yos7c GAs3e31TvRJgyEBcPKtl+xmh36wNC+V1KKRYKAfENqB6v7b1GDZrVtH7uHvSPCDQ ccKklF2thomO4cm6UpbCF7/5i50OLwtr1TcUI/YT+nR/YsCuc/PKdyXITpP0CNR9 Wcw6pb5LsWgLisFh4my4PzTbwTcPGFUJHq0CUUsRP1YijMNRtiZsoMJwspMZyg9d 9Ufxf7HjbugazUfvfIKMfX/s/pIZLiDLx5lV9RbHmjvlCt4OfH3FhqBveguqDLq7 LbBC6tYm1E21izUqXy4Zh+oogvZeZGUBQL/JUJ6XOkUnffv1nf2HDsCmlW64NLce 9gc25BtXAAf5/tMQL2J3t5SZ+Ladk+nklQXz6eClFcLEbRcd15bZ3QFXCajeLWcP Y5TZFgDQFFr7/FjDhr2bByMOJEkCAwEAAaNjMGEwHQYDVR0OBBYEFKfrobRNKmLV OJDtRPp3f/m66pkEMB8GA1UdIwQYMBaAFKfrobRNKmLVOJDtRPp3f/m66pkEMBIG A1UdEwEB/wQIMAYBAf8CAQEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IC AQByRvLd0xO238AwgDep4hBshcFH9tcSg5hDPFFeJWRnC4c63vNGXMVV7iEAoPSH LPbuELCIRqiZFYW5A8Olv2MGGZ3kjiFrWYfbLFU0/z1y82/E2NtM65cvOQKYxMk1 HBmaGF8s43LdDiGUZ0yFMTwe+da+zWcfDPgSYml36ReCsn2dGFmfkPFhqSf810kI yl2EKQnjEf51AGDfrA6fmEafsQy8eFf0uH6cR8nrsa+0aXIkTHZ9erXrXujD30iL 9M4T3uW/0Qk/kqSN3wUgHYWDBRyTKxCDPMiEixXDq21Jm1VzSKJAFE+xEuFHtqXl nfZQCzihdx3ckZnH3qfrJt0V0cu6qSNr6sbyrb7FVO8aCNyumdCDM9VdJ64UFAme Xd/1/195PcoFOVEokoH15EH0mPr+/DDWA39c+FaRHH0A3LmuzX/P5rTRLO3wldpL XiZkLrfG43UNq3PIdh3YZEabpFcQYTmab7N8nZnmoMRM6YoEnHjdqPcDv3xs4gJS U24bVnFqzgSW3V1GfZGnlQyGXFrlrU+wWJ55eJ59ucQn8PDYlrSz7+x9RiqoFZcQ c9L8j7dFMBx6zI4dI5Ddx5q9KNtxPJb4Hk9HEd4C5OQ0qqdBR/hSD9mDjBpqEyc8 aXIzmrTpGm7A9lbyXCEaOzN+2Jvdq5KtWh/halEgqgToqQ== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/not_a_cert.txt000066400000000000000000000000331321724515000225560ustar00rootroot00000000000000This is not a certificate. cursive-0.2.1/cursive/tests/unit/data/orphaned_cert.pem000066400000000000000000000026301321724515000232250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIID8zCCAtugAwIBAgIJAMDwfYBIkXEGMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD VQQGEwJVUzERMA8GA1UECAwIVGVzdG9uaWExDTALBgNVBAcMBFRlc3QxJDAiBgNV BAoMG0NvbXByZWhlbnNpdmUgVGVzdGluZywgSW5jLjEaMBgGA1UECwwRVGVzdGlu ZyBPdmVyc2lnaHQxHDAaBgNVBAMME1Rlc3RpbmcgQ2VydGlmaWNhdGUwHhcNMTYw NjMwMTcyOTUwWhcNMTcwNjMwMTcyOTUwWjCBjzELMAkGA1UEBhMCVVMxETAPBgNV BAgMCFRlc3RvbmlhMQ0wCwYDVQQHDARUZXN0MSQwIgYDVQQKDBtDb21wcmVoZW5z aXZlIFRlc3RpbmcsIEluYy4xGjAYBgNVBAsMEVRlc3RpbmcgT3ZlcnNpZ2h0MRww GgYDVQQDDBNUZXN0aW5nIENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAm+W78acV27U11m7E3iUUUGb+JXMW0okP8epD9OsLtVHxR+oq iOt19rgNIH/wJzaT+CnJ1jUerjzjFu2RwGhEr8Ph2KrWWQ7vxkhJzuXmKmGBZJm3 FJcADrxcmZ8V3Yqxf3zO36Rg27jqDgxSy3uzxgO7ZXrkrJjrgrg+x8wVQ/pkhd8Y gQ/YQ2r1DF1GcpS/tSkCSc3lbIpCCHhORaRmHZXURML5q7vibLmc55Ad90WxtS1d WI8RAsWnQMvP1OmZcRcPKrUlRc/w+nIrxNF9HdeOweQv2tcnNlxBOcr6MwIL+Gle N4TmmthyVYCXxNWhW1VFA3atfEfmyEpiKIcQGwIDAQABo1AwTjAdBgNVHQ4EFgQU IkPrrGyB6+XUlWbd287uFbfCvkkwHwYDVR0jBBgwFoAUIkPrrGyB6+XUlWbd287u FbfCvkkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMWi3C19pjWs9 9hg1rTC0D/5C9K/0nmQ1pstVMXOKn9Z3ndUqRvLzxhHZHhQ/ATQwHKeSM2vpCmKa eV7PGivF6W+CAXJImvgNrsP2fMBnTsg2Q3hBHSIkTgwJxAHlYZ3NXSxWoDSuozvU +qjRY3hMpYLSXpfGFKh73GHBNWXyjlo7pn+I4gAEoHOqDKTelOONz6PiKKi4Un2g j5FqmLZEq9VvzqSEC5VuFLZs4BpGmsKBM16+q+8JWMa025wNcdq4DxuNAkb3Zsty QZkgVYJgIeuEKOCubCQfDOya5W7ik3mtZZm9dFD5dZ3+CDB53a/AlKdi9YUAJOUW xBJzlRBlLg== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/parent_cert.pem000066400000000000000000000137441321724515000227260ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 9 (0x9) Signature Algorithm: sha256WithRSAEncryption Issuer: C=US, ST=Test, L=Test, O=Test, OU=Test, CN=Test Grandparent/emailAddress=gp@example.com Validity Not Before: Oct 3 17:58:30 2017 GMT Not After : Oct 1 17:58:30 2027 GMT Subject: C=US, ST=Test, L=Test, O=Test, CN=Test Parent Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:9f:9d:95:c4:a3:2f:37:52:e4:7c:cf:0b:0e:7f: 14:69:63:1e:7a:cc:a8:19:a7:88:59:c8:17:f2:21: 13:1b:45:21:fa:cc:93:40:71:cf:77:52:5a:1e:2e: 5a:91:16:a9:67:3a:a3:6a:ea:cb:a2:bf:24:9b:8c: 08:96:33:19:46:f9:7a:04:f9:c2:ee:87:f3:c3:23: 73:37:59:0e:c0:71:f4:cd:0b:ad:23:63:51:0a:4f: dc:d2:9b:ab:ab:8a:99:07:d4:c8:c8:70:fd:18:73: 25:0a:48:82:32:0d:64:46:b1:63:84:24:03:0b:3c: b8:17:92:78:6c:2b:4d:21:1b:46:3e:c1:cf:98:0b: a8:43:91:c0:39:48:f5:4e:71:77:c5:43:0e:68:8f: 01:c6:fb:59:77:d5:b3:f3:fe:95:27:ea:6e:ae:fc: 8e:59:ad:06:97:0c:f7:a6:e7:61:df:23:91:26:d0: bc:80:c6:2b:02:9b:fa:0f:e6:32:69:5a:90:29:c9: 9c:34:eb:50:ed:1d:e3:eb:0f:67:88:e3:ec:2b:1a: ab:41:c3:fa:d6:e8:aa:e3:7b:6a:16:3d:d8:da:6b: af:92:81:32:98:2f:f7:c0:bd:c4:25:bb:02:60:43: d5:e6:0c:29:7f:31:5d:09:4b:6a:a9:31:9b:92:24: 09:8f Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 7A:BE:7D:09:5A:5F:5C:DE:CC:82:1A:3B:FE:A8:ED:CA:BA:16:58:49 X509v3 Authority Key Identifier: keyid:A7:EB:A1:B4:4D:2A:62:D5:38:90:ED:44:FA:77:7F:F9:BA:EA:99:04 X509v3 Basic Constraints: CA:TRUE, pathlen:0 X509v3 Key Usage: Certificate Sign, CRL Sign X509v3 Subject Alternative Name: DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com Netscape Comment: OpenSSL Generated Certificate Signature Algorithm: sha256WithRSAEncryption 81:55:0d:1d:73:54:1d:72:73:72:dc:cf:ed:c1:47:c8:38:2a: 78:33:5e:55:6f:02:cc:c0:6a:6f:7e:c9:fa:4c:3d:a0:5b:25: 37:5e:87:69:7f:d8:66:73:4f:58:7d:c7:3e:6d:be:2a:85:43: 6a:cb:ff:68:59:1d:72:d2:68:ad:e9:5b:2f:8d:f6:95:31:ba: 1d:de:16:45:d9:12:51:85:12:bb:fb:89:fc:3a:7c:f5:e4:75: 64:b4:7d:ff:9f:f6:15:fa:1e:cb:18:4a:9d:e8:d8:5e:5a:d7: dd:78:c7:df:3d:21:2d:99:ef:b4:2c:78:2f:fb:fa:a0:7e:f3: cb:3b:05:5a:65:7d:9b:0f:9b:a3:9b:a9:ad:25:f8:32:cb:08: fd:c2:68:d3:92:15:09:59:5f:8b:c4:84:01:5f:75:7b:f0:55: 5f:20:39:f1:26:65:3d:d8:a2:19:de:fb:79:a0:27:2a:24:ae: 95:02:84:61:72:7a:47:37:4e:9f:af:20:5b:21:ec:c4:bf:ee: 80:5b:35:4e:ee:20:46:e6:cb:a6:e2:2f:c6:3e:5a:fa:f9:97: c3:97:09:1d:ce:08:a3:e9:09:cb:c3:59:3f:98:f3:b6:bf:00: 8b:a7:40:de:0a:1c:09:88:f7:74:fa:b1:1c:05:44:ff:ba:73: 84:3b:93:8d:a8:51:d0:d8:59:e6:cd:a8:79:d3:db:0a:1d:99: 3f:7c:a0:f9:d5:9e:dd:13:58:ee:ef:0d:3d:e2:4a:8b:85:18: 0c:86:f8:97:4d:18:54:c0:52:b8:10:38:1a:b8:8a:06:71:a5: e7:78:11:00:5b:9f:19:92:34:28:0f:19:3f:b0:57:ea:11:69: 29:ca:ed:05:36:08:f6:8d:ec:5d:34:79:92:8e:4c:e0:1c:a4: ad:1a:31:90:b7:16:60:da:e3:8f:ee:ea:66:df:13:e8:46:8d: a3:e2:3b:0a:f5:87:14:3d:4b:14:ea:da:89:c7:ae:e0:60:e3: a0:4c:04:2f:a1:0f:a9:84:5a:5a:f7:3d:4f:7b:d4:7c:e1:cd: ef:8b:28:45:19:ea:a9:4c:9e:59:f8:41:43:10:77:89:09:3e: 30:d0:e9:58:96:45:07:50:0e:4d:cc:6a:53:9e:64:c4:8a:e0: 51:96:3a:c6:8a:e2:94:af:9c:26:9a:fe:e3:7a:cd:cc:55:60: f0:dc:bf:f3:0d:e8:69:e4:cf:49:e1:f4:d2:87:91:31:cf:42: f7:2c:a7:f7:7b:88:90:e4:17:96:f6:34:d2:bf:a1:66:3c:03: db:aa:07:fa:a6:c3:4b:d3:29:d1:d1:40:6f:a7:88:a5:7f:bd: 5f:f5:00:94:db:53:5d:24 -----BEGIN CERTIFICATE----- MIIFKzCCAxOgAwIBAgIBCTANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx DTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTAL BgNVBAsMBFRlc3QxGTAXBgNVBAMMEFRlc3QgR3JhbmRwYXJlbnQxHTAbBgkqhkiG 9w0BCQEWDmdwQGV4YW1wbGUuY29tMB4XDTE3MTAwMzE3NTgzMFoXDTI3MTAwMTE3 NTgzMFowUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRl c3QxDTALBgNVBAoMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUGFyZW50MIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn52VxKMvN1LkfM8LDn8UaWMeesyoGaeI WcgX8iETG0Uh+syTQHHPd1JaHi5akRapZzqjaurLor8km4wIljMZRvl6BPnC7ofz wyNzN1kOwHH0zQutI2NRCk/c0purq4qZB9TIyHD9GHMlCkiCMg1kRrFjhCQDCzy4 F5J4bCtNIRtGPsHPmAuoQ5HAOUj1TnF3xUMOaI8BxvtZd9Wz8/6VJ+purvyOWa0G lwz3pudh3yORJtC8gMYrApv6D+YyaVqQKcmcNOtQ7R3j6w9niOPsKxqrQcP61uiq 43tqFj3Y2muvkoEymC/3wL3EJbsCYEPV5gwpfzFdCUtqqTGbkiQJjwIDAQABo4Hb MIHYMB0GA1UdDgQWBBR6vn0JWl9c3syCGjv+qO3KuhZYSTAfBgNVHSMEGDAWgBSn 66G0TSpi1TiQ7UT6d3/5uuqZBDAPBgNVHRMECDAGAQH/AgEAMAsGA1UdDwQEAwIB BjBKBgNVHREEQzBBggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tghBtYWls LmV4YW1wbGUuY29tgg9mdHAuZXhhbXBsZS5jb20wLAYJYIZIAYb4QgENBB8WHU9w ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4ICAQCB VQ0dc1QdcnNy3M/twUfIOCp4M15VbwLMwGpvfsn6TD2gWyU3Xodpf9hmc09Yfcc+ bb4qhUNqy/9oWR1y0mit6VsvjfaVMbod3hZF2RJRhRK7+4n8Onz15HVktH3/n/YV +h7LGEqd6NheWtfdeMffPSEtme+0LHgv+/qgfvPLOwVaZX2bD5ujm6mtJfgyywj9 wmjTkhUJWV+LxIQBX3V78FVfIDnxJmU92KIZ3vt5oCcqJK6VAoRhcnpHN06fryBb IezEv+6AWzVO7iBG5sum4i/GPlr6+ZfDlwkdzgij6QnLw1k/mPO2vwCLp0DeChwJ iPd0+rEcBUT/unOEO5ONqFHQ2Fnmzah509sKHZk/fKD51Z7dE1ju7w094kqLhRgM hviXTRhUwFK4EDgauIoGcaXneBEAW58ZkjQoDxk/sFfqEWkpyu0FNgj2jexdNHmS jkzgHKStGjGQtxZg2uOP7upm3xPoRo2j4jsK9YcUPUsU6tqJx67gYOOgTAQvoQ+p hFpa9z1Pe9R84c3viyhFGeqpTJ5Z+EFDEHeJCT4w0OlYlkUHUA5NzGpTnmTEiuBR ljrGiuKUr5wmmv7jes3MVWDw3L/zDehp5M9J4fTSh5Exz0L3LKf3e4iQ5BeW9jTS v6FmPAPbqgf6psNL0ynR0UBvp4ilf71f9QCU21NdJA== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/self_signed_cert.der000066400000000000000000000016351321724515000237040ustar00rootroot000000000000000‚™0‚  ©¢¹"`‡0  *†H†÷  0{1 0 UUS1 0 UTest1 0 UTest1 0 U Test1 0 U Test1 0 UTest1!0 *†H†÷  test@email.address0 160630175755Z 170630175755Z0{1 0 UUS1 0 UTest1 0 UTest1 0 U Test1 0 U Test1 0 UTest1!0 *†H†÷  test@email.address0‚"0  *†H†÷ ‚0‚ ‚à¡ÝÊÛúh°ÒËÿeGE9ïø+!@3»Ñ9öfY$“̈ã B.ùÄòºÆÉ~öUúvt¸A%†LœEé“7ÚG™Ñ‹‹ª8ŠUw—kdg ×ÙÝ=á¹#1o •—~®gi Ê}‹•Ã6 µƒjæ·ËdˆqXòo\£Î/É#É ?»¶™D°áxfïÀmõ±Ò8ôª5Œ†ß™†ÖŽ U:þË+<ðAb<9Ê4=ºø½ÖÐ2â:q_×3ÙV…àB¿5“PºŠ§/y‡% O?ftmñoœ…Çã 00U0ÿ0 U0  *†H†÷  ‚?€¬.ìÿÿU·ß<ÖvinÕÓ£I* ‚ÕÂÚ\) -¢ºö¤jÂh;lxHÇ‚÷*'ûr˜wŽBí·¨ùó‹,øjì•#•¤Ñ¡tmÿ“Ðë¡ò¬•åÿhÖ.Ôþï%6ùKNÅdš|MñR|Ù•nKHbú•‚¤¯–hLšò­~jO¢æ|‘ çEÄõB: ˜žîðaˆeÞÁÆèR~/åùÛ9:–†ŽŒ,šÇÞ®bÚ™þ$fJÉÅ(>nÉØ¹ÎÞ”†Ø%y{V:…'ÙazYE_·cursive-0.2.1/cursive/tests/unit/data/self_signed_cert.pem000066400000000000000000000024361321724515000237130ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDmTCCAoGgAwIBAgIJAKmiuSJghxIGMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTc1NzU1WhcNMTcwNjMwMTc1 NzU1WjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEA4KHdygQZ2/posNLLE/9lR0U57/iQKyFAfzO70RA5 9mYZWSQTk8yI4wsDQi75xBjyuhYExsl+9lX6dgV0uEElhkycRemTN9pHmdGLi6of OIpVd5drZGcK19ndPeG5IzFvCpWXfsKuZ2kJf8p9i5XDNhigtYNq5rfLZBOIE3FY HPKBbx9cBaPOL8kjyX8LPwG7tpmNRLAF4XgQZu/AbfWx0jg8UqqJhOKwPQz+YOPY 1eJ55BDyDYYiRj70qhQ1jIbfmYbWjg1VOv7LKzzwQWI8gTnKND26+L0D1tAy4joO cV/XM9lWheBCvzWTULqKpy95hyUMTz9mdG3xb5yFEccYwwIDAQABoyAwHjAPBgNV HRMECDAGAQH/AgECMAsGA1UdDwQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAPwKA rC4S7P//VbffPNYOdmlu1dOjSSoNgtXC2lwpCy2iuvakasIAaDtseEjHgvcqJ/ty mHeOQu23qAP584ss+GoR7JUjlaTRoXRt/5PQ66HyrJXl/2jWLtT+7yU2+UtOxWSa fE3xUnzZlW4ES0hi+pWCpK+WaEya8q1+ak+i5oF8kQ3nRcT1f0IcOgyYnhvu8GGI Zd7BA8boUhR+L+X52zk6loaOEIwsmsfero9i2pn+JGZKyQfFKI8+bsnYuc7elIbY PER8fGWHid/DoIgQety153LLKtfR/20rYBrlnNtatg3ePTRdFZ1p7lnLPfA+AiV5 e1Y6hSfZYXpZRV+Qtw== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/self_signed_cert_invalid_ca_constraint.pem000066400000000000000000000024261321724515000303270ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDkzCCAnugAwIBAgIJAKnOrxg9gSXNMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxOTQyWhcNMTcwNjMwMTgx OTQyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEAnhmtSiir3aB5igvVEQlHqIw2K98q336cyjipFq9b Pt1YslTwLfUAagr7224i0tny45PIZ3o1YlBxhEwd/i1tMnCz2+DQyat+p+vVbbiI ceN1ZzRFE4zJV0QjG+H+TOWqzjtdtq04jkdrKMOsp3Lv4NHIuEuLocQLPLuT79wP VUO+BCHlU/0bUQHhAU/Jx9B81GQ1/4lYS400AYtANSEccMR1djUTjFha4wiwSDH2 QZQBgmiqmDhf22uoioFgay9+yhOJ3SJx/lIiMavM2LMgNbns1DcbAD8oKGS69Mmo TsQlgOVMQTIDbwsm3WaxIcY8BipUACSe3E+RdqDrP/MTDwIDAQABoxowGDAJBgNV HRMEAjAAMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEACP8ZAbyAtffS sWZ6WbLcm+BT7FFdUm62gGqC2knaIZQud3AV5/wtj2CAw1lXgGbaCqfWNQrTDY9z PpGXyQ5tvNUtBZG23K7nMvid+U1WDh1fhlRC0kxCK2MsPwv9T5BM3tj/YF0MGWuQ 3GlFL8wU8UoAP3alUhxQl5qXqfXc6qfMW2ec4Jb3j6nbezL7ttn15LiBCsvoJ6/V Go9bbox81UrbtxnVin5+cYczUdB+Q9+fe23B/6MG99hzWU9arkkU7ZOlnI/bW9Zb fx4+atZfpi18nyd2ljgiTapB6Ex6uxVPrzwuKxpGMt9wU1++ZYc3YDke4EOT+OIX nKc7UFfjyg== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/self_signed_cert_invalid_key_usage.pem000066400000000000000000000024361321724515000274550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDmTCCAoGgAwIBAgIJAKOf0EhCGUdQMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgxNDMwWhcNMTcwNjMwMTgx NDMwWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEA575xHdxVuT5CYPom/PbFdDLt0PgG3CZFkiRNxPAG rDN8cG5ouTw0R9RMBFA+nYcTx/4GnPJmSBEBVqMoPSzsB6Rx9k21KymNlaEs2O1W jSMsYd9gW4NlHdyoomYw0nXQjkstmtdJxDNWg0zSZrHMPnkOVh+JNV58i4FXOx5O bxWo4sSyAvNjAEH9GwDwy+Jz0X4RdFGQrGjm+/v+ohvy8JqU5ZKpz2oP2oQURjDj +AH3ghmgNAVAk0syjtSqydEJd9aeMLTmTaUtP+gPnXdj/ZBj+TQH01RNlSECH2l4 WrymS3g4+X5xsA7DeLIbiXB4K1xjbJFCSfDYrV52H/fE6QIDAQABoyAwHjAPBgNV HRMECDAGAQH/AgECMAsGA1UdDwQEAwIDCDANBgkqhkiG9w0BAQsFAAOCAQEARNRE XC8y/RoPLUVAVKJ/RwcH4cwaHoSSu6HnygIpg9Qs7Xc7u1aKCL0dRF1NqfmaqHZ6 ZjllxDi5t0CFIXPfDQIYchfSzOafhJGEH3gilBwfmN43N4/eCSvdRKfhRbRFOD9j 0JHRAHkn2JRcwSTTjJEcJUJJETAIIbX1ovobJZuJOY0faI1O/Z2KILYrwdmcfnZ5 3i0kUps5BWrBrcs70gBsDBugeM24ANa7hJzFk+9TztfLWF1AUfjpZ4Bj/rb21+Gp 08FRjvn80Y5bGlNh0Q7Qbu8NS8VbAHHF3t3PUVRymJhIycpvBBBS7dcQqHf+v1gs Z/UpzuobJvnhq+7mcQ== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/self_signed_cert_missing_ca_constraint.pem000066400000000000000000000024051321724515000303470ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDiDCCAnCgAwIBAgIJAOYNT7MuoypuMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNV BAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MSEwHwYJKoZIhvcNAQkB FhJ0ZXN0QGVtYWlsLmFkZHJlc3MwHhcNMTYwNjMwMTgyMTMyWhcNMTcwNjMwMTgy MTMyWjB7MQswCQYDVQQGEwJVUzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVz dDENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEh MB8GCSqGSIb3DQEJARYSdGVzdEBlbWFpbC5hZGRyZXNzMIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEA3qypxOJ2X0k47SIEkRGvA/ECqzVAX3nsC0yPnbF3 14SGe7xFBzi0VMAXOcpVj0BL+G5TL95O5loVN/UnU5/xtjSa712HOOlJfvnqmv63 BLq9cMS2strwufeOK3YUtQExtJdxMjcEYuCMt+NlQ3Hl+xNfBc0LXWNBdlusP4fs 6sLEgyD4ywSLC9oHzyzgDxi0pr52itu+KnZv2iET/Wotg/8Aiw5Q5fiTc8DoysdZ MF1ix56oGo1SFFGFf+n2iwYbImtNGt6//jKEDP8P4iLdLAxHxmfsXXnl+Zs/6VoA RrnS3Xt7F5xj4CWuoZy4CLo8YXhXdznRQZ2r5Qha65cFNwIDAQABow8wDTALBgNV HQ8EBAMCAwgwDQYJKoZIhvcNAQELBQADggEBALOBnrWz3xMg4Yh92zsXfrSm2uAL P8jgXQtQdsYWSEWcfYNYOSlmLnICweDnAn0V5Kzp0E5wZSHP8Ut4IYEKwe+IF7HA pv3mpg1CYtwVbVsN0dhlLHDVuF27i0r8LuOv9yh0wxY3hPYrd2WvQ/qTP8NO0EoD fM8w2fjNeTu2jB+lYWhGWOHfzEvltosxMZIPWBJxrh3PbYdbuJJZlm/NPqj5Urxx nRB89AEBHKEKHlmNIMMOM3mQ+ShssgGrbRV6U6iJ5qv4H5RdaZtmXMGhjt8dFJrD A2YZYW15QLQDEyud4flSztaw6UMHJi+4FChBAnuJuOcNRZA83v3szuM9t64= -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/self_signed_cert_missing_key_usage.pem000066400000000000000000000023151321724515000274740ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDXTCCAkWgAwIBAgIJAI20mCsVbBjpMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTYwNjMwMTM0NTU5WhcNMTcwNjMwMTM0NTU5WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA5NjoTq0D6LM6z7V16E1+Eul25L6X1BSLcppsHhVMB/NM1e7hjoUtAODv 71L/bAAgV4ky4aphOfwYQLfeAP8nkq4CU30LaAyAQwrT4RyW1NG7AA00xylauebt sc2GUUy06gQ0Z6OjjEfA5HA4W+HYfeyuNzQpWXHWz/6K8xcKb9w10qAjDhNilHbj 3RVtL6u6rsZgQi0DlMxpHsp6gLezIRMN72B/AOKrzaobw4nY4hOVkqbRlOHB/tsk 4BJLUuW5WM30TVpfsKe07jCBgqUwb9XD9lZa1alkFRsSTZoWijeQBM6kiLD/VFNC YlIKBrN7HZtfOlqhftMknCsoyrWsjQIDAQABo1AwTjAdBgNVHQ4EFgQUnDu25deh bIOsCaj3LAcE9r2gx0QwHwYDVR0jBBgwFoAUnDu25dehbIOsCaj3LAcE9r2gx0Qw DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAowntOCmRXKgz7M6hnbQc sEyIt5tN3QILHSeprVO7/ONVIiPJMCfB+S8gJKJ0d5R0xXDYN/6+HyYlgfaL33Gt HY75y8MRnfqpgbEWhXWsBkxgeuqWiM/OFMTqLtgkVbxsVzoUl6V+tHsZaaM9yuyb iUBM9McAPGIgodpMGG86BV6qg07VjqWjl5pBUU4B2zvvzZjwrC8jqUYksVESHB9U WBzwfPLXoj0PUfAog34ZtT33UXX8M3oXTw+yb/hx0rContYMc78Lnlk6mV9gGG+X +3gSwAHn0SMZNNkKc3gdb1CLfluvHw2Od2jat0yfHHawh1JBtnfHrAU0px3Kzw5U 0A== -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/data/signed_cert.pem000066400000000000000000000030421321724515000226740ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEWzCCA0OgAwIBAgIBATANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJVUzEN MAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVzdDENMAsGA1UEChMEVGVzdDENMAsG A1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDEhMB8GCSqGSIb3DQEJARYSdGVzdEBl bWFpbC5hZGRyZXNzMB4XDTE2MDYzMDE5NDA1N1oXDTE3MDYzMDE5NDA1N1owbzEL MAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAoMBFRlc3QxDTALBgNV BAsMBFRlc3QxDzANBgNVBAMMBkNsaWVudDEiMCAGCSqGSIb3DQEJARYTdGVzdEBj bGllbnQuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALOK dKWvu8NLDee4KvHf5WmmJsNeqrfZt+5EBXlp9wEJ3i//6vRpZe9Gr/k3xfbQPVng PS8LUBancZ/zPos6ZibUuJi+ZjgVXUm61S18536wq4S1LH4Hkb4RgJW+IKqlqi0z RVC3xeNcUhGprcH9JtjinOusQ1HLWy4mSr5aaCfCVshj3YEN5uCrfDOXPkS5B1kd kpnEZJt2tAUPLlIKD4Ytjq9A84bL6wHTrUg5NmZ8j+yfXfD0qE1rN69AZFQ2V72R uKUbxvvx2T8ObJh1VpiSlMLLEeoEY1OUCZWB+Xz6GX9uL9B9zeD0+f9WcswI3UCw 9WEFgDHYWxgFciu7IYsCAwEAAaOB9TCB8jAJBgNVHRMEAjAAMCwGCWCGSAGG+EIB DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUDs4X d7VArujtnCJ3Ppht7hSfdL0wgZcGA1UdIwSBjzCBjKF/pH0wezELMAkGA1UEBhMC VVMxDTALBgNVBAgTBFRlc3QxDTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3Qx DTALBgNVBAsTBFRlc3QxDTALBgNVBAMTBFRlc3QxITAfBgkqhkiG9w0BCQEWEnRl c3RAZW1haWwuYWRkcmVzc4IJAKmiuSJghxIGMA0GCSqGSIb3DQEBCwUAA4IBAQBm f2VVj4Eqb+5pAgimkejDrYRzDgDQ4Eyr45vdUtu7JoGovGmkxg5z3izW/UKKj8GC 04aXIJiIu8d7mn5ZxuaIS0/mtVN167tVVI0wBlkQRK5dJNjn47fTixymEy4lwdUl 0iSb1JP6beVmSMIywD5lFxGPiW/MEJSvDCdlOT2Ojiv/Sbn9Q09PsXei0fAmNGZn FEUSnlqgWkeGIIv3+//kY8pHlZ1RyYSShQ+3Vb8Qifx0lbiFQWDP82EgETu7JKWn fKCoogSDybcLqB/WeGOQ0myXgEth5Lhkdo0n08J/FYL/bA1thADVnV66ZpERgb3h 38P4rEcobzZdVPcS4zwP -----END CERTIFICATE----- cursive-0.2.1/cursive/tests/unit/test_certificate_utils.py000066400000000000000000000406521321724515000241150ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import mock import os from cryptography.hazmat.backends import default_backend from cryptography import x509 from cursive import certificate_utils from cursive import exception from cursive.tests import base class TestCertificateUtils(base.TestCase): """Test methods for the certificate verification context and utilities""" def setUp(self): super(TestCertificateUtils, self).setUp() self.cert_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'data' ) def tearDown(self): super(TestCertificateUtils, self).tearDown() def load_certificate(self, cert_name): # Load the raw certificate file data. path = os.path.join(self.cert_path, cert_name) with open(path, 'rb') as cert_file: data = cert_file.read() # Convert the raw certificate data into a certificate object, first # as a PEM-encoded certificate and, if that fails, then as a # DER-encoded certificate. If both fail, the certificate cannot be # loaded. try: return x509.load_pem_x509_certificate(data, default_backend()) except Exception: try: return x509.load_der_x509_certificate(data, default_backend()) except Exception: raise exception.SignatureVerificationError( "Failed to load certificate: %s" % path ) def load_certificates(self, cert_names): certs = list() for cert_name in cert_names: cert = self.load_certificate(cert_name) certs.append(cert) return certs @mock.patch('oslo_utils.timeutils.utcnow') def test_is_within_valid_dates(self, mock_utcnow): # Verify a certificate is valid at a time within its valid date range cert = self.load_certificate('self_signed_cert.pem') mock_utcnow.return_value = datetime.datetime(2017, 1, 1) result = certificate_utils.is_within_valid_dates(cert) self.assertEqual(True, result) @mock.patch('oslo_utils.timeutils.utcnow') def test_is_before_valid_dates(self, mock_utcnow): # Verify a certificate is invalid at a time before its valid date range cert = self.load_certificate('self_signed_cert.pem') mock_utcnow.return_value = datetime.datetime(2000, 1, 1) result = certificate_utils.is_within_valid_dates(cert) self.assertEqual(False, result) @mock.patch('oslo_utils.timeutils.utcnow') def test_is_after_valid_dates(self, mock_utcnow): # Verify a certificate is invalid at a time after its valid date range cert = self.load_certificate('self_signed_cert.pem') mock_utcnow.return_value = datetime.datetime(2100, 1, 1) result = certificate_utils.is_within_valid_dates(cert) self.assertEqual(False, result) def test_is_issuer(self): # Test issuer and subject name matching for a self-signed certificate. cert = self.load_certificate('self_signed_cert.pem') result = certificate_utils.is_issuer(cert, cert) self.assertEqual(True, result) def test_is_not_issuer(self): # Test issuer and subject name mismatching. cert = self.load_certificate('self_signed_cert.pem') alt = self.load_certificate('orphaned_cert.pem') result = certificate_utils.is_issuer(cert, alt) self.assertEqual(False, result) def test_is_issuer_with_invalid_certs(self): # Test issuer check with invalid certificates cert = self.load_certificate('self_signed_cert.pem') result = certificate_utils.is_issuer(cert, None) self.assertEqual(False, result) result = certificate_utils.is_issuer(None, cert) self.assertEqual(False, result) def test_can_sign_certificates(self): # Test that a well-formatted certificate can sign cert = self.load_certificate('self_signed_cert.pem') result = certificate_utils.can_sign_certificates(cert, 'test-ID') self.assertEqual(True, result) def test_cannot_sign_certificates_without_basic_constraints(self): # Verify a certificate without basic constraints cannot sign cert = self.load_certificate( 'self_signed_cert_missing_ca_constraint.pem' ) result = certificate_utils.can_sign_certificates(cert, 'test-ID') self.assertEqual(False, result) def test_cannot_sign_certificates_with_invalid_basic_constraints(self): # Verify a certificate with invalid basic constraints cannot sign cert = self.load_certificate( 'self_signed_cert_invalid_ca_constraint.pem' ) result = certificate_utils.can_sign_certificates(cert, 'test-ID') self.assertEqual(False, result) def test_cannot_sign_certificates_without_key_usage(self): # Verify a certificate without key usage cannot sign cert = self.load_certificate('self_signed_cert_missing_key_usage.pem') result = certificate_utils.can_sign_certificates(cert, 'test-ID') self.assertEqual(False, result) def test_cannot_sign_certificates_with_invalid_key_usage(self): # Verify a certificate with invalid key usage cannot sign cert = self.load_certificate('self_signed_cert_invalid_key_usage.pem') result = certificate_utils.can_sign_certificates(cert, 'test-ID') self.assertEqual(False, result) def test_verify_signing_certificate(self): signing_certificate = self.load_certificate('self_signed_cert.pem') signed_certificate = self.load_certificate('signed_cert.pem') certificate_utils.verify_certificate_signature( signing_certificate, signed_certificate ) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('oslo_utils.timeutils.utcnow') def test_verify_valid_certificate(self, mock_utcnow, mock_get_cert): mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der', 'signed_cert.pem'] ) mock_get_cert.side_effect = certs cert_uuid = '3' trusted_cert_uuids = ['1', '2'] certificate_utils.verify_certificate( None, cert_uuid, trusted_cert_uuids ) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('oslo_utils.timeutils.utcnow') def test_verify_invalid_certificate(self, mock_utcnow, mock_get_cert): mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der', 'orphaned_cert.pem'] ) mock_get_cert.side_effect = certs cert_uuid = '3' trusted_cert_uuids = ['1', '2'] self.assertRaisesRegex( exception.SignatureVerificationError, "Certificate chain building failed. Could not locate the " "signing certificate for the base certificate in the set of " "trusted certificates.", certificate_utils.verify_certificate, None, cert_uuid, trusted_cert_uuids ) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('oslo_utils.timeutils.utcnow') def test_verify_valid_certificate_with_no_root(self, mock_utcnow, mock_get_cert): mock_utcnow.return_value = datetime.datetime(2017, 1, 1) # Test verifying a valid certificate against an empty list of trusted # certificates. certs = self.load_certificates(['signed_cert.pem']) mock_get_cert.side_effect = certs cert_uuid = '3' trusted_cert_uuids = [] self.assertRaisesRegex( exception.SignatureVerificationError, "Certificate chain building failed. Could not locate the " "signing certificate for the base certificate in the set of " "trusted certificates.", certificate_utils.verify_certificate, None, cert_uuid, trusted_cert_uuids ) @mock.patch('oslo_utils.timeutils.utcnow') def test_context_init(self, mock_utcnow): # Test constructing a context object with a valid set of certificates mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] context = certificate_utils.CertificateVerificationContext( cert_tuples ) self.assertEqual(2, len(context._signing_certificates)) for t in cert_tuples: path, cert = t self.assertIn(cert, [x[1] for x in context._signing_certificates]) @mock.patch('cursive.certificate_utils.LOG') @mock.patch('oslo_utils.timeutils.utcnow') def test_context_init_with_invalid_certificate(self, mock_utcnow, mock_log): # Test constructing a context object with an invalid certificate mock_utcnow.return_value = datetime.datetime(2017, 1, 1) alt_cert_tuples = [('path', None)] context = certificate_utils.CertificateVerificationContext( alt_cert_tuples ) self.assertEqual(0, len(context._signing_certificates)) self.assertEqual(1, mock_log.error.call_count) @mock.patch('cursive.certificate_utils.LOG') @mock.patch('oslo_utils.timeutils.utcnow') def test_context_init_with_non_signing_certificate(self, mock_utcnow, mock_log): # Test constructing a context object with an non-signing certificate mock_utcnow.return_value = datetime.datetime(2017, 1, 1) non_signing_cert = self.load_certificate( 'self_signed_cert_missing_key_usage.pem' ) alt_cert_tuples = [('path', non_signing_cert)] context = certificate_utils.CertificateVerificationContext( alt_cert_tuples ) self.assertEqual(0, len(context._signing_certificates)) self.assertEqual(1, mock_log.warning.call_count) @mock.patch('cursive.certificate_utils.LOG') @mock.patch('oslo_utils.timeutils.utcnow') def test_context_init_with_out_of_date_certificate(self, mock_utcnow, mock_log): # Test constructing a context object with out-of-date certificates mock_utcnow.return_value = datetime.datetime(2100, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] context = certificate_utils.CertificateVerificationContext(cert_tuples) self.assertEqual(0, len(context._signing_certificates)) self.assertEqual(2, mock_log.warning.call_count) @mock.patch('oslo_utils.timeutils.utcnow') def test_context_update_with_valid_certificate(self, mock_utcnow): # Test updating the context with a valid certificate mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] context = certificate_utils.CertificateVerificationContext(cert_tuples) cert = self.load_certificate('orphaned_cert.pem') context.update(cert) self.assertEqual(cert, context._signed_certificate) @mock.patch('oslo_utils.timeutils.utcnow') def test_context_update_with_date_invalid_certificate(self, mock_utcnow): # Test updating the context with an out-of-date certificate mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] context = certificate_utils.CertificateVerificationContext(cert_tuples) cert = self.load_certificate('orphaned_cert.pem') mock_utcnow.return_value = datetime.datetime(2100, 1, 1) self.assertRaisesRegex( exception.SignatureVerificationError, "The certificate is outside its valid date range.", context.update, cert ) def test_context_update_with_invalid_certificate(self): # Test updating the context with an invalid certificate certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] context = certificate_utils.CertificateVerificationContext( cert_tuples ) self.assertRaisesRegex( exception.SignatureVerificationError, "The certificate must be an x509.Certificate object.", context.update, None ) @mock.patch('oslo_utils.timeutils.utcnow') def test_context_verify(self, mock_utcnow): mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] # Test verification with a two-link certificate chain. context = certificate_utils.CertificateVerificationContext( cert_tuples ) cert = self.load_certificate('signed_cert.pem') context.update(cert) context.verify() # Test verification with a single-link certificate chain. context = certificate_utils.CertificateVerificationContext( cert_tuples ) context.update(certs[0]) context.verify() @mock.patch('oslo_utils.timeutils.utcnow') def test_context_verify_disable_checks(self, mock_utcnow): mock_utcnow.return_value = datetime.datetime(2017, 1, 1) certs = self.load_certificates( ['self_signed_cert.pem', 'self_signed_cert.der'] ) cert_tuples = [('1', certs[0]), ('2', certs[1])] # Test verification with a two-link certificate chain. context = certificate_utils.CertificateVerificationContext( cert_tuples, enforce_valid_dates=False, enforce_signing_extensions=False, enforce_path_length=False ) cert = self.load_certificate('signed_cert.pem') context.update(cert) context.verify() # Test verification with a single-link certificate chain. context = certificate_utils.CertificateVerificationContext( cert_tuples, enforce_valid_dates=False, enforce_signing_extensions=False, enforce_path_length=False ) context.update(certs[0]) context.verify() @mock.patch('oslo_utils.timeutils.utcnow') def test_context_verify_invalid_chain_length(self, mock_utcnow): mock_utcnow.return_value = datetime.datetime(2017, 11, 1) certs = self.load_certificates( ['grandparent_cert.pem', 'parent_cert.pem', 'child_cert.pem'] ) cert_tuples = [ ('1', certs[0]), ('2', certs[1]), ('3', certs[2]) ] cert = self.load_certificate('grandchild_cert.pem') context = certificate_utils.CertificateVerificationContext( cert_tuples ) context.update(cert) self.assertRaisesRegex( exception.SignatureVerificationError, "Certificate validation failed. The signing certificate '1' is " "not configured to support certificate chains of sufficient " "length.", context.verify ) context = certificate_utils.CertificateVerificationContext( cert_tuples, enforce_path_length=False ) context.update(cert) context.verify() cursive-0.2.1/cursive/tests/unit/test_signature_utils.py000066400000000000000000000412611321724515000236310ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import datetime import mock from castellan.common.exception import KeyManagerError from castellan.common.exception import ManagedObjectNotFoundError import cryptography.exceptions as crypto_exceptions from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import rsa from oslo_utils import timeutils from cursive import exception from cursive import signature_utils from cursive.tests import base TEST_RSA_PRIVATE_KEY = rsa.generate_private_key(public_exponent=3, key_size=1024, backend=default_backend()) # secp521r1 is assumed to be available on all supported platforms TEST_ECC_PRIVATE_KEY = ec.generate_private_key(ec.SECP521R1(), default_backend()) TEST_DSA_PRIVATE_KEY = dsa.generate_private_key(key_size=3072, backend=default_backend()) # Required image property names (SIGNATURE, HASH_METHOD, KEY_TYPE, CERT_UUID) = ( signature_utils.SIGNATURE, signature_utils.HASH_METHOD, signature_utils.KEY_TYPE, signature_utils.CERT_UUID ) class FakeKeyManager(object): def __init__(self): self.certs = {'invalid_format_cert': FakeCastellanCertificate('A' * 256, 'BLAH'), 'valid_format_cert': FakeCastellanCertificate('A' * 256, 'X.509'), 'invalid-cert-uuid': ManagedObjectNotFoundError() } def get(self, context, cert_uuid): cert = self.certs.get(cert_uuid) if cert is None: raise KeyManagerError("No matching certificate found.") if isinstance(cert, ManagedObjectNotFoundError): raise cert return cert class FakeCastellanCertificate(object): def __init__(self, data, cert_format): self.data = data self.cert_format = cert_format @property def format(self): return self.cert_format def get_encoded(self): return self.data class FakeCryptoCertificate(object): def __init__(self, pub_key=TEST_RSA_PRIVATE_KEY.public_key(), not_valid_before=(timeutils.utcnow() - datetime.timedelta(hours=1)), not_valid_after=(timeutils.utcnow() + datetime.timedelta(hours=2))): self.pub_key = pub_key self.cert_not_valid_before = not_valid_before self.cert_not_valid_after = not_valid_after def public_key(self): return self.pub_key @property def not_valid_before(self): return self.cert_not_valid_before @property def not_valid_after(self): return self.cert_not_valid_after class BadPublicKey(object): def verifier(self, signature, padding, hash_method): return None class TestSignatureUtils(base.TestCase): """Test methods of signature_utils""" def setUp(self): super(TestSignatureUtils, self).setUp() def tearDown(self): super(TestSignatureUtils, self).tearDown() def test_should_create_verifier(self): image_props = {CERT_UUID: 'CERT_UUID', HASH_METHOD: 'HASH_METHOD', SIGNATURE: 'SIGNATURE', KEY_TYPE: 'SIG_KEY_TYPE'} self.assertTrue(signature_utils.should_create_verifier(image_props)) def test_should_create_verifier_fail(self): bad_image_properties = [{CERT_UUID: 'CERT_UUID', HASH_METHOD: 'HASH_METHOD', SIGNATURE: 'SIGNATURE'}, {CERT_UUID: 'CERT_UUID', HASH_METHOD: 'HASH_METHOD', KEY_TYPE: 'SIG_KEY_TYPE'}, {CERT_UUID: 'CERT_UUID', SIGNATURE: 'SIGNATURE', KEY_TYPE: 'SIG_KEY_TYPE'}, {HASH_METHOD: 'HASH_METHOD', SIGNATURE: 'SIGNATURE', KEY_TYPE: 'SIG_KEY_TYPE'}] for bad_props in bad_image_properties: result = signature_utils.should_create_verifier(bad_props) self.assertFalse(result) @mock.patch('cursive.signature_utils.get_public_key') def test_verify_signature_PSS(self, mock_get_pub_key): data = b'224626ae19824466f2a7f39ab7b80f7f' mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key() for hash_name, hash_alg in signature_utils.HASH_METHODS.items(): signer = TEST_RSA_PRIVATE_KEY.signer( padding.PSS( mgf=padding.MGF1(hash_alg), salt_length=padding.PSS.MAX_LENGTH ), hash_alg ) signer.update(data) signature = base64.b64encode(signer.finalize()) img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' verifier = signature_utils.get_verifier(None, img_sig_cert_uuid, hash_name, signature, signature_utils.RSA_PSS) verifier.update(data) verifier.verify() @mock.patch('cursive.signature_utils.get_public_key') def test_verify_signature_ECC(self, mock_get_pub_key): data = b'224626ae19824466f2a7f39ab7b80f7f' # test every ECC curve for curve in signature_utils.ECC_CURVES: key_type_name = 'ECC_' + curve.name.upper() try: signature_utils.SignatureKeyType.lookup(key_type_name) except exception.SignatureVerificationError: import warnings warnings.warn("ECC curve '%s' not supported" % curve.name) continue # Create a private key to use private_key = ec.generate_private_key(curve, default_backend()) mock_get_pub_key.return_value = private_key.public_key() for hash_name, hash_alg in signature_utils.HASH_METHODS.items(): signer = private_key.signer( ec.ECDSA(hash_alg) ) signer.update(data) signature = base64.b64encode(signer.finalize()) img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' verifier = signature_utils.get_verifier(None, img_sig_cert_uuid, hash_name, signature, key_type_name) verifier.update(data) verifier.verify() @mock.patch('cursive.signature_utils.get_public_key') def test_verify_signature_DSA(self, mock_get_pub_key): data = b'224626ae19824466f2a7f39ab7b80f7f' mock_get_pub_key.return_value = TEST_DSA_PRIVATE_KEY.public_key() for hash_name, hash_alg in signature_utils.HASH_METHODS.items(): signer = TEST_DSA_PRIVATE_KEY.signer( hash_alg ) signer.update(data) signature = base64.b64encode(signer.finalize()) img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' verifier = signature_utils.get_verifier(None, img_sig_cert_uuid, hash_name, signature, signature_utils.DSA) verifier.update(data) verifier.verify() @mock.patch('cursive.signature_utils.get_public_key') def test_verify_signature_bad_signature(self, mock_get_pub_key): data = b'224626ae19824466f2a7f39ab7b80f7f' mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key() img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' verifier = signature_utils.get_verifier(None, img_sig_cert_uuid, 'SHA-256', 'BLAH', signature_utils.RSA_PSS) verifier.update(data) self.assertRaises(crypto_exceptions.InvalidSignature, verifier.verify) def test_get_verifier_invalid_image_props(self): self.assertRaisesRegex(exception.SignatureVerificationError, 'Required image properties for signature' ' verification do not exist. Cannot verify' ' signature. Missing property: .*', signature_utils.get_verifier, None, None, 'SHA-256', 'BLAH', signature_utils.RSA_PSS) @mock.patch('cursive.signature_utils.get_public_key') def test_verify_signature_bad_sig_key_type(self, mock_get_pub_key): mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key() img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' self.assertRaisesRegex(exception.SignatureVerificationError, 'Invalid signature key type: .*', signature_utils.get_verifier, None, img_sig_cert_uuid, 'SHA-256', 'BLAH', 'BLAH') @mock.patch('cursive.signature_utils.get_public_key') def test_get_verifier_none(self, mock_get_pub_key): mock_get_pub_key.return_value = BadPublicKey() img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693' self.assertRaisesRegex(exception.SignatureVerificationError, 'Error occurred while creating' ' the verifier', signature_utils.get_verifier, None, img_sig_cert_uuid, 'SHA-256', 'BLAH', signature_utils.RSA_PSS) def test_get_signature(self): signature = b'A' * 256 data = base64.b64encode(signature) self.assertEqual(signature, signature_utils.get_signature(data)) def test_get_signature_fail(self): self.assertRaisesRegex(exception.SignatureVerificationError, 'The signature data was not properly' ' encoded using base64', signature_utils.get_signature, '///') def test_get_hash_method(self): hash_dict = signature_utils.HASH_METHODS for hash_name in hash_dict.keys(): hash_class = signature_utils.get_hash_method(hash_name).__class__ self.assertIsInstance(hash_dict[hash_name], hash_class) def test_get_hash_method_fail(self): self.assertRaisesRegex(exception.SignatureVerificationError, 'Invalid signature hash method: .*', signature_utils.get_hash_method, 'SHA-2') def test_signature_key_type_lookup(self): for sig_format in [signature_utils.RSA_PSS, signature_utils.DSA]: sig_key_type = signature_utils.SignatureKeyType.lookup(sig_format) self.assertIsInstance(sig_key_type, signature_utils.SignatureKeyType) self.assertEqual(sig_format, sig_key_type.name) def test_signature_key_type_lookup_fail(self): self.assertRaisesRegex(exception.SignatureVerificationError, 'Invalid signature key type: .*', signature_utils.SignatureKeyType.lookup, 'RSB-PSS') @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('cursive.certificate_utils.verify_certificate') def test_get_public_key_rsa(self, mock_verify_cert, mock_get_cert): fake_cert = FakeCryptoCertificate() mock_get_cert.return_value = fake_cert sig_key_type = signature_utils.SignatureKeyType.lookup( signature_utils.RSA_PSS ) result_pub_key = signature_utils.get_public_key(None, None, sig_key_type) self.assertEqual(fake_cert.public_key(), result_pub_key) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('cursive.certificate_utils.verify_certificate') def test_get_public_key_ecc(self, mock_verify_cert, mock_get_cert): fake_cert = FakeCryptoCertificate(TEST_ECC_PRIVATE_KEY.public_key()) mock_get_cert.return_value = fake_cert sig_key_type = signature_utils.SignatureKeyType.lookup('ECC_SECP521R1') result_pub_key = signature_utils.get_public_key(None, None, sig_key_type) self.assertEqual(fake_cert.public_key(), result_pub_key) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('cursive.certificate_utils.verify_certificate') def test_get_public_key_dsa(self, mock_verify_cert, mock_get_cert): fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key()) mock_get_cert.return_value = fake_cert sig_key_type = signature_utils.SignatureKeyType.lookup( signature_utils.DSA ) result_pub_key = signature_utils.get_public_key(None, None, sig_key_type) self.assertEqual(fake_cert.public_key(), result_pub_key) @mock.patch('cursive.signature_utils.get_certificate') @mock.patch('cursive.certificate_utils.verify_certificate') def test_get_public_key_invalid_key(self, mock_verify_certificate, mock_get_certificate): bad_pub_key = 'A' * 256 mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key) sig_key_type = signature_utils.SignatureKeyType.lookup( signature_utils.RSA_PSS ) self.assertRaisesRegex(exception.SignatureVerificationError, 'Invalid public key type for ' 'signature key type: .*', signature_utils.get_public_key, None, None, sig_key_type) @mock.patch('cryptography.x509.load_der_x509_certificate') @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager()) def test_get_certificate(self, mock_key_manager_API, mock_load_cert): cert_uuid = 'valid_format_cert' x509_cert = FakeCryptoCertificate() mock_load_cert.return_value = x509_cert self.assertEqual(x509_cert, signature_utils.get_certificate(None, cert_uuid)) @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager()) def test_get_certificate_key_manager_fail(self, mock_key_manager_API): bad_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0695' self.assertRaisesRegex(exception.SignatureVerificationError, 'Unable to retrieve certificate with ID: .*', signature_utils.get_certificate, None, bad_cert_uuid) @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager()) def test_get_certificate_invalid_format(self, mock_API): cert_uuid = 'invalid_format_cert' self.assertRaisesRegex(exception.SignatureVerificationError, 'Invalid certificate format: .*', signature_utils.get_certificate, None, cert_uuid) @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager()) def test_get_certificate_id_not_exist(self, mock_key_manager): bad_cert_uuid = 'invalid-cert-uuid' self.assertRaisesRegex(exception.SignatureVerificationError, 'Certificate not found with ID: .*', signature_utils.get_certificate, None, bad_cert_uuid) cursive-0.2.1/doc/000077500000000000000000000000001321724515000137375ustar00rootroot00000000000000cursive-0.2.1/doc/source/000077500000000000000000000000001321724515000152375ustar00rootroot00000000000000cursive-0.2.1/doc/source/conf.py000077500000000000000000000046271321724515000165520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'oslosphinx' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'cursive' copyright = u'2016, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} cursive-0.2.1/doc/source/contributing.rst000066400000000000000000000001131321724515000204730ustar00rootroot00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rst cursive-0.2.1/doc/source/index.rst000066400000000000000000000007611321724515000171040ustar00rootroot00000000000000.. cursive documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to cursive's documentation! ======================================================== Contents: .. toctree:: :maxdepth: 2 readme installation usage contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` cursive-0.2.1/doc/source/installation.rst000066400000000000000000000002761321724515000204770ustar00rootroot00000000000000============ Installation ============ At the command line:: $ pip install cursive Or, if you have virtualenvwrapper installed:: $ mkvirtualenv cursive $ pip install cursive cursive-0.2.1/doc/source/readme.rst000066400000000000000000000000361321724515000172250ustar00rootroot00000000000000.. include:: ../../README.rst cursive-0.2.1/doc/source/usage.rst000066400000000000000000000001131321724515000170700ustar00rootroot00000000000000======== Usage ======== To use cursive in a project:: import cursive cursive-0.2.1/releasenotes/000077500000000000000000000000001321724515000156635ustar00rootroot00000000000000cursive-0.2.1/releasenotes/notes/000077500000000000000000000000001321724515000170135ustar00rootroot00000000000000cursive-0.2.1/releasenotes/notes/add-certificate-validation-68a1ffbd5369a8d1.yaml000066400000000000000000000041051321724515000273430ustar00rootroot00000000000000--- prelude: > The cursive library supports the verification of digital signatures. However, there is no way currently to validate the certificate used to generate a given signature. Adding certificate validation improves the security of signature verification when each is used together. features: - Adds a variety of certificate utility functions that inspect certificate attributes and extensions for different settings. - Adds the CertificateVerificationContext class which uses a set of trusted certificates to conduct certificate validation, verifying that a given certificate is part of a certificate chain rooted with a trusted certificate. - Adds a verify_certificate method that loads all certificates needed for certificate validation from the key manager and uses them to create a CertificateVerificationContext object. The context is then used to determine if a certificate is valid. upgrade: - The addition of certificate validation as a separate operation from the signature verification process preserves backwards compatibility. Signatures previously verifiable with cursive will still be verifiable. However, their signing certificates may not be valid. Each signing certificate should be checked for validity before it is used to conduct signature verification. security: - The usage of certificate validation with the signature verification process improves the security of signature verification. A signature should not be considered valid unless its corresponding certificate is also valid. other: - The CertificateVerificationContext is built using a set of trusted certificates. However, to conduct certificate verification the context builds the full certificate chain, starting with the certificate to validate and ending with the self-signed root certificate. If this self-signed root certificate is not present in the context, or if one of the intermediate certificates is not present in the context, the certificate chain cannot be built and certificate validation will fail. cursive-0.2.1/releasenotes/source/000077500000000000000000000000001321724515000171635ustar00rootroot00000000000000cursive-0.2.1/releasenotes/source/_static/000077500000000000000000000000001321724515000206115ustar00rootroot00000000000000cursive-0.2.1/releasenotes/source/_static/.placeholder000066400000000000000000000000001321724515000230620ustar00rootroot00000000000000cursive-0.2.1/releasenotes/source/conf.py000066400000000000000000000215451321724515000204710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Cursive Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'cursive Release Notes' copyright = u'2016, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'CursiveReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'CursiveReleaseNotes.tex', u'Cursive Release Notes Documentation', u'Cursive Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'cursivereleasenotes', u'Cursive Release Notes Documentation', [u'Cursive Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'CursiveReleaseNotes', u'Cursive Release Notes Documentation', u'Cursive Developers', 'CursiveReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] cursive-0.2.1/releasenotes/source/index.rst000066400000000000000000000002361321724515000210250ustar00rootroot00000000000000============================================ cursive Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased cursive-0.2.1/releasenotes/source/unreleased.rst000066400000000000000000000001601321724515000220410ustar00rootroot00000000000000============================== Current Series Release Notes ============================== .. release-notes:: cursive-0.2.1/requirements.txt000066400000000000000000000007571321724515000164670ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 lxml>=2.3 # BSD cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0 netifaces>=0.10.4 # MIT six>=1.9.0 # MIT oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0 castellan>=0.4.0 # Apache-2.0 cursive-0.2.1/setup.cfg000066400000000000000000000024161321724515000150160ustar00rootroot00000000000000[metadata] name = cursive summary = Cursive implements OpenStack-specific validation of digital signatures. description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 [files] packages = cursive [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = cursive/locale domain = cursive [update_catalog] domain = cursive output_dir = cursive/locale input_file = cursive/locale/cursive.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = cursive/locale/cursive.pot [build_releasenotes] all_files = 1 build-dir = releasenotes/build source-dir = releasenotes/source [wheel] universal = 1 cursive-0.2.1/setup.py000066400000000000000000000017031321724515000147050ustar00rootroot00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=1.8'], pbr=True) cursive-0.2.1/test-requirements.txt000066400000000000000000000010731321724515000174340ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking<0.12,>=0.11.0 # Apache-2.0 coverage>=3.6 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD mock>=2.0.0 # BSD sphinx!=1.3b1,<1.3,>=1.2.1 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT # releasenotes reno>=1.8.0 # Apache2 cursive-0.2.1/tox.ini000066400000000000000000000014671321724515000145150ustar00rootroot00000000000000[tox] minversion = 2.0 envlist = py34,py27,pep8 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 {posargs} [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py test --coverage --testr-args='{posargs}' [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = oslo_debug_helper {posargs} [flake8] show-source = True ignore = H301 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build