ED LDAP Authentication Examples

Introduction

ED-Auth exists to provide an easy means for applications to do simple PID/password authentication and role based authorization (student, faculty, staff, etc.). The ability to support LDAPv3 over SSL/TLS (ldaps or ldap with startTLS) is the only thing required for connecting to ED-Auth.

Authentication and Authorization

ED-Auth offers simple PID/password authentication plus the ability to authorize on a user’s eduPersonAffiliation with Virginia Tech.

Typical Authentication with ED-Auth

The basic steps to authenticate against ED-Auth are:

Typical Authorization with ED-Auth

The basic steps to authorize against ED-Auth are:

ED-Auth vs. ED-ID

Middleware provides two directories for authentication and authorization purposes. Before you begin the process of interfacing your system with ED-Auth or ED-ID (or both!), you must first consider which system better suits your needs. Both ED-Auth and ED-ID support authentication and provide authorization data, however, ED-ID provides much more authorization data. Below are some criteria to help you decide which system will work best for you. Please note you may have to ask the vendor of your product whether it has some of the functionality listed below.

Use ED-Auth if:

Use ED-ID if:

Some applications may even be able to support the usage of both directories. If this is the case, it is strongly recommended that you use ED-Auth for authentication and use ED-ID for the lookup of data pertaining to a person. This guarantees fast response times on authentication requests and allows access to all the information about a person that has been collected for placement in the directory.

Primary Affiliations vs. Standard Affiliations

Primary Affiliations

The eduPersonPrimaryAffiliation attribute is an attribute used to communicate, to other institutions, the most basic affiliation a person has with Virginia Tech. This attribute is used in conjunction with systems like Shibboleth* to allow other universities to make authorization decisions about this person. This attribute should NEVER be used by internal Virginia Tech systems for purposes of authorization, it is strictly meant as an external, to VT, facing attribute.

Standard Affiliations

The eduPersonAffiliation attribute gives all the affiliations a person associated with Virginia Tech has with the university. This attribute is meant to be used by internal applications, and will often be used in authorization logic. It is vitally important to realize that this attribute can, and almost always will, have more than one value, which is a change from the current affiliation tracking systems.

Java Applications

SSL Prerequisites for Java Applications

Required JDK Configuration

The following steps are required for all Java applications that talk to ED-Auth. Only JDK versions 1.5 and greater are supported.

keytool -import -keystore $JAVA_HOME/jre/lib/security/cacerts -file $PATH_TO/cacert.pem

Optional JDK Configuration

The following steps are optional, but may provide additional functionality or better performance in some cases.

Installing the Bouncy Castle Provider

The BC cryptographic provider has a number of additional ciphers compared to the default JSSE provider. For example the AES cipher is provided in 3 flavors (AESEngine, AESFastEngine, AESLightEngine) to allow optimization for performance versus resource consumption, so the use of these features of BC can improve application performance accordingly. Additionally, the implementation of the common ciphers is arguably better.

security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.crypto.provider.SunJCE
security.provider.3=sun.security.jgss.SunProvider
security.provider.4=org.bouncycastle.jce.provider.BouncyCastleProvider
security.provider.5=com.sun.net.ssl.internal.ssl.Provider
security.provider.6=com.sun.rsajca.Provider

Debugging

If problems persist after following these instructions it is useful to verify that your JVM is configured properly. To do so, add the following switch when starting the JVM:

java -Djavax.net.debug=ssl ...

This will provide a trace of the SSL negoitation and verification of the keystores that the JVM is configured to use. This switch is not recommmended for production use.

Using ED-Auth with Applications that Use JNDI

The following example uses JNDI to communicate with ED-Auth over an explicit TLS connection.

Download ED-Auth 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.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;

