summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/common/shared/conf/server.xml45
-rw-r--r--base/common/src/com/netscape/certsrv/authentication/AuthToken.java4
-rw-r--r--base/common/src/com/netscape/certsrv/authentication/IAuthToken.java9
-rw-r--r--base/common/src/com/netscape/certsrv/usrgrp/IUGSubsystem.java9
-rw-r--r--base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java947
-rw-r--r--base/common/src/com/netscape/cmscore/realm/PKIRealm.java376
-rw-r--r--base/common/src/com/netscape/cmscore/usrgrp/UGSubsystem.java31
-rw-r--r--base/kra/shared/conf/server.xml45
-rw-r--r--base/kra/shared/webapps/kra/WEB-INF/web.xml4
9 files changed, 433 insertions, 1037 deletions
diff --git a/base/common/shared/conf/server.xml b/base/common/shared/conf/server.xml
index 375764294..d3c781a6b 100644
--- a/base/common/shared/conf/server.xml
+++ b/base/common/shared/conf/server.xml
@@ -239,51 +239,8 @@ Tomcat Port = [TOMCAT_SERVER_PORT] (for shutdown)
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"
- />
-
+ <Realm className="com.netscape.cmscore.realm.PKIRealm" />
-->
<!-- Define the default virtual host
diff --git a/base/common/src/com/netscape/certsrv/authentication/AuthToken.java b/base/common/src/com/netscape/certsrv/authentication/AuthToken.java
index 1b5bf2350..827278711 100644
--- a/base/common/src/com/netscape/certsrv/authentication/AuthToken.java
+++ b/base/common/src/com/netscape/certsrv/authentication/AuthToken.java
@@ -112,6 +112,10 @@ public class AuthToken implements IAuthToken {
set(TOKEN_AUTHTIME, new Date());
}
+ public Object get(String attrName) {
+ return mAttrs.get(attrName);
+ }
+
public String getInString(String attrName) {
return (String) mAttrs.get(attrName);
}
diff --git a/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java b/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
index e469f3786..3c03cc1f5 100644
--- a/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
+++ b/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
@@ -55,6 +55,15 @@ public interface IAuthToken {
* @exception EBaseException on attribute handling errors.
* @return the attribute value
*/
+ public Object get(String name);
+
+ /**
+ * Gets an attribute value.
+ *
+ * @param name the name of the attribute to return.
+ * @exception EBaseException on attribute handling errors.
+ * @return the attribute value
+ */
public String getInString(String name);
/**
diff --git a/base/common/src/com/netscape/certsrv/usrgrp/IUGSubsystem.java b/base/common/src/com/netscape/certsrv/usrgrp/IUGSubsystem.java
index bbd051324..eb7f84ebf 100644
--- a/base/common/src/com/netscape/certsrv/usrgrp/IUGSubsystem.java
+++ b/base/common/src/com/netscape/certsrv/usrgrp/IUGSubsystem.java
@@ -125,6 +125,15 @@ public interface IUGSubsystem extends ISubsystem, IUsrGrp {
public Enumeration<IGroup> findGroups(String filter) throws EUsrGrpException;
/**
+ * Finds groups that contain the user.
+ *
+ * @param userDn the user DN
+ * @return a list of groups that contain the given user
+ * @throws EUsrGrpException
+ */
+ public Enumeration<IGroup> findGroupsByUser(String userDn) throws EUsrGrpException;
+
+ /**
* Find a group for the given name
*
* @param name the given name
diff --git a/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java b/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java
deleted file mode 100644
index bd551baf0..000000000
--- a/base/common/src/com/netscape/cmscore/realm/PKIJNDIRealm.java
+++ /dev/null
@@ -1,947 +0,0 @@
-package com.netscape.cmscore.realm;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.Principal;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-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.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.servlet.http.HttpServletResponse;
-
-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 org.apache.catalina.Wrapper;
-
-/*
- * 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);
-
- Wrapper wrapper = request.getWrapper();
-
- 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, wrapper);
- 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, Wrapper wrapper) {
-
- 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, wrapper);
-
- if (isEntryNegative) {
- allowed = !allowed;
- }
-
- // Our current ACLs require that every entry passes for
- // the entire ACL to pass.
- // For some reason the original code allows the negative acls (deny)
- // to be evaluated first or second based on configuration. Here, simply
- // traverse the list as is.
-
- if (!allowed) {
- break;
- }
- }
-
- 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, Wrapper wrapper) {
-
- 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 = left.trim();
- } else {
- return allowed;
- }
- //Massage the right hand side of this expression to be a legal string value.
- if (right != null) {
- 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(wrapper, 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, Wrapper wrapper) {
-
- 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(), wrapper);
-
- 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(), wrapper);
-
- v.addElement(Boolean.valueOf(passed));
- v.addElement("||");
- s = s.substring(orIndex + 2);
- // && first
- } else {
- String s1 = s.substring(0, andIndex);
- boolean passed = evaluateExpression(principal, s1.trim(), wrapper);
-
- v.addElement(Boolean.valueOf(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
- // ToDo: Check into more sophisticated
- // methods of doing this mapping.
- // Perhaps Rest gives us this more
- // sophisticated mapping ability.
-
- 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 = 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) {
-
- 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/base/common/src/com/netscape/cmscore/realm/PKIRealm.java b/base/common/src/com/netscape/cmscore/realm/PKIRealm.java
new file mode 100644
index 000000000..53b31131c
--- /dev/null
+++ b/base/common/src/com/netscape/cmscore/realm/PKIRealm.java
@@ -0,0 +1,376 @@
+package com.netscape.cmscore.realm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletResponse;
+
+import netscape.security.x509.X509CertImpl;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.realm.RealmBase;
+
+import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.authentication.IAuthManager;
+import com.netscape.certsrv.authentication.IAuthSubsystem;
+import com.netscape.certsrv.authentication.IAuthToken;
+import com.netscape.certsrv.authorization.AuthzToken;
+import com.netscape.certsrv.authorization.IAuthzSubsystem;
+import com.netscape.certsrv.usrgrp.EUsrGrpException;
+import com.netscape.certsrv.usrgrp.IGroup;
+import com.netscape.certsrv.usrgrp.IUGSubsystem;
+import com.netscape.certsrv.usrgrp.IUser;
+import com.netscape.cms.servlet.common.AuthCredentials;
+import com.netscape.cmscore.authentication.CertUserDBAuthentication;
+import com.netscape.cmscore.authentication.PasswdUserDBAuthentication;
+
+/**
+ * PKI Realm
+ *
+ * This realm provides an authentication service against PKI user database.
+ * The realm also provides an authorization service that validates request
+ * URL's against the access control list defined in the internal database.
+ */
+
+public class PKIRealm extends RealmBase {
+
+ public final static String PROP_AUTH_FILE_PATH = "/WEB-INF/auth.properties";
+ public final static int EXPRESSION_SIZE = 2;
+
+ ThreadLocal<IAuthToken> authToken = new ThreadLocal<IAuthToken>();
+ Properties authzProperties;
+
+ public PKIRealm() {
+ logDebug("Creating PKI realm");
+ }
+
+ @Override
+ protected void initInternal() throws LifecycleException {
+ logDebug("Initializing PKI realm");
+ super.initInternal();
+ }
+
+ @Override
+ protected void startInternal() throws LifecycleException {
+ logDebug("Starting PKI realm");
+ super.startInternal();
+ }
+
+ @Override
+ protected String getName() {
+ return "PKIRealm";
+ }
+
+ @Override
+ public Principal authenticate(String username, String password) {
+ logDebug("Authenticating username "+username+" with password.");
+
+ try {
+ IAuthSubsystem authSub = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH);
+ IAuthManager authMgr = authSub.getAuthManager(IAuthSubsystem.PASSWDUSERDB_AUTHMGR_ID);
+
+ AuthCredentials creds = new AuthCredentials();
+ creds.set(PasswdUserDBAuthentication.CRED_UID, username);
+ creds.set(PasswdUserDBAuthentication.CRED_PWD, password);
+
+ IAuthToken token = authMgr.authenticate(creds); // throws exception if authentication fails
+ authToken.set(token);
+
+ return getPrincipal(username);
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Override
+ public Principal authenticate(final X509Certificate certs[]) {
+ logDebug("Authenticating certificate chain:");
+
+ try {
+ X509CertImpl certImpls[] = new X509CertImpl[certs.length];
+ for (int i=0; i<certs.length; i++) {
+ X509Certificate cert = certs[i];
+ logDebug(" "+cert.getSubjectDN());
+
+ // Convert sun.security.x509.X509CertImpl to netscape.security.x509.X509CertImpl
+ certImpls[i] = new X509CertImpl(cert.getEncoded());
+ }
+
+ IAuthSubsystem authSub = (IAuthSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTH);
+ IAuthManager authMgr = authSub.getAuthManager(IAuthSubsystem.CERTUSERDB_AUTHMGR_ID);
+
+ AuthCredentials creds = new AuthCredentials();
+ creds.set(CertUserDBAuthentication.CRED_CERT, certImpls);
+
+ IAuthToken token = authMgr.authenticate(creds); // throws exception if authentication fails
+ authToken.set(token);
+
+ String username = token.getInString(CertUserDBAuthentication.TOKEN_USERID);
+ logDebug("User ID: "+username);
+
+ return getPrincipal(username);
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected Principal getPrincipal(String username) {
+ try {
+ IUser user = getUser(username);
+ return getPrincipal(user);
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ protected Principal getPrincipal(IUser user) throws EUsrGrpException {
+ List<String> roles = getRoles(user);
+ return new GenericPrincipal(user.getUserID(), null, roles);
+ }
+
+ protected IUser getUser(String username) throws EUsrGrpException {
+ IUGSubsystem ugSub = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG);
+ IUser user = ugSub.getUser(username);
+ logDebug("User DN: "+user.getUserDN());
+ return user;
+ }
+
+ protected List<String> getRoles(IUser user) throws EUsrGrpException {
+
+ List<String> roles = new ArrayList<String>();
+
+ IUGSubsystem ugSub = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG);
+ Enumeration<IGroup> groups = ugSub.findGroupsByUser(user.getUserDN());
+
+ logDebug("Roles:");
+ while (groups.hasMoreElements()) {
+ IGroup group = groups.nextElement();
+
+ String name = group.getName();
+ logDebug(" "+name);
+ roles.add(name);
+ }
+
+ return roles;
+ }
+
+ @Override
+ protected String getPassword(String username) {
+ return null;
+ }
+
+ /**
+ * 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 {
+
+ String requestURI = request.getDecodedRequestURI();
+ logDebug("Checking permission: "+requestURI);
+
+ boolean allowed = super.hasResourcePermission(request, response, constraints, context);
+ logDebug("Resource permission: "+allowed);
+
+ if (allowed) {
+ allowed = checkACL(request, response, constraints, context);
+ logDebug("ACL permission: "+allowed);
+ }
+
+ if (!allowed) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("realmBase.forbidden"));
+ }
+
+ return allowed;
+ }
+
+ public boolean checkACL(Request request,
+ Response response,
+ SecurityConstraint[] constraints,
+ Context context) {
+
+ try {
+ loadAuthzProperties(context);
+ if (!hasAuthzProperties()) return false;
+
+ String requestURI = request.getDecodedRequestURI();
+ String match = getACLEntry(requestURI);
+ if (match == null) return false;
+
+ logDebug("ACL: "+match);
+ String[] authzParams = match.split("\\,");
+
+ String resource = null;
+ String operation = null;
+
+ if (authzParams.length >= EXPRESSION_SIZE) {
+ resource = authzParams[0];
+ operation = authzParams[1];
+
+ if (resource != null) {
+ resource = resource.trim();
+ }
+
+ if (operation != null) {
+ operation = operation.trim();
+ }
+ }
+
+ IAuthzSubsystem mAuthz = (IAuthzSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTHZ);
+ IAuthToken token = authToken.get();
+
+ logDebug("Auth token:");
+ Enumeration<String> names = token.getElements();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ Object value = token.get(name);
+ logDebug(" " + name +": " + value);
+ }
+
+ logDebug("Resource: " + resource);
+ logDebug("Operation: " + operation);
+
+ AuthzToken authzToken = mAuthz.authorize("DirAclAuthz", token, resource, operation);
+ if (authzToken != null) return true;
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ // 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.
+
+ public String getACLEntry(String requestURI) {
+
+ if (!hasAuthzProperties()) {
+ return null;
+ }
+
+ logDebug("Checking path: "+requestURI);
+ String aclEntryData = authzProperties.getProperty(requestURI);
+
+ if (aclEntryData != null) {
+ logDebug("Found exact match: "+aclEntryData);
+ return aclEntryData;
+ }
+
+ // Check for a partial match such as
+ // ex: /kra/pki/keyrequest/2
+ // TODO: Check into more sophisticated
+ // methods of doing this mapping.
+ // Perhaps Rest gives us this more
+ // sophisticated mapping ability.
+
+ Properties props = authzProperties;
+ Enumeration<?> e = props.propertyNames();
+
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ if (requestURI.startsWith(key)) {
+ aclEntryData = props.getProperty(key);
+ logDebug("Found partial match ["+key+"]: "+aclEntryData);
+ break;
+ }
+ }
+
+ if (aclEntryData == null) {
+ logDebug("No match found");
+ }
+
+ return aclEntryData;
+
+ }
+
+ // Check to see if we have read in the auth properties file
+ public boolean hasAuthzProperties() {
+
+ if (authzProperties != null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // 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.
+ public synchronized void loadAuthzProperties(Context context) throws IOException {
+
+ if (authzProperties == null && context != null) {
+
+ InputStream inputStream = context.getServletContext().getResourceAsStream(PROP_AUTH_FILE_PATH);
+
+ if (inputStream == null) {
+ logDebug("Resource "+PROP_AUTH_FILE_PATH+" not found.");
+ throw new IOException("Resource "+PROP_AUTH_FILE_PATH+" not found.");
+ }
+
+ try {
+ logDebug("Loading authorization properties");
+
+ Properties properties = new Properties();
+ properties.load(inputStream);
+
+ authzProperties = properties;
+
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /*
+ * TODO: Figure out how to do real logging
+ */
+ public void logErr(String msg) {
+ System.err.println(msg);
+ }
+
+ public void logDebug(String msg) {
+ System.out.println("PKIRealm: "+msg);
+ }
+}
diff --git a/base/common/src/com/netscape/cmscore/usrgrp/UGSubsystem.java b/base/common/src/com/netscape/cmscore/usrgrp/UGSubsystem.java
index 0489fa30a..259173078 100644
--- a/base/common/src/com/netscape/cmscore/usrgrp/UGSubsystem.java
+++ b/base/common/src/com/netscape/cmscore/usrgrp/UGSubsystem.java
@@ -1225,6 +1225,37 @@ public final class UGSubsystem implements IUGSubsystem {
return null;
}
+ public Enumeration<IGroup> findGroupsByUser(String userDn) throws EUsrGrpException {
+ if (userDn == null) {
+ return null;
+ }
+
+ LDAPConnection ldapconn = null;
+
+ try {
+ String attrs[] = new String[2];
+
+ attrs[0] = "cn";
+ attrs[1] = "description";
+
+ ldapconn = getConn();
+ LDAPSearchResults res =
+ ldapconn.search(getGroupBaseDN(), LDAPv2.SCOPE_SUB,
+ "(&(objectclass=groupofuniquenames)(uniqueMember=" + userDn + "))",
+ attrs, false);
+
+ return buildGroups(res);
+ } catch (LDAPException e) {
+ log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_USRGRP_LIST_GROUPS", e.toString()));
+ } catch (ELdapException e) {
+ log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_USRGRP_LIST_GROUPS", e.toString()));
+ } finally {
+ if (ldapconn != null)
+ returnConn(ldapconn);
+ }
+ return null;
+ }
+
/**
* builds an instance of a Group entry
* @throws EUsrGrpException
diff --git a/base/kra/shared/conf/server.xml b/base/kra/shared/conf/server.xml
index 96e396b72..54ba3272b 100644
--- a/base/kra/shared/conf/server.xml
+++ b/base/kra/shared/conf/server.xml
@@ -235,51 +235,8 @@ Tomcat Port = [TOMCAT_SERVER_PORT] (for shutdown)
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"
- />
-
+ <Realm className="com.netscape.cmscore.realm.PKIRealm" />
-->
<!-- Define the default virtual host
diff --git a/base/kra/shared/webapps/kra/WEB-INF/web.xml b/base/kra/shared/webapps/kra/WEB-INF/web.xml
index 273ca1fa4..7b4072085 100644
--- a/base/kra/shared/webapps/kra/WEB-INF/web.xml
+++ b/base/kra/shared/webapps/kra/WEB-INF/web.xml
@@ -1010,13 +1010,13 @@
<!-- Customized SSL Client auth login config
- uncomment to activate PKIJNDI realm as in conf/server.xml
+ uncomment to activate PKI realm as in conf/server.xml
-->
<!--
<login-config>
- <realm-name>PKIJNDIRealm</realm-name>
+ <realm-name>PKIRealm</realm-name>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>Client Cert Protected Area</realm-name>
</login-config>