ED-ID Usage Examples

This document provides a number of platform-specific examples of binding to the Enterprise Directory with an ED service account using an ED service certificate credential with the SASL EXTERNAL authentication mechanism. The configuration and mechanics of SASL EXTERNAL can vary dramatically with platform and is easily the most difficult aspect of querying the ED-ID view of the directory.

Java Applications

Installing the Unlimited Strength JCE Policy Files

The key sizes of the VT Root CA certificates are too large to be used by the Java keytool implementation. In order to work around these problems the Unlimited Strength JCE Policy Files must be installed.

Creating a Java keystore

Java uses keystores for all SSL negotiations. These instructions detail how to create a keystore with your client certificate and private key.

The key sizes of the VT Root CA certificates are too large to be used by the Java keytool implementation. In order to work around these problems the Bouncy Castle Keystore type must be used.

openssl x509 -outform DER -in service.crt -out service.crt.der
openssl rsa -outform DER -in service.key -out service.key.der
bin/keystore.sh import-keypair service.crt.der service.key.der edid.keystore service
bin/keystore.sh list edid.keystore

Using JNDI to Access ED-ID

The following code sample demonstrates how to query ED-ID via SSL client authentication to bind as an ED Service user mentioned in the CN of the X.509 client certificate.

Requirements:

The sample code may be executed by a command like the following:

java \
-Djavax.net.ssl.keyStore=/path/to/edid.keystore \
-Djavax.net.ssl.keyStoreType=BKS \
-Djavax.net.ssl.keyStorePassword=changeit \
EdIdTlsExample '(uupid=serac)'

Download JNDI example

import java.io.IOException;
import java.util.Hashtable;
import java.util.ArrayList;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;

public class EdIdTlsExample
{
  public static void main(String[] args) {
    if (args.length < 1) {
      System.err.println("USAGE: EdIdTlsExample query");
      return;
    }
    String hostName = "ldap://ed-dev.middleware.vt.edu:10389";
    String baseDn = "ou=People,dc=vt,dc=edu";
    String query = args[0];
    String[] returnAttributes = {
      "givenname",
      "surname",
      "displayName"
    };

    // Initial JNDI environment setup
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, hostName);
    env.put("java.naming.ldap.version", "3");
    LdapContext ctx = null;
    StartTlsResponse tls = null;
    try {
      ctx = new InitialLdapContext(env, null);

      // Authentication must be performed over a secure channel
      tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
      tls.negotiate();
      
      // Authenticate via SASL EXTERNAL mechanism using client X.509
      // certificate contained in JVM keystore
      ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "EXTERNAL");
      ctx.reconnect(null);

      // Perform search for privileged attributes under authenticated context
      SearchControls controls = new SearchControls();
      controls.setReturningAttributes(returnAttributes);
      controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      System.out.println("Executing query "+query);
      NamingEnumeration ne = ctx.search(baseDn, query, controls);
      while (ne.hasMore()) {
        SearchResult sr = (SearchResult) ne.next();
        System.out.println("Result DN " + sr.getNameInNamespace());
        NamingEnumeration attributes = sr.getAttributes().getAll();
        while (attributes.hasMore()) {
          Attribute attr = (Attribute) attributes.next();
          System.out.println(attr.getID());
          NamingEnumeration values = attr.getAll();
          while (values.hasMore()) {
            System.out.println("\t" + values.next());
          }
        }
      }
    } catch (IOException e) {
      System.err.println("TLS negotiation error:");
      e.printStackTrace();
    } catch (NamingException e) {
      System.err.println("JNDI error:");
      e.printStackTrace();
    } finally {
      if (tls != null) {
        try {
          // Tear down TLS connection
          tls.close();
        } catch (IOException e) {
          System.err.println("Error tearing down TLS connection.");
        }
      }
      if (ctx != null) {
        try {
          // Close LDAP connection
          ctx.close();
        } catch (NamingException e) {
          System.err.println("Error closing JNDI context.");
        }
      }
    }
  }
}

Using Middleware’s LDAP Library (EDLdap)

Requirements:

The sample code may be executed by a command like the following:

java -cp .:path/to/edldap.jar:path/to/vt-ldap.jar:path/to/commons-logging.jar \
-Djavax.net.ssl.keyStore=/path/to/edid.keystore \
-Djavax.net.ssl.keyStoreType=BKS \
-Djavax.net.ssl.keyStorePassword=changeit \
EdIdEdLdapExample

Download EDLdap Example

import java.util.Iterator;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchResult;
import edu.vt.middleware.ldap.ed.DirectoryEnv;
import edu.vt.middleware.ldap.ed.EdId;

