/*
* Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package sun.security.x509;
import java.lang.reflect.*;
import java.io.IOException;
import java.io.StringReader;
import java.security.PrivilegedExceptionAction;
import java.security.AccessController;
import java.security.Principal;
import java.util.*;
import sun.security.util.*;
import sun.security.pkcs.PKCS9Attribute;
import javax.security.auth.x500.X500Principal;
/**
* RDNs are a set of {attribute = value} assertions. Some of those
* attributes are "distinguished" (unique w/in context). Order is
* never relevant.
*
* Some X.500 names include only a single distinguished attribute
* per RDN. This style is currently common.
*
* Note that DER-encoded RDNs sort AVAs by assertion OID ... so that
* when we parse this data we don't have to worry about canonicalizing
* it, but we'll need to sort them when we expose the RDN class more.
* <p>
* The ASN.1 for RDNs is:
* <pre>
* RelativeDistinguishedName ::=
* SET OF AttributeTypeAndValue
*
* AttributeTypeAndValue ::= SEQUENCE {
* type AttributeType,
* value AttributeValue }
*
* AttributeType ::= OBJECT IDENTIFIER
*
* AttributeValue ::= ANY DEFINED BY AttributeType
* </pre>
*
* Note that instances of this class are immutable.
*
*/
public class RDN {
// currently not private, accessed directly from X500Name
final AVA[] assertion;
// cached immutable List of the AVAs
private volatile List<AVA> avaList;
// cache canonical String form
private volatile String canonicalString;
/**
* Constructs an RDN from its printable representation.
*
* An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
* using '+' as a separator.
* If the '+' should be considered part of an AVA value, it must be
* preceded by '\'.
*
* @param name String form of RDN
* @throws IOException on parsing error
*/
public RDN(String name) throws IOException {
this(name, Collections.<String, String>emptyMap());
}
/**
* Constructs an RDN from its printable representation.
*
* An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
* using '+' as a separator.
* If the '+' should be considered part of an AVA value, it must be
* preceded by '\'.
*
* @param name String form of RDN
* @param keyword an additional mapping of keywords to OIDs
* @throws IOException on parsing error
*/
public RDN(String name, Map<String, String> keywordMap) throws IOException {
int quoteCount = 0;
int searchOffset = 0;
int avaOffset = 0;
List<AVA> avaVec = new ArrayList<AVA>(3);
int nextPlus = name.indexOf('+');
while (nextPlus >= 0) {
quoteCount += X500Name.countQuotes(name, searchOffset, nextPlus);
/*
* We have encountered an AVA delimiter (plus sign).
* If the plus sign in the RDN under consideration is
* preceded by a backslash (escape), or by a double quote, it
* is part of the AVA. Otherwise, it is used as a separator, to
* delimit the AVA under consideration from any subsequent AVAs.
*/
if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\'
&& quoteCount != 1) {
/*
* Plus sign is a separator
*/
String avaString = name.substring(avaOffset, nextPlus);
if (avaString.length() == 0) {
throw new IOException("empty AVA in RDN \"" + name + "\"");
}
// Parse AVA, and store it in vector
AVA ava = new AVA(new StringReader(avaString), keywordMap);
avaVec.add(ava);
// Increase the offset
avaOffset = nextPlus + 1;
// Set quote counter back to zero
quoteCount = 0;
}
searchOffset = nextPlus + 1;
nextPlus = name.indexOf('+', searchOffset);
}
// parse last or only AVA
String avaString = name.substring(avaOffset);
if (avaString.length() == 0) {
throw new IOException("empty AVA in RDN \"" + name + "\"");
}
AVA ava = new AVA(new StringReader(avaString), keywordMap);
avaVec.add(ava);
assertion = avaVec.toArray(new AVA[avaVec.size()]);
}
/*
* Constructs an RDN from its printable representation.
*
* An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
* using '+' as a separator.
* If the '+' should be considered part of an AVA value, it must be
* preceded by '\'.
*
* @param name String form of RDN
* @throws IOException on parsing error
*/
RDN(String name, String format) throws IOException {
this(name, format, Collections.<String, String>emptyMap());
}
/*
* Constructs an RDN from its printable representation.
*
* An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
* using '+' as a separator.
* If the '+' should be considered part of an AVA value, it must be
* preceded by '\'.
*
* @param name String form of RDN
* @param keyword an additional mapping of keywords to OIDs
* @throws IOException on parsing error
*/
RDN(String name, String format, Map<String, String> keywordMap)
throws IOException {
if (format.equalsIgnoreCase("RFC2253") == false) {
throw new IOException("Unsupported format " + format);
}
int searchOffset = 0;
int avaOffset = 0;
List<AVA> avaVec = new ArrayList<AVA>(3);
int nextPlus = name.indexOf('+');
while (nextPlus >= 0) {
/*
* We have encountered an AVA delimiter (plus sign).
* If the plus sign in the RDN under consideration is
* preceded by a backslash (escape), or by a double quote, it
* is part of the AVA. Otherwise, it is used as a separator, to
* delimit the AVA under consideration from any subsequent AVAs.
*/
if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\' ) {
/*
* Plus sign is a separator
*/
String avaString = name.substring(avaOffset, nextPlus);
if (avaString.length() == 0) {
throw new IOException("empty AVA in RDN \"" + name + "\"");
}
// Parse AVA, and store it in vector
AVA ava = new AVA
(new StringReader(avaString), AVA.RFC2253, keywordMap);
avaVec.add(ava);
// Increase the offset
avaOffset = nextPlus + 1;
}
searchOffset = nextPlus + 1;
nextPlus = name.indexOf('+', searchOffset);
}
// parse last or only AVA
String avaString = name.substring(avaOffset);
if (avaString.length() == 0) {
throw new IOException("empty AVA in RDN \"" + name + "\"");
}
AVA ava = new AVA(new StringReader(avaString), AVA.RFC2253, keywordMap);
avaVec.add(ava);
assertion = avaVec.toArray(new AVA[avaVec.size()]);
}
/*
* Constructs an RDN from an ASN.1 encoded value. The encoding
* of the name in the stream uses DER (a BER/1 subset).
*
* @param value a DER-encoded value holding an RDN.
* @throws IOException on parsing error.
*/
RDN(DerValue rdn) throws IOException {
if (rdn.tag != DerValue.tag_Set) {
throw new IOException("X500 RDN");
}
DerInputStream dis = new DerInputStream(rdn.toByteArray());
DerValue[] avaset = dis.getSet(5);
assertion = new AVA[avaset.length];
for (int i = 0; i < avaset.length; i++) {
assertion[i] = new AVA(avaset[i]);
}
}
/*
* Creates an empty RDN with slots for specified
* number of AVAs.
*
* @param i number of AVAs to be in RDN
*/
RDN(int i) { assertion = new AVA[i]; }
public RDN(AVA ava) {
if (ava == null) {
throw new NullPointerException();
}
assertion = new AVA[] { ava };
}
public RDN(AVA[] avas) {
assertion = avas.clone();
for (int i = 0; i < assertion.length; i++) {
if (assertion[i] == null) {
throw new NullPointerException();
}
}
}
/**
* Return an immutable List of the AVAs in this RDN.
*/
public List<AVA> avas() {
List<AVA> list = avaList;
if (list == null) {
list = Collections.unmodifiableList(Arrays.asList(assertion));
avaList = list;
}
return list;
}
/**
* Return the number of AVAs in this RDN.
*/
public int size() {
return assertion.length;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RDN == false) {
return false;
}
RDN other = (RDN)obj;
if (this.assertion.length != other.assertion.length) {
return false;
}
String thisCanon = this.toRFC2253String(true);
String otherCanon = other.toRFC2253String(true);
return thisCanon.equals(otherCanon);
}
/*
* Calculates a hash code value for the object. Objects
* which are equal will also have the same hashcode.
*
* @returns int hashCode value
*/
public int hashCode() {
return toRFC2253String(true).hashCode();
}
/*
* return specified attribute value from RDN
*
* @params oid ObjectIdentifier of attribute to be found
* @returns DerValue of attribute value; null if attribute does not exist
*/
DerValue findAttribute(ObjectIdentifier oid) {
for (int i = 0; i < assertion.length; i++) {
if (assertion[i].oid.equals(oid)) {
return assertion[i].value;
}
}
return null;
}
/*
* Encode the RDN in DER-encoded form.
*
* @param out DerOutputStream to which RDN is to be written
* @throws IOException on error
*/
void encode(DerOutputStream out) throws IOException {
out.putOrderedSetOf(DerValue.tag_Set, assertion);
}
/*
* Returns a printable form of this RDN, using RFC 1779 style catenation
* of attribute/value assertions, and emitting attribute type keywords
* from RFCs 1779, 2253, and 3280.
*/
public String toString() {
if (assertion.length == 1) {
return assertion[0].toString();
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < assertion.length; i++) {
if (i != 0) {
sb.append(" + ");
}
sb.append(assertion[i].toString());
}
return sb.toString();
}
/*
* Returns a printable form of this RDN using the algorithm defined in
* RFC 1779. Only RFC 1779 attribute type keywords are emitted.
*/
public String toRFC1779String() {
return toRFC1779String(Collections.<String, String>emptyMap());
}
/*
* Returns a printable form of this RDN using the algorithm defined in
* RFC 1779. RFC 1779 attribute type keywords are emitted, as well
* as keywords contained in the OID/keyword map.
*/
public String toRFC1779String(Map<String, String> oidMap) {
if (assertion.length == 1) {
return assertion[0].toRFC1779String(oidMap);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < assertion.length; i++) {
if (i != 0) {
sb.append(" + ");
}
sb.append(assertion[i].toRFC1779String(oidMap));
}
return sb.toString();
}
/*
* Returns a printable form of this RDN using the algorithm defined in
* RFC 2253. Only RFC 2253 attribute type keywords are emitted.
*/
public String toRFC2253String() {
return toRFC2253StringInternal
(false, Collections.<String, String>emptyMap());
}
/*
* Returns a printable form of this RDN using the algorithm defined in
* RFC 2253. RFC 2253 attribute type keywords are emitted, as well as
* keywords contained in the OID/keyword map.
*/
public String toRFC2253String(Map<String, String> oidMap) {
return toRFC2253StringInternal(false, oidMap);
}
/*
* Returns a printable form of this RDN using the algorithm defined in
* RFC 2253. Only RFC 2253 attribute type keywords are emitted.
* If canonical is true, then additional canonicalizations
* documented in X500Principal.getName are performed.
*/
public String toRFC2253String(boolean canonical) {
if (canonical == false) {
return toRFC2253StringInternal
(false, Collections.<String, String>emptyMap());
}
String c = canonicalString;
if (c == null) {
c = toRFC2253StringInternal
(true, Collections.<String, String>emptyMap());
canonicalString = c;
}
return c;
}
private String toRFC2253StringInternal
(boolean canonical, Map<String, String> oidMap) {
/*
* Section 2.2: When converting from an ASN.1 RelativeDistinguishedName
* to a string, the output consists of the string encodings of each
* AttributeTypeAndValue (according to 2.3), in any order.
*
* Where there is a multi-valued RDN, the outputs from adjoining
* AttributeTypeAndValues are separated by a plus ('+' ASCII 43)
* character.
*/
// normally, an RDN only contains one AVA
if (assertion.length == 1) {
return canonical ? assertion[0].toRFC2253CanonicalString() :
assertion[0].toRFC2253String(oidMap);
}
StringBuilder relname = new StringBuilder();
if (!canonical) {
for (int i = 0; i < assertion.length; i++) {
if (i > 0) {
relname.append('+');
}
relname.append(assertion[i].toRFC2253String(oidMap));
}
} else {
// order the string type AVA's alphabetically,
// followed by the oid type AVA's numerically
List<AVA> avaList = new ArrayList<AVA>(assertion.length);
for (int i = 0; i < assertion.length; i++) {
avaList.add(assertion[i]);
}
java.util.Collections.sort(avaList, AVAComparator.getInstance());
for (int i = 0; i < avaList.size(); i++) {
if (i > 0) {
relname.append('+');
}
relname.append(avaList.get(i).toRFC2253CanonicalString());
}
}
return relname.toString();
}
}
class AVAComparator implements Comparator<AVA> {
private static final Comparator<AVA> INSTANCE = new AVAComparator();
private AVAComparator() {
// empty
}
static Comparator<AVA> getInstance() {
return INSTANCE;
}
/**
* AVA's containing a standard keyword are ordered alphabetically,
* followed by AVA's containing an OID keyword, ordered numerically
*/
public int compare(AVA a1, AVA a2) {
boolean a1Has2253 = a1.hasRFC2253Keyword();
boolean a2Has2253 = a2.hasRFC2253Keyword();
if (a1Has2253 == a2Has2253) {
return a1.toRFC2253CanonicalString().compareTo
(a2.toRFC2253CanonicalString());
} else {
if (a1Has2253) {
return -1;
} else {
return 1;
}
}
}
}