|
7 | 7 | from __future__ import unicode_literals |
8 | 8 |
|
9 | 9 | from os import strerror |
| 10 | +import os.path |
10 | 11 |
|
11 | 12 | from ldap.pkginfo import __version__, __author__, __license__ |
12 | 13 |
|
@@ -934,6 +935,127 @@ def set_option(self,option,invalue): |
934 | 935 | invalue = RequestControlTuples(invalue) |
935 | 936 | return self._ldap_call(self._l.set_option,option,invalue) |
936 | 937 |
|
| 938 | + def set_tls_options(self, cacertfile=None, cacertdir=None, |
| 939 | + require_cert=None, protocol_min=None, |
| 940 | + cipher_suite=None, certfile=None, keyfile=None, |
| 941 | + crlfile=None, crlcheck=None, start_tls=True): |
| 942 | + """Set TLS/SSL options |
| 943 | +
|
| 944 | + :param cacertfile: path to a PEM bundle file containing root CA certs |
| 945 | + :param cacertdir: path to a directory with hashed CA certificates |
| 946 | + :param require_cert: cert validation strategy, one of |
| 947 | + ldap.OPT_X_TLS_NEVER, OPT_X_TLS_DEMAND, OPT_X_TLS_HARD. Hard and |
| 948 | + demand have the same meaning for client side sockets. |
| 949 | + :param protocol_min: minimum protocol version, one of 0x303 (TLS 1.2) |
| 950 | + or 0x304 (TLS 1.3). |
| 951 | + :param cipher_suite: cipher suite string |
| 952 | + :param certfile: path to cert file for client cert authentication |
| 953 | + :param keyfile: path to key file for client cert authentication |
| 954 | + :param crlfile: path to a CRL file |
| 955 | + :param crlcheck: CRL verification strategy, one of |
| 956 | + ldap.OPT_X_TLS_CRL_NONE, ldap.OPT_X_TLS_CRL_PEER, or |
| 957 | + ldap.OPT_X_TLS_CRL_ALL |
| 958 | + :param start_tls: automatically perform StartTLS for ldap:// connections |
| 959 | + """ |
| 960 | + if not hasattr(ldap, "OPT_X_TLS_NEWCTX"): |
| 961 | + raise ValueError("libldap does not have TLS support") |
| 962 | + # OpenSSL and GnuTLS support these options |
| 963 | + tls_pkg = self.get_option(ldap.OPT_X_TLS_PACKAGE) |
| 964 | + if tls_pkg not in {"OpenSSL", "GnuTLS"}: |
| 965 | + raise ValueError("Unsupport TLS package '{}'.".format(tls_pkg)) |
| 966 | + # block ldapi ('in' because libldap supports multiple URIs) |
| 967 | + if "ldapi://" in self._uri: |
| 968 | + raise ValueError("IPC (ldapi) does not support TLS.") |
| 969 | + if self._ldap_call(self._l.tls_inplace): |
| 970 | + raise ValueError("TLS connection already established") |
| 971 | + |
| 972 | + def _checkfile(option, filename): |
| 973 | + # check that the file exists and is readable. |
| 974 | + # libldap doesn't verify paths until it establishes a connection |
| 975 | + with open(filename, "rb"): |
| 976 | + pass |
| 977 | + |
| 978 | + if cacertfile is not None: |
| 979 | + _checkfile("certfile", certfile) |
| 980 | + self.set_option(ldap.OPT_X_TLS_CACERTFILE, cacertfile) |
| 981 | + |
| 982 | + if cacertdir is not None: |
| 983 | + if not os.path.isdir(cacertdir): |
| 984 | + raise OSError( |
| 985 | + "'{}' does not exist or is not a directory".format(cacertdir) |
| 986 | + ) |
| 987 | + self.set_option(ldap.OPT_X_TLS_CACERTDIR, cacertdir) |
| 988 | + |
| 989 | + if require_cert is not None: |
| 990 | + supported = { |
| 991 | + ldap.OPT_X_TLS_NEVER, |
| 992 | + # ALLOW is a server-side setting |
| 993 | + # ldap.OPT_X_TLS_ALLOW, |
| 994 | + ldap.OPT_X_TLS_DEMAND, |
| 995 | + ldap.OPT_X_TLS_HARD |
| 996 | + } |
| 997 | + if require_cert not in supported: |
| 998 | + raise ValueError("Unsupported value for require_cert") |
| 999 | + self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, require_cert) |
| 1000 | + |
| 1001 | + if protocol_min is not None: |
| 1002 | + # let's not support TLS 1.0 and 1.1 |
| 1003 | + supported = {0x303, 0x304} |
| 1004 | + if protocol_min not in supported: |
| 1005 | + raise ValueError("Unsupported value for protocol_min") |
| 1006 | + self.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, protocol_min) |
| 1007 | + |
| 1008 | + if cipher_suite is not None: |
| 1009 | + self.set_option(ldap.OPT_X_TLS_CIPHER_SUITE, cipher_suite) |
| 1010 | + |
| 1011 | + if certfile is not None: |
| 1012 | + if keyfile is None: |
| 1013 | + raise ValueError("certfile option requires keyfile option") |
| 1014 | + _checkfile("certfile", certfile) |
| 1015 | + self.set_option(ldap.OPT_X_TLS_CERTFILE, certfile) |
| 1016 | + |
| 1017 | + if keyfile is not None: |
| 1018 | + if certfile is None: |
| 1019 | + raise ValueError("keyfile option requires certfile option") |
| 1020 | + _checkfile("keyfile", keyfile) |
| 1021 | + self.set_option(ldap.OPT_X_TLS_KEYFILE, keyfile) |
| 1022 | + |
| 1023 | + if crlfile is not None: |
| 1024 | + _checkfile("crlfile", crlfile) |
| 1025 | + self.set_option(ldap.OPT_X_TLS_CRLFILE, crlfile) |
| 1026 | + |
| 1027 | + if crlcheck is not None: |
| 1028 | + # no check for crlfile. OpenSSL supports CRLs in CACERTDIR, too. |
| 1029 | + supported = { |
| 1030 | + ldap.OPT_X_TLS_CRL_NONE, |
| 1031 | + ldap.OPT_X_TLS_CRL_PEER, |
| 1032 | + ldap.OPT_X_TLS_CRL_ALL |
| 1033 | + } |
| 1034 | + if crlcheck not in supported: |
| 1035 | + raise ValueError("Unsupported value for crlcheck") |
| 1036 | + self.set_option(ldap.OPT_X_TLS_CRLCHECK, crlcheck) |
| 1037 | + |
| 1038 | + # materialize settings |
| 1039 | + # 0 means client-side socket |
| 1040 | + try: |
| 1041 | + self.set_option(ldap.OPT_X_TLS_NEWCTX, 0) |
| 1042 | + except ValueError as e: |
| 1043 | + # libldap doesn't return better error message here, global debug log |
| 1044 | + # may contain more information. |
| 1045 | + raise ValueError( |
| 1046 | + "libldap or {} does not support one or more options: {}".format( |
| 1047 | + tls_pkg, e |
| 1048 | + ) |
| 1049 | + ) |
| 1050 | + |
| 1051 | + # Cannot use OPT_X_TLS with OPT_X_TLS_HARD to enforce StartTLS. |
| 1052 | + # libldap ldap_int_open_connection() calls ldap_int_tls_start() when |
| 1053 | + # mode is HARD, but it does not send LDAP_EXOP_START_TLS first. |
| 1054 | + if start_tls and "ldap://" in self._uri: |
| 1055 | + if self.protocol_version != ldap.VERSION3: |
| 1056 | + self.protocol_version = ldap.VERSION3 |
| 1057 | + self.start_tls_s() |
| 1058 | + |
937 | 1059 | def search_subschemasubentry_s(self,dn=None): |
938 | 1060 | """ |
939 | 1061 | Returns the distinguished name of the sub schema sub entry |
|
0 commit comments