diff options
Diffstat (limited to 'base/util/src/netscape/security/x509/LdapV3DNStrConverter.java')
-rw-r--r-- | base/util/src/netscape/security/x509/LdapV3DNStrConverter.java | 824 |
1 files changed, 824 insertions, 0 deletions
diff --git a/base/util/src/netscape/security/x509/LdapV3DNStrConverter.java b/base/util/src/netscape/security/x509/LdapV3DNStrConverter.java new file mode 100644 index 000000000..1245cc6bd --- /dev/null +++ b/base/util/src/netscape/security/x509/LdapV3DNStrConverter.java @@ -0,0 +1,824 @@ +// --- 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.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Arrays; +import java.util.Vector; + +import netscape.security.util.DerValue; +import netscape.security.util.ObjectIdentifier; + +/** + * 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); + + 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); + + 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<RDN> rdnVector = new Vector<RDN>(); + 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<AVA> avaVector = new Vector<AVA>(); + 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. + * <p> + * <strong><i>NOTE:</i></strong> 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 < avas.length) { + s = encodeAVA(avas[i++]); + relname.append("+"); + relname.append(s); + } + ; + return relname.toString(); + } + + /** + * Converts a AVA to a Ldap v3 DN String (except in unicode). + * + * @param ava an AVA + * + * @return a Ldap v3 DN string (except in unicode). + * + * @exception IOException If an error is encountered during exception. + */ + public String encodeAVA(AVA ava) + throws IOException { + if (ava == null) { + return ""; + } + ObjectIdentifier oid = ava.getOid(); + DerValue value = ava.getValue(); + String keyword, valueStr; + + // get attribute name + + keyword = encodeOID(oid); + valueStr = encodeValue(value, oid); + + return keyword + "=" + valueStr; + } + + /** + * Converts an OID to a attribute keyword in a Ldap v3 DN string + * - either a keyword if known or a string of "1.2.3.4" syntax. + * + * @param oid a ObjectIdentifier + * + * @return a keyword to use in a Ldap V3 DN string. + * + * @exception IOException if an error is encountered during conversion. + */ + public String encodeOID(ObjectIdentifier oid) + throws IOException { + String keyword = attrMap.getName(oid); + if (keyword == null) { + if (acceptUnknownOids) + keyword = oid.toString(); + else + throw new IOException("Unknown OID"); + } + return keyword; + } + + /** + * Converts a value as a DerValue to a string in a Ldap V3 DN String. + * If the value cannot be converted to a string it will be encoded in + * octothorpe form. + * + * @param attrValue a value as a DerValue. + * @param oid OID for the attribute. + * @return a string for the value in a LDAP v3 DN String + * @exception IOException if an error occurs during conversion. + */ + public String encodeValue(DerValue attrValue, ObjectIdentifier oid) + throws IOException { + /* + * Construct the value with as little copying and garbage + * production as practical. + */ + StringBuffer retval = new StringBuffer(30); + int i; + String temp = null; + AVAValueConverter valueConverter; + + X500NameAttrMap lAttrMap = attrMap; + + if (attrValue.tag == DerValue.tag_UTF8String) { + lAttrMap = X500NameAttrMap.getDirDefault(); + + } + + valueConverter = lAttrMap.getValueConverter(oid); + if (valueConverter == null) { + if (acceptUnknownOids) + valueConverter = new GenericValueConverter(); + else + throw new IOException( + "Unknown AVA type for encoding AVA value"); + } + + try { + temp = valueConverter.getAsString(attrValue); + + if (temp == null) { + // convert to octothorpe form. + byte data[] = attrValue.toByteArray(); + + retval.append('#'); + for (i = 0; i < data.length; i++) { + retval.append(hexDigits.charAt((data[i] >> 4) & 0x0f)); + retval.append(hexDigits.charAt(data[i] & 0x0f)); + } + + } else { + + retval.append(encodeString(temp)); + + } + } catch (IOException e) { + throw new IllegalArgumentException("malformed AVA DER Value"); + } + + return retval.toString(); + } + + /** + * converts a raw value string to a string in Ldap V3 DN string format. + * + * @param valueStr a 'raw' value string. + * @return a attribute value string in Ldap V3 DN string format. + */ + public String encodeString(String valueStr) { + int i, j; + int len; + StringBuffer retval = new StringBuffer(); + + /* + * generate string according to ldapv3 DN. escaping is used. + * Strings generated this way are acceptable by rfc1779 + * implementations. + */ + len = valueStr.length(); + + // get index of first space at the end of the string. + for (j = len - 1; j >= 0 && valueStr.charAt(j) == ' '; j--) + continue; + + // escape spaces at the beginning of the string. + for (i = 0; i <= j && valueStr.charAt(i) == ' '; i++) { + retval.append('\\'); + retval.append(valueStr.charAt(i)); + } + + // escape special characters in the middle of the string. + for (; i <= j; i++) { + if (valueStr.charAt(i) == '\\') { + retval.append('\\'); + retval.append(valueStr.charAt(i)); + } else if (specialChars.indexOf(valueStr.charAt(i)) != -1) { + retval.append('\\'); + retval.append(valueStr.charAt(i)); + } else if (valueStr.charAt(i) == '"') { + retval.append('\\'); + retval.append(valueStr.charAt(i)); + } else + retval.append(valueStr.charAt(i)); + } + + // esacape spaces at the end. + for (; i < valueStr.length(); i++) { + retval.append('\\'); + retval.append(' '); + } + + return retval.toString(); + } + + // + // public get/set methods + // + + /** + * gets the X500NameAttrMap used by the converter. + * + * @return X500NameAttrMap used by this converter. + */ + public X500NameAttrMap getAttrMap() { + return attrMap; + } + + /** + * returns true if the converter accepts unregistered attributes i.e. + * OIDS not in the X500NameAttrMap. + * + * @return true if converter converts attributes not in the + * X500NameAttrMap. + */ + public boolean getAcceptUnknownOids() { + return acceptUnknownOids; + } + + // + // private and protected variables + // + + protected X500NameAttrMap attrMap; + protected boolean acceptUnknownOids; + + // + // private and protected static variables & methods. + // + + protected static final String specialChars = ",+=<>#;"; + + protected static final String valueEndChars = "+,;>"; + protected static final String quotedEndChars = "\""; + protected static final String octoEndChars = " " + valueEndChars; + + /* + * Values that aren't printable strings are emitted as BER-encoded + * hex data. + */ + protected static final String hexDigits = "0123456789ABCDEFabcdef"; + + /** + * Parse a sequence of hex pairs, each pair a UTF8 byte to a java string. + * For example, "4C75C48D" is "Luc", the last c with caron. + */ + protected static char[] getStringFromHexpairs(char[] hexPairs) throws UnsupportedEncodingException { + try { + byte[] buffer = new byte[hexPairs.length / 2]; + + for (int i = 0; i < buffer.length; i++) { + buffer[i] = (byte) + ((Character.digit(hexPairs[i * 2], 16) << 4) + + Character.digit(hexPairs[i * 2 + 1], 16)); + } + + Charset charset = Charset.forName("UTF-8"); + CharsetDecoder decoder = charset.newDecoder(); + + CharBuffer charBuffer = decoder.decode(ByteBuffer.wrap(buffer)); + + return Arrays.copyOfRange(charBuffer.array(), + charBuffer.arrayOffset(), charBuffer.arrayOffset() + charBuffer.limit()); + + } catch (UnsupportedCharsetException e) { + throw new UnsupportedEncodingException( + "No UTF8 byte to char converter to use for " + + "parsing LDAP DN String"); + + } catch (CharacterCodingException e) { + throw new IllegalArgumentException( + "Invalid hex pair in LDAP DN String."); + } + } +} |