public class EdAuthTlsExample
{
  public static void main(String[] args) {
    if (args.length < 2) {
      System.err.println("USAGE: EdAuthSslExample uupid pass");
      return;
    }
    String hostName = "ldap://authn.directory.vt.edu";
    String baseDn = "ou=People,dc=vt,dc=edu";
    String pid = args[0];
    String credential = args[1];

    // Set up JNDI context for an anonymous search for uupid
    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();

      // Perform anonymous lookup of user DN based on uupid attribute
      BasicAttributes attrs = new BasicAttributes("uupid", pid);
      String[] retAttrs = new String[] { "dn" };
      NamingEnumeration ne1 = ctx.search(baseDn, attrs, retAttrs);
      SearchResult pidSearchResult = (SearchResult) ne1.next();
      if (pidSearchResult == null) {
        System.out.println(pid+" not found in ED-Auth");
        return;
      }
      String userDn = pidSearchResult.getNameInNamespace();
      System.out.println("Found user DN "+userDn);

      // Authenticate the user and search for privileged attributes
      // belonging to authenticated user
      ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
      ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDn);
      ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credential);
      ctx.reconnect(null);
      NamingEnumeration ne2 = ctx.search(baseDn, attrs);
      while (ne2.hasMore()) {
        SearchResult sr = (SearchResult) ne2.next();
        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.");
        }
      }
    }
  }
}

As an alternative to explicitly managing TLS, for ED-Auth it is sufficient to specify “ssl” for the Context.SECURITY_PROTOCOL initial environment parameter to establish a TLS connection. Since ED-Auth is intended for the authentication/authorization use case where all operations need to be performed over a secure channel, an implicit TLS connection may be preferable.

Using ED-Auth via Middleware’s LDAP Library

Requires the Middleware EDLdap Library

Download the Middleware EDLdap 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
35
36
37
import edu.vt.middleware.ldap.ed.EdAuth;

/**
 * <p>
 * EdAuthLibTest provides a test for the EdAuth class.
 * </p>
 */
public final class EdAuthLibTest
{
  public void doTest()
    throws Exception
  {
    String uupid = "UUPID";
    String credential  = "PASSWORD";
    String filter = "AUTHORIZATION FILTER";

    final EdAuth auth = new EdAuth();
    if (auth.authenticateAndAuthorize(uupid, credential, filter)) {
      System.out.println("Primary affiliation: "+
                         auth.getPrimaryAffiliation(uupid, credential));
      final String[] affil = auth.getAffiliations(uupid, credential);
      System.out.println("Affiliations: ");
      for (int i = 0; i < affil.length; i++) {
        System.out.println("  "+affil[i]);
      }
    } else {
      System.out.println("Authentication or Authorization failed");
    }
  }

  public static void main(final String[] args)
    throws Exception
  {
    final EdAuthLibTest test = new EdAuthLibTest();
    test.doTest();
  }
}

C/C++ Applications

A C/C++ LDAP library supporting LDAP with the startTLS extension is required in order to connect to ED-Auth. 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-auth ed-auth.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdio.h>
#include <ldap.h>