public class EdIdEdLdapExample
{
  public static void main(String[] args) throws Exception {
    if (args.length < 1) {
      System.err.println("USAGE: EdIdEdLdapExample query");
      return;
    }
    String query = args[0];
    String[] returnAttributes = {
      "givenname",
      "surname",
      "displayName"
    };
    EdId directory = new EdId(DirectoryEnv.DEV);
    Iterator<SearchResult> it = directory.search(query, returnAttributes);
    while (it.hasNext()) {
      SearchResult result = it.next();
      NamingEnumeration attributes = result.getAttributes().getAll();
      while (attributes.hasMore()) {
        Attribute attr = (Attribute) attributes.next();
        System.out.println(attr.getID());
        NamingEnumeration values = attr.getAll();
        while (values.hasMore()) {
          System.out.println("\t" + values.next());
        }
      }     
    }
  }
}

EDLdap Maven Integration

The EDLdap module may be included in Java projects with a Maven build system by including the following in the project pom.xml file:

...
<dependencies>
  <dependency>
      <groupId>edu.vt.middleware</groupId>
      <artifactId>edldap</artifactId>
      <version>2.0</version>
  </dependency>
<dependencies>
...
<repositories>
  <repository>
    <id>middleware.repo</id>
    <url>http://middleware.vt.edu/maven2</url>
  </repository>
</repositories>
...

IMPORTANT: If you filter resources in your build, be careful not to filter binary files like the ED-ID keystore. If you see strange authentication errors or NullPointerException errors thrown on operations that perform client authentication, it may be a sign that the ED-ID keystore has been inadvertently corrupted by resource filtering. We are aware of at least two occurrences of this problem at the time of writing, and both were capable developers with Maven experience. It happens, so doublecheck that the build does not filter binary files.

Spring Framework Configuration

The Middleware EDLdap Libraries may be used in conjunction with the Spring LDAP LdapContextSource object to connect to ED-ID in a way that is natural for Spring applications. In order for the following Spring context configuration example to work, the following conditions must be met:

<!-- Define LDAP context source pointing at ED-ID -->
<bean id="edIdContextSource"
  class="org.springframework.ldap.core.support.LdapContextSource">
  <property name="pooled" value="true" />
  <property name="baseEnvironmentProperties">
    <map>
      <entry
        key="java.naming.ldap.factory.socket"
        value="edu.vt.middleware.ldap.ed.EdIdTLSSocketFactory" />
      <entry
        key="java.naming.security.protocol"
        value="SSL" />
      <!-- Use SASL EXTERNAL authentication -->
      <entry
        key="java.naming.security.authentication"
        value="EXTERNAL" />
    </map>
  </property>
  <property name="urls">
    <list>
      <value>ldaps://id.directory.vt.edu</value>
    </list>
  </property>
</bean>

Common Problems

javax.naming.AuthenticationNotSupportedException: [LDAP: error code 7 - SASL(-4): no mechanism available: ]

You did not successfully negotiate client authentication, most likely because your keystore could not be found.

java.security.UnrecoverableKeyException: no match

You need to install the Unlimited Strength JCE jars.

javax.naming.AuthenticationNotSupportedException: EXTERNAL

Java 1.5

You do not have the SASL security provider installed.

Troubleshooting Certificate Problems

The edldap/edldap.sh script provided in the bin directory of the unpacked Middleware EDLdap Libraries is an indispensible tool for troubleshooting ED-ID certificate problems. To perform ED-ID operations with this tool, copy the keystore containing your ED-ID certificate to $EDLDAP_HOME/edid.keystore, where $EDLDAP_HOME is the location where the library archive was unpacked.

1 # The following code executes a simple LDAP query against the development
2 # instance of ED-ID using the ED-ID certificate in $EDLDAP_HOME/edid.keystore
3 # as a credential.
4
5 cd $EDLDAP_HOME
6 bin/edldap.sh edid-dev -query uupid=dhawes

If you are interested in particular attributes, you can specify a space-delimited list of attributes after the LDAP filter:

1 # Query to display only the values of the uid and uupid attributes.
4
5 cd $EDLDAP_HOME
6 bin/edldap.sh edid-dev -query uupid=dhawes uid uupid

C/C++ Applications

A C/C++ LDAP library supporting LDAP with the startTLS extension and SASL support is required in order to connect to ED-ID. Instructions for compiling such a library can be found at Appendix: Compiling OpenLDAP Libraries These instructions use the OpenLDAP C LDAP library. You must make sure this library is in your applications library path. Please see Appendix: OpenLDAP and Certificates for information on how to set up your environment for SSL/TLS.

