Index: org/apache/catalina/Realm.java =================================================================== --- org/apache/catalina/Realm.java (revision 1237538) +++ org/apache/catalina/Realm.java (working copy) @@ -191,4 +191,26 @@ public void removePropertyChangeListener(PropertyChangeListener listener); + /** + * @param userNameRetrieveField + * The parameter indicates which certificate field contains the User Name. + * Options are: SubjectDN or SubjectAlternativeName. + * + */ + public void setX509UserNameRetrieveField(String userNameRetrieveField); + + /** + * @param userNameRetrieveFieldPart + * If a part of the input field is used, + * this parameter indicates which part of the relevant field constitutes the username. + * The value is a code letter based on a legend defined in the certificate itself + */ + public void setX509UserNameRetrieveFieldPart(String userNameRetrieveFieldPart); + + /** + * @param className + * Class that implements UserNameRetriever + */ + public void setX509UserNameRetrieverClassName(String className); + } 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,10 @@ +/** + * @author Michael Furman + */ +package org.apache.catalina.realm; + +import java.security.cert.X509Certificate; + +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,140 @@ +/** + * @author Michael Furman + */ +package org.apache.catalina.realm; + +import java.security.cert.X509Certificate; +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; + + +public class SubjectDnRetriever implements UserNameRetriever { + /** + * Logger for this class + */ + protected final Log logger = LogFactory.getLog(getClass()); + + + + private String subjectDnAttribute = null; + private String subjectDnAttributeConfiguration = null; + + protected SubjectDnRetriever() { + setSubjectDnAttribute(null); + } + + protected SubjectDnRetriever(String retrieveAttr) { + setSubjectDnAttribute(retrieveAttr); + } + + public String getUserName(X509Certificate clientCert) { + if (logger.isDebugEnabled()) { + logger.debug("getUserName(X509Certificate) - start"); + } + String subject = getSubjectDN(clientCert); + String userName = null; + + if (subject != null) { + if (logger.isDebugEnabled()) { + logger.debug("Subject is [" + subject + "]."); + } + if (subjectDnAttribute == null) { + if (logger.isDebugEnabled()) { + logger.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 (logger.isDebugEnabled()) { + logger.debug("Success to retreive userName [" + userName + "]."); + } + break; + } + } + } + } + } catch (InvalidNameException e) { + logger.info("subject [" + subject + "] is not valid name : [" + e.getMessage() + "]."); + } + if (!foundUserName) { + logger.info("subject [" + subject + "] does not contain the required attribute [" + subjectDnAttributeConfiguration + "]. Return the whole subject."); + userName = subject; + } + } + + } + + if (logger.isDebugEnabled()) { + logger.debug("getUserName(X509Certificate) - end; Ret is [" + userName + "]."); + } + return userName; + } + + private void setSubjectDnAttribute(String subjectDnAttributeConfiguration) { + if (logger.isDebugEnabled()) { + logger.debug("setSubjectDnAttribute(String) - start; subjectDnAttribute [" + subjectDnAttributeConfiguration + "]."); + } + this.subjectDnAttributeConfiguration = subjectDnAttributeConfiguration; + subjectDnAttribute = mapSubjectDnAttribute(subjectDnAttributeConfiguration); + if (logger.isDebugEnabled()) { + logger.debug("setSubjectDnAttribute(String) - end; subjectDnAttribute [" + subjectDnAttribute + "]; subjectDnAttributeConfiguration [" + subjectDnAttributeConfiguration + "]"); + } + } + + + + private String mapSubjectDnAttribute(String subjectDnAttributeConfiguration) { + String ret = null; + if (subjectDnAttributeConfiguration != null) { + if (ClientCertificateConstants.EmailOptions.contains(subjectDnAttributeConfiguration.toLowerCase())) { + ret = ClientCertificateConstants.EMAIL_SUBJECT_ATTR; + } else { + ret = subjectDnAttributeConfiguration; + } + } + return ret; + } + + protected String getSubjectDN(X509Certificate clientCert) { + if (logger.isDebugEnabled()) { + logger.debug("getSubjectDN(X509Certificate) - start"); + } + String subject = null; + if (clientCert != null) { + if ((clientCert.getSubjectDN()!= null) + && (clientCert.getSubjectDN().getName() != null)) { + subject = clientCert.getSubjectDN().getName(); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Can not getSubjectDN, SubjectDN is null"); + } + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("Can not getSubjectDN, clientCert is null"); + } + } + if (logger.isDebugEnabled()) { + logger.debug("getSubjectDN(X509Certificate) - end; Ret is [" + subject + "]."); + } + return subject; + + } +} Index: org/apache/catalina/realm/ClientCertificateConstants.java =================================================================== --- org/apache/catalina/realm/ClientCertificateConstants.java (revision 0) +++ org/apache/catalina/realm/ClientCertificateConstants.java (revision 0) @@ -0,0 +1,60 @@ +/** + * @author Michael Furman + */ +package org.apache.catalina.realm; + +import java.util.Arrays; +import java.util.List; + +public class ClientCertificateConstants { + + + public enum UserNameRetrieveField { + SubjectDN, SubjectAlternativeName; + public boolean equals(final String str) { + return name().equals(str); + } + } + + + public static final String EMAIL_SUBJECT_ATTR = "emailAddress"; + + + public enum SubjectAlternativeNameGeneralNames { + + otherName, // byte arrays containing the ASN.1 DER encoded form + rfc822Name, // String + dNSName, // String + x400Address, // byte arrays containing the ASN.1 DER encoded form + directoryName, // String: RFC 2253 string format + ediPartyName, // byte arrays containing the ASN.1 DER encoded form + uniformResourceIdentifier, // String + iPAddress, // String: IPv4 address - dotted quad notation, IPv6 address - form "a1:a2:...:a8" + registeredID; + + public boolean equals(final String str) { + return name().equals(str); + } + + public boolean equalsIgnoreCase(final String str) { + return name().equalsIgnoreCase(str); + } + } + + // !!! important - set value only in lower case !!! + + // SubjectDN + public static final List EmailOptions = Arrays.asList(EMAIL_SUBJECT_ATTR.toLowerCase(), "e") ; + + // Subject Alternative Name + public static final List OtherNameOptions = Arrays.asList("other name", "principalname", "principal name", "microsoft principal name") ; + public static final List RFC822NameOptions = Arrays.asList("rfc822 name", "rfc822name", "emailaddress", "email address", "e-mail address", "e-mailaddress") ; + public static final List DNSNameOptions = Arrays.asList("dns name", "dnsname") ; + // x400Address - empty + public static final List DirectoryNameOptions = Arrays.asList("directory address", "directory address", "x500 name", "x500name", "x.500 name", "x.500name") ; + // ediPartyName - empty + public static final List UriOptions = Arrays.asList("url", "uri") ; + public static final List IPAddressOptions = Arrays.asList("ip address", "ipaddress"); + public static final List RegisteredIDOptions = Arrays.asList("registered id", "registeredid","registered oid", "registeredoid"); + +} Index: org/apache/catalina/realm/SubjectAlternativeNameRetriever.java =================================================================== --- org/apache/catalina/realm/SubjectAlternativeNameRetriever.java (revision 0) +++ org/apache/catalina/realm/SubjectAlternativeNameRetriever.java (revision 0) @@ -0,0 +1,202 @@ +/** + * @author Michael Furman + */ +package org.apache.catalina.realm; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.ASN1TaggedObject; +import org.bouncycastle.asn1.DERObject; +import org.bouncycastle.asn1.DERUTF8String; + + +public class SubjectAlternativeNameRetriever extends SubjectDnRetriever { + private static final int NOT_EXISTING_TYPE = -1; + + + /** + * Logger for this class + */ + protected final Log logger = LogFactory.getLog(getClass()); + + + private String alternativeNameConfiguration = null; + + private int alternativeNameTypeValue = NOT_EXISTING_TYPE; + + protected SubjectAlternativeNameRetriever(String alternativeNameConfiguration) { + setSubjectAlternativeNameGeneralName(alternativeNameConfiguration); + } + + + + @SuppressWarnings("unchecked") + public String getUserName(X509Certificate clientCert) { + if (logger.isDebugEnabled()) { + logger.debug("getUserName(X509Certificate) - start"); + } + + String userName = null; + if (clientCert != null) { + boolean foundUserName = false; + if (alternativeNameTypeValue != NOT_EXISTING_TYPE) { + try { + if (clientCert.getSubjectAlternativeNames() != null) { + Collection subjectAlternativeNames = clientCert.getSubjectAlternativeNames(); + Iterator iter = subjectAlternativeNames.iterator(); + while (iter.hasNext()) { + List subjectAlternativeName = (List) iter.next(); + Integer type = (Integer) subjectAlternativeName.get(0); + if (type.intValue() == alternativeNameTypeValue) { + Object subjectAlternativeNameValue = subjectAlternativeName.get(1); + if (subjectAlternativeNameValue instanceof String) { + userName = (String) subjectAlternativeNameValue; + foundUserName = true; + break; + } else if (subjectAlternativeNameValue instanceof byte[]) { + byte[] subjectAlternativeNameValueBytes = (byte[]) subjectAlternativeNameValue; + userName = getStringFromASNDerEncodedByteArray(subjectAlternativeNameValueBytes); + if (userName != null) { + foundUserName = true; + break; + } + } else { + if (logger.isInfoEnabled()) { + logger.info("Can not get UserName, the subjectAlternativeName not supported [" + subjectAlternativeNameValue + "]."); + } + } + } + } + } + } catch (CertificateParsingException e) { + logger.info("Can not get UserName, can not get subjectAlternativeNames from certificate [" + e.getMessage() + "]."); + } + + + } else { + if (logger.isDebugEnabled()) { + logger.debug("Can not get UserName, generalName is null"); + } + } + if (!foundUserName) { + logger.info("Can not found userName as part of subjectAlternativeName [" + alternativeNameConfiguration + "]. Return the whole subject."); + userName = getSubjectDN(clientCert); + } + + } else { + if (logger.isDebugEnabled()) { + logger.debug("Can not get UserName, clientCert is null"); + } + } + if (logger.isDebugEnabled()) { + logger.debug("getUserName(X509Certificate) - end; Ret is [" + userName + "]."); + } + + return userName; + } + + + + + + private void setSubjectAlternativeNameGeneralName(String alternativeNameConfiguration) { + if (logger.isDebugEnabled()) { + logger.debug("setSubjectAlternativeNameGeneralName(String) - start; alternativeName [" + alternativeNameConfiguration + "]."); + } + this.alternativeNameConfiguration = null; + alternativeNameTypeValue = NOT_EXISTING_TYPE; + + if (alternativeNameConfiguration != null) { + this.alternativeNameConfiguration = alternativeNameConfiguration; + String alternativeNameConfigurationLowerCase = alternativeNameConfiguration.toLowerCase(); + if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.otherName.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.OtherNameOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.otherName.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.rfc822Name.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.RFC822NameOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.rfc822Name.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.dNSName.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.DNSNameOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.dNSName.ordinal(); + } else if (ClientCertificateConstants.SubjectAlternativeNameGeneralNames.x400Address.equalsIgnoreCase (alternativeNameConfiguration)) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.x400Address.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.directoryName.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.DirectoryNameOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.directoryName.ordinal(); + } else if (ClientCertificateConstants.SubjectAlternativeNameGeneralNames.ediPartyName.equalsIgnoreCase (alternativeNameConfiguration)) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.ediPartyName.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.uniformResourceIdentifier.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.UriOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.uniformResourceIdentifier.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.iPAddress.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.IPAddressOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.iPAddress.ordinal(); + } else if ((ClientCertificateConstants.SubjectAlternativeNameGeneralNames.registeredID.equalsIgnoreCase (alternativeNameConfiguration)) + || (ClientCertificateConstants.RegisteredIDOptions.contains(alternativeNameConfigurationLowerCase))) { + alternativeNameTypeValue = ClientCertificateConstants.SubjectAlternativeNameGeneralNames.registeredID.ordinal(); + } else { + try { + alternativeNameTypeValue = (new Integer(alternativeNameConfiguration)).intValue(); + }catch (NumberFormatException e) { + alternativeNameTypeValue = NOT_EXISTING_TYPE; + } + } + + } + if (logger.isDebugEnabled()) { + logger.debug("setSubjectAlternativeNameGeneralName(String) - end; alternativeName [" + alternativeNameConfiguration + "], alternativeNameType [" + alternativeNameTypeValue + "]."); + } + } + + + private String getStringFromASNDerEncodedByteArray(byte[] byteArray) { + if (logger.isDebugEnabled()) { + logger.debug("getStringFromASNDerEncodedByteArray(byte[]) - start"); + } + + String ret = null; + try { + ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(byteArray)); + DERObject derObject = asn1InputStream.readObject(); + ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(derObject); + Object objectValue = asn1Sequence.getObjectAt(1); + if (objectValue instanceof ASN1TaggedObject) { + ASN1TaggedObject asn1TaggedObject = (ASN1TaggedObject) objectValue; + try { + if (logger.isDebugEnabled()) { + logger.debug("Try to get string from DERUTF8String."); + } + DERObject derTaggedObject = asn1TaggedObject.getObject(); + DERUTF8String derUtf8String = DERUTF8String.getInstance(derTaggedObject); + ret = derUtf8String.getString(); + } catch (IllegalArgumentException e) { + if (logger.isDebugEnabled()) { + logger.debug("Can not get String From DERUTF8String, [" + e.getMessage() + "]."); + } + } + } + } catch (Exception e) { + if (logger.isInfoEnabled()) { + logger.info("Can not get String From ASNDerEncoded ByteArray, [" + e.getMessage() + "]."); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("getStringFromASNDerEncodedByteArray(byte[]) - end. Ret is [" + ret + "]."); + } + return ret; + + } + + +} Index: org/apache/catalina/realm/RealmBase.java =================================================================== --- org/apache/catalina/realm/RealmBase.java (revision 1237538) +++ org/apache/catalina/realm/RealmBase.java (working copy) @@ -49,6 +49,7 @@ import org.apache.catalina.deploy.SecurityCollection; import org.apache.catalina.deploy.SecurityConstraint; import org.apache.catalina.mbeans.MBeanUtils; +import org.apache.catalina.realm.ClientCertificateConstants.UserNameRetrieveField; import org.apache.catalina.util.LifecycleMBeanBase; import org.apache.catalina.util.MD5Encoder; import org.apache.juli.logging.Log; @@ -153,6 +154,14 @@ protected boolean stripRealmForGss = true; + private UserNameRetriever userNameRetriever = null; + + private String x509UserNameRetrieveField = null; + + private String x509UserNameRetrieveFieldPart = null; + + private String x509UserNameRetrieverClassName; + // ------------------------------------------------------------- Properties @@ -1194,7 +1203,12 @@ * Return the Principal associated with the given certificate. */ protected Principal getPrincipal(X509Certificate usercert) { - return(getPrincipal(usercert.getSubjectDN().getName())); + createUserNameRetriever(); + String userName = userNameRetriever.getUserName(usercert); + + if (log.isDebugEnabled()) + log.debug("Get principal for [" + userName + "]"); + return(getPrincipal(userName)); } @@ -1393,5 +1407,72 @@ return name; } } + + public void setX509UserNameRetrieveFieldPart(String userNameRetrieveFieldPart) { + this.x509UserNameRetrieveFieldPart = userNameRetrieveFieldPart; + } + + public void setX509UserNameRetrieveField(String userNameRetrieveField) { + this.x509UserNameRetrieveField = userNameRetrieveField; + } + + public void setX509UserNameRetrieverClassName(String className) { + this.x509UserNameRetrieverClassName = className; + } + private void createUserNameRetriever() { + + if (userNameRetriever == null) { + boolean created = createUserNameRetrieverFromClassName(); + if (!created) { + if (UserNameRetrieveField.SubjectDN.equals(x509UserNameRetrieveField)) { + if (x509UserNameRetrieveFieldPart == null) { + userNameRetriever = new SubjectDnRetriever(); + } else { + userNameRetriever = new SubjectDnRetriever(x509UserNameRetrieveFieldPart); + } + } else if (UserNameRetrieveField.SubjectAlternativeName.equals(x509UserNameRetrieveField)) { + if (x509UserNameRetrieveFieldPart == null) { + userNameRetriever = new SubjectDnRetriever(); + String warnString = "Can not create userNameRetriever : userNameRetrieveFieldPart is null when userNameRetrieveField is [" + x509UserNameRetrieveField + "]. userNameRetriever is created for SubjectDn field."; + log.warn(warnString); + } else { + userNameRetriever = new SubjectAlternativeNameRetriever(x509UserNameRetrieveFieldPart); + } + } else { + userNameRetriever = new SubjectDnRetriever(); + } + } + } + } + + @SuppressWarnings("unchecked") + private boolean createUserNameRetrieverFromClassName() { + + boolean created = false; + if ((x509UserNameRetrieverClassName != null) && (x509UserNameRetrieverClassName != "")) { + Class x509UserNameRetrieverClass = null; + try { + x509UserNameRetrieverClass = (Class) Class.forName(x509UserNameRetrieverClassName); + userNameRetriever = x509UserNameRetrieverClass.newInstance(); + created = true; + } 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; + } + + }