# JNDI - LDAP und Active Directory: Problem mit SSL



## Grizzly (13. Dez 2011)

Hallo zusammen,

ich habe die Ehre einen Active Directory Server per LDAP anzusprechen. Hintergrund ist, dass wir für einen Test diesen mit Benutzer "befüllen" und natürlich anschließend diese auch wieder löschen wollen. Ohne SSL funktioniert alles wunderbar. Ich kann auch neue Benutzer anlegen. Allerdings scheitert die Sache dann, wenn ich das Passwort festlegen möchte. Ich habe dann im Internet nachgelesen, dass der Active Directory Server dafür aus Sicherheitsgründen eine SSL Verbindung vorschreibt.

Nach etwas suchen habe ich die folgende Anleitung gefunden:
Configuring an SSL Certificate for Microsoft Active Directory - Crowd 2.3 - Atlassian Documentation - Confluence
Die Schritte 1 und 2 habe ich so durchgeführt. Im Schritt 3 habe ich allerdings das Zertifikat in einen extra Keystore namens myCaCerts.jks aufgenommen anstatt dieses im JRE unterzubringen. Das Programm habe ich dann noch entsprechend angepasst, mit dem Ergebnis, dass ich über die SSL Verbindung ein "Connection timeout" beim Verbindungsaufbau bekomme.


```
package test;

import java.io.File;
import java.util.List;

import test.ad.ADUser;
import test.ad.ActiveDirectory;

public class Test
  {
  
  /**
   * Executes the program.
   * @param args The command line arguments.
   */
  public static void main(String[] args)
    {
    String domainName, domainRoot, serverAddress, administratorName, administratorPassword;
    ActiveDirectory ad;
    boolean success = true;
    
    System.setProperty("javax.net.ssl.keyStore", "files" + File.separatorChar + "myCaCerts.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "XXXXX");
    System.setProperty("javax.net.ssl.trustStore", "files" + File.separatorChar + "myCaCerts.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", "XXXXX");
    System.setProperty("javax.net.debug", "all");
    
    domainName = "test.local";
    domainRoot = "DC=test,DC=local";
    serverAddress = "172.XXX.XXX.XXX";
    administratorName = "CN=Administrator,CN=Users," + domainRoot;
    administratorPassword = "XXXXX";
    
    ad = new ActiveDirectory(domainName, domainRoot, serverAddress, administratorName, administratorPassword);
    success = ad.initialize();
    if (success)
      {
      users = ad.searchUsers("foo");
      for (ADUser fooUser : users)
        {
        System.out.println("CN=" + fooUser.getCommonName());
        }
      }
    if (success)
      success = ad.close();
    
    }
  
  }
```