int main(int argc, char* argv[])
{
    LDAP* ldap;
    LDAPMessage *result, *entry;
    BerElement* ber;
    char *HOST_NAME = "authn.directory.vt.edu";
    int PORT_NUMBER = 389;
    char *PASSWORD = "PASSWORD";
    char *filter = "(uupid=UUPID)";
    char  *attrs[] = {"eduPersonPrimaryAffiliation",
                     "eduPersonAffiliation", 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;

    /* 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");
    }

    resultCode = ldap_search_ext_s(ldap, base, LDAP_SCOPE_SUBTREE,
                                   filter, NULL, 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);
        ldap_msgfree(result);
    }
    else
    {
        printf("search on filter: %s returned no entries\n", filter);
        ldap_msgfree(result);
        ldap_unbind_ext_s(ldap, NULL, NULL);
        return 1;
    }

    /* Bind as a user. If PASSWORD is NULL, resultCode will be LDAP_UNWILLING_TO_PERFORM. 
       Always make sure password is not NULL. */
    resultCode = ldap_simple_bind_s(ldap, dn, PASSWORD);
    if(resultCode != LDAP_SUCCESS)
    {
        ldap_perror(ldap, "ldap_simple_bind_s");
        ldap_memfree(dn);
        return 1;
    }

    /* Search for the user. */
    resultCode = ldap_search_ext_s(ldap, dn, LDAP_SCOPE_BASE,
                                   filter, attrs, 0, NULL, NULL,
                                   NULL, LDAP_NO_LIMIT, &result);
    if( resultCode != LDAP_SUCCESS)
    {
        ldap_perror(ldap, "ldap_search_ext_s");
        ldap_memfree(dn);
        return 1;
    }

    /* Since we are doing a base search, there should be only one 
       matching entry */
    entry = ldap_first_entry(ldap, result);
    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";
ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, (void *) cacertfile);

WinLDAP C Applications (Windows)

This example uses the native Windows LDAP API WinLDAP to connect to ED-Auth. Similar code could probably be compiled as a COM object or DLL for use with .Net or VB.

Download WinLDAP 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
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
/**
 * winldap-edauth.c
 * This code is an example of how to connect to ED-Auth,
 * search for an entry by a person's UUPID, bind as that
 * UUPID, and then determine if that person is an active
 * member at VT with the winldap library.  
 * This illustrates the basic authentication/
 * authorization ED-Auth is to be used for.
 *
 * Notes:  * You must have imported the VTCA chain into the 
 *           Windows keystore before this code will work properly.
 *           This is available at
 *           https://4help.vt.edu/sp?id=kb_article&sysparm_article=KB0011371
 *         * You must link this against wldap32.lib
 */

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

/**
 * Search for the DN with the supplied UUPID, bind as that DN with the
 * given credentials, and then determine if the entry has the 
 * specified affiliation.
 */
int main(int argc, char* argv[])
{
    LDAP* ld = NULL;
    INT retVal = 0;
    PCHAR pHost = "authn.directory.vt.edu";
    int port = 636;
    char* base = "ou=People,dc=vt,dc=edu";
    char* filter = "(uupid=UUPID)";
    LDAPMessage *result, *entry;
    char *dn;
    char *pass = "PASSWORD";
    char *cmpAttr = "eduPersonAffiliation";
    char *cmpVal  = "VT-ACTIVE-MEMBER";

    ULONG version = LDAP_VERSION3;
    SecPkgContext_ConnectionInfo sslInfo;
    LONG lv = 0;

    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);
    if (retVal != LDAP_SUCCESS)
        return 1;

    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;
    }

    // Search for the 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);
        return 1;
    }

    // Get the DN
    entry = ldap_first_entry(ld, result);
    dn = ldap_get_dn(ld, entry);
    ldap_msgfree(result);

    // Bind with current credentials. 
    printf("Binding with dn %s...\n", dn);
    retVal = ldap_bind_s(ld,dn,pass,LDAP_AUTH_SIMPLE);
    if (retVal != LDAP_SUCCESS)
        printf("Bind failed with 0x%x.\n", retVal);
    else
        printf("Bind as %s succeeded.\n", dn);

    // Determine if this person has the affiliation we want
    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("%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 authenticator is show below in C#.

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
using System;
using System.Net;
using System.DirectoryServices.Protocols;
using System.Security.Cryptography.X509Certificates;

using EdCommon;

namespace EdAuthTest
{
  class Program
  {
    static void Main(string[] args)
    {
      if (args.Length < 1)
      {
        Console.WriteLine("USAGE: EdAuthTest uupid");
        return;
      }
      string uupid = args[0];
      string password = GetPassword(string.Format("Password for {0}:", uupid));
      string ldapHost = EdConstants.ED_AUTH;
      int ldapPort = 389;
      string ldapFilter = "uupid=" + uupid;

      Console.WriteLine(string.Format("Attempting to authenticate to {0} as {1}", ldapHost, uupid));

      // Create connection and attempt to bind and search
      LdapConnection conn = null;
      try
      {
        conn = new LdapConnection(
          new LdapDirectoryIdentifier(ldapHost, ldapPort),
          null,
          AuthType.Basic);
        // 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);

        // A QueryClientCertificateCallback is required based on our testing.
        // We can only imagine that this handler is required when the SSL handshake
        // contains a client certificate request directive.
        conn.SessionOptions.QueryClientCertificate
        delegate(LdapConnection c, byte[][] trustedCAs)
        {
          return null;
        };

        // Must conduct initial search over TLS connection to overcome suppressed PIDs
        conn.SessionOptions.StartTransportLayerSecurity(null);

        // Bind anonymously for initial search
        conn.Bind();

        // Search for the DN of the user with given uupid
        SearchResponse response = (SearchResponse)conn.SendRequest(
          new SearchRequest(EdConstants.SEARCH_BASE, ldapFilter, SearchScope.Subtree, "dn"));

        if (response.Entries.Count == 0)
        {
          throw new ArgumentException("Cannot find DN for uupid=" + uupid);
        }
        string dn = response.Entries[0].DistinguishedName;
        Console.WriteLine("Found user DN " + dn);

        // Rebind as authenticated user
        conn.Bind(new NetworkCredential(dn, password));

        // 4th parameter, attributeList, is omitted to indicate all available attributes
        response = (SearchResponse)conn.SendRequest(
          new SearchRequest(dn, ldapFilter, SearchScope.Base));

        // Print attributes of entry
        SearchResultEntry entry = response.Entries[0];
        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();
        }
      }
    }

    /// <summary>
    /// Lightly adapted from http://msdn.microsoft.com/en-us/library/ms733131.aspx.
    /// </summary>
    static string GetPassword(string prompt)
    {
      Console.WriteLine(prompt);
      string password = "";
      ConsoleKeyInfo info = Console.ReadKey(true);
      while (info.Key != ConsoleKey.Enter)
      {
        if (info.Key != ConsoleKey.Backspace)
        {
          password += info.KeyChar;
          info = Console.ReadKey(true);
        }
        else if (info.Key == ConsoleKey.Backspace)
        {
          if (!string.IsNullOrEmpty(password))
          {
            password = password.Substring
            (0, password.Length - 1);
          }
          info = Console.ReadKey(true);
        }
      }
      for (int i = 0; i < password.Length; i++)
        Console.Write("*");
      Console.WriteLine();
      return password;
    }
  }
}

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 Middleware git repository.

