Source code for smc.administration.certificates.tls_common

"""
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 })