Download C Example

Example compilation:

gcc -I/usr/local/openldap/include -L/usr/local/openldap/lib -o ed-id ed-id.c -lldap
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <stdio.h>
#include <ldap.h> 
 
int main(int argc, char* argv[])
{   
    LDAP* ldap;   
    LDAPMessage *result, *entry;
    BerElement* ber; 
    char *HOST_NAME = "id.directory.vt.edu";
    int PORT_NUMBER = 389;
    char *filter = "(uupid=UUPID)"; 
    char *attrs[] = { NULL };
    char *base = "ou=People,dc=vt,dc=edu";
    char *attribute = NULL; 
    char **values = NULL;
    char *dn = NULL; 
    char *cmpAttr = "eduPersonAffiliation";
    char *cmpVal  = "VT-ACTIVE-MEMBER";
    int i, resultCode, version;
    struct berval cred;
  
    cred.bv_val = "";
    cred.bv_len = strlen(cred.bv_val)*sizeof(char);

    /* Initialize an LDAP connection. */
    if( (ldap = ldap_init(HOST_NAME, PORT_NUMBER)) == NULL)
    {
        perror("ldap_init");
        return 1;
    }

    /* Set the version number so that we may use startTLS. */
    version = LDAP_VERSION3;
    ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);

    if(ldap_start_tls_s(ldap, NULL, NULL) != LDAP_SUCCESS)
    {
        ldap_perror(ldap, "ldap_start_tls_s");
    }

    resultCode = ldap_sasl_bind_s(ldap, NULL, "EXTERNAL", &cred, NULL,
                                  NULL, NULL);
    if(resultCode != LDAP_SUCCESS)
    {
        ldap_perror(ldap, "ldap_sasl_bind_s");
    }

    resultCode = ldap_search_ext_s(ldap, base, LDAP_SCOPE_SUBTREE,
                                   filter, attrs, 0, NULL, NULL,
                                   NULL, LDAP_NO_LIMIT, &result);
    if(resultCode != LDAP_SUCCESS)
    {
        /* another way to print errors...
        fprintf(stderr, "ldap_search_ext_s: %s\n", 
                ldap_err2string(resultCode)); 
        */
        ldap_perror(ldap, "ldap_search_ext_s");
        ldap_unbind_ext_s(ldap, NULL, NULL);
        return 1;
    }

    entry = ldap_first_entry(ldap, result);
    if(entry != NULL)
        dn = ldap_get_dn(ldap, entry);
    else
    {
        printf("search on filter: %s returned no entries\n", filter);
        ldap_msgfree(result);
        ldap_unbind_ext_s(ldap, NULL, NULL);
        return 1;
    }

    /* print out all attribute/value pairs */
    if(entry != NULL)
    {
        printf("\ndn: %s\n", dn);
        /* Iterate through each attribute in the entry. */
       for( attribute = ldap_first_attribute(ldap, entry, &ber);
            attribute != NULL;
            attribute = ldap_next_attribute(ldap, entry, ber))
       {
           /*For each attribute, print the name and values.*/
           values = ldap_get_values(ldap, entry, attribute);
           if( values != NULL)
           {
               for(i = 0; values[i] != NULL; i++)
               {
                   printf("%s: %s\n", attribute, values[i]);
               }
               ldap_value_free(values);
           }
           ldap_memfree(attribute);
       }
       if(ber != NULL)
       {
           ber_free(ber, 0);
       }
    }

    /* see if user has a specific affiliation */
    resultCode = ldap_compare_s(ldap, dn, cmpAttr, cmpVal);
    if(resultCode == LDAP_COMPARE_TRUE)
        printf("ldap_compare_s: %s has %s=%s\n", dn, cmpAttr, cmpVal);
    else
        printf("ldap_compare_s: %s does not have %s=%s\n", dn, cmpAttr, cmpVal); 
    ldap_msgfree(result);
    ldap_memfree(dn);
    ldap_unbind_ext_s(ldap, NULL, NULL);
    return 0;
}

Note that in addition to setting up the OpenLDAP Library for certificates (Appendix: OpenLDAP and Certificates), you can do this in the code directly:

char *cacertfile = "/path/to/cachain.pem";
char *clientcertfile = "/path/to/clientcertfile.pem";
char *clientkeyfile = "/path/to/clientkeyfile.pem"
ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, (void *) cacertfile);
ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, (void *) clientcertfile);
ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, (void *) clientkeyfile);

WinLDAP C Applications (Windows)