Perl Applications

This document uses the NET::LDAP LDAP module, which in turn requires the IO::Socket::SSL module. If you choose to use a different LDAP module it must be able to support either LDAP over SSL (LDAPS) or LDAP with the startTLS extension.

Download Perl 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/perl 

use Net::LDAP;
use strict;

my $server = "authn.directory.vt.edu";
my $port = 389;
my $dn = "";
my $pass = "PASSWORD";
my $cafile = "/PATH/TO/ED_AUTH/CA_file.pem";
my $base = "ou=People,dc=vt,dc=edu";
my $filter = "(uupid=UUPID)";

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');
$mesg->code && die $mesg->error;

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

my $entry = $mesg->entry(0);
if($entry)
{
    $dn = $entry->dn;
}

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

$mesg = $ldap->search(base => $base, filter => $filter,
                      attrs => ['eduPersonPrimaryAffiliation',
                                'eduPersonAffiliation']);
$mesg->code && die $mesg->error;

$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 $entry ($mesg->all_entries) { $entry->dump; }

$ldap->unbind;

Python Applications

This example uses python-ldap to communicate with ED-Auth. Since python-ldap is a wrapper around the OpenLDAP libraries, OpenLDAP and OpenSSL 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 and OpenSSL include and library paths).

Download Python 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
#!/usr/bin/python

import ldap

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

try:
    # initialize the ldap connection
    edauth = ldap.initialize("ldap://authn.directory.vt.edu:389")
    edauth.protocol_version=ldap.VERSION3
    edauth.start_tls_s()
    results = edauth.search(base, scope, searchFilter, None)
    resultType, result_data = edauth.result(results, 0)
    if result_data:
        dn = result_data[0][0]
        edauth.simple_bind_s(dn, password)
        if edauth.compare_s(dn, attribute, affiliation) == 0:
            print dn, "does not have", attribute, "=", affiliation
        else:
            print dn, "has", attribute, "=", affiliation
    else:
        print "UUPID not found"
