# 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 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
},
)