From 088d8dc0d4611ebb36443a9aa8724f2d6776befd Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Tue, 24 Feb 2009 02:25:04 +0000
Subject: [PATCH] Add unit test for Issue 3805 (SASL phase2). Also, fix buffer overflow in SASL Byte Channel found by the test.
---
opends/src/server/org/opends/server/extensions/SASLByteChannel.java | 5
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 319 insertions(+), 2 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/SASLByteChannel.java b/opends/src/server/org/opends/server/extensions/SASLByteChannel.java
index 0d8555f..7498594 100644
--- a/opends/src/server/org/opends/server/extensions/SASLByteChannel.java
+++ b/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();
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java
new file mode 100644
index 0000000..07fcbed
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SASLOverTLSTestCase.java
@@ -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;
+ }
+ }
+}
--
Gitblit v1.10.0