3

We have an application that authenticates against a remote AD using LDAP, by IP address, over a VPN tunnel, using the following code:

using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, ldap.Host, ldap.Path.Replace("/", ""), ContextOptions.Negotiate, UserName, Password))
{
    using (UserPrincipal user = UserPrincipal.FindByIdentity(pc, domainAndUsername))
    {
        if (user != null)
        {
            SAMAccountName = user.SamAccountName;
            retVal = true;
        }
    }
}

This works great for normal, non-SSL LDAP. However, we've encountered a situation where we need to be connecting to LDAPS, via SSL, and it doesn't work. I've tried a ton of variations on the PrincipalContext constructor, but everything we do results in a failed connection, with this error:

System.DirectoryServices.AccountManagement.PrincipalServerDownException: The server could not be contacted. ---> System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapConnection.Connect()
   at System.DirectoryServices.Protocols.LdapConnection.SendRequestHelper(DirectoryRequest request, Int32& messageID)
   at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
   at System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
   --- End of inner exception stack trace ---
   at System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
   at System.DirectoryServices.AccountManagement.PrincipalContext.DoServerVerifyAndPropRetrieval()
   at System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name, String container, ContextOptions options, String userName, String password)

We know it's not the LDAP server itself, as trying by the method discussed here connects without errors. I'm not really comfortable with using try...catch for logic flow, and I've read of some other issues with this method (like not properly respecting certificates and such), so I'm trying to make this work with PrincipalContext.

Could anybody offer me some guidance here? I'm going crazy on this one.

EDIT: Upon some further research, it looks like this may be a problem with self-signed certificates.

EDIT 2: After breaking it down into X509 chain calls, I'm getting this specific error:

PartialChain A certificate chain could not be built to a trusted root authority.

Looking at that, you'd think it's just a matter of adding the CA as a trusted root, but that didn't seem to fix the problem.

BRW
  • 354
  • 3
  • 18
  • Are you using port 636? – scotru Oct 20 '17 at 06:07
  • Does this question help? https://stackoverflow.com/questions/19596010/ldapconnection-vs-principalcontext – scotru Oct 20 '17 at 06:11
  • @scotru Yes, we are using port 636. The whole problem here is that the certificate validation always fails. – BRW Oct 20 '17 at 18:38
  • Is the cert installed on the computer you are testing from if self-signed? I know with LdapConnection you can implement your own certificate validation. Not sure if something like that is possible with PrincipalContext. Maybe try LdapConnection? – scotru Oct 20 '17 at 19:23
  • @scotru Yeah, we installed the cert locally. I ended up breaking it down into a bunch of X509 chain calls and found specific errors. See edit for details. – BRW Oct 23 '17 at 13:35
  • Is this application ASP.NET or WinForms/WPF? – scotru Oct 24 '17 at 03:17

2 Answers2

4

I'm wondering if this CodeProject article may be of assistance:

"Recently, I ran into trouble using System.DirectoryServices.DirectoryEntry to connect to a Novell eDirectory server because the certificate was self-signed. When run in an ASP.NET application, the machine-level certificate store was not examined. So, even though the self-signed certificate was in the trusted store, DirectoryEntry was still refusing to establish a connection.

The LdapConnection class is a better choice for this situation, as it allows the user to validate the certificate manually. Note that the DirectoryEntry approach works fine with a trusted self-signed certificate when run in a Windows Forms application."

With LdapConnection, you could use your own certificate authentication as shown here:

LdapConnection con = new LdapConnection(new LdapDirectoryIdentifier("EDIRECTORYSERVER:636"));
con.SessionOptions.SecureSocketLayer = true;
con.SessionOptions.VerifyServerCertificate = 
   new VerifyServerCertificateCallback(ServerCallback);
con.Credential = new NetworkCredential(String.Empty, String.Empty);
con.AuthType = AuthType.Basic;

And a verification callback like this:

public static bool ServerCallback(LdapConnection connection, X509Certificate certificate)
{
  try
  {
    X509Certificate expectedCert = 
         X509Certificate.CreateFromCertFile("C:\\certificates\\certificate.cer");

    if (expectedCert.Equals(certificate))
    {
        return true;
    }
    else
    {
        return false;
    }
  }
  catch (Exception ex)
  {
      return false;
  }
}
scotru
  • 2,563
  • 20
  • 37
  • This seems like exactly my problem. A little annoying that I can't solve this using PrincipalContext (largely because we also need to be searching for the user's groups and other things, and it's much cleaner with PrincipalContext). This can't be customer specific, so i suppose I'll need to add config options for this. – BRW Oct 24 '17 at 13:55
  • Thanks for accepting this. One more question: Is the certificate you are using signed by another cert, and if so is the signing cert installed in the trusted certs (and so on and so forth, all the way up the chain)? – scotru Oct 25 '17 at 07:24
  • Possibly useful: https://stackoverflow.com/questions/43897497/the-ldap-server-is-unavailable-using-principalcontext-and-adlds – scotru Oct 25 '17 at 07:30
0

Check this answer by Gabriel Luci

Are you sure it supports SSL and that the firewall is open to allow that connection?

LDAP uses port 389. LDAPS uses port 636.

If you have the telnet client installed, you can use it to check the connectivity:

telnet yourdomain.com 636 If you get a blank screen, it worked. If it can't connect, it will tell you.

If that is open and it still does not work, it could be using a self-signed SSL certificate. Check the Windows event log for certificate-related errors.

I've also used Chrome to check the certificate. You have to run chrome like this:

chrome.exe --explicitly-allowed-ports=636 Then browse to https://yourdomain.com:636 and see if it gives you any certificate errors. Then you can actually see the certificate. If that's the problem, you may be able to import the certificate and explicitly trust it.

Hussein Khalil
  • 1,395
  • 11
  • 29