```
package test.ad;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.ContextNotEmptyException;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.InvalidAttributesException;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

/**
 * This class represents the active directory.
 */
public class ActiveDirectory
  {
  /** The format for a date time value. */
  private final static DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
  
  /** The domain name. */
  private String domainName;
  /** The domain root. */
  private String domainRoot;
  /** The address of the server. */
  private String serverAddress;
  /** The administrator name. */
  private String administratorName;
  /** The administrator password. */
  private String administratorPassword;
  /** The LDAP environment. */
  private Hashtable<String, String> environment;
  /** The LDAP context. */
  private LdapContext context;
  
  /**
   * Creates an active directory object.
   * @param domainName The domain name.
   * @param domainRoot The domain root.
   * @param serverAddress The address of the server.
   * @param administratorName The administrator name.
   * @param administratorPassword The administrator password.
   */
  public ActiveDirectory(String domainName, String domainRoot, String serverAddress, String administratorName,
      String administratorPassword)
    {
    super();
    this.domainName = domainName;
    this.domainRoot = domainRoot;
    this.serverAddress = serverAddress;
    this.administratorName = administratorName;
    this.administratorPassword = administratorPassword;
    this.environment = new Hashtable<String, String>();
    }
  
  /**
   * Initializes the active directory context.
   * @return <code>true</code>, if the context was initialized successfully, otherwise <code>false</code>.
   * @see #close()
   */
  public boolean initialize()
    {
    boolean success = true;
    
    this.environment.clear();
    this.environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    // --- Set security credentials ---
    this.environment.put(Context.SECURITY_AUTHENTICATION, "simple");
    this.environment.put(Context.SECURITY_PRINCIPAL, this.administratorName);
    this.environment.put(Context.SECURITY_CREDENTIALS, this.administratorPassword);
    this.environment.put(Context.SECURITY_PROTOCOL, "ssl");
    // --- Connect to domain controller ---
    this.environment.put(Context.PROVIDER_URL, "ldap://" + this.serverAddress + ":636");
//    this.environment.put(Context.PROVIDER_URL, "ldap://" + this.serverAddress);
    try
      {
      this.context = new InitialLdapContext(this.environment, null);
      }
    catch (NamingException e)
      {
      e.printStackTrace();
      success = false;
      }
    return success;
    }
  
  /**
   * <p>Closes the active directory context.</p>
   * <p>If the context isn't initialized nothing will happen.</p>
   * @return <code>true</code>, if the context was closed successfully, otherwise <code>false</code>.
   * @see #initialize()
   */
  public boolean close()
    {
    boolean success = true;
    
    if (this.context != null)
      {
      try
        {
        this.context.close();
        }
      catch (NamingException e)
        {
        e.printStackTrace();
        success = false;
        }
      this.context = null;
      }
    return success;
    }
  
  public String getStringValue(SearchResult result, String attrID) throws NamingException
    {
    Attribute attr;
    String value = null;
    
    attr = result.getAttributes().get(attrID);
    if ((attr != null) && (attr.get() != null))
      {
      value = (String) attr.get();
      }
    return value;
    }
  
  public Long getLongValue(SearchResult result, String attrID) throws NamingException
    {
    String str;
    Long value = null;
    
    str = this.getStringValue(result, attrID);
    if (str != null)
      {
      try
        {
        value = Long.valueOf(Long.parseLong(str));
        }
      catch (NumberFormatException e)
        {
        System.err.println("Couldn't parse '" + str + "' of field '" + attrID + "' into long.");
        e.printStackTrace();
        }
      }
    return value;
    }
  
  public FileTime getFileTimeValue(SearchResult result, String attrID) throws NamingException
    {
    String str;
    FileTime value = null;
    long l;
    
    str = this.getStringValue(result, attrID);
    if (str != null)
      {
      if (str.equals(Long.toString(Long.MAX_VALUE)))
        value = new FileTime(Long.MAX_VALUE);
      else
        {
        try
          {
          l = Long.parseLong(str);
          value = new FileTime(l);
          }
        catch (NumberFormatException e)
          {
          System.err.println("Couldn't parse '" + str + "' of field '" + attrID + "' into long for FILETIME.");
          e.printStackTrace();
          }
        }
      
      }
    return value;
    }
  
  public Date getDateValue(SearchResult result, String attrID) throws NamingException
    {
    String str;
    Date value = null;
    
    str = this.getStringValue(result, attrID);
    if (str != null)
      {
      if (str.equals(Long.toString(Long.MAX_VALUE)))
        value = new Date(Long.MAX_VALUE);
      else if (str.length() != 17)
        System.err.println("Date '" + str + "' of field '" + attrID + "' has wrong length. 17 expected; "
            + "read: " + str.length());
      else if (!str.endsWith(".0Z"))
        System.err.println("Date '" + str + "' of field '" + attrID + "' has wrong suffix. '.0Z' expected.");
      else
        {
        try
          {
          value = DATE_TIME_FORMAT.parse(str.substring(0, 14));
          }
        catch (ParseException e)
          {
          System.err.println("Couldn't parse '" + str + "' of field '" + attrID + "' into date.");
          e.printStackTrace();
          }
        }
      }
    return value;
    }
  
  /**
   * Returns the user stored in the answers as a list.
   * @param answer The answer.
   * @return The user list.
   * @throws NamingException If an error occurred.
   */
  private List<ADUser> getUsers(NamingEnumeration<SearchResult> answer) throws NamingException
    {
    Vector<ADUser> users;
    SearchResult result;
    NamingEnumeration<? extends Attribute> attrEnum;
    Attribute attr;
    ADUser user;
    
    users = new Vector<ADUser>();
    
    while (answer.hasMore())
      {
      result = answer.next();
      user = new ADUser();
      // --- Object class ---
      attr = result.getAttributes().get("objectClasses");
      if (attr != null)
        {
        for (int index = 0; index < attr.size(); index++)
          user.addObjectClass((String) attr.get(index));
        }
      // --- Common name ---
      user.setCommonName(this.getStringValue(result, "cn"));
      // --- Description ---
      user.setDescription(this.getStringValue(result, "discription"));
      // --- Display name ---
      user.setDisplayName(this.getStringValue(result, "displayName"));
      // --- Distinguished name ---
      user.setDistinguishedName(this.getStringValue(result, "distinguishedName"));
      // --- Given name ---
      user.setGivenName(this.getStringValue(result, "givenName"));
      // --- Member of ---
      attr = result.getAttributes().get("memberOf");
      if (attr != null)
        {
        for (int index = 0; index < attr.size(); index++)
          user.addMemberOf((String) attr.get(index));
        }
      // --- Name ---
      user.setName(this.getStringValue(result, "name"));
      // --- Object category ---
      user.setObjectCategory(this.getStringValue(result, "objectCategory"));
      // --- SAM account name ---
      user.setSamAccountName(this.getStringValue(result, "sAMAccountName"));
      // --- Surname ---
      user.setSurname(this.getStringValue(result, "sn"));
      // --- User principal name ---
      user.setUserPrincipalName(this.getStringValue(result, "userPrincipalName"));
      // --- Account expires ---
      user.setAccountExpires(this.getFileTimeValue(result, "accountExpires"));
      // --- Last log-on time-stamp ---
      user.setLastLogonTimestamp(this.getFileTimeValue(result, "lastLogonTimestamp"));
      // --- Log-on count ---
      user.setLogonCount(this.getLongValue(result, "logonCount"));
      // --- Password last set ---
      user.setPasswordLastSet(this.getFileTimeValue(result, "pwdLastSet"));
      // --- When changed ---
      user.setWhenChanged(this.getDateValue(result, "whenChanged"));
      // --- When created ---
      user.setWhenCreated(this.getDateValue(result, "whenCreated"));
      
      users.addElement(user);
      
      System.out.println(result.getName());
      attrEnum = result.getAttributes().getAll();
      while (attrEnum.hasMore())
        {
        attr = attrEnum.next();
        System.out.print("   " + attr.getID() + " :\t");
        for (int index = 0; index < attr.size(); index++)
          {
          if (index > 0)
            System.out.print(" | ");
          System.out.print(attr.get(index));
          if (!(attr.get(index) instanceof String))
            {
            System.out.print(" [" + attr.get(index).getClass().getCanonicalName() + "]");
            }
          }
        System.out.println();
        }
      }
    
    return users;
    }
  
  /**
   * Searches for all active directory users with the given surname.
   * @param surname The surname.
   * @return A list of all users which have the given surname.
   */
  public List<ADUser> searchUsers(String surname)
    {
    List<ADUser> users = null;
    Attributes matchingAttributes;
    NamingEnumeration<SearchResult> answer;
    
    // --- Create the search criteria ---
    matchingAttributes = new BasicAttributes(true);
    matchingAttributes.put(new BasicAttribute("sn", surname));
    // --- Perform the search ---
    try
      {
      answer = this.context.search("CN=Users," + this.domainRoot, matchingAttributes);
      users = this.getUsers(answer);
      }
    catch (NamingException e)
      {
      e.printStackTrace();
      }
    return users;
    }
  
  /**
   * Returns the active directory user with the given distinguished name.
   * @param distinguishedName The distinguished name.
   * @return The user or <code>null</code>, if no or more than <code>1</code> users exists with the given
   * distinguished name.
   */
  public ADUser getUser(String distinguishedName)
    {
    List<ADUser> users;
    Attributes matchingAttributes;
    NamingEnumeration<SearchResult> answer;
    ADUser user = null;
    
    // --- Create the search criteria ---
    matchingAttributes = new BasicAttributes(true);
    matchingAttributes.put(new BasicAttribute("distinguishedName", distinguishedName));
    // --- Perform the search ---
    try
      {
      answer = this.context.search("CN=Users," + this.domainRoot, matchingAttributes);
      users = this.getUsers(answer);
      if (users.size() == 1)
        {
        user = users.get(0);
        }
      }
    catch (NamingException e)
      {
      e.printStackTrace();
      }
    return user;
    }
  
  /**
   * Returns the distinguished name for the given common name.
   * @param cn The common name.
   * @return The distinguished name.
   */
  private String getDistinguishedName(String cn)
    {
    return "CN=" + cn + ",CN=Users," + this.domainRoot;
    }
  
  }
```


