Index: org/apache/catalina/realm/X509SubjectDnRetriever.java =================================================================== --- org/apache/catalina/realm/X509SubjectDnRetriever.java (revision 0) +++ org/apache/catalina/realm/X509SubjectDnRetriever.java (revision 0) @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.catalina.realm; + +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + + +/** + * Implementation of X509UserNameRetriever that takes a user name from the Subject Field of the X509Certificate. + * The user name is the unique part of information from the client certificate that used to identify the identity of the user. + * The Subject field (also called Subject Distinguish Name or SubjectDN) identifies the entity associated with the public key. + * The Subject field contains the following relevant attributes (it can also contain other attributes). + * + * Subject Attribute Subject Attribute Description Example + * CN Common Name CN=Bob BobFamily + * emailAddress Email Address emailAddress=bob@example.com + * C Country Name C=US + * ST State or Province Name ST=NY + * L Locality Name L=New York + * O Organization Name O=Work Organization + * OU Organizational Unit Name OU=Managers + * + * To retrieve the user name from the subject, you can use the entire SubjectDN field or the SubjectDN attribute. + * To retrieve the user name from entire SubjectDN field use the default constructor + * To retrieve the user name from the SubjectDN attribute, please provide the retrieve attribute name + * The the retrieve attribute name is a code letter based on a legend defined in the certificate itself. + * + * For example, the Email attribute is used to hold the User Name. + * Please provide "e" or "emailAddress" for the constructor. + * + * For example, the Common Name attribute is used to hold the User Name. + * Please provide "CN" for the constructor. + * + * + */ +public class X509SubjectDnRetriever implements X509UserNameRetriever { + /** + * Logger for this class + */ + protected final Log log = LogFactory.getLog(getClass()); + + private static final String EMAIL_SUBJECT_ATTR = "emailAddress"; + + + /** + * The value is based on a legend defined in the certificate itself. + * It tested with the following browsers: IE, FF and Chrome + * For new browsers may be will required to add a new legend value to the list. + */ + private static final List EmailOptions = Arrays.asList(EMAIL_SUBJECT_ATTR.toLowerCase(), "e") ; + + + private String subjectDnAttribute = null; + private String subjectDnAttributeConfiguration = null; + + protected X509SubjectDnRetriever() { + setSubjectDnAttribute(null); + } + + protected X509SubjectDnRetriever(String retrieveAttr) { + setSubjectDnAttribute(retrieveAttr); + } + + public void setX509UserNameRetrieveConfiguration(String userNameRetrieveConfiguration) { + setSubjectDnAttribute(userNameRetrieveConfiguration); + + } + /* (non-Javadoc) + * @see org.apache.catalina.realm.X509UserNameRetriever#getUserName(java.security.cert.X509Certificate) + */ + public String getUserName(X509Certificate clientCert) { + if (log.isDebugEnabled()) { + log.debug("getUserName(X509Certificate) - start"); + } + String subject = getSubjectDN(clientCert); + String userName = null; + + if (subject != null) { + if (log.isDebugEnabled()) { + log.debug("Subject is [" + subject + "]."); + } + if (subjectDnAttribute == null) { + if (log.isDebugEnabled()) { + log.debug("subjectDnAttribute is null, so return the whole subject."); + } + userName = subject; + } else { + boolean foundUserName = false; + try { + LdapName ldapName = new LdapName(subject); + List list = ldapName.getRdns(); + if (list != null) { + for (Rdn rdn : list) { + String type = rdn.getType(); + if (subjectDnAttribute.equalsIgnoreCase(type.toString())) { + Object value = rdn.getValue(); + if (value instanceof String) { + userName = (String) value; + foundUserName = true; + if (log.isDebugEnabled()) { + log.debug("Success to retreive userName [" + userName + "]."); + } + break; + } + } + } + } + } catch (InvalidNameException e) { + log.info("subject [" + subject + "] is not valid name : [" + e.getMessage() + "]."); + } + if (!foundUserName) { + log.info("subject [" + subject + "] does not contain the required attribute [" + subjectDnAttributeConfiguration + "]. Return the whole subject."); + userName = subject; + } + } + + } + + if (log.isDebugEnabled()) { + log.debug("getUserName(X509Certificate) - end; Ret is [" + userName + "]."); + } + return userName; + } + + private void setSubjectDnAttribute(String subjectDnAttributeConfiguration) { + this.subjectDnAttributeConfiguration = subjectDnAttributeConfiguration; + subjectDnAttribute = mapSubjectDnAttribute(subjectDnAttributeConfiguration); + if (log.isDebugEnabled()) { + log.debug("setSubjectDnAttribute(String) - end; subjectDnAttribute [" + subjectDnAttribute + "]; subjectDnAttributeConfiguration [" + subjectDnAttributeConfiguration + "]"); + } + } + + + + private String mapSubjectDnAttribute(String subjectDnAttributeConfiguration) { + String ret = null; + if (subjectDnAttributeConfiguration != null) { + if (EmailOptions.contains(subjectDnAttributeConfiguration.toLowerCase())) { + ret = EMAIL_SUBJECT_ATTR; + } else { + ret = subjectDnAttributeConfiguration; + } + } + return ret; + } + + /** + * @param clientCert + * @return the whole SubjectSN of the X509Certificate + */ + protected String getSubjectDN(X509Certificate clientCert) { + String subject = null; + if (clientCert != null) { + if ((clientCert.getSubjectDN()!= null) + && (clientCert.getSubjectDN().getName() != null)) { + subject = clientCert.getSubjectDN().getName(); + } else { + if (log.isDebugEnabled()) { + log.debug("Can not getSubjectDN, SubjectDN is null"); + } + } + } else { + if (log.isDebugEnabled()) { + log.debug("Can not getSubjectDN, clientCert is null"); + } + } + if (log.isDebugEnabled()) { + log.debug("getSubjectDN(X509Certificate) - end; Ret is [" + subject + "]."); + } + return subject; + + } +} Index: org/apache/catalina/realm/RealmBase.java =================================================================== --- org/apache/catalina/realm/RealmBase.java (revision 1245889) +++ org/apache/catalina/realm/RealmBase.java (working copy) @@ -150,6 +150,26 @@ protected boolean stripRealmForGss = true; + /** + * The X509UserNameRetriever is used to get the user name during the client certificate authentication + */ + private X509UserNameRetriever x509UserNameRetriever = null; + + + /** + * The value indicates which part of constitutes the username. + * The value is a code letter based on a legend defined in the certificate itself. + */ + + private String x509UserNameRetrieveConfiguration = null; + + /** + * @param className - the class that implements X509UserNameRetriever + * If the value is provided a realm will create X509UserNameRetriever. + * + */ + private String x509UserNameRetrieverClassName = null; + // ------------------------------------------------------------- Properties @@ -1034,6 +1054,8 @@ if (container != null) { this.containerLog = container.getLogger(); } + // Create x509UserNameRetriever for the client certificate authentication. + createUserNameRetriever(x509UserNameRetrieveConfiguration, x509UserNameRetrieverClassName); } /** @@ -1191,7 +1213,12 @@ * Return the Principal associated with the given certificate. */ protected Principal getPrincipal(X509Certificate usercert) { - return(getPrincipal(usercert.getSubjectDN().getName())); + // get user name using x509UserNameRetriever + String userName = x509UserNameRetriever.getUserName(usercert); + + if (log.isDebugEnabled()) + log.debug("Get principal for [" + userName + "]"); + return(getPrincipal(userName)); } @@ -1390,5 +1417,104 @@ return name; } } + + /** + * @param userNameRetrieveConfiguration - The value is used to configure how to get the user name during the client certificate authentication. + * + * The user name is the unique part of information from the client certificate that used to identify the identity of the user. + * The Subject field (also called Subject Distinguish Name or SubjectDN) identifies the entity associated with the public key. + * The Subject field contains the following relevant attributes (it can also contain other attributes). + * + * Subject Attribute Subject Attribute Description Example + * CN Common Name CN=Bob BobFamily + * emailAddress Email Address emailAddress=bob@example.com + * C Country Name C=US + * ST State or Province Name ST=NY + * L Locality Name L=New York + * O Organization Name O=Work Organization + * OU Organizational Unit Name OU=Managers + * + * To retrieve the user name from the subject, you can use the entire SubjectDN field or the SubjectDN attribute. + * To retrieve the user name from entire SubjectDN field leave the value empty. + * To retrieve the user name from the SubjectDN attribute, please provide the retrieve attribute name. + * The the retrieve attribute name is a code letter based on a legend defined in the certificate itself. + * + * For example, the Email attribute is used to hold the User Name. + * Please provide "e" or "emailAddress" for the constructor. + * + * For example, the Common Name attribute is used to hold the User Name. + * Please provide "CN" for the constructor. + * + */ + public void setX509UserNameRetrieveConfiguration(String userNameRetrieveConfiguration) { + x509UserNameRetrieveConfiguration = userNameRetrieveConfiguration; + } + /** + * @param className - The Java class name that is used to override the default X509UserNameRetriever. + * The X509UserNameRetriever is used to get the user name during the client certificate authentication. + * If the value is provided a realm will create X509UserNameRetriever from the provided class. + * This class must implement the org.apache.catalina.realm.X509UserNameRetriever interface. + */ + + public void setX509UserNameRetrieverClassName(String className) { + x509UserNameRetrieverClassName = className; + } + + + private void createUserNameRetriever(String x509UserNameRetrieveConfiguration, String x509UserNameRetrieverClassName) { + + boolean createdFromClassName = createUserNameRetrieverFromClassName(x509UserNameRetrieverClassName); + if (createdFromClassName) { + if (x509UserNameRetriever instanceof X509SubjectDnRetriever) { + // set the configuration to x509UserNameRetriever + ((X509SubjectDnRetriever) x509UserNameRetriever).setX509UserNameRetrieveConfiguration(x509UserNameRetrieveConfiguration); + } + } else { + if (x509UserNameRetrieveConfiguration == null) { + x509UserNameRetriever = new X509SubjectDnRetriever(); + } else { + x509UserNameRetriever = new X509SubjectDnRetriever(x509UserNameRetrieveConfiguration); + } + } + } + + + + @SuppressWarnings("unchecked") + private boolean createUserNameRetrieverFromClassName(String x509UserNameRetrieverClassName) { + + boolean created = false; + if ((x509UserNameRetrieverClassName != null) + && (x509UserNameRetrieverClassName != "")) { + Class x509UserNameRetrieverClass = null; + try { + x509UserNameRetrieverClass = (Class) Class.forName(x509UserNameRetrieverClassName); + x509UserNameRetriever = x509UserNameRetrieverClass.newInstance(); + created = true; + if (log.isDebugEnabled()) { + log.debug("Success to create x509UserNameRetriever [" + x509UserNameRetriever + "]."); + } + } catch (ClassCastException e) { + String warnString = "Class [" + x509UserNameRetrieverClassName + "] is not instance of [" + X509UserNameRetriever.class.getSimpleName() + "]."; + log.warn(warnString); + } catch (ClassNotFoundException e) { + String warnString = "Class [" + x509UserNameRetrieverClassName + "] was not found."; + log.warn(warnString, e); + } catch (InstantiationException e) { + String warnString = "Cannot instantiate class [" + x509UserNameRetrieverClassName + "]."; + log.warn(warnString); + } catch (IllegalAccessException e) { + String warnString = "Cannot instantiate class [" + x509UserNameRetrieverClassName + "]."; + log.warn(warnString); + } + } + return created; + } + + + + + + } Index: org/apache/catalina/realm/X509UserNameRetriever.java =================================================================== --- org/apache/catalina/realm/X509UserNameRetriever.java (revision 0) +++ org/apache/catalina/realm/X509UserNameRetriever.java (revision 0) @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.catalina.realm; + +import java.security.cert.X509Certificate; + +/** + * The interface X509UserNameRetriever defines how to retrieve a user name from X509Certificate. + * The user name is the unique part of information from the client certificate + * that used to identify the identity of the user. + * The interface is used to get the user name during the client certificate authentication + */ +public interface X509UserNameRetriever { + String getUserName(X509Certificate clientCert); +}