mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

dugan
24.25.2009 088d8dc0d4611ebb36443a9aa8724f2d6776befd
Add unit test for Issue 3805 (SASL phase2). Also, fix buffer overflow in SASL Byte Channel found by the test.
1 files added
1 files modified
321 ■■■■■ changed files
opends/src/server/org/opends/server/extensions/SASLByteChannel.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java 316 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLByteChannel.java
@@ -92,7 +92,8 @@
      this.saslContext = saslContext;
      this.channel = connection.getChannel();
      this.readBuffer = ByteBuffer.allocate(connection.getAppBufferSize());
      this.decodeBuffer = ByteBuffer.allocate(connection.getAppBufferSize());
      this.decodeBuffer =
                ByteBuffer.allocate(connection.getAppBufferSize() + lengthSize);
    }
    /**
@@ -217,7 +218,7 @@
      //The buffer length is greater than what is there, save what is there,
      //figure out how much more is needed and return.
      if(bufLength > readBuffer.position()) {
        neededBytes = bufLength - readBuffer.position() + 4;
        neededBytes = bufLength - readBuffer.position() + lengthSize;
        readBuffer.flip();
        decodeBuffer.put(readBuffer);
        readBuffer.clear();
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java
New file
@@ -0,0 +1,316 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Random;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.naming.ldap.*;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.testng.Assert;
/**
 * This class tests SASL confidentiality/integrity over TLS (SSL). It
 * generates binary data larger than the TLS buffer size to make sure
 * that the data is processed correctly.
 *
 */
