Index: org/apache/catalina/realm/UserNameRetrieverConfiguration.java
===================================================================
--- org/apache/catalina/realm/UserNameRetrieverConfiguration.java (revision 0)
+++ org/apache/catalina/realm/UserNameRetrieverConfiguration.java (revision 0)
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+
+/**
+ * The interface UserNameRetrieverConfiguration allows to configure UserNameRetriever
+ *
+ * @author Michael Furman
+ */
+public interface UserNameRetrieverConfiguration {
+ /**
+ * @param userNameRetrieveConfiguration - the parameter for the UserNameRetriever configuration
+ */
+ public void setX509UserNameRetrieveConfiguration(String userNameRetrieveConfiguration);
+}
Index: org/apache/catalina/realm/UserNameRetriever.java
===================================================================
--- org/apache/catalina/realm/UserNameRetriever.java (revision 0)
+++ org/apache/catalina/realm/UserNameRetriever.java (revision 0)
@@ -0,0 +1,31 @@
+/*
+ * 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 UserNameRetriever 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
+ * @author Michael Furman
+ */
+public interface UserNameRetriever {
+ String getUserName(X509Certificate clientCert);
+}
Index: org/apache/catalina/realm/SubjectDnRetriever.java
===================================================================
--- org/apache/catalina/realm/SubjectDnRetriever.java (revision 0)
+++ org/apache/catalina/realm/SubjectDnRetriever.java (revision 0)
@@ -0,0 +1,188 @@
+/*
+ * 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 UserNameRetriever 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.
+ *
+ * @author Michael Furman
+ *
+ */
+public class SubjectDnRetriever implements UserNameRetriever {
+ /**
+ * 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 SubjectDnRetriever() {
+ setSubjectDnAttribute(null);
+ }
+
+ protected SubjectDnRetriever(String retrieveAttr) {
+ setSubjectDnAttribute(retrieveAttr);
+ }
+
+ 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;
+ }
+
+ 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/UserNameRetrieverDecorator.java
===================================================================
--- org/apache/catalina/realm/UserNameRetrieverDecorator.java (revision 0)
+++ org/apache/catalina/realm/UserNameRetrieverDecorator.java (revision 0)
@@ -0,0 +1,118 @@
+/*
+ * 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 org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Implementation of UserNameRetriever that allows to create an implementation from the class name.
+ *
+ * If the created implementation of UserNameRetriever not returns the user name,
+ * the UserNameRetrieverDecorator class returns the the entire SubjectDN field by defaultUserNameRetriever.
+ *
+ * @author Michael Furman
+ *
+ */
+public class UserNameRetrieverDecorator implements UserNameRetriever {
+ private static final Log log = LogFactory
+ .getLog(UserNameRetrieverDecorator.class);
+
+ private UserNameRetriever userNameRetriever;
+
+ private UserNameRetriever defaultUserNameRetriever;
+
+ private boolean createdFromClassName = false;
+
+ /**
+ * @param x509UserNameRetrieveConfiguration - the parameter for the configuration of UserNameRetriever.
+ * @param x509UserNameRetrieverClassName - the class that implements UserNameRetriever
+ * If the x509UserNameRetrieverClassName class implements UserNameRetrieverConfiguration, the userNameRetrieveConfiguration value
+ * will be passed to the UserNameRetriever during the creation.
+ * The UserNameRetriever is used to get the user name during the client certificate authentication
+ */
+ public UserNameRetrieverDecorator(String x509UserNameRetrieveConfiguration, String x509UserNameRetrieverClassName) {
+ super();
+ createdFromClassName = createUserNameRetrieverFromClassName(x509UserNameRetrieverClassName);
+ if (createdFromClassName) {
+ if (userNameRetriever instanceof UserNameRetrieverConfiguration) {
+ // set the configuration to userNameRetriever
+ ((UserNameRetrieverConfiguration) userNameRetriever).setX509UserNameRetrieveConfiguration(x509UserNameRetrieveConfiguration);
+ }
+ // the defaultUserNameRetriever will be used if the created UserNameRetriever will return an empty value.
+ defaultUserNameRetriever = new SubjectDnRetriever();
+ } else {
+ if (x509UserNameRetrieveConfiguration == null) {
+ userNameRetriever = new SubjectDnRetriever();
+ } else {
+ userNameRetriever = new SubjectDnRetriever(x509UserNameRetrieveConfiguration);
+ }
+ }
+ }
+
+ public String getUserName(X509Certificate clientCert) {
+ String userName = null;
+ if (createdFromClassName) {
+ userName = userNameRetriever.getUserName(clientCert);
+ if ((userName == null) || ("".equals(userName))) {
+ if (log.isDebugEnabled()) {
+ log.debug("Created userNameRetriever returns the empty user name. Returnts the user name by defaultUserNameRetriever.");
+ }
+ userName = defaultUserNameRetriever.getUserName(clientCert);
+ }
+ } else {
+ userName = userNameRetriever.getUserName(clientCert);
+ }
+ return userName;
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean createUserNameRetrieverFromClassName(String x509UserNameRetrieverClassName) {
+
+ boolean created = false;
+ if ((x509UserNameRetrieverClassName != null)
+ && (x509UserNameRetrieverClassName != "")) {
+ Class extends UserNameRetriever> x509UserNameRetrieverClass = null;
+ try {
+ x509UserNameRetrieverClass = (Class extends UserNameRetriever>) Class.forName(x509UserNameRetrieverClassName);
+ userNameRetriever = x509UserNameRetrieverClass.newInstance();
+ created = true;
+ if (log.isDebugEnabled()) {
+ log.debug("Success to create userNameRetriever [" + userNameRetriever + "].");
+ }
+ } catch (ClassCastException e) {
+ String warnString = "Class [" + x509UserNameRetrieverClassName + "] is not instance of [" + UserNameRetriever.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/RealmBase.java
===================================================================
--- org/apache/catalina/realm/RealmBase.java (revision 1243153)
+++ org/apache/catalina/realm/RealmBase.java (working copy)
@@ -150,6 +150,26 @@
protected boolean stripRealmForGss = true;
+ /**
+ * The UserNameRetriever is used to get the user name during the client certificate authentication
+ */
+ private UserNameRetriever userNameRetriever = 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 UserNameRetriever
+ * If the value is provided a realm will create UserNameRetriever.
+ *
+ */
+ private String x509UserNameRetrieverClassName = null;
+
// ------------------------------------------------------------- Properties
@@ -1034,6 +1054,8 @@
if (container != null) {
this.containerLog = container.getLogger();
}
+ // Create userNameRetriever for the client certificate authentication.
+ userNameRetriever = new UserNameRetrieverDecorator(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 userNameRetriever
+ String userName = userNameRetriever.getUserName(usercert);
+
+ if (log.isDebugEnabled())
+ log.debug("Get principal for [" + userName + "]");
+ return(getPrincipal(userName));
}
@@ -1390,5 +1417,22 @@
return name;
}
}
+
+ /* (non-Javadoc)
+ * @see org.apache.catalina.Realm#setX509UserNameRetrieveConfiguration(java.lang.String)
+ */
+ public void setX509UserNameRetrieveConfiguration(String userNameRetrieveConfiguration) {
+ x509UserNameRetrieveConfiguration = userNameRetrieveConfiguration;
+ }
+ /* (non-Javadoc)
+ * @see org.apache.catalina.Realm#setX509UserNameRetrieverClassName(java.lang.String)
+ */
+ public void setX509UserNameRetrieverClassName(String className) {
+ x509UserNameRetrieverClassName = className;
+ }
+
+
+
+
}
Index: org/apache/catalina/Realm.java
===================================================================
--- org/apache/catalina/Realm.java (revision 1243153)
+++ org/apache/catalina/Realm.java (working copy)
@@ -191,4 +191,20 @@
public void removePropertyChangeListener(PropertyChangeListener listener);
+
+ /**
+ * @param userNameRetrieveConfiguration - the parameter indicates which part of constitutes the username.
+ * The value is a code letter based on a legend defined in the certificate itself.
+ */
+ public void setX509UserNameRetrieveConfiguration(String userNameRetrieveConfiguration);
+
+ /**
+ * @param className - the class that implements UserNameRetriever
+ * If the value is provided a realm will create UserNameRetriever.
+ * If the class implements UserNameRetrieverConfiguration, the userNameRetrieveConfiguration value
+ * will be passed to the UserNameRetriever during the creation.
+ * The UserNameRetriever is used to get the user name during the client certificate authentication
+ */
+ public void setX509UserNameRetrieverClassName(String className);
+
}