C# How to connect to LDAP (AD DC) server using SSL or TLS from Linux Ubuntu 22.04
00:19 15 Sep 2023

I am running a C# .NET 6 App in a Linux Ubuntu 22.04 container. I need the app to connect to an Active Directory Domain Controller in order to authenticate users of the app. I can get non-secure LDAP connections to the DC to work, but I get exceptions for any attempts at SSL or TLS LDAP connections. The exception messages are fairly vague so I am stuck on how to investigate further. I need to use secure connections to LDAP so that the app does not send users' credentials in the clear.

My Linux container is not connected to the Active Directory Domain.

I am using nuget packages System.DirectoryServices Version 7.0.1 and System.DirectoryServices.Protocols Version 7.0.1. According to Pull Request #52904, support for TLS has been added but I'm not clear on what the prerequisites are or on which scenarios are supported.

Should I be using a different AuthType or am I missing a prerequiste package? Any help would be much appreciated.

Testing

I have been testing using the code below. (Where username is a sAMAccountName and userDN is a Distinguished Name). My thinking had been that I was using the wrong connection options so this code cycles through various options.

internal class LdapConnectionTest
    {

        enum EncryptionOption
        {
            None = 0,
            SSL = 1,
            TLS = 2
        }

        public static void StartTest(
            string ldapServer,
            string userName,
            string userDN,
            string password)
        {

            foreach (AuthType authType in new AuthType[] { 
                AuthType.Anonymous, 
                AuthType.Basic, 
                AuthType.Negotiate, 
                AuthType.Ntlm })
            {
                
                foreach (EncryptionOption encryptionOption in new EncryptionOption[] { 
                    EncryptionOption.None, 
                    EncryptionOption.SSL, 
                    EncryptionOption.TLS})
                {

                    foreach (int protocolVersion in new int[] {2,3})
                    {

                        foreach (ReferralChasingOptions referralChasingOption in new ReferralChasingOptions[] {
                            ReferralChasingOptions.None,
                            ReferralChasingOptions.All})
                        {

                            foreach (bool skipVerifyCertificate in new bool[] { false, true })
                            {

                                int port = 389; //TLS is also on port 389

                                if (encryptionOption == EncryptionOption.SSL)
                                {
                                    port = 636;
                                }

                                if (encryptionOption == EncryptionOption.TLS && protocolVersion == 2) continue; //TLS not supported in LDAP v2

                                Console.WriteLine();
                                Console.WriteLine($"##### Attempt with  AuthType: {authType}  Encryption: {encryptionOption}  Port: {port}  Protocol Version: {protocolVersion}  ReferalChasing: {referralChasingOption}  Skip Verify Cert: {skipVerifyCertificate}");

                                try
                                {

                                    LdapDirectoryIdentifier id = new(ldapServer, port);


                                    LdapConnection connection = new(id)
                                    {
                                        AuthType = authType
                                    };


                                    connection.SessionOptions.ReferralChasing = referralChasingOption;

                                    if (encryptionOption == EncryptionOption.SSL)
                                    {
                                        connection.SessionOptions.SecureSocketLayer = true;

                                        if (skipVerifyCertificate)
                                        {
                                            connection.SessionOptions.VerifyServerCertificate = (con, cer) => true; //this is insecure
                                        }
                                    }


                                    connection.SessionOptions.ProtocolVersion = protocolVersion;


                                    NetworkCredential credential = new(authType == AuthType.Basic ? userDN : userName, password);

                                    if (encryptionOption == EncryptionOption.TLS)
                                    {
                                        if (skipVerifyCertificate)
                                        {
                                            connection.SessionOptions.VerifyServerCertificate = (con, cer) => true; //this is insecure
                                        }
                                        connection.SessionOptions.StartTransportLayerSecurity(null);
                                    }

                                    if (authType == AuthType.Anonymous)
                                    {
                                        connection.Bind();
                                    }
                                    else
                                    {
                                        connection.Bind(credential);
                                    }

                                    Console.WriteLine($"Successfull connection  Protocol Version: {connection.SessionOptions.ProtocolVersion}  Secure: {connection.SessionOptions.SecureSocketLayer}");

                                    connection.Dispose();

                                }
                                catch (Exception? ex)
                                {
                                    while (ex != null)
                                    {
                                        Console.WriteLine($"Exception {ex.GetType().ToString()}  {ex.Message}");
                                        Console.WriteLine(ex.StackTrace);

                                        //exit on wrong credential in order to not lock out account
                                        if (ex.Message.Contains("credential", StringComparison.InvariantCultureIgnoreCase)) return;

                                        ex = ex.InnerException;

                                        if (ex != null)
                                        {
                                            Console.WriteLine("Inner Exception:");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }


    }

All the test scenarios work when I run the test code from a Windows workstation. When I try it on Ubuntu 22.04 the insecure connections work but I get the following Exceptions for secure connections (summary of output from the code above). I was expecting at least one of the secure connection options to work.

##### Attempt with  AuthType: Basic  Encryption: None  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Successfull connection  Protocol Version: 3  Secure: False

##### Attempt with  AuthType: Basic  Encryption: SSL  Port: 636  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind(NetworkCredential newCredential)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 110

##### Attempt with  AuthType: Basic  Encryption: TLS  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The connection cannot be established.
   at System.DirectoryServices.Protocols.LdapSessionOptions.StartTransportLayerSecurity(DirectoryControlCollection controls)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 101

##### Attempt with  AuthType: Negotiate  Encryption: None  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The feature is not supported.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind(NetworkCredential newCredential)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 110

##### Attempt with  AuthType: Negotiate  Encryption: SSL  Port: 636  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The feature is not supported.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind(NetworkCredential newCredential)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 110

##### Attempt with  AuthType: Negotiate  Encryption: TLS  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The connection cannot be established.
   at System.DirectoryServices.Protocols.LdapSessionOptions.StartTransportLayerSecurity(DirectoryControlCollection controls)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 101

##### Attempt with  AuthType: Ntlm  Encryption: None  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  An unknown authentication error occurred.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind(NetworkCredential newCredential)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 110

##### Attempt with  AuthType: Ntlm  Encryption: SSL  Port: 636  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  An unknown authentication error occurred.
   at System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential)
   at System.DirectoryServices.Protocols.LdapConnection.Bind(NetworkCredential newCredential)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 110

##### Attempt with  AuthType: Ntlm  Encryption: TLS  Port: 389  Protocol Version: 3  ReferalChasing: None  Skip Verify Cert: False
Exception System.DirectoryServices.Protocols.LdapException  The connection cannot be established.
   at System.DirectoryServices.Protocols.LdapSessionOptions.StartTransportLayerSecurity(DirectoryControlCollection controls)
   at MyTestApp.Ldap.LdapConnectionTest.StartTest(String ldapServer, String userName, String userDN, String password) in /src/MyTestApp/MyTestApp/Ldap/LdapConnectionTest.cs:line 101

Environment

I have installed the AD root certificate using update-ca-certificates. My ldap.conf file points to the crt file where all my CAs are installed.

# cat ldap.conf
#
# LDAP Defaults
#

# See ldap.conf(5) for details
# This file should be world readable but not world writable.

#BASE   dc=example,dc=com
#URI    ldap://ldap.example.com ldap://ldap-provider.example.com:666

#SIZELIMIT      12
#TIMELIMIT      15
#DEREF          never

# TLS certificates (needed for GnuTLS)
TLS_CACERT      /etc/ssl/certs/ca-certificates.crt

Other details of my environment:

# dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.122
 Commit:    dc5a76ad5c

Runtime Environment:
 OS Name:     ubuntu
 OS Version:  22.04
 OS Platform: Linux
 RID:         ubuntu.22.04-x64
 Base Path:   /usr/lib/dotnet/sdk/6.0.122/

global.json file:
  Not found

Host:
  Version:      6.0.22
  Architecture: x64
  Commit:       4bb6dc195c

.NET SDKs installed:
  6.0.122 [/usr/lib/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.22 [/usr/lib/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.22 [/usr/lib/dotnet/shared/Microsoft.NETCore.App]

Download .NET:
  https://aka.ms/dotnet-download

Learn about .NET Runtimes and SDKs:
  https://aka.ms/dotnet/runtimes-sdk-info
#

I have installed the following packages in my container:

FROM ubuntu:22.04

#update software package lists
RUN apt-get update && \
    apt update && \
#install .NET ASP Core runtime
    apt-get install -y aspnetcore-runtime-6.0 && \
#install LDAP library + symlink from old version that .NET wants https://github.com/dotnet/runtime/issues/69456
    apt-get install -y libldap-2.5-0 && \
    ln -s /usr/lib/x86_64-linux-gnu/libldap-2.5.so.0 /usr/lib/x86_64-linux-gnu/libldap-2.4.so.2 && \
#install ca certificates (required for ssl)
    apt-get install -y ca-certificates && \
#tidyup
    apt clean && \
    apt-get clean

I am using the symlink work-around described in #69456 for the hard-coded reference to libldap-2.4.so.2 which is not available in Ubuntu 22.04.

I have successfully run ldapsearch to make an ldaps:// connection to the domain controller from my Linux container.

c# linux ssl ldap