public class SASLOverTLSTestCase extends ExtensionsTestCase {
  private static int KB = 1024;
  private static final String factory = "com.sun.jndi.ldap.LdapCtxFactory";
  //Password policy
  private static final String pwdPolicy = "Temp PWD Policy";
  private static final String pwdPolicyDN =
                     "cn=" + pwdPolicy + ",cn=Password Policies,cn=config";
  //Keystore/truststore paths
  private String keyStorePath =
         DirectoryServer.getInstanceRoot() + File.separator + "config" +
         File.separator + "client.keystore";
  private String trustStorePath =
          DirectoryServer.getInstanceRoot() + File.separator +  "config" +
          File.separator + "client.truststore";
  //DNS
  private static String testUserDN = "cn=test.User, o=test";
  private static final String digestDN = "dn:"+ testUserDN;
  private static String dirMgr = "cn=Directory Manager";
  //Auth methods
  private static String simple = "simple";
  private static String digest =  "DIGEST-MD5";
  //Test QOS
  private static String confidentiality = "auth-conf";
  private static String integrity = "auth-int";
  //Go from 8KB to 64KB.
  @DataProvider(name = "kiloBytes")
   public Object[][] kiloBytes() {
     return new Object[][] {
          {8},
          {16},
          {24},
          {32},
          {64}
     };
   }
  @BeforeClass
  public void setup() throws Exception {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "create-password-policy",
            "--policy-name", pwdPolicy,
            "--set", "password-attribute:userPassword",
            "--set", "default-password-storage-scheme: Clear"
            );
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "quality-of-protection:" + "confidentiality",
            "--set", "server-fqdn:localhost");
    keyStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                          "config" + File.separator + "client.keystore";
    trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    System.setProperty("javax.net.ssl.keyStore",keyStorePath);
    System.setProperty("javax.net.ssl.keyStorePassword", "password");
    System.setProperty("javax.net.ssl.trustStore", trustStorePath);
    System.setProperty("javax.net.ssl.trustStorePassword", "password");
    addTestEntry();
  }
  @AfterClass(alwaysRun = true)
  public void tearDown() throws Exception {
    deleteTestEntry();
    TestCaseUtils.dsconfig(
        "delete-password-policy",
        "--policy-name", pwdPolicy
    );
    TestCaseUtils.dsconfig(
        "set-sasl-mechanism-handler-prop",
        "--handler-name", "DIGEST-MD5",
        "--reset", "server-fqdn",
        "--reset", "quality-of-protection");
  }
  /**
   * Test DIGEST-MD5 integrity over TLS.
   *
   * @throws NamingException If there was an JNDi naming error.
   * @throws IOException If there was an IO error occurs.
   */
  @Test(dataProvider = "kiloBytes")
  public void sslIntegrity(int size)throws NamingException, IOException {
    TestCaseUtils.dsconfig(
        "set-sasl-mechanism-handler-prop",
        "--handler-name", "DIGEST-MD5",
        "--set", "quality-of-protection:" + "integrity");
    sslTest(size, integrity);
  }
  /**
   * Test DIGEST-MD5 confidentiality over TLS.
   *
   * @throws NamingException If there was an JNDi naming error.
   * @throws IOException If there was an IO error occurs.
   */
  @Test(dataProvider = "kiloBytes")
  public void sslConfidentiality(int size)throws NamingException, IOException {
    TestCaseUtils.dsconfig(
        "set-sasl-mechanism-handler-prop",
        "--handler-name", "DIGEST-MD5",
        "--set", "quality-of-protection:" + "confidentiality");
    sslTest(size, confidentiality);
  }
 /**
  * Generate the test attributes, replace it in the entry, then read it
  * back to make sure it is the same as the original.
  *
  * @param size The number of KBs to generate in the random bytes.
  * @param qop The quality of protection.
  *
  * @throws NamingException If a JNDI naming error occurs.
  * @throws IOException If there was an IO error.
  */
  private void
  sslTest(int size, String qop) throws NamingException, IOException {
    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
    String url = "ldaps://localhost:" + TestCaseUtils.getServerLdapsPort();
    env.put(Context.PROVIDER_URL, url);
    env.put(Context.SECURITY_AUTHENTICATION,  digest);
    env.put(Context.SECURITY_PRINCIPAL, digestDN);
    env.put(Context.SECURITY_CREDENTIALS, "password");
    env.put("java.naming.ldap.attributes.binary", "jpegPhoto");
    env.put("javax.security.sasl.qop", qop);
    LdapContext ctx = new InitialLdapContext(env, null);
    byte[] jpegBytes = getRandomBytes(size);
    ModificationItem[] mods = new ModificationItem[1];
    Attribute jpegPhoto = new BasicAttribute("jpegPhoto", jpegBytes);
    mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, jpegPhoto);
    ctx.modifyAttributes(testUserDN, mods);
    Attributes testAttributes = ctx.getAttributes(testUserDN);
    Attribute jpegPhoto1 = testAttributes.get("jpegPhoto");
    byte[] jpegBytes1 = (byte[]) jpegPhoto1.get();
    Assert.assertTrue(Arrays.equals(jpegBytes, jpegBytes1));
    ctx.close();
  }
  /**
   * This test was originally testing DIGEST-MD5 confidentiality over StartTLS,
   * but JNDI had problems doing DIGEST-MD5 over StartTLS so the auth method was
   * changed to simple.
   *
   * @throws NamingException If there was an JNDi naming error.
   * @throws IOException If there was an IO error.
   */
  @Test(dataProvider = "kiloBytes")
  public void StartTLS(int size) throws NamingException, IOException {
    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
    String url = "ldap://localhost:" + TestCaseUtils.getServerLdapPort();
    env.put(Context.PROVIDER_URL, url);
    env.put("java.naming.ldap.attributes.binary", "jpegPhoto");
    LdapContext ctx = new InitialLdapContext(env, null);
    StartTlsResponse tls =
      (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
    tls.setHostnameVerifier(new SampleVerifier());
    tls.negotiate();
    ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION,  simple);
    ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, testUserDN);
    ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, "password");
    byte[] jpegBytes = getRandomBytes(size);
    ModificationItem[] mods = new ModificationItem[1];
    Attribute jpegPhoto = new BasicAttribute("jpegPhoto", jpegBytes);
    mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, jpegPhoto);
    ctx.modifyAttributes(testUserDN, mods);
    Attributes testAttributes = ctx.getAttributes(testUserDN);
    Attribute jpegPhoto1 = testAttributes.get("jpegPhoto");
    byte[] jpegBytes1 = (byte[]) jpegPhoto1.get();
    Assert.assertTrue(Arrays.equals(jpegBytes, jpegBytes1));
    ctx.close();
  }
  /**
   * Add the entry we will use. It has it's own password
   * policy that uses clear a storage scheme.
   *
   * @throws NamingException If the entry cannot be added.
   */
  private void addTestEntry() throws NamingException {
    Attribute objectClass = new BasicAttribute("objectclass");
    objectClass.add("top");
    objectClass.add("person");
    objectClass.add("organizationalPerson");
    objectClass.add("inetOrgPerson");
    Attribute pwdPolicy =
                  new BasicAttribute("ds-pwp-password-policy-dn",pwdPolicyDN);
    Attribute cn = new BasicAttribute("cn", "test");
    cn.add("test.User");
    Attribute sn = new BasicAttribute("sn","User");
    Attributes entryAttrs = new BasicAttributes();
    entryAttrs.put(objectClass);
    entryAttrs.put(cn);
    entryAttrs.put(sn);
    entryAttrs.put(pwdPolicy);
    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
    String url = "ldaps://localhost:" + TestCaseUtils.getServerLdapsPort();
    env.put(Context.PROVIDER_URL, url);
    env.put(Context.SECURITY_PRINCIPAL, dirMgr);
    env.put(Context.SECURITY_CREDENTIALS, "password");
    env.put(Context.SECURITY_AUTHENTICATION, simple);
    DirContext ctx = new InitialDirContext(env);
    ctx.createSubcontext(testUserDN, entryAttrs);
    ModificationItem[] mods = new ModificationItem[1];
    Attribute pwd = new BasicAttribute("userPassword", "password");
    mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, pwd);
    ctx.modifyAttributes(testUserDN, mods);
    ctx.close();
  }
  /**
   * Get a byte buffer with a random set of bytes.
   *
   * @param kbs The number of KB (kilo-bytes) to generate.
   * @return Byte array of random bytes.
   */
  private static byte[] getRandomBytes(int kbs) {
    byte[] randomBytes = new byte[kbs * KB];
    Random r = new Random(0);
    for (int i = 0; i < randomBytes.length; i++) {
      randomBytes[i] = (byte) r.nextInt();
    }
    return randomBytes;
  }
/**
 * Delete the test entry.
 *
 * @throws NamingException If the entry cannot be deleted.
 */
  private void deleteTestEntry() throws NamingException {
    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
    String url = "ldaps://localhost:" + TestCaseUtils.getServerLdapsPort();
    env.put(Context.PROVIDER_URL, url);
    env.put(Context.SECURITY_PRINCIPAL, dirMgr);
    env.put(Context.SECURITY_CREDENTIALS, "password");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    DirContext ctx = new InitialDirContext(env);
    ctx.destroySubcontext(testUserDN);
    ctx.close();
  }
  /**
   * Verifier class so JNDI startTLS will work with "localhost" host name.
   * Returns trues, accepting any host name.
   */
  class SampleVerifier implements HostnameVerifier {
    public boolean verify(String hostname, SSLSession session) {
      return true;
    }
  }
}