except ldap.LDAPError,e:
    print str(e)

PHP Applications

This example uses PHP to connect to ED-Auth. You must compile PHP with LDAP for this example to work. See Appendix: Compiling OpenLDAP Libraries for instructions on installing OpenLDAP libraries.

You also need to make sure that certificates are set up properly. See Appendix: OpenLDAP and Certificates for instructions.

Download PHP 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
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
 <?php
     
 $host = 'ldap://authn.directory.vt.edu';
 $baseDn = 'ou=People,dc=vt,dc=edu';
 $userfield = 'uupid';
 $pid = 'UUPID';                                                               
 $credential = 'PASSWORD';
 $attr = 'eduPersonAffiliation';
 $value = 'VT-ACTIVE-MEMBER';
 $groupAttr = 'groupMembership';
 $group = 'uugid=department.staff,ou=Groups,dc=vt,dc=edu';
 
 /*ldap will bind anonymously, make sure we have some credentials*/
 if (isset($pid) && $pid != '' && isset($credential)) {
     $ldap = ldap_connect($host);
     ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
     if (!@ldap_start_tls($ldap)) {                                           
         print('Could not start TLS');
     } else if (isset($ldap) && $ldap != '') {
         /* search for pid dn */
         $result = @ldap_search($ldap, $baseDn, $userfield.'='.$pid, array('1.1'));               
         if ($result != 0) {
             $entries = ldap_get_entries($ldap, $result);                     
             $principal = $entries[0]['dn'];
             if (isset($principal)) {
                     
                 /* bind as this user */
                 if (@ldap_bind($ldap, $principal, $credential)) {
                     print("Authenticate success\n");
                     /* determine if the user has the $attr */
                     if(@ldap_compare($ldap, $principal, $attr, $value) === true) {              
                        print("$principal has $attr = $value\n");
                     } else {
                        print("$principal does not have $attr = $value\n");
                     }   
                     /* determine if the user is in the $group */
                     if(@ldap_compare($ldap, $principal, $groupAttr, $group) === true) {         
                        print("$principal is a member of $group\n");
                     } else {
                        print("$principal is not a member of $group\n");
                     }
                     /* Do a base search on the dn to view all
                        eduPersonAffilation(s). */
                     $r = ldap_read($ldap, $principal, $userfield.'='.$pid);
                     $e  = ldap_first_entry($ldap, $r);
                     $attrs = ldap_get_attributes($ldap, $e);
                     print("$attr: \n");
                     for($i = 0; $i < $attrs[$attr]['count'];
                         $i++)
                     {
                         print("\t".$attrs[$attr][$i]."\n");
                     }
                     ldap_free_result($r);
                 } else {
                     print('Authenticate failure');
                 }
             } else {
                 print('User not found in LDAP');
             }
             ldap_free_result($result);
         } else {
             print('Error occured searching the LDAP');
         }
         ldap_close($ldap);
     } else {
         print('Could not connect to LDAP at '.$host);
     }
 }
 ?>

Note: If you are testing ldaps you must specify a fully qualified URL:

ldap_connect("ldaps://$hostname:$port");

you cannot use:

ldap_connect($hostname, $port);

See the PHP Documentation for ldap_connect().

Hosting Users

If your website runs on Hosting, you can only test authentication using the https://secure.hosting.vt.edu URL associated with your website.

A note about the magic_quotes_gpc option and web forms

If you have magic_quotes_gpc set to On in your php.ini, people with certain special characters (‘,”,\, for example) in their passwords will be unable to authenticate, as the characters will be escaped. Either set magic_quotes_gpc to Off or use stripslashes() to ensure that these people can authenticate. Note that Hosting currently has this option On.

PEAR Net_LDAP

Here is a simplistic example that shows how to bind with PEAR Net_LDAP:

Download PHP Example

<?php

require_once 'Net/LDAP.php';

$host = 'directory.vt.edu';
$port = 389;
$base = 'ou=people,dc=vt,dc=edu';
$uupid = 'UUPID';
$pass = 'PASS';

$config = array (
    'basedn'    => $base,
    'host'      => $host,
    'port'      => $port,
    'starttls'  => true);

