From 742e59f35198b42fb0ae8575aad3a08464f79e5c Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 05 Jul 2013 13:01:34 +0000
Subject: [PATCH] Backport fix for OPENDJ-1033: The Rest2LDAP servlet does not support SSL

---
 opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json       |   65 ++++++++++++++++-----
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java |   72 ++++++++++++++++++++++-
 2 files changed, 116 insertions(+), 21 deletions(-)

diff --git a/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index 743d308..7c2e135 100644
--- a/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -4,9 +4,40 @@
     "ldapConnectionFactories" : {
         // Unauthenticated connections used for performing bind requests.
         "default" : {
-            "connectionPoolSize"       : 10,
+            // Indicates whether or not LDAP connections should be secured using
+            // SSL or StartTLS. Acceptable values are:
+            //
+            // "none"     - use plain LDAP connections (default)
+            // "ssl"      - secure connection using LDAPS
+            // "startTLS" - secure connection using LDAP+StartTLS
+            //
+            "connectionSecurity"       : "none",
+
+            // Specifies the policy for trusting server certificates exchanged
+            // during SSL/StartTLS negotiation. This setting and the following
+            // trust policy settings will be ignored if there is no connection
+            // security. Acceptable values are:
+            //
+            // "trustAll" - blindly trust all server certificates (default)
+            // "jvm"      - only certificates signed by the authorities
+            //              associated with the host JVM will be accepted
+            // "file"     - use a file-based trust store for validating
+            //              certificates. This option requires the following
+            //              "fileBasedTrustManager*" settings to be configured.
+            //
+            "trustManager"             : "trustAll",
+
+            // File based trust manager configuration (see above).
+            "fileBasedTrustManagerType"     : "JKS",
+            "fileBasedTrustManagerFile"     : "/path/to/truststore",
+            "fileBasedTrustManagerPassword" : "password",
+
+            // Re-usable pool of 24 connections per server.
+            "connectionPoolSize"       : 24,
+
+            // Check pooled connections are alive every 30 seconds.
             "heartBeatIntervalSeconds" : 30,
-            
+
             // The preferred load-balancing pool.
             "primaryLDAPServers"       : [
                 {
@@ -19,13 +50,13 @@
                 // Empty.
             ]
         },
-        
+
         // Authenticated connections which will be used for searches during
         // authentication and proxied operations (if enabled). This factory
         // will re-use the server "default" configuration.
         "root" : {
             "inheritFrom"    : "default",
-            
+
             // Defines how authentication should be performed. Only "simple"
             // authentication is supported at the moment.
             "authentication" : {
@@ -36,7 +67,7 @@
             }
         }
     },
-    
+
     // The Rest2LDAP authentication filter configuration. The filter will be
     // disabled if the configuration is not present. Upon successful
     // authentication the filter will create a security context containing the
@@ -47,21 +78,21 @@
     "authenticationFilter" : {
         // Indicates whether the filter should allow HTTP BASIC authentication.
         "supportHTTPBasicAuthentication" : true,
-        
+
         // Indicates whether the filter should allow alternative authentication
         // and, if so, which HTTP headers it should obtain the username and
         // password from.
-        "supportAltAuthentication"        : true, 
+        "supportAltAuthentication"        : true,
         "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
         "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
-        
+
         // Indicates whether the authenticated LDAP connection should be cached
         // for use within the Rest2LDAP Servlet for subsequent LDAP operations.
         // If this is set to true then the Servlet will not need its own LDAP
         // connection factory and will also not need to use proxied
         // authorization.
         "reuseAuthenticatedConnection" : true,
-        
+
         // Specifies how LDAP authentications should be performed. The method
         // must be one of:
         //
@@ -74,29 +105,29 @@
         //                   substituting the username into the
         //                   "searchFilterTemplate" using %s substitution.
         "method" : "search-simple",
-        
+
         // The connection factory which will be exclusively used for
         // authenticating users using LDAP bind operations.
         "bindLDAPConnectionFactory" : "default",
-        
+
         // The SASL AuthzID template which will be used for "sasl-plain"
         // authentication. The %s format parameters will be substituted with
         // the client-provided username, using DN character escaping for DN
         // AuthzIDs.
         "saslAuthzIdTemplate" : "dn:uid=%s,ou=people,dc=example,dc=com",
-        
+
         // The connection factory which will be used for performing LDAP
         // searches to locate users when "search-simple" authentication is
         // enabled.
         "searchLDAPConnectionFactory" : "root",
-        
+
         // The search parameters to use for "search-simple" authentication. The
         // %s filter format parameters will be substituted with the
         // client-provided username, using LDAP filter string character escaping.
         "searchBaseDN"         : "ou=people,dc=example,dc=com",
         "searchScope"          : "sub", // Or "one".
         "searchFilterTemplate" : "(&(objectClass=inetOrgPerson)(uid=%s))"
-        
+
         // TODO: support for HTTP sessions?
     },
 
@@ -110,7 +141,7 @@
         // be passed on from the filter, which may not always be the case
         // if the filter is configured to use HTTP sessions.
         "ldapConnectionFactory" : "root",
-        
+
         // Specifies how LDAP authorization should be performed. The method
         // must be one of:
         //
@@ -125,12 +156,12 @@
         //                 authorization will only be used if there is no
         //                 pre-authenticated connection available.
         "authorizationPolicy" : "proxy",
-        
+
         // The AuthzID template which will be used for proxied authorization.
         // The template should contain fields which are expected to be found in
         // the security context create during authentication, e.g. "dn" and "id".
         "proxyAuthzIdTemplate" : "dn:{dn}",
-        
+
         // The REST APIs and their LDAP attribute mappings.
         "mappings" : {
             "/users" : {
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 7c97bcd..b9cbbe1 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -21,6 +21,8 @@
 import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
 import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
 
+import java.io.IOException;
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
@@ -52,13 +54,16 @@
 import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
 import org.forgerock.opendj.ldap.RDN;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
+import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.TrustManagers;
 import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -70,6 +75,23 @@
  * collections.
  */
 public final class Rest2LDAP {
+
+    /**
+     * Indicates whether or not LDAP client connections should use SSL or
+     * StartTLS.
+     */
+    private enum ConnectionSecurity {
+        NONE, SSL, STARTTLS
+    }
+
+    /**
+     * Specifies the mechanism which should be used for trusting certificates
+     * presented by the LDAP server.
+     */
+    private enum TrustManagerType {
+        TRUSTALL, JVM, FILE
+    }
+
     /**
      * A builder for incrementally constructing LDAP resource collections.
      */
@@ -965,6 +987,48 @@
             bindRequest = null;
         }
 
+        // Parse SSL/StartTLS parameters.
+        final ConnectionSecurity connectionSecurity =
+                configuration.get("connectionSecurity").defaultTo(ConnectionSecurity.NONE).asEnum(
+                        ConnectionSecurity.class);
+        final LDAPOptions options = new LDAPOptions();
+        if (connectionSecurity != ConnectionSecurity.NONE) {
+            try {
+                // Configure SSL.
+                final SSLContextBuilder builder = new SSLContextBuilder();
+
+                // Parse trust store configuration.
+                final TrustManagerType trustManagerType =
+                        configuration.get("trustManager").defaultTo(TrustManagerType.TRUSTALL)
+                                .asEnum(TrustManagerType.class);
+                switch (trustManagerType) {
+                case TRUSTALL:
+                    builder.setTrustManager(TrustManagers.trustAll());
+                    break;
+                case JVM:
+                    // Do nothing: JVM trust manager is the default.
+                    break;
+                case FILE:
+                    final String fileName =
+                            configuration.get("fileBasedTrustManagerFile").required().asString();
+                    final String password =
+                            configuration.get("fileBasedTrustManagerPassword").asString();
+                    final String type = configuration.get("fileBasedTrustManagerType").asString();
+                    builder.setTrustManager(TrustManagers.checkUsingTrustStore(fileName,
+                            password != null ? password.toCharArray() : null, type));
+                    break;
+                }
+                options.setSSLContext(builder.getSSLContext());
+                options.setUseStartTLS(connectionSecurity == ConnectionSecurity.STARTTLS);
+            } catch (GeneralSecurityException e) {
+                // Rethrow as unchecked exception.
+                throw new IllegalArgumentException(e);
+            } catch (IOException e) {
+                // Rethrow as unchecked exception.
+                throw new IllegalArgumentException(e);
+            }
+        }
+
         // Parse primary data center.
         final JsonValue primaryLDAPServers = configuration.get("primaryLDAPServers");
         if (!primaryLDAPServers.isList() || primaryLDAPServers.size() == 0) {
@@ -972,7 +1036,7 @@
         }
         final ConnectionFactory primary =
                 parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
-                        heartBeatIntervalSeconds);
+                        heartBeatIntervalSeconds, options);
 
         // Parse secondary data center(s).
         final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
@@ -981,7 +1045,7 @@
             if (secondaryLDAPServers.size() > 0) {
                 secondary =
                         parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
-                                heartBeatIntervalSeconds);
+                                heartBeatIntervalSeconds, options);
             } else {
                 secondary = null;
             }
@@ -1029,12 +1093,12 @@
 
     private static ConnectionFactory parseLDAPServers(final JsonValue config,
             final BindRequest bindRequest, final int connectionPoolSize,
-            final int heartBeatIntervalSeconds) {
+            final int heartBeatIntervalSeconds, final LDAPOptions options) {
         final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
         for (final JsonValue server : config) {
             final String host = server.get("hostname").required().asString();
             final int port = server.get("port").required().asInteger();
-            ConnectionFactory factory = new LDAPConnectionFactory(host, port);
+            ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
             if (bindRequest != null) {
                 factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
             }

--
Gitblit v1.10.0