```
keyStore is : files\myCaCerts.jks
keyStore type is : jks
keyStore provider is : 
init keystore
init keymanager of type SunX509
trustStore is: files\myCaCerts.jks
trustStore type is : jks
trustStore provider is : 
init truststore
adding as trusted cert:
  Subject: CN=test-DOM-CA, DC=test, DC=aurenz, DC=local
  Issuer:  CN=test-DOM-CA, DC=test, DC=aurenz, DC=local
  Algorithm: RSA; Serial number: 0x287c4f809bb8d8a7429f18481c7dc59c
  Valid from Mon Dec 12 17:04:30 CET 2011 until Sun Dec 12 17:14:29 CET 2021

trigger seeding of SecureRandom
done seeding SecureRandom
javax.naming.CommunicationException: 172.XXX.XXX.XXX:636 [Root exception is java.net.ConnectException: Connection timed out: connect]
	at com.sun.jndi.ldap.Connection.<init>(Unknown Source)
	at com.sun.jndi.ldap.LdapClient.<init>(Unknown Source)
	at com.sun.jndi.ldap.LdapClient.getInstance(Unknown Source)
	at com.sun.jndi.ldap.LdapCtx.connect(Unknown Source)
	at com.sun.jndi.ldap.LdapCtx.<init>(Unknown Source)
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(Unknown Source)
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(Unknown Source)
	at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(Unknown Source)
	at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(Unknown Source)
	at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
	at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
	at javax.naming.InitialContext.init(Unknown Source)
	at javax.naming.ldap.InitialLdapContext.<init>(Unknown Source)
	at test.ad.ActiveDirectory.initialize(ActiveDirectory.java:91)
	at test.Test.main(Test.java:35)
Caused by: java.net.ConnectException: Connection timed out: connect
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.PlainSocketImpl.doConnect(Unknown Source)
	at java.net.PlainSocketImpl.connectToAddress(Unknown Source)
	at java.net.PlainSocketImpl.connect(Unknown Source)
	at java.net.SocksSocketImpl.connect(Unknown Source)
	at java.net.Socket.connect(Unknown Source)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.connect(Unknown Source)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.<init>(Unknown Source)
	at com.sun.net.ssl.internal.ssl.SSLSocketFactoryImpl.createSocket(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at com.sun.jndi.ldap.Connection.createSocket(Unknown Source)
	... 15 more
```