$ldap = Net_LDAP::connect($config);

if(Net_LDAP::isError($ldap))
{
    die('Could not connect to LDAP server: '.$ldap->getMessage());
}

$filter = "uupid=$uupid";

$search = $ldap->search($base, $filter);

if(Net_LDAP::isError($search))
{
    die('Could not fetch entry: '.$search->getMessage());
}

$entry = $search->entries();

if($entry)
{
    $dn = $entry[0]->dn();

    $result = $ldap->bind($dn,  $pass);
    if(Net_LDAP::isError($result))
    {
        die($result->getMessage()."\n");
    }
    else
    {
        echo "Authenticate success\n";
    }
}
else
{
    echo "No entries found for uupid = $uupid\n";
}

$ldap->done();
?>

Ruby Applications

To use this class 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

# Simple ED-Auth library; requires ruby-ldap built
# against OpenLDAP and OpenSSL.
# Sam Stephenson <sams@vt.edu> 2004-06-17
# Modified a bit by Brad Tilley < rtilley@vt.edu> 2006-12-13

require 'ldap'

class NotAuthenticatedError < Exception
end

class EdAuth
    HOST, PORT = 'authn.directory.vt.edu', 389
    DN, FILTER = 'ou=People,dc=vt,dc=edu', '(uupid=%s)'

    def initialize(pid, pass)
        @ldap = LDAP::SSLConn.new(HOST, PORT, true)
        @authenticated = false
        @pid, @pass = pid, pass
        @dn, @filter = DN, format(FILTER, pid)

        @authenticity = false
        @pri_affil, @affil = nil, nil
    end

    def authenticate
        return @authenticity if @authenticated
        begin
            @ldap.search(@dn, LDAP::LDAP_SCOPE_ONELEVEL,
                        @filter) {|c| @dn = c.get_dn}
            @ldap.bind(@dn, @pass)
            @authenticity = true
        rescue LDAP::ResultError
            @authenticity = false
        ensure
            @authenticated = true
        end
        @authenticity
    end

    def get_primary_affiliation
        query if @pri_affil.nil?
    puts @pri_affil
        @pri_affil
    end

    def get_affiliations
        query if @affil.nil?
    puts @affil
        @affil
    end

    def close
        begin
            @ldap.unbind
        rescue LDAP::InvalidDataError
        end
    end

    private
    def query
        raise NotAuthenticatedError unless @authenticated and
        @authenticity
        @ldap.search(@dn, LDAP::LDAP_SCOPE_SUBTREE, @filter,
                    ['eduPersonPrimaryAffiliation',
                    'eduPersonAffiliation']) do |entry|
                    @pri_affil
                    entry.get_values('edupersonprimaryaffiliation').shift
                    @affil = entry.get_values('edupersonaffiliation')
        end
    end
end

x = EdAuth.new('your_pid', 'your_password')
x.authenticate
x.get_primary_affiliation
x.get_affiliations
x.close

Using ED-Auth via Apache Modules

Please note you must accept PID/password credentials securely.

Since these credentials are given to the Apache server using HTTP Basic Auth, this means that all your restricted resources must be served over an SSL (HTTPS) encrypted connection. Failure to do so is a violation of the ED Usage Requirements.

Apache 1.3

These instructions require the OpenSSL, mod_ssl, OpenLDAP, and auth_ldap libraries. Links to these are provided in the resource appendix. You must have these libraries available to Apache for this to work (See Appendix: Compiling OpenLDAP Libraries for help). These instructions do not describe how to compile Apache, OpenSSL, or mod_ssl. Please refer to the documentation included with those individual programs for compilation instructions.

    ./configure --with-apxs=../apache/bin/apxs
                --with-ldap-sdk=openldap
                --with-sdk-headers=/usr/local/openldap/include
                --with-sdk-libs=/usr/local/openldap/lib
    LoadModule auth_ldap_module libexec/auth_ldap.so