This example uses the native Windows LDAP API (WinLDAP) to connect to ED-ID. Similar code could probably be compiled as a COM object or DLL for use with .Net or VB. 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 LDAP connection \
  • Lines 64-70 set LDAPv3 and ensure SSL 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * winldap-edid.c
 * This code is an example of how to connect to ED-ID, do a 
 * SASL EXTERNAL bind with a client certificate, search for a user,
 * print out the user's attributes, and then determine if the user
 * has the proper affiliation specified.  This is but the tip of the 
 * iceberg for authorization that can be done with ED-ID.  
 * 
 * Notes: * You must have a client certificate that has been issued
 *          by the VT Middleware CA to connect to ED-ID.
 *        * You must have a service entry that corresponds to your
 *          client certificate to be able to view entries
 *        * You must have imported the VTCA chain into the 
 *          Windows keystore before this code will work properly.
 *          This is available at http://www.middleware.vt.edu/pubs/vt-cachain.pem, 
 *          or click on "immediate installation" and run the .exe at
 *          http://www.pki.vt.edu/download/ie6.html.  This will 
 *          automatically install the CA for you.
 *        * Your client certificate must also be in the Windows 
 *          keystore (see Appendix for instructions)
 *        * You must link this against wldap32.lib
 */

#include <windows.h>
#include <ntldap.h>
#include <winldap.h>
#include <stdio.h>
#include <winber.h>

/**
 * Do a SASL EXTERNAL bind, search for the user with the supplied UUPID,
 * print all attributes for the person, determine if the person has the
 * specified affiliation.
 */
int main(int argc, char* argv[])
{
    LDAP* ld = NULL;
    INT retVal = 0;
    PCHAR pHost = "id.directory.vt.edu";
    int port = 636;
    char* base = "ou=people,dc=vt,dc=edu";
    char* filter = "(uupid=UUPID)";
    LDAPMessage *result, *entry;
    char *dn;
    char *cmpAttr = "eduPersonAffiliation";
    char *cmpVal  = "VT-ACTIVE-MEMBER";
    struct berval cred;
    struct berval *servercredp;
    ULONG version = LDAP_VERSION3;

    cred.bv_val = "";
    cred.bv_len = strlen(cred.bv_val)*sizeof(char);

    printf("\nConnecting to host \"%s\" ...\n",pHost);

    // Create an LDAP session.
    ld = ldap_sslinit(pHost, port, 1);
    if (ld == NULL)
    {
        printf( "ldap_sslinit failed with 0x%x.\n",GetLastError());
        return -1;
    }

    // Specify version 3; the default is version 2.
    printf("Setting Protocol version to 3.\n");
    retVal = ldap_set_option(ld,
                             LDAP_OPT_PROTOCOL_VERSION,
                             (void*)&version);

    retVal = ldap_set_option(ld,LDAP_OPT_SSL,LDAP_OPT_ON);

    // Connect to the server.
    retVal = ldap_connect(ld, NULL);

    if(retVal == LDAP_SUCCESS)
        printf("ldap_connect succeeded \n");
    else
    {
        printf("ldap_connect failed with 0x%x.\n",retVal);
        return 1;
    }

    // Perform a SASL EXTERNAL bind.  The CN of your client certificate
    // will be your service's UUSID
    retVal = ldap_sasl_bind_s(ld, "", "EXTERNAL" , &cred, NULL, NULL, &servercredp);

    if(retVal != LDAP_SUCCESS)
        printf("ldap_sasl_bind_s failed with 0x%x\n", retVal);
    else
        printf("ldap_sasl_bind_s succeeded\n");

    // Search for a person by UUPID
    retVal = ldap_search_s(ld, base, LDAP_SCOPE_SUBTREE, filter, NULL, NULL, &result);

    if(retVal != LDAP_SUCCESS)
        printf("ldap_search_s failed with 0x%x.\n",retVal);

    // Get the first entry and its DN
    entry = ldap_first_entry(ld, result);
    dn = ldap_get_dn(ld, entry);

    // Print out all viewable attributes for the person
    if(entry != NULL)
    {
        char *attribute;
        BerElement *ber;
        char **values;

        for(attribute = ldap_first_attribute(ld, entry, &ber);
            attribute != NULL;
            attribute = ldap_next_attribute(ld, entry, ber))
        {
            if((values = ldap_get_values(ld, entry, attribute)) != NULL)
            {
                for(int i = 0; values[i] != NULL; i++)
                {
                    printf("%s: %s\n", attribute, values[i]);
                }
                ldap_value_free(values);
            }
            ldap_memfree(attribute);
        }

        if(ber != NULL)
        {
            ber_free(ber, 0);
        }
    }

    ldap_msgfree(result);

    // Determine if the person has the specified affiliation
    retVal = ldap_compare_s(ld, dn, cmpAttr, cmpVal);

    if(retVal != LDAP_COMPARE_TRUE)
        printf("ldap_compare_s failed with 0x%x.\n",retVal);
    else
        printf("\n%s == %s", cmpAttr, cmpVal);

    ldap_memfree(dn);
    ldap_unbind_s(ld);
    return 0;
}

