|
4 | 4 | See https://www.python-ldap.org/ for details. |
5 | 5 | """ |
6 | 6 | from os import strerror |
| 7 | +import os.path |
7 | 8 |
|
8 | 9 | from ldap.pkginfo import __version__, __author__, __license__ |
9 | 10 |
|
@@ -697,6 +698,127 @@ def set_option(self,option,invalue): |
697 | 698 | invalue = RequestControlTuples(invalue) |
698 | 699 | return self._ldap_call(self._l.set_option,option,invalue) |
699 | 700 |
|
| 701 | + def set_tls_options(self, cacertfile=None, cacertdir=None, |
| 702 | + require_cert=None, protocol_min=None, |
| 703 | + cipher_suite=None, certfile=None, keyfile=None, |
| 704 | + crlfile=None, crlcheck=None, start_tls=True): |
| 705 | + """Set TLS/SSL options |
| 706 | +
|
| 707 | + :param cacertfile: path to a PEM bundle file containing root CA certs |
| 708 | + :param cacertdir: path to a directory with hashed CA certificates |
| 709 | + :param require_cert: cert validation strategy, one of |
| 710 | + ldap.OPT_X_TLS_NEVER, OPT_X_TLS_DEMAND, OPT_X_TLS_HARD. Hard and |
| 711 | + demand have the same meaning for client side sockets. |
| 712 | + :param protocol_min: minimum protocol version, one of 0x303 (TLS 1.2) |
| 713 | + or 0x304 (TLS 1.3). |
| 714 | + :param cipher_suite: cipher suite string |
| 715 | + :param certfile: path to cert file for client cert authentication |
| 716 | + :param keyfile: path to key file for client cert authentication |
| 717 | + :param crlfile: path to a CRL file |
| 718 | + :param crlcheck: CRL verification strategy, one of |
| 719 | + ldap.OPT_X_TLS_CRL_NONE, ldap.OPT_X_TLS_CRL_PEER, or |
| 720 | + ldap.OPT_X_TLS_CRL_ALL |
| 721 | + :param start_tls: automatically perform StartTLS for ldap:// connections |
| 722 | + """ |
| 723 | + if not hasattr(ldap, "OPT_X_TLS_NEWCTX"): |
| 724 | + raise ValueError("libldap does not have TLS support") |
| 725 | + # OpenSSL and GnuTLS support these options |
| 726 | + tls_pkg = self.get_option(ldap.OPT_X_TLS_PACKAGE) |
| 727 | + if tls_pkg not in {"OpenSSL", "GnuTLS"}: |
| 728 | + raise ValueError("Unsupport TLS package '{}'.".format(tls_pkg)) |
| 729 | + # block ldapi ('in' because libldap supports multiple URIs) |
| 730 | + if "ldapi://" in self._uri: |
| 731 | + raise ValueError("IPC (ldapi) does not support TLS.") |
| 732 | + if self._ldap_call(self._l.tls_inplace): |
| 733 | + raise ValueError("TLS connection already established") |
| 734 | + |
| 735 | + def _checkfile(option, filename): |
| 736 | + # check that the file exists and is readable. |
| 737 | + # libldap doesn't verify paths until it establishes a connection |
| 738 | + with open(filename, "rb"): |
| 739 | + pass |
| 740 | + |
| 741 | + if cacertfile is not None: |
| 742 | + _checkfile("certfile", certfile) |
| 743 | + self.set_option(ldap.OPT_X_TLS_CACERTFILE, cacertfile) |
| 744 | + |
| 745 | + if cacertdir is not None: |
| 746 | + if not os.path.isdir(cacertdir): |
| 747 | + raise OSError( |
| 748 | + "'{}' does not exist or is not a directory".format(cacertdir) |
| 749 | + ) |
| 750 | + self.set_option(ldap.OPT_X_TLS_CACERTDIR, cacertdir) |
| 751 | + |
| 752 | + if require_cert is not None: |
| 753 | + supported = { |
| 754 | + ldap.OPT_X_TLS_NEVER, |
| 755 | + # ALLOW is a server-side setting |
| 756 | + # ldap.OPT_X_TLS_ALLOW, |
| 757 | + ldap.OPT_X_TLS_DEMAND, |
| 758 | + ldap.OPT_X_TLS_HARD |
| 759 | + } |
| 760 | + if require_cert not in supported: |
| 761 | + raise ValueError("Unsupported value for require_cert") |
| 762 | + self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, require_cert) |
| 763 | + |
| 764 | + if protocol_min is not None: |
| 765 | + # let's not support TLS 1.0 and 1.1 |
| 766 | + supported = {0x303, 0x304} |
| 767 | + if protocol_min not in supported: |
| 768 | + raise ValueError("Unsupported value for protocol_min") |
| 769 | + self.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, protocol_min) |
| 770 | + |
| 771 | + if cipher_suite is not None: |
| 772 | + self.set_option(ldap.OPT_X_TLS_CIPHER_SUITE, cipher_suite) |
| 773 | + |
| 774 | + if certfile is not None: |
| 775 | + if keyfile is None: |
| 776 | + raise ValueError("certfile option requires keyfile option") |
| 777 | + _checkfile("certfile", certfile) |
| 778 | + self.set_option(ldap.OPT_X_TLS_CERTFILE, certfile) |
| 779 | + |
| 780 | + if keyfile is not None: |
| 781 | + if certfile is None: |
| 782 | + raise ValueError("keyfile option requires certfile option") |
| 783 | + _checkfile("keyfile", keyfile) |
| 784 | + self.set_option(ldap.OPT_X_TLS_KEYFILE, keyfile) |
| 785 | + |
| 786 | + if crlfile is not None: |
| 787 | + _checkfile("crlfile", crlfile) |
| 788 | + self.set_option(ldap.OPT_X_TLS_CRLFILE, crlfile) |
| 789 | + |
| 790 | + if crlcheck is not None: |
| 791 | + # no check for crlfile. OpenSSL supports CRLs in CACERTDIR, too. |
| 792 | + supported = { |
| 793 | + ldap.OPT_X_TLS_CRL_NONE, |
| 794 | + ldap.OPT_X_TLS_CRL_PEER, |
| 795 | + ldap.OPT_X_TLS_CRL_ALL |
| 796 | + } |
| 797 | + if crlcheck not in supported: |
| 798 | + raise ValueError("Unsupported value for crlcheck") |
| 799 | + self.set_option(ldap.OPT_X_TLS_CRLCHECK, crlcheck) |
| 800 | + |
| 801 | + # materialize settings |
| 802 | + # 0 means client-side socket |
| 803 | + try: |
| 804 | + self.set_option(ldap.OPT_X_TLS_NEWCTX, 0) |
| 805 | + except ValueError as e: |
| 806 | + # libldap doesn't return better error message here, global debug log |
| 807 | + # may contain more information. |
| 808 | + raise ValueError( |
| 809 | + "libldap or {} does not support one or more options: {}".format( |
| 810 | + tls_pkg, e |
| 811 | + ) |
| 812 | + ) |
| 813 | + |
| 814 | + # Cannot use OPT_X_TLS with OPT_X_TLS_HARD to enforce StartTLS. |
| 815 | + # libldap ldap_int_open_connection() calls ldap_int_tls_start() when |
| 816 | + # mode is HARD, but it does not send LDAP_EXOP_START_TLS first. |
| 817 | + if start_tls and "ldap://" in self._uri: |
| 818 | + if self.protocol_version != ldap.VERSION3: |
| 819 | + self.protocol_version = ldap.VERSION3 |
| 820 | + self.start_tls_s() |
| 821 | + |
700 | 822 | def search_subschemasubentry_s(self,dn=None): |
701 | 823 | """ |
702 | 824 | Returns the distinguished name of the sub schema sub entry |
|
0 commit comments