AuthLDAPURL ldap://authn.directory.vt.edu:389/ou=People,dc=vt,dc=edu?uupid 
AuthLDAPStartTLS on 
AuthType Basic 
AuthName "Virginia Tech ED-Auth (PID/pass)"  
require valid-user
TLS_CACERT /path/to/cert/download/above/cert.pem

Apache 2.0

CPPFLAGS=-I/path/to/openldap/include \ 
LDFLAGS=-L/path/to/openldap/lib \ 
./configure --enable-ssl \ 
            --with-ldap \ 
            --enable-ldap=shared \ 
            --enable-auth-ldap=shared \ 
            --prefix=/location/to/install/apache
LDAPTrustedCA /path/to/CA.pem 
LDAPTrustedCAType BASE64_FILE 

<Directory  /some/directory > 
    AuthType Basic 
    AuthName "Virginia Tech ED-Auth (PID/pass)"  
    AuthLDAPURL  ldaps://authn.directory.vt.edu:636/ou=People,dc=vt,dc=edu?uupid  
    require valid-user 
</Directory>

Apache 2.2

CPPFLAGS=-I/path/to/openldap/include \ 
LDFLAGS=-L/path/to/openldap/lib \ 
./configure --enable-ssl \ 
            --with-ldap \ 
            --enable-ldap=shared \ 
            --enable-authnz-ldap=shared \ 
            --prefix=/location/to/install/apache
LDAPTrustedGlobalCert CA_BASE64 /path/to/CA.pem 

<Directory  /some/directory > 
    AuthType Basic
    AuthBasicProvider ldap
    AuthzLDAPAuthoritative Off
    AuthName "Virginia Tech ED-Auth (PID/pass)"  
    AuthLDAPURL  ldaps://authn.directory.vt.edu:636/ou=People,dc=vt,dc=edu?uupid  
    require valid-user 
</Directory>

Falling Through to file-based Authentication

Sometimes it is desirable to use both ED-Auth and some form of local authentication together. The following is an example of how to use Apache’s password files.

LDAPTrustedGlobalCert CA_BASE64 /path/to/CA.pem 

<Directory  /some/directory > 
    AuthType Basic
    AuthBasicProvider ldap file
    AuthUserFile /path/to/apache.passwd
    AuthzLDAPAuthoritative Off
    AuthName "Virginia Tech ED-Auth (PID/pass)"  
    AuthLDAPURL  ldaps://authn.directory.vt.edu:636/ou=People,dc=vt,dc=edu?uupid  
    require valid-user 
</Directory>

The AuthBasicProvider directive sets the order of auth modules to attempt to use. In this case it uses file-based auth with apache.passwd if the user is not found in the LDAP.

Note: If you reverse the order of the modules in AuthBasicProvider, you will be able to override users that exist in the LDAP in the password file. This is probably not desirable, and you should ensure that the Apache password file is properly protected.

Tomcat Servlet Container Authentication to ED-Auth

The Tomcat Servlet container can be configured to use the EdAuthRealm provided by the Middleware EDLdap Library to provide container-based authentication and authorization.

Integration steps:

Template server.xml file for configuring an ED-Auth container authentication realm.

<Server port="8005" shutdown="SHUTDOWN">

  <Listener className="org.apache.catalina.core.AprLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"
            descriptors="/edu/vt/middleware/ldap/catalina/mbeans/mbeans-descriptors.xml"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>

  <!-- Global JNDI resources -->
  <GlobalNamingResources>

    <!-- Add EdAuth Database as global resource -->
    <Resource name="EdAuthDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="EdAuth role database"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/edauth-users.xml" />

  </GlobalNamingResources>

  <!-- Define the Tomcat Stand-Alone Service -->
  <Service name="Catalina">

    <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
    <Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" 
               enableLookups="false" redirectPort="8443" protocol="AJP/1.3" />

    <!-- Define the top level container in our container hierarchy -->
    <Engine name="Catalina" defaultHost="localhost">

      <!-- Because this Realm is here, an instance will be shared globally -->

      <!-- Add EdAuth Realm to this Engine -->
      <Realm className="edu.vt.middleware.ldap.catalina.realm.EdAuthRealm"/>

      <!-- Define the default virtual host
           Note: XML Schema validation will not work with Xerces 2.2.
       -->
      <Host name="localhost" appBase="webapps"
       unpackWARs="true" autoDeploy="true"
       xmlValidation="false" xmlNamespaceAware="false">

      </Host>
    </Engine>
  </Service>