.NET Framework Applications (Windows)

The System.DirectoryServices.Protocols assembly available in .NET Framework 2.0 and later provides a convenient integration option for Windows applications based on the .NET Framework. A simple command-line ED-ID query program is demonstrated in the following C# sample.

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System;
using System.Security.Cryptography.X509Certificates;
using System.DirectoryServices.Protocols;

using EdCommon;

namespace EdIdTest
{
  class Program
  {
    static void Main(string[] args)
    {
      if (args.Length < 2)
      {
        Console.WriteLine("USAGE: EdIdTest uusid query");
        return;
      }
      // The following should be the uusid of your ED Service
      string certCN = args[0];
      string ldapQuery = args[1];
      string ldapHost = EdConstants.ED_ID;
      int ldapPort = 389;

      Console.WriteLine(string.Format("Querying {0} as service {1} for {2}", ldapHost, certCN, ldapQuery));

      // Create connection and attempt to bind and search
      LdapConnection conn = null;
      try
      {
        conn = new LdapConnection(
          new LdapDirectoryIdentifier(ldapHost, ldapPort),
          null,
          AuthType.External);
        // VT Enterprise Directory requires LDAPv3
        conn.SessionOptions.ProtocolVersion = 3;

        // Must use custom hostname verification strategy due to DNS aliases
        conn.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(
          new EdCertificateVerifier(ldapHost).VerifyCertificate);

        // Look up client cert in Local Machine store by subject CN
        conn.SessionOptions.QueryClientCertificate
          delegate(LdapConnection c, byte[][] trustedCAs)
          {
            X509Store lmStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            try
            {
              lmStore.Open(OpenFlags.ReadOnly);
              // Uncomment the following lines to help diagnose cert problems
              //Console.WriteLine();
              //Console.WriteLine("Available certificates in Local Machine store:");
              //foreach (X509Certificate cert in lmStore.Certificates)
              //{
              //  Console.WriteLine("   " + cert.Subject);
              //}
              //Console.WriteLine("Querying Local Machine store for valid cert with subject " + certCN);
              X509Certificate2Collection clientCerts = lmStore.Certificates.Find(
              X509FindType.FindBySubjectName, certCN, true);
              if (clientCerts.Count == 0)
              {
                throw new ArgumentException("Cannot find valid certificate with subject " + certCN);
              }
              return clientCerts[0];
            }
            finally
            {
              lmStore.Close();
            }
          };
        conn.SessionOptions.StartTransportLayerSecurity(null);
        conn.Bind();

        // 4th parameter, attributeList, is omitted to indicate all available attributes
        SearchResponse response = (SearchResponse)conn.SendRequest(
          new SearchRequest(EdConstants.SEARCH_BASE, ldapQuery, SearchScope.Subtree));

        // Stopping TLS is demonstrated for completeness.
        // Ideally ED-ID connections are pooled and the TLS session is maintained
        // for the life of the connection.
        conn.SessionOptions.StopTransportLayerSecurity();

        // Print attributes of result entries
        Console.WriteLine();
        Console.WriteLine(response.Entries.Count + " entries found:");
        foreach (SearchResultEntry entry in response.Entries)
        {
          Console.WriteLine("   " + entry.DistinguishedName);
          foreach (String name in entry.Attributes.AttributeNames)
          {
            Console.Write("    " + name + "=");
            int n = 0;
            foreach (object value in entry.Attributes[name].GetValues(typeof(string)))
            {
              if (n++ > 0)
              {
                Console.Write(',');
              }
              Console.Write(value);
            }
            Console.WriteLine();
          }
        }
      }
      catch (Exception e)
      {
        Console.WriteLine("Application error: \n" + e);
      }
      finally
      {
        if (conn != null)
        {
          conn.Dispose();
        }
      }
    }
  }
}

The EdCertificateVerifier is available in a standalone library assembly, EdCommon, to facilitate use in real applications. A complete Visual Studio solution file containing the source for EdCommon and all .NET ED samples is available in the ED Samples Project.

Perl Applications

