7

I have a rather simple LDAP client that works ok when connecting to the 389 port (LDAP) but fails with a "LDAP server unavailable" when I try to connect to the 636 port (LDAPS).

namespace MyNS
{
  class ProgramLdap
  {
    private static LdapConnection CreateConnection(String baseDn, string usuario, string password)
    {
      LdapConnection ldapConnection = new LdapConnection(
        new LdapDirectoryIdentifier("myserver.example", 636, true, false));
      ldapConnection.SessionOptions.SecureSocketLayer = true;
      ldapConnection.SessionOptions.ProtocolVersion = 3;
      // [CODE MODIFICATION HERE]
      ldapConnection.Credential = new NetworkCredential(usuario, password);
      ldapConnection.AuthType = AuthType.Basic;
      ldapConnection.Timeout = new TimeSpan(1, 0, 0);
      return ldapConnection;
    }

    static void Main(string[] args)
    {

      LdapConnection ldapConnection = CreateConnection("", "myLdapUser", "noneOfYourBusiness");
      SearchRequest searchRequest = new SearchRequest(
        "ou=usuarios,dc=Dexter,dc=local",
         String.Format("(&(objectclass=person)(cn={0}))", user),
         SearchScope.Subtree,
         new string[0]);
       SearchResponse searchResponse =
          (SearchResponse) ldapConnection.SendRequest(searchRequest);
      System.Diagnostics.Debug.WriteLine("Resultados " + searchResponse.Entries.Count);
    }
  }
}

If I add the following at [CODE MODIFICATION HERE] to accept all server certificates, it works:

ldapConnection.SessionOptions.VerifyServerCertificate =
   new VerifyServerCertificateCallback((conn, certificate) => true);

The certificate is signed by a self-signed CA, I have added the CA public certificate to the Local Computer list of "Trusted Root Certification Authorities"1.

If I check the server certificate with openSSL using that CA's certificate, it validates it. Also, I have tried LdapAdmin and when the CA is in that list, no warning is shown when connecting to the LDAP server.

If I use the VerifyServerCertificateCallback to print the contents of the certificate:

ldapConnection.SessionOptions.VerifyServerCertificate =
  new VerifyServerCertificateCallback(
    (conn, certificate) => {
       X509Certificate2 certificate2 = new X509Certificate2(certificate);
       bool verify = certificate2.Verify();
       System.Diagnostics.Debug.WriteLine(
         String.Format(
           "certificate2.Verify {0}; Name {1}; NameOID {2}; FriendlyName {3}; Thumbprint {4}; Algorithm FriendlyName {5}",
           verify,
           certificate2.SubjectName.Name,
           certificate2.SubjectName.Oid,
           certificate2.FriendlyName,
           certificate2.Thumbprint,
           certificate2.SignatureAlgorithm.FriendlyName));

           foreach (X509Extension extension in certificate2.Extensions) 
           {
             System.Diagnostics.Debug.WriteLine(extension.ToString() + " " + extension.Oid.FriendlyName + " " + Encoding.UTF8.GetString(extension.RawData));
            }
            return verify;
         });

it shows me the thumbprint of the server certificate but yet verify fails.

What can I be? It seems that I am missing something very basic, but I cannot understand what.


UPDATE:

I checked @FrankNielsen's suggestion and I added this code in the VerifyServerCertificateCallback:

(conn, certificate) => {
  X509Chain ch = new X509Chain();
  ch.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
  ch.Build(new X509Certificate2(certificate));
  System.Diagnostics.Debug.WriteLine("Chain Information");
  System.Diagnostics.Debug.WriteLine(String.Format("Chain revocation flag: {0}", ch.ChainPolicy.RevocationFlag));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain revocation mode: {0}", ch.ChainPolicy.RevocationMode));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain verification flag: {0}", ch.ChainPolicy.VerificationFlags));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain verification time: {0}", ch.ChainPolicy.VerificationTime));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain status length: {0}", ch.ChainStatus.Length));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain application policy count: {0}", ch.ChainPolicy.ApplicationPolicy.Count));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain certificate policy count: {0} {1}", ch.ChainPolicy.CertificatePolicy.Count, Environment.NewLine));

  System.Diagnostics.Debug.WriteLine("Chain Element Information");
  System.Diagnostics.Debug.WriteLine(String.Format("Number of chain elements: {0}", ch.ChainElements.Count));
  System.Diagnostics.Debug.WriteLine(String.Format("Chain elements synchronized? {0} {1}", ch.ChainElements.IsSynchronized, Environment.NewLine));
  foreach (X509ChainElement element in ch.ChainElements)
  {
    System.Diagnostics.Debug.WriteLine(String.Format("Element issuer name: {0}", element.Certificate.Issuer));
    System.Diagnostics.Debug.WriteLine(String.Format("Element certificate valid from: {0}", element.Certificate.NotBefore));
    System.Diagnostics.Debug.WriteLine(String.Format("Element certificate valid until: {0}", element.Certificate.NotAfter));
    System.Diagnostics.Debug.WriteLine(String.Format("Element certificate is valid: {0}", element.Certificate.Verify()));
    System.Diagnostics.Debug.WriteLine(String.Format("Element error status length: {0}", element.ChainElementStatus.Length));
    System.Diagnostics.Debug.WriteLine(String.Format("Element information: {0}", element.Information));
    System.Diagnostics.Debug.WriteLine(String.Format("Thumbprint: {0}", element.Certificate.Thumbprint));
    System.Diagnostics.Debug.WriteLine(String.Format("Number of element extensions: {0}{1}", element.Certificate.Extensions.Count, Environment.NewLine));
    if (ch.ChainStatus.Length > 1)
    {
      for (int index = 0; index < element.ChainElementStatus.Length; index++)
      {
         System.Diagnostics.Debug.WriteLine(element.ChainElementStatus[index].Status);
        System.Diagnostics.Debug.WriteLine(element.ChainElementStatus[index].StatusInformation);
       }
     }
   }
   return true;
 });

And it returns:

Chain Information
Chain revocation flag: ExcludeRoot Chain revocation mode: NoCheck
Chain verification flag: NoFlag
Chain verification time: 07/10/2019 15:53:00
Chain status length: 0
Chain application policy count: 0
Chain certificate policy count: 0

Chain Element Information
Number of chain elements: 2
Chain elements synchronized? False

Element issuer name: CN=dexter-SCPDPRDEXTER01V-CA, DC=dexter, DC=local
Element certificate valid from: 02/09/2019 12:24:22
Element certificate valid until: 01/09/2020 12:24:22
Element certificate is valid: False
Element error status length: 0
Element information:
Thumbprint: 63DCF4EFE0C96EF021BCC9CE662E2627A3CDF399
Number of element extensions: 9

Element issuer name: CN=dexter-SCPDPRDEXTER01V-CA, DC=dexter, DC=local
Element certificate valid from: 11/06/2019 7:39:01
Element certificate valid until: 11/06/2069 7:49:01
Element certificate is valid: True
Element error status length: 0 Element information:
Thumbprint: 7BD9C718E336A50FA006CAEF539895C7E3EA5DA0
Number of element extensions: 4

The certificates match what would be expected (the CA is retrieved), the CA return true to Verify() but the server certificate returns false to Verify().


1And for good measure, I also did try adding it to "Intermediate Certification Authorities" to no avail.

