diff options
Diffstat (limited to 'base')
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> |