To connect to ED-ID with Perl you will need the following Perl modules: Net::SSLeay, IO::Socket::SSL, Convert::BER, Authen::SASL, and Net::LDAP. These modules can be found on the CPAN website. For your convenience, a link to the current versions are supplied in the resource appendix.

Note that certain versions of perl-ldap can cause various errors, including SASL errors. Be sure to use the latest version, 0.34.

Download Perl Example

  • Lines 3-4 use required modules
  • Lines 7-16 declare variables
  • Line 17 set up SASL ojbect
  • Line 20 create a new LDAP
  • Lines 22-27 start TLS
  • Lines 29-31 perform a SASL EXTERNAL bind
  • Lines 33-34 search on the filter
  • Line 46 print the entry
  • Lines 48-59 get the entry and do an LDAP compare operation
  • Line 61 disconnect from ED-ID
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/perl 

use Net::LDAP;
use Authen::SASL;
use strict;

my $server = "id.directory.vt.edu";
my $port = 389;
my $dn = "";
my $cafile = "/PATH/TO/ED_ID/CA_file.pem";
my $clientcert = "/PATH/TO/ED_ID/clientcert.pem";
my $clientkey = "/PATH/TO/ED_ID/clientkey.pem";
my $base = "ou=people,dc=vt,dc=edu";
my $filter = "(uupid=UUPID)";
my $attr = "eduPersonAffiliation";
my $value = "VT-ACTIVE-MEMBER";
my $sasl = Authen::SASL->new(mechanism => 'EXTERNAL',
                             callback  => { pass => '', user => ''});

my $ldap = Net::LDAP->new($server, port => $port, version => 3) or die $@;

my $mesg = $ldap->start_tls(verify     => 'require',
                            cafile     => $cafile,
                            ciphers    => 'AES256-SHA256', sslversion => 'tlsv1_2',
                            clientcert => $clientcert,
                            clientkey  => $clientkey);
$mesg->code && die $mesg->error;

$mesg = $ldap->bind(dn   => '',
                    sasl => $sasl);
$mesg->code && die $mesg->error;

$mesg = $ldap->search(base => $base, filter => $filter);
$mesg->code && die $mesg->error;

# print specific attribute
#$entry = $mesg->entry(0); 
#if($entry) 
#{ 
#    print $entry->get_value('edupersonprimaryaffiliation')."\n"; 
#    my $ref = $entry->get_value('edupersonaffiliation', asref => 1); 
#    foreach(@{$ref}){ print $_."\n"; } 
#} 

# print all attributes for the entry
foreach my $entry ($mesg->all_entries) { $entry->dump; }

my $entry = $mesg->entry(0);
$mesg = $ldap->compare($entry,
                       attr => $attr,
                       value => $value);
if($mesg->code == 6) # error code 6 is LDAP_COMPARE_TRUE
{
    print $entry->dn.": $attr == $value\n";
}
else
{
    print $entry->dn.": $attr != $value\n";
}

$ldap->unbind;

Python Applications

This example uses python-ldap to communicate with ED-ID. Since python-ldap is a wrapper around the OpenLDAP libraries, OpenLDAP, OpenSSL, and Cyrus SASL are required for this example to work. Certificates can be set up according to Appendix: OpenLDAP and Certificates. See Appendix: Compiling OpenLDAP Libraries for help with compiling the OpenLDAP library. See the INSTALL document bundled with python-ldap for installation instructions (hint: modify setup.cfg to give OpenLDAP, OpenSSL, and Cyrus SASL include and library paths).

Download Python Example

  • Line 3 import the ldap modules
  • Lines 5-9 declare variables
  • Line 12 initialize the connection
  • Line 13 set LDAPv3
  • Line 14 startTLS on the connection
  • Line 15 perform a SASL EXTERNAL bind
  • Line 16 search on the filter
  • Line 17 get the results from the search
  • Line 18 get the DN
  • Line 21 print out the entire entry
  • Lines 22-25 determine if the entry has the specified eduPersonAffiliation
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/python

import ldap,ldap.sasl

base = "ou=People,dc=vt,dc=edu"
scope = ldap.SCOPE_SUBTREE
searchFilter = "uupid=UUPID"
attribute = "eduPersonAffiliation"
value = "VT-ACTIVE-MEMBER";

try:
    edid = ldap.initialize("ldap://id.directory.vt.edu:389")
    edid.protocol_version=ldap.VERSION3
    edid.start_tls_s()
    edid.sasl_interactive_bind_s("", ldap.sasl.external())
    results = edid.search(base, scope, searchFilter, None)
    resultType, result_data = edid.result(results, 1)
    dn = result_data[0][0]
    # barf out all user data for first entry returned (not pretty)
    print dn
    print result_data[0][1]
    if edid.compare_s(dn, attribute, value) == 0:
        print dn, attribute, "!=", value
    else:
        print dn, attribute, "==", value
