From 2a448112a3561266927aa3352f3fe25c72683a1f Mon Sep 17 00:00:00 2001
From: Jack Magne <jmagne@dhcp-32-224.sjc.redhat.com>
Date: Fri, 9 Mar 2012 13:15:02 -0800
Subject: [PATCH] Provide Custom PKI JNDI Realm.

Provide a Realm that provides the following:

1. Allows SSL client certificate authentation upon protected URLs.
For now we are protecting the new DRM Rest functions.

2. Allows simple PKI ACL checking like we have in the current server.
  This is accomplished with the help of a simple file that maps URLs
  to ACL resourceIDs and operations.

3. DRMRestClient now support SSL Client authentication to test the feature.

How to test this:

Install new KRA server, after installing build pki-core rpm.
Uncomment "PKIJNDIRealm" settings in conf/server.xml
Some customization will be needed for instance specific info. See
the sample in server.xml.
Uncomment the "Security Constraint" and "login-config" settings webapps/kra/WEB-INF/web.xml

In running DRMTest.java in eclipse do the following:
   Change the arguments to support SSL Client auth such as:
      -h localhost -p 10443 -w secret -d ~/archive-test -s true -c "KRA Administrator of Instance pki-kra's SjcRedhat Domain ID"
       where the new flags are -s = true for SSL and -c = <client auth cert name>

   Export the KRA's admin/agent client auth cert from Firefox to a pk12 file.
   Import this cert into ~/archive-test by using "pk12util" utility.

Run the DRMTest.java program in eclipse and observe the results. There should be a prompt
for a client cert.
---
 pki/.classpath                                     |    1 +
 pki/base/common/src/CMakeLists.txt                 |   22 +-
 .../common/src/com/netscape/cmscore/realm/ACL.java |  193 ++++
 .../src/com/netscape/cmscore/realm/ACLEntry.java   |  243 +++++
 .../com/netscape/cmscore/realm/PKIJNDIRealm.java   |  935 ++++++++++++++++++++
 .../netscape/cms/servlet/test/DRMRestClient.java   |  156 +++-
 .../src/com/netscape/cms/servlet/test/DRMTest.java |   32 +-
 pki/base/kra/shared/conf/acl.ldif                  |   10 +
 pki/base/kra/shared/conf/server.xml                |   50 +
 .../kra/shared/webapps/kra/WEB-INF/auth.properties |   16 +
 pki/base/kra/shared/webapps/kra/WEB-INF/web.xml    |   79 ++-
 pki/specs/pki-core.spec                            |   21 +-
 12 files changed, 1741 insertions(+), 17 deletions(-)
 create mode 100644 pki/base/common/src/com/netscape/cmscore/realm/ACL.java
 create mode 100644 pki/base/common/src/com/netscape/cmscore/realm/ACLEntry.java
 create mode 100644 pki/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java
 create mode 100644 pki/base/kra/shared/webapps/kra/WEB-INF/auth.properties

diff --git a/pki/.classpath b/pki/.classpath
index 48aa456..c8f6d3b 100644
--- a/pki/.classpath
+++ b/pki/.classpath
@@ -32,5 +32,6 @@
 	<classpathentry kind="lib" path="/usr/share/java/commons-codec.jar"/>
 	<classpathentry kind="lib" path="/usr/share/candlepin/lib/jaxb-impl-2.1.12.jar"/>
 	<classpathentry kind="lib" path="/usr/share/candlepin/lib/resteasy-jaxb-provider-2.2.1.GA.jar"/>
+	<classpathentry kind="lib" path="/usr/share/tomcat6/lib/catalina.jar"/>
 	<classpathentry kind="output" path="build/classes"/>
 </classpath>
diff --git a/pki/base/common/src/CMakeLists.txt b/pki/base/common/src/CMakeLists.txt
index ed83bb6..16620fd 100644
--- a/pki/base/common/src/CMakeLists.txt
+++ b/pki/base/common/src/CMakeLists.txt
@@ -23,6 +23,13 @@ find_file(COMMONS_CODEC_JAR
         /usr/share/java
 )
 
