locked
help with LDAP SASL RRS feed

  • Question

  • User271633065 posted

    I am a newbie with enterprise directories. I am trying to write an ASP.NET application to fetch some data from my university LDAP enterprise directory. There are 2 types of access allowed to the LDAP server. One is a anonymous access and another is the access that exists mainly to give privileged accounts access to person information that can otherwise not be publicly viewed. These privileged accounts, called Y Services, are primarily used to look up person data and authorize people on this data.

    Now, i was able to use the anonymous access priviliges and view the data from LDAP server. What i want to do is to use the Y services and view the person information that cannot be accessed via the anonymous access. For example i want to view the date of birth for the person which is available in the Y Services access.

    The university instructions say the following:

    What you see in Y Services is dependent on how you bind (anonymous, simple, SASL EXTERNAL) and the amount of privileges the bound user has. Connecting to Y Services requires the use of <ACRONYM title="Transport Layer Security">TLS</ACRONYM> client certificate authentication, meaning you must have a signed certificate from the uiniversity in order to connect. Users bound anonymously can only search on ID and can only see the DN (distinguished name) of any user. Users that have performed a SASL EXTERNAL bind can only see those attributes they have been approved to see (for all users), and only if the corresponding service is ACTIVE.

    Now, i know that the TLS client certificate has been installed on my server by my Sys admin. Please tell me the steps to do the bind and fetch the date of birth for all people in department X.

    Here is the anonymous bind code.

    Dim deLdapConn As DirectoryEntry = New DirectoryEntry("LDAP://directory.a.edu/dc=a,dc=edu")

    Dim searcherLdap As New DirectorySearcher(deLdapConn)

    Dim Results As SearchResultCollection

    Dim propcoll As ResultPropertyCollection

    Dim Result As SearchResult

    Dim strKey As String

    Dim obProp As Object

    iNumProperties = 0

     

    Try

    searcherLdap.Filter = "(department=X)"

    searcherLdap.PropertiesToLoad.Add("sn")

    searcherLdap.PropertiesToLoad.Add("givenname")

    searcherLdap.PropertiesToLoad.Add("telephonenumber")

    searcherLdap.PropertiesToLoad.Add("uupid")

    Results = searcherLdap.FindAll

    iNumProperties = Results.Count()

    ReDim arrFName(iNumProperties - 1)

    ReDim arrLName(iNumProperties - 1)

    ReDim arrPhone(iNumProperties - 1)

    ReDim arrEmail(iNumProperties - 1)

    ReDim arrDob(iNumProperties - 1)

    iNumProperties = 0 ' Sets the start index for arrays

    For Each Result In Results ' Starts the loop where result stores 1 record and resultS stores all records

    propcoll = Result.Properties ' Gets the all the properties (fieldnames) for that record

    For Each strKey In propcoll.PropertyNames ' Loop through each field name for the selected record

    iOnce = 0

    For Each obProp In propcoll(strKey)

    If strKey = "givenname" Then

    arrFName(iNumProperties) = obProp

    End If

    If strKey = "sn" Then

    arrLName(iNumProperties) = obProp

    End If

    If strKey = "telephonenumber" Then

    arrPhone(iNumProperties) = obProp

    End If

    If strKey = "uupid" Then

    arrEmail(iNumProperties) = obProp

    End If

    Next

    Next

    iNumProperties = iNumProperties + 1

    Next

    searcherLdap.Dispose()

    searcherLdap = Nothing

    deLdapConn.Close()

    deLdapConn = Nothing

    Catch Ex As Exception

    Response.Write(Ex.ToString)

    End Try

     

    Please help me!! THANKS IN ADVANCE!!

     

     

    Wednesday, December 28, 2005 11:40 AM