except ldap.LDAPError,e:
    print str(e)

PHP Applications

This example uses PHP to connect to ED-ID. You must compile PHP with LDAP for this example to work (–with-ldap and –with-ldap-sasl). See Appendix: Compiling OpenLDAP Libraries for instructions on installing OpenLDAP libraries.

Download PHP Example

  • Lines 3-8 create and initialize variables
  • Lines 10-13 get a connection and do a SASL EXTERNAL bind
  • Lines 16-18 search for a user
  • Lines 21-29 print out the user’s entry
  • Lines 30-37 check for affiliation
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

$host = 'ldap://id.directory.vt.edu';
$baseDn = 'ou=People,dc=vt,dc=edu';
$userfield = 'uupid';
$pid = "UUPID";
$compareAttr = 'eduPersonAffiliation';
$compareValue = 'VT-ACTIVE-MEMBER';

$ldap = ldap_connect($host);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_start_tls($ldap);
ldap_sasl_bind($ldap, null, null, "EXTERNAL");

if (isset($ldap) && $ldap != '') {
    /* search for pid dn */
    $result = @ldap_search($ldap, $baseDn, $userfield.'='.$pid);
    if ($result != 0) {
        $entries = ldap_get_entries($ldap, $result);
        for ($i = 0; $i < $entries["count"]; $i++) {
            $dn = $entries[$i]["dn"];
            print "dn: $dn\n";
            /* print out attributes and values */
            for ($j = 0; $j < $entries[$i]["count"]; $j++) {
                $attrib = $entries[$i][$j];
                for ($k = 0; $k < $entries[$i][$attrib]["count"]; $k++) {
                    print $attrib.": ".$entries[$i][$attrib][$k]."\n";
                }
            }
            /* check for affiliation */
            print("\nDoes user ".$compareAttr."=".$compareValue.": ");
            $cmp = ldap_compare($ldap, $dn, $compareAttr, $compareValue);
            if($cmp == true) {
                print("YES\n");
            } else {
                print("NO\n");
            }
        }
        
        ldap_free_result($result);
    } else {
        print('Error occured searching the LDAP');
    }
    ldap_close($ldap);
} else {
    print('Could not connect to LDAP at '.$host);
}
?>

Ruby Applications

To use this script to connect to ED-Auth you must have the ruby-ldap module compiled against the OpenLDAP and OpenSSL libraries. See Appendix: Compiling OpenLDAP Libraries.

Download Ruby Example

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/ruby
# Simple ED-ID script; requires ruby-ldap built against OpenLDAP (with SASL)
# and OpenSSL.  Add error checking at your own discretion.

require 'ldap'

HOST, PORT = 'id.directory.vt.edu', 389
BASEDN = 'ou=People,dc=vt,dc=edu'
FILTER = 'uupid=UUPID'
ATTRIBUTE = 'eduPersonAffiliation'
VALUE = 'VT-ACTIVE-MEMBER'

dn =  nil

ldap = LDAP::SSLConn.new(HOST, PORT, true)
ldap.sasl_quiet = true
ldap.sasl_bind('', 'EXTERNAL')

# search for affiliations
ldap.search(BASEDN, LDAP::LDAP_SCOPE_SUBTREE, FILTER,
            ['eduPersonAffiliation']) do |entry|
    dn = entry.get_dn
    puts dn
    puts entry.get_values(ATTRIBUTE)
    end

# determine if we have a specific affiliation
if ldap.compare(dn, ATTRIBUTE, VALUE) == true
    puts dn + ':  ' + ATTRIBUTE + ' == ' + VALUE
else
    puts dn + ':  ' + ATTRIBUTE + ' != ' + VALUE
end

ldap.unbind

Appendix: OpenLDAP and Certificates

If the application you are using to connect to ED-ID or ED-Auth uses the OpenLDAP libraries, it may be necessary to set up the certificates for your client. This is done with the following directives: TLS_CACERT, TLS_CERT, and TLS_KEY. TLS_CACERT refers to the certificate chain used to verify the server. TLS_CERT and TLS_KEY refer to the client certificate and private key, respectively, that are used for TLS client authentication (only used for ED-ID). These directives can be set up in the following ways:

Download the VT Middleware CA chain file

  • Add the following to the OpenLDAP library’s ldap.conf. This must be the ldap.conf that corresponds to the OpenLDAP library you are using for your application (note that there may be multiple ldap.conf files on your system, but only one will actually be used by a particular OpenLDAP library).
