/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE file for licensing information.
*/
package eu.emi.security.authn.x509.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import eu.emi.security.authn.x509.helpers.CertificateHelpers;
import eu.emi.security.authn.x509.helpers.DNComparator;
import eu.emi.security.authn.x509.helpers.JavaAndBCStyle;
/**
* Contains utility static methods which are helpful in manipulating X.500 Distinguished
* Names, especially encoded in String form using RFC 2253.
*
* @author K. Benedyczak
*/
public class X500NameUtils
{
static
{
CertificateUtils.configureSecProvider();
}
/**
* Convenience method, based on the standard JDK algorithm for DNs comparison.
* However this method is less strict then the original: it compares DC and EMAIL
* attributes in a case insensitive way. Input arguments with values encoded
* in hex are also correctly handled. What is more it supports DNs with attribute
* names normally not recognized by the X500Principial class.
*
* @param rfc2253dn1 to be compared (need not to strictly follow the RFC encoding)
* @param rfc2253dn2 to be compared (need not to strictly follow the RFC encoding)
* @return true if DNs are equivalent
* @throws IllegalArgumentException if at least one of the DNs can not be parsed
*/
public static boolean equal(String rfc2253dn1, String rfc2253dn2) throws IllegalArgumentException
{
//first part: ensures that popular attribute names unsupported by JDK are encoded with OIDs
//and converts all DC and EMAIL attributes to lower case.
String rfcA = DNComparator.preNormalize(rfc2253dn1);
String rfcB = DNComparator.preNormalize(rfc2253dn2);
//Finally compare using CANONICAL forms.
return new X500Principal(rfcA).equals(new X500Principal(rfcB));
}
/**
* Convenience method for DN comparison. Is is equivalent to usage of the
* {@link #equal(String, String)}, after retrieving a String representation of
* the first argument.
* @param dn to be compared
* @param rfc2253dn2 to be compared
* @return true if DNs are equivalent
* @throws IllegalArgumentException if the String DN can not be parsed
*/
public static boolean equal(X500Principal dn, String rfc2253dn2) throws IllegalArgumentException
{
//do it carefully: first loose any ASN.1 info, then compare text versions
String dn1Str = dn.getName();
return equal(dn1Str, rfc2253dn2);
}
/**
* Uses the strict RFC 3280 algorithm to compare two DNs. This method should be used
* when both arguments were retrieved directly from the certificate, and therefore possess
* the full type information for the attributes forming the DNs.
* <p>
* Note 1: that in certain situations it is possible to get a false answer when
* comparing DNs with this method, while other DN equality tests from this class
* (operating on String DN representations) return true.
* <p>
* Note 2: it is nearly always wrong to convert a string representation of a DN to the
* X500Principal object and then to compare it against another using this method.
* In such a case always use the other equal methods from this class with one or
* two String arguments.
* <p>
* Note 3: this implementation is actually delegating to the JDK's {@link X500Principal}
* equals method, which seems to follow (one of the versions of) the rules of the RFC.
*
* @param dn to be compared
* @param dn2 to be compared
* @return if DNs are equivalent
*/
public static boolean rfc3280Equal(X500Principal dn, X500Principal dn2)
{
return dn.equals(dn2);
}
/**
* Returns a human-readable representation of this DN. The output is very similar
* to the output of X500Principial.getName() but additional attributes like
* EMAIL are recognized, correctly parsed and are not output as OIDs.
* <p>
* Note: it may happen that output of this method won't be parseable by
* the X500Principal constructor.
*
* @param srcDn to be output
* @return human readable form
* @throws IllegalArgumentException if the source DN can not be parsed
*/
public static String getReadableForm(String srcDn) throws IllegalArgumentException
{
JavaAndBCStyle style = new JavaAndBCStyle();
X500Name x500Name = new X500Name(style, srcDn);
return style.toStringFull(x500Name);
}
/**
* Returns a human-readable representation of this DN. The output is very similar
* to the output of X500Principial.toString() but additional attributes like
* EMAIL are recognized and are not output as OIDs.
* <p>
* Note: it may happen that output of this method won't be parseable by
* the X500Principal constructor.
* @param srcDn to be output
* @return human readable form
*/
public static String getReadableForm(X500Principal srcDn)
{
return getReadableForm(srcDn.getName());
}
/**
* Returns a form of the source DN in RFC 2253 form (or similar - some
* minor format violations are properly handled) which is strictly RFC2253
* and is guaranteed to be correctly parsed by the JDK methods.
* What is more it should be correctly parsed by other implementations.
* However this form can be not human readable.
*
* @param srcDn to be reformatted
* @return portable, RFC 2253 form
*/
public static String getPortableRFC2253Form(String srcDn)
{
String preNorm = DNComparator.preNormalize(srcDn);
return new X500Principal(preNorm).getName();
}
/**
* Returns a form of the source DN in RFC 2253 form (or similar - some
* minor format violations are properly handled) which is suitable for string comparison.
* I.e. it is guaranteed that all equivalent DNs will result in the same string.
* This method do not guarantee that always two non equivalent DNs produce a different output:
* this can not be guaranteed as there is no information on attribute type in the source DN.
* However this is unlikely.
*
* @param srcDn input to be reformatted
* @return string-comparable form
* @since 1.1.0
*/
public static String getComparableForm(String srcDn)
{
String preNorm = DNComparator.preNormalize(srcDn);
return new X500Principal(preNorm).getName(X500Principal.CANONICAL);
}
/**
* Returns an array of values of a provided attribute from the DN. Usually the returned array
* contains only a single value. 0-length array is returned if the attribute is not present.
* If attribute is present in multiple RDNs all values are returned.
* Note that values which are returned are converted to String. Values which can't
* be string encoded, are returned as HEX string (starting with '#'). Note that it may
* happen that even if you passed a DN with attribute encoded in HEX you will
* get its string representation - if it is only possible to retrieve it for the attribute.
*
* @param srcDn DN to be parsed in RFC 2253 form
* @param attribute to be retrieved. {@link JavaAndBCStyle} class and its parent
* contain useful constants.
* @return array of attribute values, decoded
* @throws IllegalArgumentException if the provided DN can not be parsed
*/
public static String[] getAttributeValues(String srcDn, ASN1ObjectIdentifier attribute) throws IllegalArgumentException
{
JavaAndBCStyle style = new JavaAndBCStyle();
X500Name x500Name = new X500Name(style, srcDn);
return getAttributeValues(x500Name, attribute);
}
/**
* Returns an array of values of a provided attribute from the DN.
* See {@link #getAttributeValues(String, ASN1ObjectIdentifier)} for details.
*
* @param srcDn DN to be parsed in RFC 2253 form
* @param attribute to be retrieved {@link JavaAndBCStyle} class and its parent contain
* useful constants.
* @return array of attribute values, decoded
*/
public static String[] getAttributeValues(X500Principal srcDn, ASN1ObjectIdentifier attribute)
{
X500Name dn = CertificateHelpers.toX500Name(srcDn);
return getAttributeValues(dn, attribute);
}
private static String[] getAttributeValues(X500Name x500Name, ASN1ObjectIdentifier attribute)
{
List<String> ret = new ArrayList<String>();
RDN[] rdns = x500Name.getRDNs();
for (RDN rdn: rdns)
{
AttributeTypeAndValue[] atvs = rdn.getTypesAndValues();
for (AttributeTypeAndValue atv: atvs)
{
if (atv.getType().equals(attribute))
ret.add(IETFUtils.valueToString(atv.getValue()));
}
}
return ret.toArray(new String[ret.size()]);
}
/**
* Returns a set with all attribute identifiers which are present in the passed DN.
* @param srcDn DN to be examined
* @return array of all attribute ids
*/
public static Set<ASN1ObjectIdentifier> getAttributeNames(String srcDn)
{
JavaAndBCStyle style = new JavaAndBCStyle();
X500Name x500Name = new X500Name(style, srcDn);
return getAttributeNames(x500Name);
}
/**
* Returns a set with all attribute identifiers which are present in the passed DN.
* @param srcDn DN to be examined
* @return array of all attribute ids
*/
public static Set<ASN1ObjectIdentifier> getAttributeNames(X500Principal srcDn)
{
X500Name dn = CertificateHelpers.toX500Name(srcDn);
return getAttributeNames(dn);
}
private static Set<ASN1ObjectIdentifier> getAttributeNames(X500Name dn)
{
RDN[] rdns = dn.getRDNs();
Set<ASN1ObjectIdentifier> ret = new HashSet<ASN1ObjectIdentifier>();
for (RDN rdn: rdns)
{
for (AttributeTypeAndValue ava: rdn.getTypesAndValues())
ret.add(ava.getType());
}
return ret;
}
/**
* Constructs a {@link X500Principal} object from a RFC 2253 string. This
* method can handle DNs with attributes not supported by the {@link X500Principal}
* constructor.
* @param rfcDn RFC 2253 DN
* @return the created object
* @throws IOException IO exception
*/
public static X500Principal getX500Principal(String rfcDn) throws IOException
{
JavaAndBCStyle style = new JavaAndBCStyle();
X500Name x500Name = new X500Name(style, rfcDn);
RDN[] rdns = x500Name.getRDNs();
for (int i=0; i<rdns.length/2; i++)
{
RDN bak = rdns[i];
rdns[i] = rdns[rdns.length-1-i];
rdns[rdns.length-1-i] = bak;
}
X500Name x500Name2 = new X500Name(rdns);
byte []encoded = x500Name2.getEncoded(ASN1Encoding.DER);
return new X500Principal(encoded);
}
}