All replies

  • User1354132231 posted
    Yes, certificate binds are supported via the AuthenticationTypes.SecureSocketLayer option on your DirectoryEntry.  Assuming you have the certificate correctly setup for the user, it will just work.  I will check on a couple things with a guy I know that does this regularly and see what caveats, if any, to expect.  I will post back here and let you know what I find.

    Thursday, January 5, 2006 12:39 PM
  • User271633065 posted

    Thanks Ryan. Appreciate your help. Could you please also find out if ASP.NET supports SASL External Binds? But please do post whatever your pal has to say.

    Thanks

    Thursday, January 5, 2006 3:26 PM
  • User1354132231 posted
    I hope I am not mangling terminology here, but SASL External bind in your case means certificate bind as opposed to SSPNEGO.  I know that using AuthenticationTypes.SecureSocketsLayer it is supported with AD and ADAM.  I have not tried with 3rd party, but I would assume it would work fine.

    If it is not working for you using this flag, then you should turn up the Schannel logging (according to my friend) which will help you determine why the certificate bind is failing.

    Have you tried using SecureSocketsLayer option?
    Friday, January 6, 2006 2:33 PM
  • User271633065 posted

    Thanks again.

    Yes i have tried using the securesockets layer option. But can you please show me an example code snippet in VB.NET that binds a particular client certificate (or tells the application to use a particular client certificate) while connecting to the campus LDAP server.?

    Thanks

    Tuesday, January 10, 2006 9:41 AM
  • User1354132231 posted
    A few questions for you:

    Are you using a single certificate for the university server for each account?  Or do you have a unique certificate per user?  Are you using this in an ASP.NET app (I have to ask) or a desktop app?

    If you have the certificate (public key side) of a single university certficate, then you need to use the Certificate Wizard in the Cert MMC to install it in the machine cert store for it to be used in ASP.NET.  Otherwise the certificate will not be found when using ASP.NET.  This certificate should also be set to be trusted if it does not come from Verisign or other CA.  The negotiation of SSL is automatic when using this authtype.  There is no 'selection' per se.  If you turn on SChannel logging you might get more information why your secure SSL bind is failing.  What error are you getting now?  What VB.NET code snippet are you attempting to use now with SSL?
    Wednesday, January 11, 2006 9:28 AM
  • User271633065 posted

    Thanks dunnry.

    Are you using a single certificate for the university server for each account? Or do you have a unique certificate per user?

    - THE UNIVERSITY MIDDLEWARE GROUP PROVIDED US WITH A CLIENT CERTIFICATE TO USE ON OUR WEBSERVER WHILE CONNECTING TO THE LDAP. THEY SAID THAT THIS CERTIFICATE IS FOR THE SERVICE ASSIGNED TO MY DEPARTMENT ON LDAP SERVER. SO I BELIEVE IT IS NOT ACCOUNT SPECIFIC RATHER MACHINE SPECIFIC CERTIFICATE THAT IDENTIFIES THAT THE AUTHORIZED CLIENT (IN THIS CASE REQUEST SENT OUT BY WEBSERVER) IS TRYING TO ACCESS THE SERVICE ON LDAP.

    Are you using this in an ASP.NET app (I have to ask) or a desktop app?
    - I AM USING ASP.NET APPLICATION.

    If you have the certificate (public key side) of a single university certficate, then you need to use the Certificate Wizard in the Cert MMC to install it in the machine cert store for it to be used in ASP.NET.  Otherwise the certificate will not be found when using ASP.NET.  This certificate should also be set to be trusted if it does not come from Verisign or other CA.

    - THE CERTIFICATE IS IN THE MACHINE STORE ALONG WITH THE PRIVATE KEY.AND I AM PRETTY SURE IT IS TRUSTED SINCE WE HAVE A UNIVERSITY CERTIFICATE CHAIN IN THE TRUSTED LIST.

    If you turn on SChannel logging you might get more information why your secure SSL bind is failing. 

    -I WILL TRY THIS

    What error are you getting now? 

    -THE FOLLOWING IS THE ERROR THAT I AM GETTING.

    System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) at System.DirectoryServices.DirectoryEntry.Bind() at System.DirectoryServices.DirectoryEntry.get_AdsObject() at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne) at System.DirectoryServices.DirectorySearcher.FindAll() at rabiespersonnel.WebForm1.btnReset_Click(Object sender, EventArgs e) in \\serve\appname\WebForm1.aspx.vb:line 206

    THE CODE FAILS AT THE FINDALL() METHOD.

    What VB.NET code snippet are you attempting to use now with SSL?

    - THE FOLLOWING IS MY VB.NET CODE

    Dim deLdapConn As DirectoryEntry = New DirectoryEntry(LDAP://id.x.y.edu/ou=people,dc=y,dc=edu)

    Dim searcherLdap As New DirectorySearcher(deLdapConn)

    Dim Results As SearchResultCollection

    Dim propcoll As ResultPropertyCollection

    Dim Result As SearchResult

    Dim strKey As String

    Dim obProp As Object

    iNumProperties = 0

     

    Try

    deLdapConn.AuthenticationType = AuthenticationTypes.SecureSocketsLayer

    searcherLdap.Filter = "(department=Personnel)"

    searcherLdap.PropertiesToLoad.Add("givenName")

    searcherLdap.PropertiesToLoad.Add("sn")

    searcherLdap.PropertiesToLoad.Add("uupid")

    Results = searcherLdap.FindAll

    iNumProperties = Results.Count()

    ReDim arrFName(iNumProperties - 1)

    ReDim arrLName(iNumProperties - 1)

    ReDim arrPhone(iNumProperties - 1)

    iNumProperties = 0 ' Sets the start index for arrays

    For Each Result In Results ' Starts the loop where result stores 1 record and resultS stores all records

    propcoll = Result.Properties ' Gets all the properties (fieldnames) for that record

    For Each strKey In propcoll.PropertyNames ' Loop through each field name for the selected record

    For Each obProp In propcoll(strKey)

    If strKey = "givenName" Then ' If the fieldname is firstname then

    arrFName(iNumProperties) = obProp

    End If

    If strKey = "sn" Then

    arrLName(iNumProperties) = obProp

    End If

    If strKey = "uupid" Then

    arrPhone(iNumProperties) = obProp

    End If

    Next

    Next

    iNumProperties = iNumProperties + 1

    Next

    searcherLdap.Dispose()

    searcherLdap = Nothing

    deLdapConn.Close()

    deLdapConn = Nothing

    Catch Ex As Exception

    Response.Write(Ex.ToString)

    End Try

     

    Thanks again Dunnry. I really appreciate your help.

    <META content="Microsoft Visual Studio .NET 7.1" name=GENERATOR> <META content="Visual Basic .NET 7.1" name=CODE_LANGUAGE> <META content=JavaScript name=vs_defaultClientScript> <META content=http://schemas.microsoft.com/intellisense/ie5 name=vs_targetSchema>
    Wednesday, January 11, 2006 3:10 PM
  • User271633065 posted

    Dunnry

    I turned on the SChannel logging. Looks like when i try to access the aspx website in my browser the server certificate prompt asks me whether i trust the certificate, i say yes then it shows me the webpage. At the end of this process i see 2 events logged in by SChannel. One is the server handshake and one is the client handshake.

    Now, when i click a button on the webpage to connect to the LDAP server, it just returns an error on the webpage saying "The server is not operational"

    I do not see any event logging by SChannel in the event viewer. I strongly suspect that when my application tries to connect to the LDAP server the Ldap server somehow does not look for the client certificate installed on my webserver for the handshake. I checked and saw that the client certificate was installed in the Machine store along with the private key. I also made sure that the application pool that ASPNET uses has access to this client certificate.

    Is there some way that you can specify in the code to use a particular client certificate while connecting to the LDAP server?

    PLEASE HELP!!

    Thanks in advance.

    Friday, January 13, 2006 11:41 AM
  • User1354132231 posted

    I don't know of any way to specify a particular cert for use with LDAP.  Since the certificate is on the server and you are only trusting it, it really does not make sense to select one either.  Either you have a trusted certificate installed on your side in negotiation with the server, or you do not.

    I have to admit that I have not ever tried doing this with a 3rd party LDAP store.  You might have more success with System.DirectoryServices.Protocols here since it will give you more control over the process.

    There are a few things you can try to eliminate the issues.  First, create a console app to get ASP.NET out of the picture.  Next, make sure you have the Schannel logging in debug mode.  It sounds like you have some configuration problems on the client with the certificate if it is prompting you to trust it.  The schannel logging is going to be the most important thing.  Try posting the log file here when you get it into debug mode.

    Finally, after you create the console app, just bind directly to an object and do not perform a search with the DirectorySearcher.  The searcher can complicate things and we just want to see if you can even get a good bind.  Simply bind directly to the object you want and try to read an attribute.

    See where this leads you for now.

    Monday, January 16, 2006 3:54 PM
  • User271633065 posted

    Thank you again Dunnry.

    My certificate is in the Local Machine Store of the webserver along with the private key. Plus the account under which asp.net runs has access to this certificate.

    I am still getting the following error "The server is not operational"

    I also tried another thing. I used the ldp.exe application which comes along with the windows support tools and tried to connect to the ldap server. But it fails saying that the server is down.

    My university has a OpenLDAP instance running on a Debian Linux (if this helps in anyway) and i am trying to connect to it using my windows 2003 server (through asp.net appl).

    You commented - "You might have more success with System.DirectoryServices.Protocols"

    I believe this is a part of the .NET 2.0 Framework and i cannot use it because i use visual studio.net 2003 which supports framework v 1.1

    About the console application, i am working on it but since it is my first time, i would appreciate if you could please post some sample code snippet that binds directly to an object and does not perform a search with the DirectorySearcher as you wanted.

    I really appreciate all the help.

    Wednesday, January 18, 2006 2:22 PM
  • User271633065 posted

    Finally got the client certificate on my webserver working. Turns out my sys admin had blocked off access by the LDAP server in the firewall.

    Anyways, now Schannel gives me a message saying that the client authentication was successfull and digital certificate was supplied and provides a bunch of details about the certificate. The central LDAP server admin also said that he can see that my webserver had been connecting to the LDAP server successfully using the client certificate.

    Now the problem is about the SASL EXTERNAL bind. The LDAP server admin says that now since i have opened up a secure channel, i will have to bind using this type of bind to fetch data from the LDAP server. He says that this type of bind is required by the LDAP server before i can proceed to query. Please tell me a way to do this type of bind.

    The following are his instructions:

    1. initialize the connection and set LDAPv3
    2. startTLS on the connection
    3. perform a SASL EXTERNAL bind
    4. search on the filter
    5. get the results from the search
    6. get the DN
    7. print out the entire entry

    I was able to achieve steps 1 and 2. But i am stuck up on step 3. I do not know how to perform this bind using ASP/VB.NET

    Thanks in advance.

    Monday, January 23, 2006 9:10 AM
  • User1354132231 posted
    The binding steps that the admin outlined are automatic for SDS.

    DirectoryEntry de = new DirectoryEntry("LDAP://server", "username", "password", AuthenticationTypes.SecureSocketsLayer):

    using (de)
    {
        DirectorySearcher ds = new DirectorySearcher(de, "(uid=foo)");
        SearchResult sr = null;
       
        using (SearchResultCollection src = ds.FindAll())
        {
           if (src.Count > 0)
           {
              SearchResult sr = src[0];
           }
        }

        if (sr != null)
        {
             Console.WriteLine(sr.Properties["whatever"][0].ToString());
        }
    }


    That is the entire sequence to find and search.  I have seen problems before where you cannot search using anything but anonymous access.  In this case, you should search using only the anonymous bind.  Once you find your object (get the DN), you should rebind to this object directly using SSL and then read it again.

    Wednesday, January 25, 2006 10:38 AM
  • User271633065 posted

    Thanks again Dunnry.

    Tried the steps that you suggested, but seems that is is failing at the FindAll() method. The LDAP server admin says that SDS probably does not have a SASL EXTERNAL bind method. This is absolutely REQUIRED before i can query for the DN.

    Another option that he suggested was :-

    "We've received several questions from people with ED-ID services whose libraries do not support SASL External.In response to this we are considering a service to act as a proxy to ED-ID.Instead of requiring LDAPv3 + SASL EXTERNAL + CLIENT AUTH, the proxy will require HTTPS + CLIENT AUTH.The proxy would not support all LDAPv3 commands, only searches.If anyone is interested in such a service please drop me a note. "

    What do you think?  Do you think we should go for this option. Or is it worthwhile to use .Net framework 2.0 with VS-2005 and try out the SASL binding?

    Thanks in advance.

     

    Monday, January 30, 2006 3:44 PM
  • User1354132231 posted
    If the admin had code in another language that demonstrated what he meant, I could determine the correct steps.  However, from what I can tell the code I gave you last does exactly what he is suggesting.  That it is not working suggests to me a client configuration error still.

    If you take a look at System.DirectoryServices.Protocols you will see AuthenticationTypes for the external SASL that he is talking about.  It is possible that there are just incompatibilities between ADSI and this directory that prevents the SASL bind from working.  However, the SDS.P library does not use ADSI so it should work fine.

    Give it a shot and see where you get.  I think in either case it would be worth using 2.0 since it supports more features and is the one that will be getting SPs and hotfixes in the future.
    Thursday, February 2, 2006 1:08 PM
  • User271633065 posted

    Thanks a ton for keeping up with my post.

    I am getting VS.NET 2005 today or tomorrow and will then use SDS.Protocols to test the things.

    Let me post a code snippet in some other language that is available from the middleware group. See if you can figure out a code snippet to achieve the same using .NET 1.1. If you think its too much of a hasssle then lets wait and see how .NET 2.0 handles the same. I will keep you posted.

     

    WinLDAP C Applications (Windows)

    This example uses the native Windows <ACRONYM title="Lightweight Directory Access Protocol">LDAP</ACRONYM> <ACRONYM title="Application Programming Interface">API</ACRONYM> (WinLDAP) to connect to ED-ID. Similar code could probably be compiled as a COM object or DLL for use with .Net or <ACRONYM title="Visual Basic">VB</ACRONYM>. Pay special attention to the notes section at the beginning of the example.

    Download WinLDAP Example

    Lines 24-28 include necessary headers
    Lines 37-49 declare variables necessary for example
    Lines 56-62 initialize the <ACRONYM title="Lightweight Directory Access Protocol">LDAP</ACRONYM> connection
    Lines 64-70 set LDAPv3 and ensure <ACRONYM title="Secure Sockets Layer">SSL</ACRONYM> in on
    Lines 73-81 connect to ED-ID
    Lines 85-90 perform a SASL EXTERNAL bind
    Lines 93-100 search on the filter, get the first entry, and grab its DN
    Lines 103-128 print out all viewable attributes and their values for the person
    Lines 132-138 determine if the person has the specified affiliation
    Lines 140-141 clean up

    1   /**
    2    * winldap-edid.c
    3    * This code is an example of how to connect to ED-ID, do a 
    4    * SASL EXTERNAL bind with a client certificate, search for a user,
    5    * print out the user's attributes, and then determine if the user
    6    * has the proper affiliation specified.  This is but the tip of the 
    7    * iceberg for authorization that can be done with ED-ID.  
    8    * 
    9    * Notes: * You must have a client certificate that has been issued
    10   *          by the VT Middleware CA to connect to ED-ID.
    11   *        * You must have a service entry that corresponds to your
    12   *          client certificate to be able to view entries
    13   *        * You must have imported the VTCA chain into the 
    14   *          Windows keystore before this code will work properly.
    15   *          This is available at https://vtmwra.eprov.iad.vt.edu/cacert, 
    16   *          or click on "immediate installation" and run the .exe at
    17   *          http://www.pki.vt.edu/download/ie6.html.  This will 
    18   *          automatically install the CA for you.
    19   *        * Your client certificate must also be in the Windows 
    20   *          keystore (see Appendix for instructions)
    21   *        * You must link this against wldap32.lib
    22   */
    23
    24  #include <windows.h>
    25  #include <ntldap.h>
    26  #include <winldap.h>
    27  #include <stdio.h>
    28  #include <winber.h>
    29
    30  /**
    31   * Do a SASL EXTERNAL bind, search for the user with the supplied UUPID,
    32   * print all attributes for the person, determine if the person has the
    33   * specified affiliation.
    34   */
    35  int main(int argc, char* argv[])
    36  {
    37      LDAP* ld = NULL;
    38      INT retVal = 0;
    39      PCHAR pHost = "id.directory.vt.edu";
    40      int port = 636;
    41      char* base = "ou=people,dc=vt,dc=edu";
    42      char* filter = "(uupid=UUPID)";
    43      LDAPMessage *result, *entry;
    44      char *dn;
    45      char *cmpAttr = "eduPersonAffiliation";
    46      char *cmpVal  = "VT-ACTIVE-MEMBER";
    47      struct berval cred;
    48      struct berval *servercredp;
    49      ULONG version = LDAP_VERSION3;
    50
    51      cred.bv_val = "";
    52      cred.bv_len = strlen(cred.bv_val)*sizeof(char);
    53  
    54      printf("\nConnecting to host \"%s\" ...\n",pHost);
    55  
    56      // Create an LDAP session.
    57      ld = ldap_sslinit(pHost, port, 1);
    58      if (ld == NULL)
    59      {
    60          printf( "ldap_sslinit failed with 0x%x.\n",GetLastError());
    61          return -1;
    62      }
    63
    64      // Specify version 3; the default is version 2.
    65      printf("Setting Protocol version to 3.\n");
    66      retVal = ldap_set_option(ld,
    67                               LDAP_OPT_PROTOCOL_VERSION,
    68                               (void*)&version);
    69
    70      retVal = ldap_set_option(ld,LDAP_OPT_SSL,LDAP_OPT_ON);
    71 
    72      // Connect to the server.
    73      retVal = ldap_connect(ld, NULL);
    74
    75      if(retVal == LDAP_SUCCESS)
    76          printf("ldap_connect succeeded \n");
    77      else
    78      {
    79          printf("ldap_connect failed with 0x%x.\n",retVal);
    80          return 1;
    81      }
    82
    83      // Perform a SASL EXTERNAL bind.  The CN of your client certificate
    84      // will be your service's UUSID
    85      retVal = ldap_sasl_bind_s(ld, "", "EXTERNAL" , &cred, NULL, NULL, &servercredp);
    86
    87      if(retVal != LDAP_SUCCESS)
    88          printf("ldap_sasl_bind_s failed with 0x%x\n", retVal);
    89      else
    90          printf("ldap_sasl_bind_s succeeded\n");
    91
    92      // Search for a person by UUPID
    93      retVal = ldap_search_s(ld, base, LDAP_SCOPE_SUBTREE, filter, NULL, NULL, &result);
    94
    95      if(retVal != LDAP_SUCCESS)
    96          printf("ldap_search_s failed with 0x%x.\n",retVal);
    97
    98      // Get the first entry and its DN
    99      entry = ldap_first_entry(ld, result);
    100     dn = ldap_get_dn(ld, entry);
    101
    102     // Print out all viewable attributes for the person
    103     if(entry != NULL)
    104     {
    105         char *attribute;
    106         BerElement *ber;
    107         char **values;
    108 
    109         for(attribute = ldap_first_attribute(ld, entry, &ber);
    110             attribute != NULL;
    111             attribute = ldap_next_attribute(ld, entry, ber))
    112         {
    113             if((values = ldap_get_values(ld, entry, attribute)) != NULL)
    114             {
    115                 for(int i = 0; values[i] != NULL; i++)
    116                 {
    117                     printf("%s: %s\n", attribute, values[i]);
    118                 }
    119                 ldap_value_free(values);
    120             }
    121             ldap_memfree(attribute);
    122         }
    123
    124         if(ber != NULL)
    125         {
    126             ber_free(ber, 0);
    127         }
    128     }
    129
    130     ldap_msgfree(result);
    131
    132     // Determine if the person has the specified affiliation
    133     retVal = ldap_compare_s(ld, dn, cmpAttr, cmpVal);
    134
    135     if(retVal != LDAP_COMPARE_TRUE)
    136         printf("ldap_compare_s failed with 0x%x.\n",retVal);
    137     else
    138         printf("\n%s == %s", cmpAttr, cmpVal);
    139
    140     ldap_memfree(dn);
    141     ldap_unbind_s(ld);
    142     return 0;
    143 }

    Thanks,

    Amar

    Thursday, February 2, 2006 2:04 PM
  • User271633065 posted

    Dunnry,

    I finally got .NET 2.0 framework and V Studio 2005. I tried working with the System.DirectoryServices.Protocols class, but it is so different. It has so many options. I think i may be able to figure out a way to do that SASL bind. But, could u please post an example code snippet if you have one ready. That will save me a lot of time of monkeying around. Also, i have posted a winldap code snippet in my earlier reply. That is exactly what i want to achieve using .NET. PLease let me know.

    Thanks in Advance.

    Monday, February 13, 2006 9:56 AM
  • User1354132231 posted
    Sorry, I have not had a chance to look into this yet... I am going out of town so I won't see to this probably until next week.  I am not ignoring you though.
    Tuesday, February 14, 2006 10:02 AM
  • User1354132231 posted
    So, I took a look into this more and perhaps the part I am missing is whether or not you are sending your unique client certificate to the server.  Perhaps SDS does not support this (I think it does for AD at least).  Usually, all of this works for us under the covers.

    Here is my take on doing this with SDS.P.  Of course, it might now work at all since I have no way of testing this unless you want to send me a certificate and there is public access to the server.

    public class LdapSasl : IDisposable
    {
        LdapConnection _connect;
       
        public LdapSasl(string server)
        {
            _connect = new LdapConnection(
                new LdapDirectoryIdentifier(server),
                null,
                AuthType.Basic
                );
               
            _connect.SessionOptions.ProtocolVersion = 3;
            _connect.SessionOptions.SaslMethod = "EXTERNAL";
            _connect.SessionOptions.SecureSocketLayer = true;

            //you need to open the store where
            //your certificate is located
            X509Store store = new X509Store(
                StoreName.My,
                StoreLocation.LocalMachine
                );
               
            X509Certificate2 cert = null;
           
            try
            {
                store.Open(OpenFlags.OpenExistingOnly);
                foreach (X509Certificate2 x509 in store.Certificates)
                {
                    //I am just taking the first one,
                    //but you can search the store using
                    //Find method if you like.
                    cert = x509;
                    break;
                }
            }
            finally
            {
                if (store.StoreHandle != IntPtr.Zero)
                    store.Close();
            }

            //add our specific client certificate
            if (cert != null)
            {
                _connect.ClientCertificates.Add(cert);
            }
        }
       
        public bool Authenticate()
        {
            try
            {
                _connect.Bind();
                return true;
            }
            catch (LdapException ex)
            {
                //49 means invalid creds
                if (ex.ErrorCode != 49)
                    throw;

                return false;
            }
        }
       
        #region IDisposable Members

        public void Dispose()
        {
            if (_connect != null)
                _connect.Dispose();
        }

        #endregion
    }


    You can use it with something like this:

    public class MyClass
    {
        public static void Main()
        {
            using (LdapSasl sasl = new LdapSasl("localhost"))
            {
                bool truth = sasl.Authenticate();
               
                Console.WriteLine(
                    "Bind {0} successful",
                    truth ? "was" : "was not"
                    );
                   
                Console.ReadLine();
            }
        }
    }


    Well... that is about all I can unfortunately without having an environment myself.  Since this is something that I have not personally tried, it would be nice if you posted back when/if you figure out what finally works.

    Thursday, February 23, 2006 1:15 PM
  • User271633065 posted

    Thank you so much Dunnry for posting the example code.

    I converted that code to VB.net and am using it. But, it fails at the line

    _connect.SessionOptions.SaslMethod = "EXTERNAL";

    The error code returned is "86" and the error is:

    System.DirectoryServices.Protocols.LdapException: An unknown authentication error occurred. at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error) at System.DirectoryServices.Protocols.LdapSessionOptions.SetStringValueHelper(LdapOption option, String value) at System.DirectoryServices.Protocols.LdapSessionOptions.set_SaslMethod(String value)

    Of course, the above is for a Vb.NET code, but i believe it will throw the same exception no matter what programming language you use.

    Second thing is, why are we doing a BASIC type authentication as Authtype.Basic in the Ldap connection?

    i am able to open the machine certificate store and return the appropriate certificate for authentication.

    So the only bottleneck right now seems to be the SASL external bind which is failing as above. And then i will use the Bind() method on the Ldapconnection object once we are through with the SASL sessionoption. Then i will be able to tell you if it doea actually work!!

    Cn you please shed some light. Thanks in advance.

    Friday, February 24, 2006 2:26 PM
  • User1354132231 posted
    We want to use AuthType.Basic because we need a simple bind for your directory.  This is most likely the only supported bind type for you.  Of course, you can try the other ones as well to see if they help.

    Checking that error code finds this:

    MSG_E_BAD_REGISTRY_CA_XCHG_CSP
    # Certificate Services could not use the provider specified
    # in the registry for encryption keys.

    I would be checking my schannel logging here for more information.  It appears to be that your certificate is not setup correctly yet.
    Monday, February 27, 2006 1:08 PM
  • User271633065 posted

    Thanks again Dunnry. But i do not think it is a client certificate problem. Here is the code that i have in VB.NET

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim connect As LdapConnection

    Dim dirident As LdapDirectoryIdentifier

    Dim credential As NetworkCredential

    Dim store As X509Store

    Dim cert As X509Certificate2

    Dim x As X509Certificate2

    Dim i As Integer = 0

    Dim attributesToReturn As String() = Nothing

    Try

    dirident = New LdapDirectoryIdentifier("id.directory.a.edu:636")

    credential = New NetworkCredential("", "")

    connect = New LdapConnection(dirident, credential, AuthType.Basic)

    connect.SessionOptions.ProtocolVersion = 3

    connect.SessionOptions.SecureSocketLayer = True

    store = New X509Store(StoreName.My, StoreLocation.LocalMachine)

    store.Open(OpenFlags.OpenExistingOnly)

    For Each x In store.Certificates

    ' I know that the client certificate for i=0 is the one that i need to use

    If i = 0 Then

    cert = x

    End If

    i = i + 1

    Next

    If (cert.SerialNumber <> 0) Then

    connect.ClientCertificates.Add(cert)

    End If

    connect.SessionOptions.SaslMethod = "External"

    connect.Bind()

    Dim searchRequest As New SearchRequest("ou=services,dc=a,dc=edu", "objectclass=*", SearchScope.Subtree, attributesToReturn)

    connect.SendRequest(searchRequest)

    Dim searchResponse As SearchResponse = CType(connect.SendRequest(searchRequest), SearchResponse)

    Response.Write(searchResponse.Entries.Count.ToString())

    ' print the returned entries & their attributes

    PrintSearchResponse(searchResponse)

    connect.Dispose()

    connect = Nothing

    Catch Ex As Exception

    Response.Write(Ex.Message.ToString)

    End Try

    End Sub

    In the above code "PrintSearchResponse(searchResponse)" is a function that just prints the number of search results, attributes and their values.

    Now, the above code works fine and returns attributes and values that public can view when i comment OUT the

    " connect.SessionOptions.SaslMethod = "External"" line.What happens is that the application connects anonymously to the directory service and hence is able to pull the public data. But as i said, to view authorized data i need to connect using SASL External bind to the directory. Once i connect using this bind then the directory will identify my application as an authorized connection and display me the authorized attributes too.

    I know for a fact that the client certificate works fine because when i comment out the sasl method statement and run the code which returns attribute & values, then schannel shows me server client handshake success messages. It displays message like "An SSL client handshake completed successfully. The negotiated cryptographic parameters are as follows............etc..."

    But when i do not comment out and infact use the statement above, schannel does not show any messages because the connect.Bind() code statement is not run ever since it fails at the earlier statement (which is the saslmethod statement) with this LDAP exception "An unknown authentication error occurred"

    Please help me with the SaslMethod() statement.Why does it keep on failing there? Are we using some wrong value, should we be using some other value?

    Thanks in advance!

    Tuesday, February 28, 2006 12:02 PM
  • User1354132231 posted
    Did you check that it wasn't case sensitive?  If you bind to your directory anonymously using a tool like LDP, you will see an attribute called 'supportedSaslMechanims'.  One of them should be EXTERNAL, and perhaps DIGEST, etc.

    If you set this name exactly as this says, it should be fine.  I suspect that it might be case sensitive.  Did you try running my example, as is, without coversion?
    Tuesday, February 28, 2006 12:34 PM
  • User271633065 posted

    Thank you again Dunnry for keeping up with me. Sorry it took so long to get back to you on this. I was working on another project which had some fixes to be done.

    I tried the case sensitive nature of EXTERNAL using almost all possible combinations. But sorry no use. Also, LDP tool does not give me that attribute you mentioned. But i am positive that it must be all caps, because on the examples mentioned on my university website they have it that way.

    I tried running your example exactly as it is on my server (except changed the LDAP path to reflect my path "id.directory.a.edu:636" and also tried without the :636. But it fails at the same line of SessionOptions.SaslMethod with the followinf error:

    Unhandled Exception: System.DirectoryServices.Protocols.LdapException: An unknow
    n authentication error occurred.
       at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int3
    2 error)
       at System.DirectoryServices.Protocols.LdapSessionOptions.SetStringValueHelper
    (LdapOption option, String value)
       at System.DirectoryServices.Protocols.LdapSessionOptions.set_SaslMethod(Strin
    g value)
       at Ldap1Console.LdapSasl..ctor(String server)
       at Ldap1Console.MyClass.Main()

    Thanks in advance.

    Friday, March 3, 2006 8:59 AM
  • User634144652 posted
    Hi,

    Did you get a solution for LDAP SASL?  I am having exactly same problem and I cant figure out how to fix it. I am also working in university environment and trying to connect to university LDAP Server, they have given a client certificate. The script which is posted is loading up the certificate, but it fails at connect.bind() statement and gives error as below:-

    [LdapException: The LDAP server is unavailable.]
       System.DirectoryServices.Protocols.LdapConnection.Connect() +417
       System.DirectoryServices.Protocols.LdapConnection.BindHelper(NetworkCredential newCredential, Boolean needSetCredential) +217
       System.DirectoryServices.Protocols.LdapConnection.Bind() +14

    Thanks for your help.

    Wednesday, May 2, 2007 11:08 PM
  • User271633065 posted

    Unfortunately i did not get a solution to that error message. What happened finally was that the university central computing department who managed the ldap servers created a proxy web address for me to use. I used the httprequest and response objects of asp.net to run my ladp queries using this proxy web address. I do have to provide the digital certificate everytime i send an ldap request though.

    Hope this helps. PLease let me know if you do find a solution to that exception.

    Thursday, May 3, 2007 10:00 AM
  • User634144652 posted

    Thanks. Is it possible to send part of your code which does the authentication by email.

    Also, is it possible to get some more information on the proxy server.

    Thanks for your help.

    Monday, May 7, 2007 2:09 AM
  • User271633065 posted

    The university provides a seperate directory for the authentication purposes and for data retrieval purposes. For simple username and pwd authentications we use the authentication directory and not the other proxy that is only used if we need to fetch more personal information for an individual. An example for a proxy for the directory to fetch detailed information is

    "https://x.y.z.A.edu:0011/directoryserver-proxy/"

    For simple authentication we do not use the above proxy, instead we connect to the authentication directory using the System.DirectoryServices namespace in .NET. Here's a sample code for authentication

        Public Function CheckLogin(ByVal sID As String, ByVal sPassword As String) As String<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p><o:p> </o:p>        Dim deLdapConn As DirectoryEntry = New DirectoryEntry("LDAP://authentication.directory.A.edu/ou=accounts,dc=A,dc=edu", "", "", AuthenticationTypes.SecureSocketsLayer)<o:p></o:p>        Dim searcherLdap As DirectorySearcher<o:p></o:p>        Dim srSearchLdap As SearchResult<o:p></o:p>        Dim sDn As String<o:p></o:p>        Dim sNewLdapPath As String<o:p></o:p>        Dim bUserName As Boolean<o:p></o:p>        Dim bPassword As Boolean<o:p></o:p><o:p> </o:p>        Try<o:p></o:p>            'Get a value indicating whether page validation succeeded.True if page validation succeeded; otherwise, false.<o:p></o:p>            If LTrim(RTrim(sID)) = "" Or Microsoft.VisualBasic.IsNothing(sID) Or LTrim(RTrim(sPassword)) = "" Or Microsoft.VisualBasic.IsNothing(sPassword) Then<o:p></o:p>                  ‘SOME ERROR MESSAGE GOES HERE<o:p></o:p>            Else<o:p></o:p>                searcherLdap = New DirectorySearcher(deLdapConn)<o:p></o:p>                searcherLdap.Filter = "(uuid=" & sID & ")"<o:p></o:p>                searcherLdap.SearchScope = System.DirectoryServices.SearchScope.Subtree<o:p></o:p>                searcherLdap.PropertiesToLoad.Add("adspath")<o:p></o:p>                srSearchLdap = searcherLdap.FindOne<o:p></o:p><o:p> </o:p>               sNewLdapPath = srSearchLdap.Properties("adspath")(0).ToString<o:p></o:p>               sDn = sNewLdapPath.Substring(30)<o:p></o:p>               bUserName = True<o:p></o:p>               searcherLdap.Dispose()<o:p></o:p>               searcherLdap = Nothing<o:p></o:p>               deLdapConn.Close()<o:p></o:p>               deLdapConn = Nothing<o:p></o:p><o:p> </o:p>                deLdapConn = New DirectoryEntry(sNewLdapPath, sDn, sPassword, AuthenticationTypes.SecureSocketsLayer)<o:p></o:p>                deLdapConn.RefreshCache()<o:p></o:p>                bPassword = True<o:p></o:p>

                    

                    deLdapConn.Close()<o:p></o:p>                deLdapConn = Nothing<o:p></o:p>                If bUserName = True And bPassword = True Then<o:p></o:p>                    CheckLogin = "True"<o:p></o:p>                Else<o:p></o:p>                    CheckLogin = "False"<o:p></o:p>                End If<o:p></o:p>            End If<o:p></o:p>            <o:p></o:p>        Catch Ex As Exception<o:p></o:p>                  ‘Some exception handling<o:p></o:p>        End Try<o:p></o:p>

        End Function

    Tuesday, May 29, 2007 11:27 AM