#############
# ldap.conf
TLS_CACERT      /path/to/cachain.pem
### only needed for ED-ID ###
### NOTE:  TLS_CERT and TLS_KEY are user-only attributes, meaning they 
###        cannot be in ldap.conf, but must be in .ldaprc or ldaprc or environment variables
TLS_CERT                /path/to/cert.pem 
TLS_KEY         /path/to/key.pem
#############

This sets up certificates system-wide for the OpenLDAP library.

  • Put the above directives in a file called ldaprc in the user’s $HOME directory that is using the application. This will override the system-wide settings for the user.

  • Put the above directives in a file called .ldaprc in the user’s $HOME directory that is using the application. This will override the system-wide settings for the user.

  • Put the above directives in a file called ldaprc in the user’s current working directory ($PWD) that is using the application. This will override the system-wide settings for the user.

  • Set the following environment variables:

export LDAPCONF=/path/to/ldap.conf
OR
export LDAPRC=/path/to/ldaprc
OR
export LDAPTLS_CACERT=/path/to/cachain.pem
export LDAPTLS_CERT=/path/to/cert.pem
export LDAPTLS_KEY=/path/to/key.pem 

These directives are searched for in this order. The last directives set override any previous directives. For more information on this, see the ldap.conf manpage for OpenLDAP.

Appendix: Testing and Debugging With OpenLDAP

The ldapsearch program distributed with OpenLDAP is a useful tool for testing and debugging your custom application, particularly if it uses the OpenLDAP libraries at some level. Below is an example of how to connect to ED-ID with ldapsearch. Please refer to Appendix: OpenLDAP and Certificates for information on how to set up your server and client certificates.

NOTE: Your OpenLDAP libraries must be compiled with SSL and SASL support to do this.

Search for a person:

ldapsearch -H ldap://id.directory.vt.edu -Y EXTERNAL -Z \ 
           -b ou=people,dc=vt,dc=edu '(uupid=<uupid to search for>)' 

View the service account:

ldapsearch -H ldap://id.directory.vt.edu -Y EXTERNAL -Z \
           -b ou=services,dc=vt,dc=edu

Meaning of options:

  • -H – specifies the LDAP URL
  • -Y – specifies the SASL Mechanism (SASL EXTERNAL) when doing a SASL bind
  • -Z – specifies that we want to do a Start TLS request
  • -b – specifies the search base

If you are having difficulties with this example, you can turn debugging on by appending -d 1 to the end of the ldapsearch command. This will produce a wealth of debugging information.

Appendix: Compiling OpenLDAP Libraries

Many of the examples contained in this document depend on the OpenLDAP LDAP Libraries for their functionality. This includes the C, Net::LDAP, Python, and PHP examples as well as the standard LDAP utilities such as ldapsearch. This appendix exists to help you compile the libraries needed for your application to interface with ED-ID.

  • Download the OpenLDAP Software Distribution. The OpenLDAP //stable// Release is always the best choice to install.

  • Configure OpenLDAP with the following line:

./configure --prefix=/path/to/openldap \
            --enable-slapd=no  \
            --enable-slurpd=no \
            --with-tls \
            --with-cyrus-sasl

If your OpenSSL and/or Cyrus SASL libraries are not in the standard include and lib paths, you may need to run the following:

CPPFLAGS="-I/path/to/ssl/include -I/path/to/cyrus-sasl/include" \
LDFLAGS="-L/path/to/ssl/lib -L/path/to/cyrus-sasl/lib" \
./configure --prefix=/path/to/openldap \
            --enable-slapd=no \ 
            --enable-slurpd=no \ 
            --with-tls \
            --with-cyrus-sasl
  • make depend && make && make install

Appendix: Using the Paged Result LDAP Control to Return More Entries

RFC 2696 describes an LDAPv3 control to return paged results. This control allows a client to control the rate LDAP search entries are returned.

This can be useful for ED Services that need to return large result sets, since search timelimits are currently set at 60 seconds. For example:

 
ldapsearch -h id.directory.vt.edu -Z -b dc=vt,dc=edu -E pr=1000

will start returning all entries your service has permission to see, 1000 entries at a time. If you keep pressing enter, you will get 1000 entries each time until the search has finished. This works because 1000 entries can easily be returned in under 60 seconds. If you use a paged results size that cannot return in under 60 seconds, you will still get a timeout. The trick here is to set a resonable results size. Of course, using 1 as the result size is a good guarantee you will never hit a timeout.

See the documentation for your LDAP library of choice to see if they have implemented LDAP paged results.

Appendix: Resources