"""
TLS module provides interactions related to importing TLS Server Credentials
for inbound SSL decryption, as well as client protection certificates used
for outbound decryption.
To properly decrypt inbound TLS connections, you must provide the Stonesoft FW
with a valid certificate and private key. Within SMC these certificate types are
known as TLS Server Credentials.
Once you have imported these certificates, you must then assign them to the
relevant engines that will perform the decryption services. Lastly you will
need a rule that enables HTTPS with decryption.
First start by importing the TLS Server Credential class::
>>> from smc.administration.certificates.tls import TLSServerCredential
If you want to create a TLS Server Credential in steps, the process is as follows::
tls = TLSServerCredential.create(name) # Create the certificate element
tls.import_certificate(certificate) # Import the certificate
tls.import_private_key(private_key) # Import the private key
tls.import_intermediate_certificate(intermediate) # Import intermediate certificate (optional)
Otherwise, use helper methods that allow you to do this in a single step.
For example, creating the TLS credential from certificate files::
>>> tls = TLSServerCredential.import_signed(
name='server.test.local',
certificate='/pathto/server.crt',
private_key='/pathto/server.key',
intermediate=None) # <-- You can also include intermediate certificates
>>> tls
TLSServerCredential(name=server.test.local)
.. note:: Certificate, private key and intermediate certificates can also be specified in raw
string format and must start with the BEGIN CERTIFICATE, etc common syntax.
You can also import certificates from a certificate chain file. When doing so, the certificates
are expected to be in the order: server certificate, intermediate/s, root certificate. You can
optionally also add the private key to the chain file or provide it separately::
tls = TLSServerCredential.import_from_chain(
name='fromchain', certificate_file='/path/cert.chain',
private_key='/path/priv.key')
.. note:: If multiple intermediate certificates are added, only the first one is imported
into the TLS Server Credential. In addition, the root certificate is ignored and should
be imported using :meth:`TLSCertificateAuthority.create`.
It is also possible to create self signed certificates using the SMC CA::
>>> tls = TLSServerCredential.create_self_signed(
name='server.test.local', common_name='CN=server.test.local')
>>> tls
TLSServerCredential(name=server.test.local)
If you would rather use the SMC to generate the CSR and have the request signed by an
external CA you can call :meth:`TLSServerCredential.create_csr` and export the request::
>>> tls = TLSServerCredential.create_csr(name='public.test.local', common_name='CN=public.test.local')
>>> tls.certificate_export()
'-----BEGIN CERTIFICATE REQUEST-----
MIIEXTCCAkcCAQAwHDEaMBgGA1UEAwwRcHVibGljLnRlc3QubG9jYWwwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC68xcXrWQ5E25nkTfmgmPQiWVPwf
....
....
-----END CERTIFICATE REQUEST-----'
Optionally export the request to a local file::
>>> tls = TLSServerCredential.create_csr(
name='public2.test.local', common_name='CN=public2.test.local')
>>> tls.certificate_export(filename='public2.test.local.csr')
If you use an external CA for signing your certficiates, you can also import
that as a TLS Certificate Authority. The link between the certificates and
root CA will be made automatically::
TLSCertificateAuthority.create(
name='myrootca',
certificate='/path/to/cert/or/string')
Once you have the TLS Server Credentials within SMC, you can then assign them to
the relevant engines::
>>> from smc.core.engine import Engine
>>> from smc.administration.certificates import TLSServerCredential
>>> engine = Engine('myfirewall')
>>> engine.tls_inspection.add_tls_credential([TLSServerCredential('public.test.local'), TLSServerCredential('server.test.local')])
>>> engine.tls_inspection.server_credentials
[TLSServerCredential(name=public.test.local), TLSServerCredential(name=server.test.local)]
.. note:: It is possible to import and export certificates from the SMC, but it is not
possible to export private keys.
"""
from smc.base.model import Element, ElementCreator
from smc.administration.certificates.tls_common import ImportExportCertificate, \
ImportPrivateKey, ImportExportIntermediate, load_cert_chain, pem_as_string
from smc.api.exceptions import CertificateImportError, ActionCommandFailed
from smc.base.util import datetime_from_ms, element_resolver
from smc.base.structs import NestedDict
[docs]class TLSProfile(Element):
"""
.. versionadded:: 0.6.2
Requires SMC >= 6.4
Represents a TLS Profile.
Contains common parameters for establishing TLS based connections.
TLS Profiles are used in various configuration areas such as SSL
VPN portal and Active Directory (when using TLS) connections.
"""
typeof = 'tls_profile'
[docs] @classmethod
def create(cls, name, tls_version, use_only_subject_alt_name=False,
accept_wildcard=False, check_revocation=True, tls_cryptography_suites=None,
crl_delay=0, ocsp_delay=0, ignore_network_issues=False,
tls_trusted_ca_ref=None, comment=None):
"""
Create a TLS Profile. By default the SMC will have a default NIST
TLS Profile but it is also possible to create a custom profile to
provide special TLS handling.
:param str name: name of TLS Profile
:param str tls_verison: supported tls verison, valid options are
TLSv1.1, TLSv1.2, TLSv1.3
:param bool use_only_subject_alt_name: Use Only Subject Alt Name
when the TLS identity is a DNS name
:param bool accept_wildcard: Does server identity check accept wildcards
:param bool check_revocation: Is certificate revocation checked
:param str,TLSCryptographySuite tls_cryptography_suites: allowed cryptography
suites for this profile. Uses NIST profile if not specified
:param int crl_delay: Delay time (hours) for fetching CRL
:param int ocsp_delay: Ignore OCSP failure for (hours)
:param bool ignore_network_issues: Ignore revocation check failures
due to network issues
:param list tls_trusted_ca_ref: Trusted Certificate Authorities, empty
list means trust any
:param str comment: optional comment
:raises CreateElementFailed: failed to create element with reason
:raises ElementNotFound: specified element reference was not found
:rtype: TLSProfile
"""
json = {
'name': name,
'tls_version': tls_version,
'use_only_subject_alt_name': use_only_subject_alt_name,
'accept_wildcard': accept_wildcard,
'check_revocation': check_revocation,
'tls_cryptography_suites': element_resolver(tls_cryptography_suites) or \
element_resolver(TLSCryptographySuite.objects.filter('NIST').first()),
'crl_delay': crl_delay,
'ocsp_delay': ocsp_delay,
'ignore_network_issues': ignore_network_issues,
'tls_trusted_ca_ref': element_resolver(tls_trusted_ca_ref) or [],
'comment': comment}
return ElementCreator(cls, json)
[docs]class TLSIdentity(NestedDict):
"""
.. versionadded:: 0.6.2
Requires SMC >= 6.4
A TLS Identity represents a field and value pair that will
be used to validate a TLS certificate. This can be used in
various areas where TLS is used such as VPN.
Valid tls field types are:
DNSName
IPAddress
CommonName
DistinguishedName
SHA-1
SHA-256
SHA-512
MD5
Email
user_principal_name
"""
def __init__(self, tls_field, tls_value):
data = {'tls_field': tls_field, 'tls_value': tls_value}
super(TLSIdentity, self).__init__(data=data)
[docs]class TLSCryptographySuite(Element):
"""
This represents a TLS Cryptography Suite Set used in various
configurations that require a TLS Profile such as SSL VPN
Tunneling, Reverse Web Proxy, ActiveDirectory TLS, etc.
"""
typeof = 'tls_cryptography_suite_set'
[docs] @staticmethod
def ciphers(from_suite=None):
"""
This is a helper method that will return all of the cipher
strings used in a specified TLSCryptographySuite or returns
the system default NIST profile list of ciphers. This can
be used as a helper to identify the ciphers to specify/add
when creating a new TLSCryptographySuite.
:rtype: dict
"""
suite = from_suite or TLSCryptographySuite.objects.filter(
'NIST').first()
return suite.data.get('tls_cryptography_suites')
[docs] @classmethod
def create(cls, name, comment=None, **ciphers):
"""
Create a new TLSCryptographySuite. The ciphers kwargs should
be a dict with the cipher suite string as key and boolean value
to indicate if this cipher should be enabled.
To obtain the valid cipher suite string name, use the following
method::
cipher_strings = TLSCryptographySuite.ciphers()
Then to create a custom cipher suite, provide the ciphers as a
dict of kwargs. In this example, create a TLS Crypto Suite that
only enables AES 256 bit ciphers::
only256 = dict(((cipher, True) for cipher in TLSCryptographySuite.ciphers()
if 'aes_256' in cipher))
mytls = TLSCryptographySuite.create(name='mytls', **only256)
:param str name: name of this TLS Crypto suite
:param dict ciphers: dict of ciphers with cipher string as key and
bool as value, True enables the cipher
:raises CreateElementFailed: failed to create element with reason
:rtype: TLSCryptographySuite
"""
json = {
'name': name,
'comment': comment,
'tls_cryptography_suites': ciphers}
return ElementCreator(cls, json)
class TLSCertificateAuthority(ImportExportCertificate, Element):
"""
TLS Certificate authorities. When using TLS Server Credentials for
decryption, import the root CA for the any TLS Server certificates
the leverage the root CA.
:ivar str certificate: base64 encoded certificate for this CA
:ivar bool crl_checking_enabled: whether CRL checking is turned on
:ivar bool internal_ca: is this an internal CA (default: false)
:ivar bool oscp_checking_enabled: is OSCP validation enabled
"""
typeof = 'tls_certificate_authority'
@classmethod
def create(cls, name, certificate):
"""
Create a TLS CA. The certificate must be compatible with OpenSSL
and be in PEM format. The certificate can be either a file with
the Root CA, or a raw string starting with BEGIN CERTIFICATE, etc.
When creating a TLS CA, you must also import the CA certificate. Once
the CA is created, it is possible to import a different certificate to
map to the CA if necessary.
:param str name: name of root CA
:param str,file certificate: The root CA contents
:raises CreateElementFailed: failed to create the root CA
:raises ValueError: if loading from file and no certificates present
:raises IOError: cannot find specified file for certificate
:rtype: TLSCertificateAuthority
"""
json = {'name': name,
'certificate': certificate if pem_as_string(certificate) else \
load_cert_chain(certificate)[0][1].decode('utf-8')}
return ElementCreator(cls, json)
[docs]class TLSServerCredential(ImportExportIntermediate, ImportPrivateKey,
ImportExportCertificate, Element):
"""
If you want to inspect TLS traffic for which an internal server is the
destination, you must create a TLS Credentials element to store the
private key and certificate of the server.
The private key and certificate allow the firewall to decrypt TLS traffic
for which the internal server is the destination so that it can be inspected.
After a TLSServerCredential has been created, you must apply this to the
engine performing decryption and create the requisite policy rule that uses
SSL decryption.
:ivar str certificate_state: State of the certificate. Available states are
'request' and 'certificate'. If the state is 'request', this represents a
CSR and needs to be signed.
"""
typeof = 'tls_server_credentials'
[docs] @classmethod
def create(cls, name):
"""
Create an empty certificate. This will only create the element
in the SMC and will then require that you import the server
certificate, intermediate (optional) and private key.
.. seealso:: :meth:`~import_signed` and :meth:`~import_from_chain`.
:raises CreateElementFailed: failed creating element
:rtype: TLSServerCredential
"""
json = {'name': name,
'certificate_state': 'certificate'}
return ElementCreator(cls, json)
[docs] @classmethod
def create_csr(cls, name, common_name, public_key_algorithm='rsa',
signature_algorithm='rsa_sha_512', key_length=4096):
"""
Create a certificate signing request.
:param str name: name of TLS Server Credential
:param str rcommon_name: common name for certificate. An example
would be: "CN=CommonName,O=Organization,OU=Unit,C=FR,ST=PACA,L=Nice".
At minimum, a "CN" is required.
:param str public_key_algorithm: public key type to use. Valid values
rsa, dsa, ecdsa.
:param str signature_algorithm: signature algorithm. Valid values
dsa_sha_1, dsa_sha_224, dsa_sha_256, rsa_md5, rsa_sha_1, rsa_sha_256,
rsa_sha_384, rsa_sha_512, ecdsa_sha_1, ecdsa_sha_256, ecdsa_sha_384,
ecdsa_sha_512. (Default: rsa_sha_512)
:param int key_length: length of key. Key length depends on the key
type. For example, RSA keys can be 1024, 2048, 3072, 4096. See SMC
documentation for more details.
:raises CreateElementFailed: failed to create CSR
:rtype: TLSServerCredential
"""
json = {
'name': name,
'info': common_name,
'public_key_algorithm': public_key_algorithm,
'signature_algorithm': signature_algorithm,
'key_length': key_length,
'certificate_state': 'initial'
}
return ElementCreator(cls, json)
[docs] @classmethod
def create_self_signed(cls, name, common_name, public_key_algorithm='rsa',
signature_algorithm='rsa_sha_512', key_length=4096):
"""
Create a self signed certificate. This is a convenience method that
first calls :meth:`~create_csr`, then calls :meth:`~self_sign` on the
returned TLSServerCredential object.
:param str name: name of TLS Server Credential
:param str rcommon_name: common name for certificate. An example
would be: "CN=CommonName,O=Organization,OU=Unit,C=FR,ST=PACA,L=Nice".
At minimum, a "CN" is required.
:param str public_key_algorithm: public key type to use. Valid values
rsa, dsa, ecdsa.
:param str signature_algorithm: signature algorithm. Valid values
dsa_sha_1, dsa_sha_224, dsa_sha_256, rsa_md5, rsa_sha_1, rsa_sha_256,
rsa_sha_384, rsa_sha_512, ecdsa_sha_1, ecdsa_sha_256, ecdsa_sha_384,
ecdsa_sha_512. (Default: rsa_sha_512)
:param int key_length: length of key. Key length depends on the key
type. For example, RSA keys can be 1024, 2048, 3072, 4096. See SMC
documentation for more details.
:raises CreateElementFailed: failed to create CSR
:raises ActionCommandFailed: Failure to self sign the certificate
:rtype: TLSServerCredential
"""
tls = TLSServerCredential.create_csr(name=name, common_name=common_name,
public_key_algorithm=public_key_algorithm, signature_algorithm=signature_algorithm,
key_length=key_length)
try:
tls.self_sign()
except ActionCommandFailed:
tls.delete()
raise
return tls
[docs] @classmethod
def import_signed(cls, name, certificate, private_key, intermediate=None):
"""
Import a signed certificate and private key to SMC, and optionally
an intermediate certificate.
The certificate and the associated private key must be compatible
with OpenSSL and be in PEM format. The certificate and private key
can be imported as a raw string, file path or file object.
If importing as a string, be sure the string has carriage returns after
each line and the final `END CERTIFICATE` line.
Import a certificate and private key::
>>> tls = TLSServerCredential.import_signed(
name='server2.test.local',
certificate='mydir/server.crt',
private_key='mydir/server.key')
>>> tls
TLSServerCredential(name=server2.test.local)
:param str name: name of TLSServerCredential
:param str certificate: fully qualified to the certificate file, string or file object
:param str private_key: fully qualified to the private key file, string or file object
:param str intermediate: fully qualified to the intermediate file, string or file object
:raises CertificateImportError: failure during import
:raises CreateElementFailed: failed to create credential
:raises IOError: failure to find certificate files specified
:rtype: TLSServerCredential
"""
tls = TLSServerCredential.create(name)
try:
tls.import_certificate(certificate)
tls.import_private_key(private_key)
if intermediate is not None:
tls.import_intermediate_certificate(intermediate)
except CertificateImportError:
tls.delete()
raise
return tls
[docs] @classmethod
def import_from_chain(cls, name, certificate_file, private_key=None):
"""
Import the server certificate, intermediate and optionally private
key from a certificate chain file. The expected format of the chain
file follows RFC 4346.
In short, the server certificate should come first, followed by
any intermediate certificates, optionally followed by
the root trusted authority. The private key can be anywhere in this
order. See https://tools.ietf.org/html/rfc4346#section-7.4.2.
.. note:: There is no validation done on the certificates, therefore
the order is assumed to be true. In addition, the root certificate
will not be imported and should be separately imported as a trusted
root CA using :class:`~TLSCertificateAuthority.create`
If the certificate chain file has only two entries, it is assumed to
be the server certificate and root certificate (no intermediates). In
which case only the certificate is imported. If the chain file has
3 or more entries (all certificates), it will import the first as the
server certificate, 2nd as the intermediate and ignore the root cert.
You can optionally provide a seperate location for a private key file
if this is not within the chain file contents.
.. warning:: A private key is required to create a valid TLS Server
Credential.
:param str name: name of TLS Server Credential
:param str certificate_file: fully qualified path to chain file or file object
:param str private_key: fully qualified path to chain file or file object
:raises IOError: error occurred reading or finding specified file
:raises ValueError: Format issues with chain file or empty
:rtype: TLSServerCredential
"""
contents = load_cert_chain(certificate_file)
for pem in list(contents):
if b'PRIVATE KEY' in pem[0]:
private_key = pem[1]
contents.remove(pem)
if not private_key:
raise ValueError('Private key was not found in chain file and '
'was not provided. The private key is required to create a '
'TLS Server Credential.')
if contents:
if len(contents) == 1:
certificate = contents[0][1]
intermediate = None
else:
certificate = contents[0][1]
intermediate = contents[1][1]
else:
raise ValueError('No certificates found in certificate chain file. Did you '
'provide only a private key?')
tls = TLSServerCredential.create(name)
try:
tls.import_certificate(certificate)
tls.import_private_key(private_key)
if intermediate is not None:
tls.import_intermediate_certificate(intermediate)
except CertificateImportError:
tls.delete()
raise
return tls
[docs] def self_sign(self):
"""
Self sign the certificate in 'request' state.
:raises ActionCommandFailed: failed to sign with reason
"""
return self.make_request(
method='create',
resource='self_sign')
@property
def valid_from(self):
"""
.. versionadded:: 0.6.0
Requires SMC version >= 6.3.4
The valid from datetime for this TLS Server Credential.
:rtype: datetime.datetime
"""
return datetime_from_ms(self.data.get('valid_from'))
@property
def valid_to(self):
"""
.. versionadded:: 0.6.0
Requires SMC version >= 6.3.4
The expiration (valid to) datetime for this TLS Server Credential.
:rtype: datetime.datetime
"""
return datetime_from_ms(self.data.get('valid_to'))
[docs]class ClientProtectionCA(ImportPrivateKey, ImportExportCertificate, Element):
"""
Client Protection Certificate Authority elements are used to inspect TLS
traffic between an internal client and an external server for outbound
decryption.
When an internal client makes a connection to an external server that uses
TLS, the engine generates a substitute certificate that allows it to establish
a secure connection with the internal client. The Client Protection Certificate
Authority element contains the credentials the engine uses to sign the substitute
certificate it generates.
:ivar str certificate: base64 encoded certificate for this CA
:ivar bool crl_checking_enabled: whether CRL checking is turned on
:ivar bool internal_ca: is this an internal CA (default: false)
:ivar bool oscp_checking_enabled: is OSCP validation enabled
.. note :: If the engine does not use a signing certificate that is already
trusted by users web browsers when it signs the substitute certificates it
generates, users receive warnings about invalid certificates. To avoid these
warnings, you must either import a signing certificate that is already trusted,
or configure users web browsers to trust the engine signing certificate.
"""
typeof = 'tls_signing_certificate_authority'
[docs] @classmethod
def import_signed(cls, name, certificate, private_key):
"""
Import a signed certificate and private key as a client protection CA.
This is a shortcut method to the 3 step process:
* Create CA with name
* Import certificate
* Import private key
Create the CA::
ClientProtectionCA.import_signed(
name='myclientca',
certificate_file='/pathto/server.crt'
private_key_file='/pathto/server.key')
:param str name: name of client protection CA
:param str certificate_file: fully qualified path or string of certificate
:param str private_key_file: fully qualified path or string of private key
:raises CertificateImportError: failure during import
:raises IOError: failure to find certificate files specified
:rtype: ClientProtectionCA
"""
ca = ClientProtectionCA.create(name=name)
try:
ca.import_certificate(certificate)
ca.import_private_key(private_key)
except CertificateImportError:
ca.delete()
raise
return ca
[docs] @classmethod
def create(cls, name):
"""
Create a client protection CA. Once the client protection CA is
created, to activate you must then call import_certificate and
import_private_key. Or optionally use the convenience classmethod
:meth:`~import_signed`.
:raises CreateElementFailed: failed to create base Client CA
:rtype: ClientProtectionCA
"""
json = {'name': name}
return ElementCreator(cls, json)
[docs] @classmethod
def create_self_signed(cls, name, prefix, password, public_key_algorithm='rsa',
life_time=365, key_length=2048):
"""
Create a self signed client protection CA. To prevent browser warnings during
decryption, you must trust the signing certificate in the client browsers.
:param str name: Name of this ex: "SG Root CA" Used as Key.
Real common name will be derivated at creation time with a uniqueId.
:param str prefix: prefix used for derivating file names
:param str password: password for private key
:param public_key_algorithm: public key algorithm, either rsa, dsa or ecdsa
:param str,int life_time: lifetime in days for CA
:param int key_length: length in bits, either 1024 or 2048
:raises CreateElementFailed: creating element failed
:raises ActionCommandFailed: failed to self sign the certificate
:rtype: ClientProtectionCA
"""
json = {'key_name': name,
'prefix': prefix,
'password': password,
'life_time': life_time,
'key_size': key_length,
'algorithm': public_key_algorithm}
tls = ClientProtectionCA.create(name)
try:
tls.make_request(
method='create',
json=json,
resource='generate_self_signed_cert')
except ActionCommandFailed:
tls.delete()
raise
return tls