// --- BEGIN COPYRIGHT BLOCK --- // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program 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 for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package netscape.security.x509; import java.io.ByteArrayOutputStream; import java.io.CharArrayWriter; import java.io.IOException; import java.io.PushbackReader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.util.Vector; import netscape.security.util.DerValue; import netscape.security.util.ObjectIdentifier; import sun.io.ByteToCharConverter; /** * A converter that converts Ldap v3 DN strings as specified in * draft-ietf-asid-ldapv3-dn-03.txt to a X500Name, RDN or AVA and * vice versa. * * @see LdapDNStrConverter * @see X500Name * @see RDN * @see AVA * @see X500NameAttrMap * * @author Lily Hsiao, Slava Galperin at Netscape Communications, Inc. */ public class LdapV3DNStrConverter extends LdapDNStrConverter { // // Constructors // /** * Constructs a LdapV3DNStrConverter using the global default * X500NameAttrMap and accept OIDs not in the default X500NameAttrMap. * @see X500NameAttrMap */ public LdapV3DNStrConverter() { attrMap = X500NameAttrMap.getDefault(); acceptUnknownOids = true; } /** * Constructs a LdapV3DNStrConverter using the specified X500NameAttrMap * and a boolean indicating whether to accept OIDs not listed in the * X500NameAttrMap. * * @param attributeMap a X500NameAttrMap * @param doAcceptUnknownOids whether to convert unregistered OIDs * (oids not in the X500NameAttrMap) * @see X500NameAttrMap */ public LdapV3DNStrConverter(X500NameAttrMap attributeMap, boolean doAcceptUnknownOids) { attrMap = attributeMap; acceptUnknownOids = doAcceptUnknownOids; } // // public parsing methods // From LdapDNStrConverter interface // /** * Parse a Ldap v3 DN string to a X500Name. * * @param dn a LDAP v3 DN String * @return a X500Name * @exception IOException if an error occurs during the conversion. */ public X500Name parseDN(String dn) throws IOException { return parseDN(dn, null); } /** * Like parseDN(String) with a DER encoding order given as argument for * Directory Strings. */ public X500Name parseDN(String dn, byte[] encodingOrder) throws IOException { StringReader dn_reader = new StringReader(dn); PushbackReader in = new PushbackReader(dn_reader, 5); Vector rdnVector = new Vector(); RDN[] names; return parseDN(in, encodingOrder); } /** * Parse a Ldap v3 DN string with a RDN component to a RDN * * @param rdn a LDAP v3 DN String * @return a RDN * @exception IOException if an error occurs during the conversion. */ public RDN parseRDN(String rdn) throws IOException { return parseRDN(rdn, null); } /** * Like parseRDN(String) with a DER encoding order given as argument for * Directory Strings. */ public RDN parseRDN(String rdn, byte[] encodingOrder) throws IOException { StringReader rdn_reader = new StringReader(rdn); PushbackReader in = new PushbackReader(rdn_reader, 5); Vector avaVector = new Vector(); AVA[] assertion; return parseRDN(in, null); } /** * Parse a Ldap v3 DN string with a AVA component to a AVA. * * @param ava a LDAP v3 DN string * @return a AVA */ public AVA parseAVA(String ava) throws IOException { return parseAVA(ava, null); } /** * Like parseDN(String) with a DER encoding order given as argument for * Directory Strings. */ public AVA parseAVA(String ava, byte[] encodingOrder) throws IOException { StringReader ava_reader = new StringReader(ava); PushbackReader in = new PushbackReader(ava_reader, 5); return parseAVA(in, encodingOrder); } // // public parsing methods called by other methods. // /** * Parses a Ldap DN string in a string reader to a X500Name. * * @param in Pushback string reader for a Ldap DN string. * The pushback reader must have a pushback buffer size > 2. * * @return a X500Name * * @exception IOException if any reading or parsing error occurs. */ public X500Name parseDN(PushbackReader in) throws IOException { return parseDN(in, null); } /** * Like parseDN(PushbackReader in) with a DER encoding order given as * argument for Directory Strings. */ public X500Name parseDN(PushbackReader in, byte[] encodingOrder) throws IOException { RDN rdn; int lastChar; Vector rdnVector = new Vector(); RDN names[]; int i,j; do { rdn = parseRDN(in, encodingOrder); rdnVector.addElement(rdn); lastChar = in.read(); } while (lastChar == ',' || lastChar == ';'); names = new RDN[rdnVector.size()]; for (i=0, j=rdnVector.size()-1; i < rdnVector.size(); i++, j--) names[j] = (RDN)rdnVector.elementAt(i); return new X500Name(names); } /** * Parses Ldap DN string with a rdn component * from a string reader to a RDN. The string reader will point * to the separator after the rdn component or -1 if at end of string. * * @param in Pushback string reader containing a Ldap DN string with * at least one rdn component. * The pushback reader must have a pushback buffer size > 2. * * @return RDN object of the first rdn component in the Ldap DN string. * * @exception IOException if any read or parse error occurs. */ public RDN parseRDN(PushbackReader in) throws IOException { return parseRDN(in, null); } /** * Like parseRDN(PushbackReader) with a DER encoding order given as * argument for Directory Strings. */ public RDN parseRDN(PushbackReader in, byte[] encodingOrder) throws IOException { Vector avaVector = new Vector(); AVA ava; int lastChar; AVA assertion[]; do { ava = parseAVA(in, encodingOrder); avaVector.addElement(ava); lastChar = in.read(); } while (lastChar == '+' ); if (lastChar != -1) in.unread(lastChar); assertion = new AVA[avaVector.size()]; for (int i = 0; i < avaVector.size(); i++) assertion[i] = (AVA)avaVector.elementAt(i); return new RDN(assertion); } /** * Parses a Ldap DN string with a AVA component * from a string reader to an AVA. The string reader will point * to the AVA separator after the ava string or -1 if end of string. * * @param in a Pushback reader containg a Ldap string with * at least one AVA component. * The Pushback reader must have a pushback buffer size > 2. * * @return AVA object of the first AVA component in the Ldap DN string. */ public AVA parseAVA(PushbackReader in) throws IOException { return parseAVA(in, null); } /** * Like parseAVA(PushbackReader) with a DER encoding order given as * argument for Directory Strings. */ public AVA parseAVA(PushbackReader in, byte[] encodingOrder) throws IOException { int c; ObjectIdentifier oid; DerValue value; StringBuffer keywordBuf; StringBuffer valueBuf; ByteArrayOutputStream berStream; char hexChar1, hexChar2; CharArrayWriter hexCharsBuf; String endChars; /* First get the keyword indicating the attribute's type, * and map it to the appropriate OID. */ keywordBuf = new StringBuffer(); for (;;) { c = in.read (); if (c == '=') break; if (c == -1) { throw new IOException("Bad AVA format: Missing '='"); } keywordBuf.append ((char)c); } oid = parseAVAKeyword(keywordBuf.toString()); /* Now parse the value. "#hex", a quoted string, or a string * terminated by "+", ",", ";", ">". Whitespace before or after * the value is stripped. */ for (c = in.read (); c == ' '; c = in.read ()) continue; if (c == -1) throw new IOException("Bad AVA format: Missing attribute value"); if (c == '#') { /* * NOTE per LDAPv3 dn string ietf standard the value represented * by this form is a BER value. But we only support DER value here * which is only a form of BER. */ berStream = new ByteArrayOutputStream(); int b; for (;;) { hexChar1 = (char)(c = in.read()); if (c == -1 || octoEndChars.indexOf(c) > 0) // end of value break; hexChar2 = (char)(c = in.read()); if (hexDigits.indexOf(hexChar1) == -1 || hexDigits.indexOf(hexChar2) == -1) throw new IOException("Bad AVA value: bad hex value."); b = (Character.digit(hexChar1, 16)<<4) + Character.digit(hexChar2, 16); berStream.write(b); } if (berStream.size() == 0) throw new IOException("bad AVA format: invalid hex value"); value = parseAVAValue(berStream.toByteArray(), oid); while (c == ' ' && c != -1) c = in.read(); } else { valueBuf = new StringBuffer (); boolean quoted = false; if (c == '"') { quoted = true; endChars = quotedEndChars; if ((c = in.read()) == -1) throw new IOException("Bad AVA format: Missing attrValue"); } else { endChars = valueEndChars; } // QUOTATION * ( quotechar / pair ) QUOTATION // quotechar = any character except '\' or QUOTATION // pair = '\' ( special | '\' | QUOTATION | hexpair ) while (c != -1 && endChars.indexOf(c) == -1) { if (c == '\\') { if ((c = in.read()) == -1) throw new IOException("Bad AVA format: expecting " + "escaped char."); // expect escaping of special chars, space and CR. if (specialChars.indexOf((char)c) != -1 || c == '\n' || c == '\\' || c == '"' || c == ' ') { valueBuf.append((char)c); } else if (hexDigits.indexOf(c) != -1) { hexCharsBuf = new CharArrayWriter(); // handle sequence of '\' hexpair do { hexChar1 = (char)c; hexChar2 = (char)(c = in.read()); if (hexDigits.indexOf((char)c) == -1) throw new IOException("Bad AVA format: " + "invalid escaped hex pair"); hexCharsBuf.write(hexChar1); hexCharsBuf.write(hexChar2); // read ahead to next '\' hex-char if any. if ((c = in.read()) == -1) break; if (c != '\\') { in.unread(c); break; } if ((c = in.read()) == -1) throw new IOException("Bad AVA format: "+ "expecting escaped char."); if (hexDigits.indexOf((char)c) == -1) { in.unread(c); in.unread((int)'\\'); break; } } while (true); valueBuf.append( getStringFromHexpairs(hexCharsBuf.toCharArray())); } else { throw new IOException("Bad AVA format: " + "invalid escaping"); } } else valueBuf.append((char)c); c = in.read(); } value = parseAVAValue( valueBuf.toString().trim(), oid, encodingOrder); if (quoted) { // move to next non-white space do { c = in.read(); } while (c == ' '); if (c != -1 && valueEndChars.indexOf(c) == -1) throw new IOException( "Bad AVA format: separator expected at end of ava."); } } if (c != -1) in.unread(c); return new AVA(oid, value); } /** * Converts a AVA keyword from a Ldap DN string to an ObjectIdentifier * from the attribute map or, if this keyword is an OID not * in the attribute map, create a new ObjectIdentifier for the keyword * if acceptUnknownOids is true. * * @param avaKeyword AVA keyword from a Ldap DN string. * * @return a ObjectIdentifier object * @exception IOException if the keyword is an OID not in the attribute * map and acceptUnknownOids is false, or * if an error occurs during conversion. */ public ObjectIdentifier parseAVAKeyword(String avaKeyword) throws IOException { String keyword = avaKeyword.toUpperCase().trim(); String oid_str = null; ObjectIdentifier oid, new_oid; if (Character.digit(keyword.charAt(0), 10) != -1) { // value is an oid string of 1.2.3.4 oid_str = keyword; } else if (keyword.startsWith("oid.") || keyword.startsWith("OID.")) { // value is an oid string of oid.1.2.3.4 or OID.1.2... oid_str = keyword.substring(4); } if (oid_str != null) { // value is an oid string of 1.2.3.4 or oid.1.2.3.4 or OID.1.2... new_oid = new ObjectIdentifier(oid_str); oid = attrMap.getOid(new_oid); if (oid == null) { if (!acceptUnknownOids) throw new IOException("Unknown AVA OID."); oid = new_oid; } } else { oid = attrMap.getOid(keyword); if (oid == null) throw new IOException("Unknown AVA keyword '"+keyword+"'."); } return oid; } /** * Converts a AVA value from a Ldap dn string to a * DerValue according the attribute type. For example, a value for * CN, OU or O is expected to be a Directory String and will be converted * to a DerValue of ASN.1 type PrintableString, T61String or * UniversalString. A Directory String is a ASN.1 CHOICE of Printable, * T.61 or Universal string. * * @param avaValueString a attribute value from a Ldap DN string. * @param oid OID of the attribute. * * @return DerValue for the value. * * @exception IOException if an error occurs during conversion. * @see AVAValueConverter */ public DerValue parseAVAValue(String avaValueString, ObjectIdentifier oid) throws IOException { return parseAVAValue(avaValueString, oid, null); } /** * Like parseAVAValue(String) with a DER encoding order given as argument * for Directory Strings. */ public DerValue parseAVAValue( String avaValueString, ObjectIdentifier oid, byte[] encodingOrder) throws IOException { AVAValueConverter valueConverter = attrMap.getValueConverter(oid); if (valueConverter == null) { if (!acceptUnknownOids) { throw new IllegalArgumentException( "Unrecognized OID for AVA value conversion"); } else { valueConverter = new GenericValueConverter(); } } return valueConverter.getValue(avaValueString, encodingOrder); } /** * Converts a value in BER encoding, for example given in octothorpe form * in a Ldap v3 dn string, to a DerValue. Checks if the BER encoded value * is a legal value for the attribute.
* NOTE: only DER encoded values are supported for
* the BER encoded value.
*
* @param berValue a value in BER encoding
* @param oid ObjectIdentifier of the attribute.
*
* @return DerValue for the BER encoded value
* @exception IOException if an error occurs during conversion.
*/
public DerValue parseAVAValue(byte[] berValue, ObjectIdentifier oid)
throws IOException
{
AVAValueConverter valueConverter = attrMap.getValueConverter(oid);
if (valueConverter == null && !acceptUnknownOids) {
throw new IllegalArgumentException(
"Unrecognized OID for AVA value conversion");
} else {
valueConverter = new GenericValueConverter();
}
return valueConverter.getValue(berValue);
}
//
// public encoding methods.
//
/**
* Converts a X500Name object to a Ldap v3 DN string (except in unicode).
*
* @param x500name a X500Name
*
* @return a Ldap v3 DN String (except in unicode).
*
* @exception IOException if an error is encountered during conversion.
*/
public String encodeDN(X500Name x500name)
throws IOException
{
RDN[] rdns = x500name.getNames();
// String fullname = null;
StringBuffer fullname = new StringBuffer();
String s;
int i;
if (rdns.length == 0)
return "";
i = rdns.length-1;
fullname.append(encodeRDN(rdns[i--]));
while (i >= 0) {
s = encodeRDN(rdns[i--]);
fullname.append(",");
fullname.append(s);
};
return fullname.toString();
}
/**
* Converts a RDN to a Ldap v3 DN string (except in unicode).
*
* @param rdn a RDN
*
* @return a LDAP v3 DN string (except in unicode).
*
* @exception IOException if an error is encountered during conversion.
*/
public String encodeRDN(RDN rdn)
throws IOException
{
AVA[] avas = rdn.getAssertion();
// String relname = null;
StringBuffer relname = new StringBuffer();
String s;
int i=0;
relname.append(encodeAVA(avas[i++]));
while (i