</Server>

EdAuthRealm also supports container-based authorization. Simply specify an in the section of the application web.xml to authorize users based on their membership in a given ED group. The following example requires the authenticated user to be a member of the //middleware.staff// ED group to access protected resources.

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  version="2.5">
...
  <security-role>
    <description>ED group describing the administrative role.</description>
    <role-name>middleware.staff</role-name>
  </security-role>
...
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Protected Resources</web-resource-name>
      <url-pattern>/protected/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>middleware.staff</role-name>
    </auth-constraint>
    <user-data-constraint>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
  </security-constraint>
...
</web-app>

PAM LDAP

PAM LDAP gives Unix and Linux machines the ability to authenticate against an LDAP server such as ED-Auth. PAM is highly tunable and powerful, and allows administrators to determine how services (login, xdm, ssh, etc.) authenticate users. These instructions assume you are compiling from source, but you are free to use your favorite distribution’s package at your own risk.

./configure --prefix=/path/to/pam_ldap \
            --with-ldap=/path/to/openldap \
            --with-ldap-lib=openldap
base ou=People,dc=vt,dc=edu
uri ldap://authn.directory.vt.edu
ldap_version 3
pam_login_attribute uupid
ssl start_tls
tls_checkpeer yes
tls_cacertfile /path/to/cachain.pem
uri ldap://authn.directory.vt.edu
base dc=vt,dc=edu
ssl start_tls
tls_cacertdir /etc/openldap/cacerts
tls_cacertfile /etc/openldap/certs/vt-cachain.pem
filter passwd (objectclass=virginiaTechPerson)
map passwd uidNumber uid
map passwd uid uupid
map passwd gidNumber uid
map passwd homeDirectory "${homeDirectory:-/home/$uupid}"
map passwd loginShell "${loginShell:-/bin/bash}"
#filter group (objectClass=virginiaTechGroup)
#map    group  uniqueMember     member
#map    group  cn               uugid
#map    group  gidNumber        uid
...
passwd:     files ldap
shadow:     files ldap
group:      files ldap
netgroup:   files ldap
automount:  files ldap
...

pGina Windows Authentication

pGina is a replacement for the authentication portion of Windows 2000/XP, and allows Windows users to authenticate against ED-Auth. This is especially useful in a lab or similar enviromment where PID/pass authentication is desired.

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 Middeware CA chain file

#############
# 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.

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 are examples for how to connect to ED-Auth. Please refer to Appendix: OpenLDAP and Certificates for information on how to set up certificates.

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

To search for a person:

ldapsearch -H ldap://authn.directory.vt.edu -x -Z \
    -b ou=People,dc=vt,dc=edu '(uupid=<uupid to search for>)'

To bind as a person:

ldapsearch -H ldap://authn.directory.vt.edu -x -Z \ 
    -b ou=People,dc=vt,dc=edu \ 
    -D uid=<uid to bind as>,ou=People,dc=vt,dc=edu \ 
    -W '(uupid=<uupid to search for>)'

Options Key:

Appendix: PHP, SSL, and Windows

To use LDAPS functionality in PHP on Windows you must create the following file:

c:\OpenLDAP\sysconf\ldap.conf

and place the following line in it:

TLS_CACERT c:\OpenLDAP\sysconf\certs\vt-cachain.pem

Next, place the Virginia Tech Middleware Certificate Chain in the

c:\OpenLDAP\sysconf\certs\

directory.

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, PHP, Ruby, and Apache 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-Auth.

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

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

CPPFLAGS=-I/path/to/ssl/include LDFLAGS=-L/path/to/ssl/lib \
./configure --prefix=/path/to/openldap \
            --enable-slapd=no \ 
            --enable-slurpd=no \ 
            --with-tls

Appendix: Resources