SJuan76
  • 24,532
  • 6
  • 47
  • 87
  • 2
    Stabbing at it but.... using System.Net; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; – Stephen McDowell Oct 03 '19 at 01:41
  • 1
    @StephenMcDowell thank you for your suggestion, but it did not work (as a matter of fact I am on .Net 4.5 and only *Ssl3* and *Tls* are available; I tried both of them and both of them failed). In any case it does not seem an issue with communication but with the validation of certificates, as when I just bypass certificate validation (by having the *VerifyServerCertificate* always return true) the communication is established correctly. – SJuan76 Oct 03 '19 at 11:48
  • 1
    Do you have configured SSL on your AD-Servers? You have to import the certificate into the **AD DS personal store**: https://www.active-directory-faq.de/2012/08/ldap-over-ssl-oder-sercure-ldap-ldaps-mit-server-2008r2/ – Andreas Schmidt Oct 03 '19 at 21:20
  • 2
    Hve you tried to get a more detailed error through the `X509Chain` - see here (under remarks): https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509chain?view=netframework-4.8 – Frank Nielsen Oct 06 '19 at 18:29
  • @FrankNielsen thanks for your help; I got a little more information (it seems that the CA is being retrieved from the store) but still I do not know why the server certificate is rejected. I have updated the question, though. – SJuan76 Oct 07 '19 at 14:06
  • Reading this: https://security.stackexchange.com/questions/43172/evaluate-the-signature-of-an-x509-certificate-in-net suggest skipping the `Verify` and do your own verify through `X509Chain`. – Frank Nielsen Oct 07 '19 at 20:35
  • 3
    @AndreasSchmidt is correct. If you're running on a Windows domain, the domain server will override local certificates. You need to have your certificate as trusted on the domain controller. – Brannon Oct 08 '19 at 15:39
  • @AndreasSchmidt sorry I skipped replying you, I think you are talking about the configuration of the LDAP server? If that is the case I consider that the server configuration is ok; when I connect through other means (OpenSsl, LdapAdmin client program) it works as expected. – SJuan76 Oct 08 '19 at 16:34
  • I'm wondering if you based your certificate on the right template, having a wrong template would validate the certificate but fail the use case. – Walter Verhoeven Oct 08 '19 at 16:42
  • As @Branonon wrote and I suggest, check the AD DS personal store on each DC... I‘m pretty sure there is some misconfiguration. – Andreas Schmidt Oct 08 '19 at 17:12
  • @SJuan76 It makes no difference if you add the certificate to trusted vertificates through certificate manager. It must be added into the AD DS personal store. – Andreas Schmidt Oct 08 '19 at 17:19
  • @SJuan76, did you create this certificate based on the CA PFX certificate? I mean, they need to be "chained" during the creation of the client certificate (the Self-Signed one). – David BS Oct 08 '19 at 17:23
  • Does the hostname `myserver.example` matches the certificates CN? And is `fullyQualifiedDnsHostName` set to `true` intended? – kapsiR Oct 09 '19 at 11:21
  • @SJuan76 Is it solved? Please update. – Andreas Schmidt Oct 09 '19 at 17:27
  • @AndreasSchmidt Still not result; I was looking for an issue with my code but if it turns out into certificates/server configuration then I am out of my turf and I need to pass this to the systems guys. – SJuan76 Oct 10 '19 at 08:59
  • @AndreasSchmidt I finally did solve it; I have added an answer. Thank you for your time. – SJuan76 Oct 18 '19 at 10:18

2 Answers2

1

Finally the way to properly debug it was inspecting the elements of ChainStatus, as stated in X509Certificate2.Verify() returns false always

That way I did found that my program could not connect to the Certificate Revocation List URL.

The solution (other than opening access to the URL) is to validate the certificate in the callback using the X509Chain class and setting it not to check CRLs:

ldapConnection.SessionOptions.VerifyServerCertificate =
    new VerifyServerCertificateCallback(
        (conn, certificate) =>
            {
                X509Chain x509Chain = new X509Chain();
                x509Chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                X509Certificate2 cert2 = new X509Certificate2(certificate);
                bool buildResult = x509Chain.Build(cert2);
                if (!buildResult)
                {
                    foreach (X509ChainStatus chainStatus in x509Chain.ChainStatus)
                    {
                        System.Diagnostics.Debug.WriteLine(
                            String.Format(
                                "Chain Status {0} : {1}", chainStatus.Status, chainStatus.StatusInformation));
                    }
                }
                return buildResult;
            });
SJuan76
  • 24,532
  • 6
  • 47
  • 87
0

Please complete this stemps on each Domain Controller to enabel SSL on LDAP.

  1. Go to each Domain Controller, open MMC
  2. Click File, Add Snap-In
  3. Select Certificate
  4. Select Service Account and click Next
  5. Select Local Computer and click Next
  6. Select Active Directory Services and click Finish to complete the dialog
  7. Click on Ok to add the Snap-In to the MMC

In the Certificate Snap-In now add your Certificate to NTDS/Personal and if needed add the CA-Certificate into the NTDS/Trusted Root Certificate Authorities

I'm from german system so I'm sorry if the technical Names mismatches with yours.

Source: https://www.active-directory-faq.de/2012/08/ldap-over-ssl-oder-sercure-ldap-ldaps-mit-server-2008r2/