Und noch eine interessante Sache am Rande: Wie ich bereits gesagt habe - ohne SSL funktioniert die Geschichte ohne Probleme (bloß halt nicht mit Passwörtern). Ich habe dann auch einmal probiert, mit anderen LDAP Browsern (Jxplorer, Softerra LDAP Administrator 2011.2, LDAP Admin [ldapadmin.sourceforge.net] und Softerra LDAP Browser 4.5.10625.0) auf den LDAP des ADS zu zugreifen. Das hat aber weder über SSL noch normal funktioniert.


----------



## KSG9|sebastian (13. Dez 2011)

Connection refused heißt dass es dort nix gibt was auf dem Port hört.
Läuft der LDAP wirklich auf Port 636?


----------



## Grizzly (13. Dez 2011)

Okay, Problem gefunden:
Zum Einen hatte ich im Programm die IP falsch eingetragen.
Zum Anderen ist der Port 636 nicht aus meinem Netz erreichbar, wohl aber aus dem Netz, in dem mein Netzwerk-Admin sitzt. Sprich bei ihm hat alles wunderbar mit seinem LDAP Browser Programm funktioniert, bei mir war tote Hose. Habe zwischenzeitlich einen virtuellen Rechner im entsprechenden Netzwerk bekommen und jetzt funktioniert alles wunderbar.


----------