+find_file(TOMCAT_CATALINA_JAR
+    NAMES
+        catalina.jar
+    PATHS
+        /usr/share/java/tomcat6
+)
+
 find_file(SERVLET_JAR
     NAMES
         servlet.jar
@@ -1025,6 +1032,12 @@ set(pki-cmscore_java_SRCS
     com/netscape/cmscore/time/SimpleTimeSource.java
 )
 
+set(pki-jndi-realm_SRCS
+   com/netscape/cmscore/realm/PKIJNDIRealm.java
+   com/netscape/cmscore/realm/ACLEntry.java
+   com/netscape/cmscore/realm/ACL.java
+)
+
 set(pki-cmsbundle_RCS
     LogMessages.properties
     UserMessages.properties
@@ -1033,7 +1046,7 @@ set(pki-cmsbundle_RCS
 set(CMAKE_JAVA_INCLUDE_PATH
     ${PKI_NSUTIL_JAR} ${PKI_CMSUTIL_JAR}
     ${LDAPJDK_JAR} ${SERVLET_JAR} ${VELOCITY_JAR} ${XALAN_JAR} ${XERCES_JAR}
-    ${JSS_JAR} ${COMMONS_CODEC_JAR} ${SYMKEY_JAR} ${JAXRS_API_JAR} ${RESTEASY_JAXRS_JAR})
+    ${JSS_JAR} ${COMMONS_CODEC_JAR} ${TOMCAT_CATALINA_JAR} ${SYMKEY_JAR} ${JAXRS_API_JAR} ${RESTEASY_JAXRS_JAR})
 
 set(CMAKE_JAVA_TARGET_VERSION ${APPLICATION_VERSION})
 
@@ -1074,3 +1087,10 @@ add_jar(pki-cmsbundle ${pki-cmsbundle_RCS})
 add_dependencies(pki-cmsbundle pki-nsutil pki-cmsutil pki-certsrv pki-cms pki-cmscore)
 install_jar(pki-cmsbundle ${JAVA_JAR_INSTALL_DIR}/pki)
 set(PKI_CMSBUNDLE_JAR ${pki-cmsbundle_JAR_FILE} CACHE INTERNAL "pki-cmsbundle jar file")
+
+# build pki jndi realm
+set(CMAKE_JAR_CLASSES_PREFIX com/netscape/cmscore/realm)
+add_jar(pki-jndi-realm ${pki-jndi-realm_SRCS})
+install_jar(pki-jndi-realm ${JAVA_JAR_INSTALL_DIR}/pki)
+set(PKI_JNDI_REALM_JAR ${pki-jndi-realm_JAR_FILE} CACHE INTERNAL "pki-jndi-realm jar file")
+
diff --git a/pki/base/common/src/com/netscape/cmscore/realm/ACL.java b/pki/base/common/src/com/netscape/cmscore/realm/ACL.java
new file mode 100644
index 0000000..4d7303f
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cmscore/realm/ACL.java
@@ -0,0 +1,193 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2007 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmscore.realm;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A class represents an access control list (ACL). An ACL
+ * is associated with an protected resources. The policy
+ * enforcer can verify the ACLs with the current
+ * context to see if the corresponding resource is accessible.
+ * <P>
+ * An <code>ACL</code> may contain one or more <code>ACLEntry</code>. However, in case of multiple <code>ACLEntry</code>
+ * , a subject must pass ALL of the <code>ACLEntry</code> evaluation for permission to be granted
+ * <P>
+ * 
+ * @version $Revision$, $Date$
+ */
+public class ACL  {
+
+    /**
+    *
+    */
+
+    protected Vector<ACLEntry> entries = new Vector<ACLEntry>(); // ACL entries
+    protected Vector<String> rights = null; // possible rights entries
+    protected String resourceACLs = null; // exact resourceACLs string on ldap server
+    protected String name = null; // resource name
+    protected String description = null; // resource description
+
+    /**
+     * Class constructor.
+     */
+    public ACL() {
+    }
+
+    /**
+     * Class constructor.
+     * Constructs an access control list associated
+     * with a resource name
+     * 
+     * @param name resource name
+     * @param rights applicable rights defined for this resource
+     * @param resourceACLs the entire ACL specification. For example:
+     *            "certServer.log.configuration:read,modify:
+     *            allow (read,modify)
+     *            group=\"Administrators\":
+     *            Allow administrators to read and modify log
+     *            configuration"
+     */
+    public ACL(String name, Vector<String> rights, String resourceACLs) {
+        setName(name);
+        if (rights != null) {
+            this.rights = rights;
+        } else {
+            this.rights = new Vector<String>();
+        }
+        this.resourceACLs = resourceACLs;
+
+    }
+
+    /**
+     * Sets the name of the resource governed by this
+     * access control.
+     * 
+     * @param name name of the resource
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Retrieves the name of the resource governed by
+     * this access control.
+     * 
+     * @return name of the resource
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Retrieves the exact string of the resourceACLs
+     * 
+     * @return resource's acl
+     */
+    public String getResourceACLs() {
+        return resourceACLs;
+    }
+
+    /**
+     * Sets the description of the resource governed by this
+     * access control.
+     * 
+     * @param description Description of the protected resource
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * Retrieves the description of the resource governed by
+     * this access control.
+     * 
+     * @return Description of the protected resource
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Adds an ACL entry to this list.
+     * 
+     * @param entry the <code>ACLEntry</code> to be added to this resource
+     */
+    public void addEntry(ACLEntry entry) {
+        entries.addElement(entry);
+    }
+
+    /**
+     * Returns ACL entries.
+     * 
+     * @return enumeration for the <code>ACLEntry</code> vector
+     */
+    public Enumeration<ACLEntry> entries() {
+        return entries.elements();
+    }
+
+    /**
+     * Returns the string reprsentation.
+     * 
+     * @return the string representation of the ACL entries in the
+     *         following format:
+     *         <resource name>[<ACLEntry1>,<ACLEntry 2>,...<ACLEntry N>]
+     */
+    public String toString() {
+        String entries = "";
+        Enumeration<ACLEntry> e = entries();
+
+        for (; e.hasMoreElements();) {
+            ACLEntry entry = (ACLEntry) e.nextElement();
+
+            entries += entry.toString();
+            if (e.hasMoreElements())
+                entries += ",";
+        }
+        return getName() + "[" + entries + "]";
+    }
+
+    /**
+     * Adds an rights entry to this list.
+     * 
+     * @param right The right to be added for this ACL
+     */
+    public void addRight(String right) {
+        rights.addElement(right);
+    }
+
+    /**
+     * Tells if the permission is one of the defined "rights"
+     * 
+     * @param permission permission to be checked
+     * @return true if it's one of the "rights"; false otherwise
+     */
+    public boolean checkRight(String permission) {
+        return (rights.contains((Object) permission));
+    }
+
+    /**
+     * Returns rights entries.
+     * 
+     * @return enumeration of rights defined for this ACL
+     */
+    public Enumeration<String> rights() {
+        return rights.elements();
+    }
+}
diff --git a/pki/base/common/src/com/netscape/cmscore/realm/ACLEntry.java b/pki/base/common/src/com/netscape/cmscore/realm/ACLEntry.java
new file mode 100644
index 0000000..8e502b0
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cmscore/realm/ACLEntry.java
@@ -0,0 +1,243 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2007 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmscore.realm;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+
+/**
+ * A class represents an ACI entry of an access control list.
+ * <P>
+ * 
+ * @version $Revision$, $Date$
+ */
+public class ACLEntry {
+    /**
+    *
+    */
+    protected Hashtable<String, String> mPerms = new Hashtable<String, String>();
+    protected String expressions = null;
+    protected boolean negative = false;
+    protected String ACLEntryString = null;
+
+    /**
+     * Class Constructor
+     */
+    public ACLEntry() {
+    }
+
+    /**
+     * Checks if this ACL entry is set to negative.
+     * 
+     * @return true if this ACL entry expression is for "deny";
+     *         false if this ACL entry expression is for "allow"
+     */
+    public boolean isNegative() {
+        return negative;
+    }
+
+    /**
+     * Sets this ACL entry negative. This ACL entry expression is for "deny".
+     */
+    public void setNegative() {
+        negative = true;
+    }
+
+    /**
+     * Sets the ACL entry string
+     * 
+     * @param s string in the following format:
+     * 
+     *            <PRE>
+     *   allow|deny (right[,right...]) attribute_expression
+     * </PRE>
+     */
+    public void setACLEntryString(String s) {
+        ACLEntryString = s;
+    }
+
+    /**
+     * Gets the ACL Entry String
+     * 
+     * @return ACL Entry string in the following format:
+     * 
+     *         <PRE>
+     *   allow|deny (right[,right...]) attribute_expression
+     * </PRE>
+     */
+    public String getACLEntryString() {
+        return ACLEntryString;
+    }
+
+    /**
+     * Adds permission to this entry. Permission must be one of the
+     * "rights" defined for each protected resource in its ACL
+     * 
+     * @param acl the acl instance that this aclEntry is associated with
+     * @param permission one of the "rights" defined for each
+     *            protected resource in its ACL
+     */
+    public void addPermission(ACL acl, String permission) {
+        if (acl.checkRight(permission) == true) {
+            mPerms.put(permission, permission);
+        } else {
+            // not a valid right...log it later
+        }
+    }
+
+    /**
+     * Returns a list of permissions associated with
+     * this entry.
+     * 
+     * @return a list of permissions for this ACL entry
+     */
+    public Enumeration<String> permissions() {
+        return mPerms.elements();
+    }
+
+    /**
+     * Sets the expression associated with this entry.
+     * 
+     * @param expressions the evaluator expressions. For example,
+     *            group="Administrators"
+     */
+    public void setAttributeExpressions(String expressions) {
+        this.expressions = expressions;
+    }
+
+    /**
+     * Retrieves the expression associated with this entry.
+     * 
+     * @return the evaluator expressions. For example,
+     *         group="Administrators"
+     */
+    public String getAttributeExpressions() {
+        return expressions;
+    }
+
+    /**
+     * Checks to see if this <code>ACLEntry</code> contains a
+     * particular permission
+     * 
+     * @param permission one of the "rights" defined for each
+     *            protected resource in its ACL
+     * @return true if permission contained in the permission list
+     *         for this <code>ACLEntry</code>; false otherwise.
+     */
+    public boolean containPermission(String permission) {
+        return (mPerms.get(permission) != null);
+    }
+
+    /**
+     * Checks if this entry has the given permission.
+     * 
+     * @param permission one of the "rights" defined for each
+     *            protected resource in its ACL
+     * @return true if the permission is allowed; false if the
+     *         permission is denied. If a permission is not
+     *         recognized by this ACL, it is considered denied
+     */
+    public boolean checkPermission(String permission) {
+        // default - if we dont know about the requested permission,
+        //           don't grant permission
+        if (mPerms.get(permission) == null)
+            return false;
+        if (isNegative()) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Parse string in the following format:
+     * 
+     * <PRE>
+     *   allow|deny (right[,right...]) attribute_expression
+     * </PRE>
+     * 
+     * into an instance of the <code>ACLEntry</code> class
+     * 
+     * @param acl the acl instance associated with this aclentry
+     * @param aclEntryString aclEntryString in the specified format
+     * @return an instance of the <code>ACLEntry</code> class
+     */
+    public static ACLEntry parseACLEntry(ACL acl, String aclEntryString) {
+        if (aclEntryString == null) {
+            return null;
+        }
+
+        String te = aclEntryString.trim();
+
+        // locate first space
+        int i = te.indexOf(' ');
+        // prefix should be "allowed" or "deny"
+        String prefix = te.substring(0, i);
+        String suffix = te.substring(i + 1).trim();
+        ACLEntry entry = new ACLEntry();
+
+        if (prefix.equals("allow")) {
+            // do nothing
+        } else if (prefix.equals("deny")) {
+            entry.setNegative();
+        } else {
+            return null;
+        }
+        // locate the second space
+        i = suffix.indexOf(' ');
+        // this prefix should be rights list, delimited by ","
+        prefix = suffix.substring(1, i - 1);
+        // the suffix is the rest, which is the "expressions"
+        suffix = suffix.substring(i + 1).trim();
+
+        StringTokenizer st = new StringTokenizer(prefix, ",");
+
+        for (; st.hasMoreTokens();) {
+            entry.addPermission(acl, st.nextToken());
+        }
+        entry.setAttributeExpressions(suffix);
+        return entry;
+    }
+
+    /**
+     * Returns the string representation of this ACLEntry
+     * 
+     * @return string representation of this ACLEntry
+     */
+    public String toString() {
+        String entry = "";
+
+        if (isNegative()) {
+            entry += "deny (";
+        } else {
+            entry += "allow (";
+        }
+        Enumeration<String> e = permissions();
+
+        for (; e.hasMoreElements();) {
+            String p = e.nextElement();
+
+            entry += p;
+            if (e.hasMoreElements())
+                entry += ",";
+        }
+        entry += ") " + getAttributeExpressions();
+        return entry;
+    }
+}
diff --git a/pki/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java b/pki/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java
new file mode 100644
index 0000000..a0cd33a
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java
@@ -0,0 +1,935 @@
+package com.netscape.cmscore.realm;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.Principal;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.realm.JNDIRealm;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.PartialResultException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.servlet.http.HttpServletResponse;
+
+/*
+ *  Self contained PKI JNDI Real that overrides the standard JNDI Realm
+ * 
+ *  The purpose is to move authentication and authorization code out of the core server.
+ *  This realm can be used standalone with only the dependency of having tomcatjss and jss installed
+ *  and having tomcatjss connectors configured in the tomcat instance.
+ *  
+ *  This realm allows for configurable SSL client authentication checking as well
+ *  as checking against the standard PKI ACLs we have configured in our ldap database.
+ *  Those not using a CS instance could either not configure the ACL checking or
+ *  override this class to read in and evaluate their own ACL's.
+ *  
+ *  This code makes use and simplifies some existing ACL and authorization code
+ *  from the main server for now.
+ * 
+ */
+
+public class PKIJNDIRealm extends JNDIRealm {
+
+    private static final String DEF_CERT_ATTR = "userCert";
+    private static final String DEF_ACL_ATTR = "resource";
+
+    private static final String PROP_USER = "user";
+    private static final String PROP_GROUP = "group";
+    private static final String PROP_USER_ANYBODY = "anybody";
+    private static final String PROP_USER_EVERYBODY = "everybody";
+    private static final String CERT_VERSION = "2";
+    private static final String PROP_AUTH_FILE_PATH = "/WEB-INF/auth.properties";
+    private static final int EXPRESSION_SIZE = 2;
+
+    private Hashtable<String, ACL> acls = new Hashtable<String, ACL>();
+
+    private Properties authzProperties = null;
+
+    /* Look up the principal user based on the incoming client auth
+     * certificate.
+     * @param usercert Incoming client auth certificate presented by user
+     * @return Principal Object representing the authenticated user.
+     */
+    @Override
+    protected synchronized Principal getPrincipal(X509Certificate usercert) {
+
+        logDebug("Entering PKIJNDIRealm.getPrincipal");
+
+        if (usercert == null)
+            return null;
+
+        String uid = null;
+        String certUIDLabel = getCertUIDLabel();
+
+        if (certUIDLabel == null) {
+            // We have no uid label, attempt to construct a description
+
+            uid = CERT_VERSION + ";" + usercert.getSerialNumber() + ";"
+                    + usercert.getIssuerDN() + ";" + usercert.getSubjectDN();
+
+            //The description field is devoid of spaces and the email label is E
+            //instead of EMAIL
+            uid = uid.replaceAll(", ", ",");
+            uid = uid.replaceAll("EMAILADDRESS", "E");
+
+            logDebug(uid);
+
+        } else {
+
+            String certUIDSrchStr = certUIDLabel + "=";
+
+            StringTokenizer dnTokens =
+                    new StringTokenizer(usercert.getSubjectDN().getName(), ",");
+
+            while (dnTokens.hasMoreTokens()) {
+                String token = dnTokens.nextToken();
+                int index = token.indexOf(certUIDSrchStr);
+
+                if (index != -1) {
+                    // Found the entry with the cert's UID
+
+                    try {
+                        uid = token.substring(index + certUIDSrchStr.length());
+                    } catch (IndexOutOfBoundsException e) {
+                        logErr("Out of Bounds Exception when attempting to extract UID from incomgin certificate.");
+                        return null;
+                    }
+
+                    if (uid != null) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        //Call the getPrincipal method of the base JNDIRealm class
+        //based on the just calculated uid. During the next call
+        // one of our methods to extract and store the user's ldap stored 
+        //client cert will be invoked
+
+        Principal user = getPrincipal(uid);
+        
+        //ToDo: Possibly perform some more cert verficiation
+        // such as OCSP, even though the tomcat jss connector
+        // can already be configured for OCSP
+
+        if (user != null) {
+            X509Certificate storedCert = getStoredUserCert();
+            setStoredUserCert(null);
+            //Compare the stored ldap cert with the incoming cert
+            if (usercert.equals(storedCert)) {
+                //Success, the incoming certificate matches the
+                //certificate stored in LDAP for this user.
+                return user;
+            }
+        }
+
+        setStoredUserCert(null);
+
+        return null;
+    }
+
+    /**
+     * Return a User object containing information about the user
+     * with the specified username, if found in the directory;
+     * otherwise return <code>null</code>.
+     * Override here to extract the client auth certificate from the
+     * ldap db.
+     * 
+     * @param context The directory context
+     * @param username Username to be looked up
+     * 
+     * @exception NamingException if a directory server error occurs
+     * 
+     * @see #getUser(DirContext, String, String, int)
+     */
+    @Override
+    protected User getUser(DirContext context, String username)
+            throws NamingException {
+
+        //ToDo: Right now we support the Realm attribute
+        //  userBase which only allows a single pattern from
+        // which to search for users in ldap.
+        // We need to use the "userPattern" attribute
+        // which supports multiple search patterns.
+        // This has not been done because the out of the box
+        // Support for SSL client auth does not appear to support
+        // the userPattern attribute. Certainly another method here
+        // could be overridden to get this working.
+        
+        User certUser = super.getUser(context, username);
+
+        if (certUser != null) {
+            extractAndSaveStoredX509UserCert(context, certUser);
+        }
+
+        return certUser;
+    }
+
+    /**
+     * Perform access control based on the specified authorization constraint.
+     * Return <code>true</code> if this constraint is satisfied and processing
+     * should continue, or <code>false</code> otherwise.
+     * override to check for custom PKI ACL's authz permissions.
+     * 
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param constraints Security constraint we are enforcing
+     * @param context The Context to which client of this class is attached.
+     * 
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean hasResourcePermission(Request request,
+            Response response,
+            SecurityConstraint[] constraints,
+            Context context)
+            throws IOException {
+
+        boolean allowed = super.hasResourcePermission(request, response, constraints, context);
+
+        if (allowed == true && hasResourceACLS()) {
+
+            loadAuthzProperties(context);
+
+            if (hasAuthzProperties()) {
+                //Let's check against our encoded acls.
+
+                String requestURI = request.getDecodedRequestURI();
+                Principal principal = request.getPrincipal();
+
+                String match = getACLEntryDataForURL(requestURI);
+
+                if (match != null) {
+                    //first part is the resourceID, second part is the operation
+                    String[] authzParams = match.split("\\,");
+
+                    String resourceID = null;
+                    String operation = null;
+
+                    if (authzParams.length >= EXPRESSION_SIZE) {
+                        resourceID = authzParams[0];
+                        operation = authzParams[1];
+
+                        if (resourceID != null) {
+                            resourceID = resourceID.trim();
+                        }
+
+                        if (operation != null) {
+                            operation = operation.trim();
+                        }
+                    }
+
+                    allowed = checkACLPermission(principal, resourceID, operation);
+                    logDebug("resourceID: " + resourceID + " operation: " + operation + " allowed: " + allowed);
+                }
+            }
+        }
+
+        // Return a "Forbidden" message denying access to this resource
+        if (!allowed) {
+            response.sendError
+                    (HttpServletResponse.SC_FORBIDDEN,
+                            sm.getString("realmBase.forbidden"));
+        }
+
+        return allowed;
+    }
+    
+    /**
+     * Return a List of roles associated with the given User.  Any
+     * roles present in the user's directory entry are supplemented by
+     * a directory search. If no roles are associated with this user,
+     * a zero-length List is returned.
+     * Override here to get the PKI Resource ACLs if so configured
+     *
+     * @param context The directory context we are searching
+     * @param user The User to be checked
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+
+    @Override
+    protected List<String> getRoles(DirContext context, User user)
+            throws NamingException {
+
+        try {
+            getResourceACLS(context);
+        } catch (NamingException e) {
+            logDebug("No aclResources found.");
+        }
+
+        return super.getRoles(context, user);
+    }
+    
+    /* Custom variables, see <Realm> element */
+
+    /* Attribute to find encoded Cert in ldap 
+     * "userCertificate" is most common value.
+     */
+    private String certAttrName;
+
+    public String getCertAttrName() {
+        return (certAttrName != null) ? certAttrName : DEF_CERT_ATTR;
+    }
+
+    public void setCertAttrName(String certAttrName) {
+        this.certAttrName = certAttrName;
+    }
+
+    /* Attribute to find encoded acl resources in ldap 
+     * "aclResources" is most common value.
+     */
+    private String aclAttrName;
+
+    public String getAclAttrName() {
+        return (aclAttrName != null) ? aclAttrName : DEF_ACL_ATTR;
+    }
+
+    public void setAclAttrName(String aclAttrName) {
+        this.aclAttrName = aclAttrName;
+    }
+
+    /* Attribute for base dn of acl resources in ldap 
+     */
+
+    private String aclBase;
+
+    public String getAclBase() {
+        return aclBase;
+    }
+
+    public void setAclBase(String aclBase) {
+        this.aclBase = aclBase;
+    }
+
+    /* Substring label to search for user id in presented client auth cert.
+     *  "UID" is most common value.
+     */
+
+    private String certUIDLabel;
+
+    public String getCertUIDLabel() {
+        return certUIDLabel;
+    }
+
+    public void setCertUIDStr(String certUIDLabel) {
+        this.certUIDLabel = certUIDLabel;
+    }
+
+    /* Saved user certificate object obtained during authentication
+     * from the user's LDAP record.
+     * Will be accessed later to compare with incoming client auth certificate. 
+     */
+    private X509Certificate storedUserCert;
+
+    protected void setStoredUserCert(X509Certificate cert) {
+        this.storedUserCert = cert;
+    }
+
+    protected X509Certificate getStoredUserCert() {
+        return storedUserCert;
+    }
+
+    // Check a PKI  ACL resourceID and operation for permissions
+    // If the check fails the user (principal) is not authorized to access the resource
+    private boolean checkACLPermission(Principal principal, String resourceId, String operation) {
+
+        boolean allowed = true;
+
+        if (!hasAuthzProperties() || !hasResourceACLS()) {
+            //We arent' configured for this sort of authz
+            return allowed;
+        }
+
+        if (principal == null || resourceId == null || operation == null) {
+            return allowed;
+        }
+
+        ACL acl = acls.get(resourceId);
+
+        if (acl == null) {
+            //No such acl, assume true
+            return allowed;
+        }
+
+        Enumeration<ACLEntry> aclEntries = acl.entries();
+        while (aclEntries != null && aclEntries.hasMoreElements()) {
+            ACLEntry entry = aclEntries.nextElement();
+            boolean isEntryNegative = entry.isNegative();
+
+            String expressions = entry.getAttributeExpressions();
+
+            allowed = evaluateExpressions(principal, expressions);
+
+            if (isEntryNegative) {
+                allowed = !allowed;
+            }
+        }
+
+        return allowed;
+    }
+
+    // Evaluate an expression as part of a PKI ACL
+    // Ex: user=anybody , group=Data Recovery Manager Agents
+    private boolean evaluateExpression(Principal principal, String expression) {
+
+        boolean allowed = true;
+        if (principal == null || expression == null) {
+            return allowed;
+        }
+
+        String operation = getExpressionOperation(expression);
+
+        if (operation == null) {
+            return allowed;
+        }
+
+        String[] expBlock = expression.split(operation);
+
+        if (expBlock.length != 2) {
+            return allowed;
+        }
+
+        String left = expBlock[0];
+        String right = expBlock[1];
+
+        if (left != null) {
+            left.trim();
+        } else {
+            return allowed;
+        }
+        //Massage the right hand side of this expression to be a legal string value.
+        if (right != null) {
+            right.trim();
+            right = right.replace("\"", "");
+            right = right.trim();
+        } else {
+            return allowed;
+        }
+
+        boolean negate = false;
+
+        //Otherwise assume "="
+        if (operation.equals("!=")) {
+            negate = true;
+        }
+
+        allowed = false;
+        if (left.equals(PROP_GROUP)) {
+            // Check JNDI to see if the user has this role/group
+            if (hasRole(principal, right)) {
+                allowed = true;
+            }
+        } else if (left.equals(PROP_USER)) {
+            if (right.equals(PROP_USER_ANYBODY) || right.equals(PROP_USER_EVERYBODY)) {
+                allowed = true;
+            }
+        } else {
+            logDebug("Unknown expression.");
+        }
+
+        if (negate) {
+            allowed = !allowed;
+        }
+
+        return allowed;
+    }
+
+    // Convenience method to find the operation in an ACL expression
+    private String getExpressionOperation(String exp) {
+        //Support only = and !=
+
+        int i = exp.indexOf("!=");
+
+        if (i == -1) {
+            i = exp.indexOf("=");
+            if (i == -1) {
+                return null;
+            } else {
+                return "=";
+            }
+        } else {
+            return "!=";
+        }
+    }
+
+    // Take a set of expressions in an ACL and evaluate it
+    private boolean evaluateExpressions(Principal principal, String s) {
+
+        Vector<Object> v = new Vector<Object>();
+
+        while (s.length() > 0) {
+            int orIndex = s.indexOf("||");
+            int andIndex = s.indexOf("&&");
+
+            // this is the last expression
+            if (orIndex == -1 && andIndex == -1) {
+                boolean passed = evaluateExpression(principal, s.trim());
+
+                v.addElement(Boolean.valueOf(passed));
+                break;
+
+                // || first
+            } else if (andIndex == -1 || (orIndex != -1 && orIndex < andIndex)) {
+                String s1 = s.substring(0, orIndex);
+                boolean passed = evaluateExpression(principal, s1.trim());
+
+                v.addElement(new Boolean(passed));
+                v.addElement("||");
+                s = s.substring(orIndex + 2);
+                // && first
+            } else {
+                String s1 = s.substring(0, andIndex);
+                boolean passed = evaluateExpression(principal, s1.trim());
+
+                v.addElement(new Boolean(passed));
+                v.addElement("&&");
+                s = s.substring(andIndex + 2);
+            }
+        }
+
+        if (v.size() == 0) {
+            return false;
+        }
+
+        if (v.size() == 1) {
+            Boolean bool = (Boolean) v.remove(0);
+
+            return bool.booleanValue();
+        }
+
+        boolean left = false;
+        String op = "";
+        boolean right = false;
+
+        while (v.size() > 0) {
+            if (op.equals(""))
+                left = ((Boolean) v.remove(0)).booleanValue();
+            op = (String) v.remove(0);
+            right = ((Boolean) v.remove(0)).booleanValue();
+
+            if (op.equals("||")) {
+                if (left == false && right == false)
+                    left = false;
+                left = true;
+            } else if (op.equals("&&")) {
+                if (left == true && right == true)
+                    left = true;
+                left = false;
+            }
+        }
+
+        return left;
+
+    }
+
+    /* Attempt to get the stored user certificate object and save it for 
+     * future reference. This all takes place within one command invocation from
+     * the getPrincipal method defined here.
+     */
+    private void extractAndSaveStoredX509UserCert(DirContext context, User certUser)
+            throws NamingException {
+
+        setStoredUserCert(null);
+
+        if (certUser != null && context != null) {
+
+            String certAttrStr = this.getCertAttrName();
+
+            // certAttrStr has a default value, can not be null
+            String[] attrs = new String[] { certAttrStr };
+
+            Attributes attributes = context.getAttributes(certUser.getDN(), attrs);
+
+            if (attributes == null) {
+                logErr("Can not get certificate attributes in extractAndSaveStoredX590UserCert.");
+                return;
+            }
+
+            Attribute certAttr = null;
+
+            certAttr = attributes.get(certAttrStr);
+
+            if (certAttr == null) {
+                logErr("Can not get certificate attribut in extractAndSaveStoredX509UserCert.");
+                return;
+            }
+
+            Object oAttr = null;
+
+            oAttr = certAttr.get();
+
+            if (oAttr == null) {
+                logErr("Can not get certificate attribute object in extractAndSaveStoredX509UserCert.");
+                return;
+            }
+
+            byte[] certData = null;
+
+            if (oAttr instanceof byte[]) {
+                certData = (byte[]) oAttr;
+            } else {
+                logErr("Can not get certificate data in extractAndSaveStoredX509UserCert.");
+                return;
+            }
+
+            ByteArrayInputStream inStream = null;
+            try {
+                X509Certificate x509Cert = null;
+                if (certData != null) {
+                    inStream = new ByteArrayInputStream(certData);
+                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+                    if (cf != null) {
+                        x509Cert = (X509Certificate) cf.generateCertificate(inStream);
+                    }
+
+                    setStoredUserCert(x509Cert);
+                }
+            } catch (Exception e) {
+                logErr("Certificate encoding error in extractAndSaveStoredX509UserCert: " + e);
+            } finally {
+                if (inStream != null) {
+                    try {
+                        inStream.close();
+                    } catch (IOException e) {
+                        logErr("Can't close ByteArrayStream in extractAndSaveStoredX509UserCert: " + e);
+                    }
+                }
+            }
+        }
+    }
+
+    // Search for the proper auth.properties entry corresponding
+    // to a particular incoming URL
+    // ToDo: In the admin interface, often the operation is sent
+    // as one of the parameters to the message.
+    // There may be a way to extract this information at this level.
+    // The parameter name to scan for could be configured with the Realm.
+    
+    private String getACLEntryDataForURL(String requestURI) {
+        String aclEntryData;
+
+        if (!hasAuthzProperties()) {
+            return null;
+        }
+
+        aclEntryData = authzProperties.getProperty(requestURI);
+
+        if (aclEntryData == null) {
+            //Check for a partial match such as
+            // ex: /kra/pki/keyrequest/2
+
+            Properties props = authzProperties;
+            Enumeration<?> e = props.propertyNames();
+
+            while (e.hasMoreElements()) {
+                String key = (String) e.nextElement();
+                if (requestURI.startsWith(key)) {
+                    aclEntryData = props.getProperty(key);
+                    break;
+                }
+            }
+        }
+
+        return aclEntryData;
+
+    }
+
+    //Go to the directory server, if configured, and go get the Resource ACLs
+    private synchronized void getResourceACLS(DirContext context) throws NamingException {
+
+        //for now lets support this on startup
+        if (hasResourceACLS()) {
+            return;
+        }
+
+        String filter = "(" + aclAttrName + "=*)";
+
+        SearchControls constraints = new SearchControls();
+
+        constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+        constraints.setReturningAttributes(null);
+
+        NamingEnumeration<SearchResult> results =
+                context.search(aclBase, filter, constraints);
+
+        try {
+            if (results == null || !results.hasMore()) {
+                return;
+            }
+        } catch (PartialResultException ex) {
+            throw ex;
+        }
+
+        SearchResult result = null;
+        try {
+            result = results.next();
+            if (result != null) {
+
+                Attributes attrs = result.getAttributes();
+                if (attrs == null)
+                    return;
+
+                Vector<String> aclVec = getAttributeValues(aclAttrName, attrs);
+
+                if (aclVec != null) {
+
+                    Enumeration<String> vEnum = aclVec.elements();
+
+                    while (vEnum.hasMoreElements())
+                    {
+                        String curAcl = vEnum.nextElement();
+                        ACL acl = parseACL(curAcl);
+                        if (acl != null) {
+                            acls.put(acl.getName(), acl);
+                        }
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return;
+        }
+
+    }
+
+    // Check to see if we have obtained the PKI ACLs from the ldap server
+    private boolean hasResourceACLS() {
+
+        if (acls != null && acls.size() > 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    // Check to see if we have read in the auth properties file
+    private boolean hasAuthzProperties() {
+
+        if (this.authzProperties != null) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Parse ACL resource attributes
+     * 
+     * @param res same format as the resource attribute:
+     * 
+     *            <PRE>
+     *     <resource name>:<permission1,permission2,...permissionn>:
+     *     <allow|deny> (<subset of the permission set>) <evaluator expression>
+     * </PRE>
+     * @exception EException ACL related parsing errors for res
+     * @return an ACL instance built from the parsed res
+     */
+    private ACL parseACL(String res) throws Exception {
+        if (res == null) {
+            throw new Exception("Bad input to parseACL.");
+        }
+
+        ACL acl = null;
+        Vector<String> rights = null;
+        int idx1 = res.indexOf(":");
+
+        if (idx1 <= 0) {
+            acl = new ACL(res, rights, res);
+        } else {
+            // getting resource id
+            String resource = res.substring(0, idx1);
+
+            if (resource == null) {
+                String infoMsg = "resource not specified in resource attribute:" +
+                        res;
+
+                String[] params = new String[2];
+
+                params[0] = res;
+                params[1] = infoMsg;
+                throw new Exception(infoMsg);
+            }
+
+            // getting list of applicable rights
+            String st = res.substring(idx1 + 1);
+            int idx2 = st.indexOf(":");
+            String rightsString = null;
+
+            if (idx2 != -1)
+                rightsString = st.substring(0, idx2);
+            else {
+                String infoMsg =
+                        "rights not specified in resource attribute:" + res;
+                String[] params = new String[2];
+
+                params[0] = res;
+                params[1] = infoMsg;
+                throw new Exception(infoMsg);
+            }
+
+            if (rightsString != null) {
+                rights = new Vector<String>();
+                StringTokenizer rtok = new StringTokenizer(rightsString, ",");
+
+                while (rtok.hasMoreTokens()) {
+                    rights.addElement(rtok.nextToken());
+                }
+            }
+
+            acl = new ACL(resource, rights, res);
+
+            String stx = st.substring(idx2 + 1);
+            int idx3 = stx.indexOf(":");
+            String tr = stx.substring(0, idx3);
+
+            // getting list of acl entries
+            if (tr != null) {
+                StringTokenizer atok = new StringTokenizer(tr, ";");
+
+                while (atok.hasMoreTokens()) {
+                    String acs = (String) atok.nextToken();
+
+                    // construct ACL entry
+                    ACLEntry entry = ACLEntry.parseACLEntry(acl, acs);
+
+                    if (entry == null) {
+                        String infoMsg = "parseACLEntry() call failed";
+                        String[] params = new String[2];
+
+                        params[0] = "ACLEntry = " + acs;
+                        params[1] = infoMsg;
+                        throw new Exception(infoMsg);
+                    }
+
+                    entry.setACLEntryString(acs);
+                    acl.addEntry(entry);
+                }
+            } else {
+                // fine
+                String infoMsg = " not specified in resource attribute:" +
+
+                        res;
+
+                String[] params = new String[2];
+
+                params[0] = res;
+                params[1] = infoMsg;
+                throw new Exception(infoMsg);
+            }
+
+            // getting description
+            String desc = stx.substring(idx3 + 1);
+
+            acl.setDescription(desc);
+        }
+
+        return (acl);
+    }
+
+    //Load the custom mapping file auth.properties, which maps urls to acl resourceID and operation value
+    //example entry: /kra/pki/config/cert/transport = certServer.kra.pki.config.cert.transport,read
+    // ToDo: Look into a more sophisticated method than this simple properties file if appropriate.
+    private synchronized void loadAuthzProperties(Context context) {
+
+        if (authzProperties == null && context != null) {
+            ClassLoader loader = this.getClass().getClassLoader();
+            if (loader == null)
+                loader = ClassLoader.getSystemClassLoader();
+
+            InputStream inputStream = context.getServletContext().getResourceAsStream(PROP_AUTH_FILE_PATH);
+
+            if (inputStream == null)
+                return;
+
+            Properties properties = new Properties();
+
+            try {
+                properties.load(inputStream);
+            } catch (IOException e) {
+                properties = null;
+            } finally {
+
+                if (properties != null) {
+                    authzProperties = properties;
+                }
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            return;
+        }
+    }
+    
+    /**
+     * Return a String representing the value of the specified attribute.
+     * Create our own since the super class has it as private
+     * 
+     * @param attrId Attribute name
+     * @param attrs Attributes containing the required value
+     * 
+     * @exception NamingException if a directory server error occurs
+     */
+    private Vector<String> getAttributeValues(String attrId, Attributes attrs)
+            throws NamingException {
+
+        if (attrId == null || attrs == null)
+            return null;
+
+        Vector<String> values = new Vector<String>();
+        Attribute attr = attrs.get(attrId);
+        if (attr == null)
+            return (null);
+        NamingEnumeration<?> value = attr.getAll();
+        if (value == null)
+            return (null);
+
+        while (value.hasMore()) {
+            Object obj = value.next();
+            String valueString = null;
+            if (obj instanceof byte[])
+                valueString = new String((byte[]) obj);
+            else
+                valueString = obj.toString();
+            values.add(valueString);
+        }
+        return values;
+    }
+    
+   /*
+    * ToDo: Figure out how to do real logging
+    */
+   private void logErr(String msg) {
+       System.err.println(msg);
+   }
+
+   private void logDebug(String msg) {
+       System.out.println(msg);
+   }
+}
diff --git a/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java b/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java
index 51cead4..6877408 100644
--- a/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java
+++ b/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMRestClient.java
@@ -1,13 +1,26 @@
 package com.netscape.cms.servlet.test;
 
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URL;
+import java.net.UnknownHostException;
 import java.util.Collection;
+import java.util.Enumeration;
 import java.util.Iterator;
 
+import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.params.HttpConnectionParams;
+import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.jboss.resteasy.client.ClientExecutor;
 import org.jboss.resteasy.client.ClientResponse;
 import org.jboss.resteasy.client.ProxyFactory;
-
 import com.netscape.certsrv.dbs.keydb.KeyId;
 import com.netscape.certsrv.request.RequestId;
+import org.jboss.resteasy.client.core.executors.ApacheHttpClientExecutor;
 import com.netscape.cms.servlet.admin.SystemCertificateResource;
 import com.netscape.cms.servlet.cert.model.CertificateData;
 import com.netscape.cms.servlet.key.KeyResource;
@@ -23,20 +36,144 @@ import com.netscape.cms.servlet.request.model.KeyRequestInfos;
 import com.netscape.cms.servlet.request.model.RecoveryRequestData;
 import com.netscape.cmsutil.util.Utils;
 
+import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
+import org.mozilla.jss.ssl.SSLClientCertificateSelectionCallback;
+import org.mozilla.jss.ssl.SSLSocket;
+
 public class DRMRestClient {
 
+    // Callback to approve or deny returned SSL server certs
+    // Right now, simply approve the cert.
+    private class ServerCertApprovalCB implements SSLCertificateApprovalCallback {
+        
+        public boolean approve(org.mozilla.jss.crypto.X509Certificate servercert,
+                SSLCertificateApprovalCallback.ValidityStatus status) {
+
+            //For now lets just accept the server cert. This is a test tool, being
+            // pointed at a well know kra instance.
+
+
+            if (servercert != null) {
+                System.out.println("Peer cert details: " +
+                        "\n     subject: " + servercert.getSubjectDN().toString() +
+                        "\n     issuer:  " + servercert.getIssuerDN().toString() +
+                        "\n     serial:  " + servercert.getSerialNumber().toString()
+                        );
+            }
+
+            SSLCertificateApprovalCallback.ValidityItem item;
+
+            Enumeration<?> errors = status.getReasons();
+            int i = 0;
+            while (errors.hasMoreElements()) {
+                i++;
+                item = (SSLCertificateApprovalCallback.ValidityItem) errors.nextElement();
+                System.out.println("item " + i +
+                        " reason=" + item.getReason() +
+                        " depth=" + item.getDepth());
+
+                int reason = item.getReason();
+
+                if (reason ==
+                        SSLCertificateApprovalCallback.ValidityStatus.UNTRUSTED_ISSUER ||
+                        reason == SSLCertificateApprovalCallback.ValidityStatus.BAD_CERT_DOMAIN) {
+
+                    //Allow these two since we haven't necessarily installed the CA cert for trust
+                    // and we are choosing "localhost" as the host for this client. 
+
+                    return true;
+
+                }
+            }
+
+            //For other errors return false
+
+            return false;
+        }
+    }
+    
+    private  class JSSProtocolSocketFactory implements ProtocolSocketFactory {
+
+        @Override
+        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+            
+            SSLSocket sock = createJSSSocket(host,port, null, 0, null);
+            return (Socket) sock;
+        }
+
+        @Override
+        public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
+                UnknownHostException {
+            
+            SSLSocket sock = createJSSSocket(host,port, clientHost, clientPort, null);
+            return (Socket) sock;
+        }
+
+        @Override
+        public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
+                throws IOException, UnknownHostException, ConnectTimeoutException {
+            
+           SSLSocket sock = createJSSSocket(host, port, localAddress, localPort, null);
+           return (Socket) sock;
+        }
+    }
+
+    private SSLSocket createJSSSocket(String host, int port, InetAddress localAddress, 
+            int localPort, SSLClientCertificateSelectionCallback clientCertSelectionCallback)
+            throws IOException, UnknownHostException, ConnectTimeoutException {
+        
+        SSLSocket sock = new SSLSocket(InetAddress.getByName(host),
+                port,
+                localAddress,
+                localPort,
+                new ServerCertApprovalCB(),
+                null);
+        
+        if(sock != null && clientCertNickname != null) {
+            sock.setClientCertNickname(clientCertNickname);
+        }
+        
+        return  sock;
+        
+    }
     private KeyResource keyClient;
     private KeysResource keysClient;
     private KeyRequestsResource keyRequestsClient;
     private KeyRequestResource keyRequestClient;
     private SystemCertificateResource systemCertClient;
     
-    public DRMRestClient(String baseUri) {
-        systemCertClient = ProxyFactory.create(SystemCertificateResource.class, baseUri);
-        keyRequestsClient = ProxyFactory.create(KeyRequestsResource.class, baseUri);
-        keyRequestClient = ProxyFactory.create(KeyRequestResource.class, baseUri);
-        keysClient = ProxyFactory.create(KeysResource.class, baseUri);
-        keyClient = ProxyFactory.create(KeyResource.class, baseUri);
+    private String clientCertNickname = null;
+    
+    public DRMRestClient(String baseUri, String clientCertNick) throws MalformedURLException {
+        
+        // For SSL we are assuming the caller has already intialized JSS and has
+        // a valid CryptoManager and CryptoToken
+        // optional clientCertNickname is provided for use if required.
+        
+        
+        URL url = new URL(baseUri);
+        
+        String protocol = url.getProtocol();
+        int port = url.getPort();
+        
+        clientCertNickname = null;
+        if(protocol != null && protocol.equals("https")) {
+            if (clientCertNick != null) {
+                clientCertNickname = clientCertNick;
+            } 
+            
+            Protocol.registerProtocol("https", 
+                new Protocol(protocol, new JSSProtocolSocketFactory(), port));
+        }
+        
+        HttpClient httpclient = new HttpClient();
+        ClientExecutor executor = new ApacheHttpClientExecutor(httpclient);
+
+        systemCertClient = ProxyFactory.create(SystemCertificateResource.class, baseUri, executor);
+        keyRequestsClient = ProxyFactory.create(KeyRequestsResource.class, baseUri, executor);
+        keyRequestClient = ProxyFactory.create(KeyRequestResource.class, baseUri, executor);
+        keysClient = ProxyFactory.create(KeysResource.class, baseUri, executor);
+        keyClient = ProxyFactory.create(KeyResource.class, baseUri, executor);
     }
     
     public String getTransportCert() {
@@ -124,7 +261,4 @@ public class DRMRestClient {
         KeyData key = keyClient.retrieveKey(data);
         return key;
     }
-     
-
-    
-}
\ No newline at end of file
+}
diff --git a/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java b/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
index 8020ec2..8d83247 100644
--- a/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
+++ b/pki/base/kra/functional/src/com/netscape/cms/servlet/test/DRMTest.java
@@ -17,6 +17,7 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cms.servlet.test;
 
+import java.net.MalformedURLException;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Iterator;
@@ -61,6 +62,8 @@ public class DRMTest {
         String port = null;
         String token_pwd = null;
         String db_dir = "./";
+        String protocol = "http";
+        String clientCertNickname = "KRA Administrator of Instance pki-kra's SjcRedhat Domain ID";
 
         // parse command line arguments
         Options options = new Options();
@@ -68,6 +71,8 @@ public class DRMTest {
         options.addOption("p", true, "Port of the DRM");
         options.addOption("w", true, "Token password");
         options.addOption("d", true, "Directory for tokendb");
+        options.addOption("s", true, "Attempt Optional Secure SSL connection");
+        options.addOption("c", true, "Optional SSL Client cert Nickname");
 
         try {
             CommandLineParser parser = new PosixParser();
@@ -97,6 +102,20 @@ public class DRMTest {
             if (cmd.hasOption("d")) {
                 db_dir = cmd.getOptionValue("d");
             }
+            
+            if (cmd.hasOption("s")) {
+                if(cmd.getOptionValue("s") != null && cmd.getOptionValue("s").equals("true")) {
+                    protocol = "https";
+                }
+            }
+            
+            if (cmd.hasOption("c")) {
+                String nick = cmd.getOptionValue("c");
+                
+                if (nick != null && protocol.equals("https")) {
+                    clientCertNickname = nick;    
+                }
+            }
 
         } catch (ParseException e) {
             System.err.println("Error in parsing command line options: " + e.getMessage());
@@ -173,8 +192,17 @@ public class DRMTest {
         }
 
         // Set base URI and get client
-        String baseUri = "http://" + host + ":" + port + "/kra/pki";
-        DRMRestClient client = new DRMRestClient(baseUri);
+        
+        
+        String baseUri = protocol + "://" + host + ":" + port + "/kra/pki";
+        DRMRestClient client;
+        try {
+            client = new DRMRestClient(baseUri, clientCertNickname);
+        } catch (MalformedURLException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+            return;
+        }
 
         // Test 1: Get transport certificate from DRM
         transportCert = client.getTransportCert();
diff --git a/pki/base/kra/shared/conf/acl.ldif b/pki/base/kra/shared/conf/acl.ldif
index 4c219ea..38a9a08 100644
--- a/pki/base/kra/shared/conf/acl.ldif
+++ b/pki/base/kra/shared/conf/acl.ldif
@@ -30,3 +30,13 @@ resourceACLS: certServer.kra.TokenKeyRecovery:submit:allow (submit) group="Data
 resourceACLS: certServer.kra.registerUser:read,modify:allow (modify,read) group="Enterprise CA Administrators" || group="Enterprise KRA Administrators" || group="Enterprise OCSP Administrators" || group="Enterprise TKS Administrators" || group="Enterprise TPS Administrators":Only Enterprise Administrators are allowed to register a new agent
 resourceACLS: certServer.kra.getTransportCert:read:allow (read) group="Enterprise CA Administrators" || group="Enterprise KRA Administrators" || group="Enterprise OCSP Administrators" || group="Enterprise TKS Administrators" || group="Enterprise TPS Administrators":Only Enterprise Administrators are allowed to retrieve the transport cert
 resourceACLS: certServer.clone.configuration:read,modify:allow (modify,read) group="Enterprise CA Administrators" || group="Enterprise KRA Administrators" || group="Enterprise OCSP Administrators" || group="Enterprise TKS Administrators":Only Enterprise Administrators are allowed to clone the configuration.
+resourceACLS: certServer.kra.pki.key.retrieve:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may retrieve archived key
+resourceACLS: certServer.kra.pki.keyrequests:read:allow (read) group="Data Recovery Manager Agents":Data Recovery Manager Agents may read keyrequests data
+resourceACLS: certServer.kra.pki.keyrequest:read:allow (read) group="Data Recovery Manager Agents":Data Recovery Manager Agents may read keyrequest data
+resourceACLS: certServer.kra.pki.keyrequest.archive:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may issue archival request
+resourceACLS: certServer.kra.pki.keyrequest.recover:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may issue recovery request
+resourceACLS: certServer.kra.pki.keyrequest.approve:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may approve security data request
+resourceACLS: certServer.kra.pki.keyrequest.reject:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may reject key security data request
+resourceACLS: certServer.kra.pki.keyrequest.cancel:execute:allow (execute) group="Data Recovery Manager Agents":Data Recovery Manager Agents may cancel security data request
+resourceACLS: certServer.kra.pki.keys:read:allow (read) group="Data Recovery Manager Agents":Data Recovery Manager Agents may read security data
+resourceACLS: certServer.kra.pki.config.cert.transport:read:allow (read) group="Data Recovery Manager Agents":Data Recovery Manager Agents may read transport cert data
diff --git a/pki/base/kra/shared/conf/server.xml b/pki/base/kra/shared/conf/server.xml
index fcd849e..58121d4 100644
--- a/pki/base/kra/shared/conf/server.xml
+++ b/pki/base/kra/shared/conf/server.xml
@@ -229,8 +229,58 @@ Tomcat Port         = [TOMCAT_SERVER_PORT] (for shutdown)
            resources under the key "UserDatabase".  Any edits
            that are performed against this UserDatabase are immediately
            available for use by the Realm.  -->
+
+      <!--
       <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
              resourceName="UserDatabase"/>
+      -->
+
+      <!-- Custom PKIJNDI realm
+
+          Example:
+
+          <Realm className="com.netscape.cmscore.realm.PKIJNDIRealm" : classpath to realm
+          connectionURL="ldap://localhost:389" : standard JNDI connection URL
+          userBase="ou=people,dc=localhost-pki-kra" : standard JNDI userBase property
+          userSearch="(description={0})" : Attribute to search for user of incoming client auth certificate
+                                         : Use userSearch="(UID={0})" if wanting to search isolate user based on UID
+                                         : Also set the following: certUIDLabel="UID" or whatever the field containing
+                                         : the user's UID happens to be. This will cause the incoming's cert dn to be
+                                         : be searched for <certUIDLabel>=<uid value>
+
+          certAttrName="userCertificate" : Attribute containing user's client auth certificate
+          roleBase="ou=groups,dc=localhost-pki-kra" : Standard JNDI search base for roles or groups
+          roleName="cn" : Standard attribute name containg roles or groups
+          roleSubtree="true" : Standard JNDI roleSubtree property
+          roleSearch="(uniqueMember={0})" : How to search for a user in a specific role or group
+          connectionName="cn=Directory Manager" : Connection name, needs elevated privileges
+          connectionPassword="secret123" : Password for elevated user
+          aclBase ="cn=aclResources,dc=localhost-pki-kra" : Custom base location of PKI ACL's in directory
+          aclAttrName="resourceACLS" : Name of attribute containing PKI ACL's
+       />
+
+          Uncomment and customize  below to activate Realm.
+          Also umcomment Security Constraints and login config  values
+          in WEB-INF/web.xml as well.
+      --> 
+
+      <!-- 
+      <Realm className="com.netscape.cmscore.realm.PKIJNDIRealm"
+          connectionURL="ldap://localhost:389"
+          userBase="ou=people,dc=localhost-pki-kra"
+          userSearch="(description={0})"
+          certAttrName="userCertificate"
+          roleBase="ou=groups,dc=localhost-pki-kra"
+          roleName="cn"
+          roleSubtree="true"
+          roleSearch="(uniqueMember={0})"
+          connectionName="cn=Directory Manager"
+          connectionPassword="netscape"
+          aclBase ="cn=aclResources,dc=localhost-pki-kra"
+          aclAttrName="resourceACLS"
+       />
+
+       -->
 
       <!-- Define the default virtual host
            Note: XML Schema validation will not work with Xerces 2.2.
diff --git a/pki/base/kra/shared/webapps/kra/WEB-INF/auth.properties b/pki/base/kra/shared/webapps/kra/WEB-INF/auth.properties
new file mode 100644
index 0000000..a206aa9
--- /dev/null
+++ b/pki/base/kra/shared/webapps/kra/WEB-INF/auth.properties
@@ -0,0 +1,16 @@
+# Restful API auth/authz mapping info
+#
+# Format:
+# <Rest API URL> = <ACL Resource ID>,<ACL resource operation>
+# ex:      /kra/pki/key/retrieve = certServer.kra.pki.key.retrieve,execute
+
+/kra/pki/key/retrieve = certServer.kra.pki.key.retrieve,execute
+/kra/pki/keyrequests = certServer.kra.pki.keyrequests,read
+/kra/pki/keyrequest = certServer.kra.pki.keyrequest,read
+/kra/pki/keyrequest/archive = certServer.kra.pki.keyrequest.archive,execute
+/kra/pki/keyrequest/recover = certServer.kra.pki.keyrequest.recover,execute
+/kra/pki/keyrequest/approve = certServer.kra.pki.keyrequest.approve,execute
+/kra/pki/keyrequest/reject = certServer.kra.pki.keyrequest.reject,execute
+/kra/pki/keyrequest/cancel = certServer.kra.pki.keyrequest.cancel,execute
+/kra/pki/keys = certServer.kra.pki.keys,read
+/kra/pki/config/cert/transport = certServer.kra.pki.config.cert.transport,read
diff --git a/pki/base/kra/shared/webapps/kra/WEB-INF/web.xml b/pki/base/kra/shared/webapps/kra/WEB-INF/web.xml
index 529aead..c6e9934 100644
--- a/pki/base/kra/shared/webapps/kra/WEB-INF/web.xml
+++ b/pki/base/kra/shared/webapps/kra/WEB-INF/web.xml
@@ -1034,5 +1034,82 @@
    <session-config>
         <session-timeout>30</session-timeout>
    </session-config>
-</web-app>
 
+<!-- Default login configuration uses form-based authentication -->
+<!-- Security Constraint for agent access to the Security Data Rest Interface -->
+
+<!-- Uncomment to activate PKIJNDI realm as in conf/server.xml -->
+<!--
+<security-constraint>
+   <display-name>KRA Top Level Constraint</display-name>
+   <web-resource-collection>
+      <web-resource-name>KRA Protected Area</web-resource-name>
+         <url-pattern>/pki/*
+         </url-pattern>
+      </web-resource-collection>
+   <user-data-constraint>
+        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+    </user-data-constraint>
+   <auth-constraint>
+      <role-name>*</role-name>
+   </auth-constraint>
+</security-constraint>
+-->
+
+<!-- Security Constraint to deny certain http methods for key/retrieve -->
+<!-- Uncomment to activate PKIJNDI realm as in conf/server.xml -->
+<!--
+<security-constraint>
+<display-name>Key forbidden</display-name>
+<web-resource-collection>
+  <web-resource-name>Key forbidden</web-resource-name>
+      <url-pattern>/pki/key/retrieve</url-pattern>
+  <http-method>GET</http-method>
+  <http-method>PUT</http-method>
+  <http-method>DELETE</http-method>
+</web-resource-collection>
+<auth-constraint/>
+</security-constraint>
+-->
+
+<!-- Security Constraint to deny certain http methods for keyrequest/* -->
+<!-- Uncomment to activate PKIJNDI realm as in conf/server.xml -->
+
+<!--
+<security-constraint>
+<display-name>KeyRequest forbidden</display-name>
+<web-resource-collection>
+  <web-resource-name>KeyRequest forbidden</web-resource-name>
+      <url-pattern>/pki/keyrequest/archive</url-pattern>
+      <url-pattern>/pki/keyrequest/recover</url-pattern>
+      <url-pattern>/pki/keyrequest/approve/*</url-pattern>
+      <url-pattern>/pki/keyrequest/reject/*</url-pattern>
+      <url-pattern>/pki/keyrequest/cancel/*</url-pattern>
+  <http-method>GET</http-method>
+  <http-method>PUT</http-method>
+  <http-method>DELETE</http-method>
+</web-resource-collection>
+<auth-constraint/>
+</security-constraint>
+-->
+
+
+<!-- Customized SSL Client auth login config 
+      uncomment to activate PKIJNDI realm as in conf/server.xml 
+-->
+
+<!--
+
+<login-config>
+   <realm-name>PKIJNDIRealm</realm-name> 
+   <auth-method>CLIENT-CERT</auth-method>
+   <realm-name>Client Cert Protected Area</realm-name>
+</login-config>
+
+<security-role>
+   <role-name>*</role-name>
+</security-role>
+
+-->
+
+</web-app>
diff --git a/pki/specs/pki-core.spec b/pki/specs/pki-core.spec
index 6e19c00..94e4a7f 100644
--- a/pki/specs/pki-core.spec
+++ b/pki/specs/pki-core.spec
@@ -7,7 +7,7 @@
 
 Name:             pki-core
 Version:          10.0.0
-Release:          %{?relprefix}7%{?prerel}%{?dist}
+Release:          %{?relprefix}8%{?prerel}%{?dist}
 Summary:          Certificate System - PKI Core Components
 URL:              http://pki.fedoraproject.org/
 License:          GPLv2
@@ -722,6 +722,9 @@ echo "D /var/run/pki/tks 0755 root root -"  >> %{buildroot}%{_sysconfdir}/tmpfil
 %{__rm} %{buildroot}%{_initrddir}/pki-krad
 %{__rm} %{buildroot}%{_initrddir}/pki-ocspd
 %{__rm} %{buildroot}%{_initrddir}/pki-tksd
+# Create symlink to the pki-jndi-realm jar
+mkdir -p %{buildroot}%{_javadir}/tomcat6
+ln -s -f %{_javadir}/pki/pki-jndi-realm.jar %{buildroot}%{_javadir}/tomcat6/pki-jndi-realm.jar
 %else
 %{__rm} %{buildroot}%{_bindir}/pkicontrol
 %{__rm} -rf %{buildroot}%{_sysconfdir}/systemd/system/pki-cad.target.wants
@@ -932,7 +935,6 @@ if [ -d /etc/sysconfig/pki/tks ]; then
 fi
 /bin/systemctl daemon-reload >/dev/null 2>&1 || :
 
-
 %preun -n pki-ca
 if [ $1 = 0 ] ; then
     /bin/systemctl --no-reload disable pki-cad.target > /dev/null 2>&1 || :
@@ -1084,6 +1086,15 @@ fi
 %{_javadir}/pki/pki-cmsbundle.jar
 %{_javadir}/pki/pki-cmscore-%{version}.jar
 %{_javadir}/pki/pki-cmscore.jar
+
+%if 0%{?fedora} >= 16
+# Create symlink to the pki-jndi-realm jar
+%{_javadir}/tomcat6/pki-jndi-realm.jar
+%endif
+
+%{_javadir}/pki/pki-jndi-realm-%{version}.jar
+%{_javadir}/pki/pki-jndi-realm.jar
+
 %{_datadir}/pki/setup/
 
 %files -n pki-common-javadoc
@@ -1222,6 +1233,12 @@ fi
 
 
 %changelog
+
+* Fri Mar 09 2018 Jack Magne <jmagne@redhat.com> 10.0.0-5.a1
+- Added support for pki-jndi-realm in tomcat6 in pki-common
+  and pki-kra.
+- Ticket #69.
+
 * Fri Mar  2 2012 Matthew Harmsen <mharmsen@redhat.com> 10.0.0-0.7.a1
 - For 'mock' purposes, removed platform-specific logic from around
   the 'patch' files so that ALL 'patch' files will be included in
-- 
1.7.7.6

