Source code for smc.administration.certificates.tls_common

#  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.
"""
TLS Common module provides mixin methods that are common to certificate
handling in SMC.
Importing certificates and private keys can be done by providing a file
where the certificates/keys are stored, or providing in string format.
"""

import re
from smc.compat import unicode
from smc.base.util import save_to_file
from smc.api.exceptions import CertificateImportError, CertificateExportError


CERT_TYPES = (b"PRIVATE KEY", b"RSA PRIVATE KEY", b"CERTIFICATE")

_PEM_RE = re.compile(
    b"-----BEGIN (" + b"|".join(CERT_TYPES) + b""")-----\r?.+?\r?-----END \\1-----\r?\n?""",
    re.DOTALL,
)


def pem_as_string(cert):
    """
    Only return False if the certificate is a file path. Otherwise it
    is a file object or raw string and will need to be fed to the
    file open context.
    """
    if hasattr(cert, "read"):  # File object - return as is
        return cert
    cert = cert.encode("utf-8") if isinstance(cert, unicode) else cert
    if re.match(_PEM_RE, cert):
        return True
    return False


def load_cert_chain(chain_file):
    """
    Load the certificates from the chain file.

    :raises IOError: Failure to read specified file
    :raises ValueError: Format issues with chain file or missing entries
    :return: list of cert type matches
    """
    if hasattr(chain_file, "read"):
        cert_chain = chain_file.read()
    else:
        with open(chain_file, "rb") as f:
            cert_chain = f.read()

    if not cert_chain:
        raise ValueError("Certificate chain file is empty!")

    cert_type_matches = []
    for match in _PEM_RE.finditer(cert_chain):
        cert_type_matches.append((match.group(1), match.group(0)))

    if not cert_type_matches:
        raise ValueError(
            "No certificate types were found. Valid types " "are: {}".format(CERT_TYPES)
        )

    return cert_type_matches


[docs] class ImportExportCertificate(object): """ Mixin to provide certificate import and export methods to relevant classes. """
[docs] def import_certificate(self, certificate): """ Import a valid certificate. Certificate can be either a file path or a string of the certificate. If string certificate, it must include the -----BEGIN CERTIFICATE----- string. :param str certificate_file: fully qualified path to certificate file :raises CertificateImportError: failure to import cert with reason :raises IOError: file not found, permissions, etc. :return: None """ multi_part = ( "signed_certificate" if self.typeof == "tls_server_credentials" else "certificate" ) self.make_request( CertificateImportError, method="create", resource="certificate_import", headers={"content-type": "multipart/form-data"}, files={ multi_part: open(certificate, "rb") if not pem_as_string(certificate) else certificate }, )
[docs] def export_certificate(self, filename=None): """ Export the certificate. Returned certificate will be in string format. If filename is provided, the certificate will also be saved to the file specified. :raises CertificateExportError: error exporting certificate :rtype: str or None """ result = self.make_request( CertificateExportError, raw_result=True, resource="certificate_export" ) if filename is not None: save_to_file(filename, result.content) return return result.content
[docs] class ImportExportIntermediate(object): """ Mixin to provide import and export capabilities for intermediate certificates """
[docs] def import_intermediate_certificate(self, certificate): """ Import a valid certificate. Certificate can be either a file path or a string of the certificate. If string certificate, it must include the -----BEGIN CERTIFICATE----- string. :param str certificate: fully qualified path or string :raises CertificateImportError: failure to import cert with reason :raises IOError: file not found, permissions, etc. :return: None """ self.make_request( CertificateImportError, method="create", resource="intermediate_certificate_import", headers={"content-type": "multipart/form-data"}, files={ "signed_certificate": open(certificate, "rb") if not pem_as_string(certificate) else certificate }, )
[docs] def export_intermediate_certificate(self, filename=None): """ Export the intermediate certificate. Returned certificate will be in string format. If filename is provided, the certificate will also be saved to the file specified. :raises CertificateExportError: error exporting certificate, can occur if no intermediate certificate is available. :rtype: str or None """ result = self.make_request( CertificateExportError, raw_result=True, resource="intermediate_certificate_export" ) if filename is not None: save_to_file(filename, result.content) return return result.content
[docs] class ImportPrivateKey(object): """ Mixin to provide import capabilities to relevant classes that require private keys. """
[docs] def import_private_key(self, private_key): """ Import a private key. The private key can be a path to a file or the key in string format. If in string format, the key must start with -----BEGIN. Key types supported are PRIVATE RSA KEY and PRIVATE KEY. :param str private_key: fully qualified path to private key file :raises CertificateImportError: failure to import cert with reason :raises IOError: file not found, permissions, etc. :return: None """ self.make_request( CertificateImportError, method="create", resource="private_key_import", headers={"content-type": "multipart/form-data"}, files={ "private_key": open(private_key, "rb") if not pem_as_string(private_key) else private_key }, )