1375 lines · 50.5 KB · Java
RawDownload
/* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. 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.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.x509; import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.io.Reader;import java.security.AccessController;import java.text.Normalizer;import java.util.*; import sun.security.action.GetBooleanAction;import sun.security.util.*;import sun.security.pkcs.PKCS9Attribute;  /** * X.500 Attribute-Value-Assertion (AVA):  an attribute, as identified by * some attribute ID, has some particular value.  Values are as a rule ASN.1 * printable strings.  A conventional set of type IDs is recognized when * parsing (and generating) RFC 1779 or RFC 2253 syntax strings. * * <P>AVAs are components of X.500 relative names.  Think of them as being * individual fields of a database record.  The attribute ID is how you * identify the field, and the value is part of a particular record. * <p> * Note that instances of this class are immutable. * * @see X500Name * @see RDN * * * @author David Brownell * @author Amit Kapoor * @author Hemma Prafullchandra */public class AVA implements DerEncoder {     private static final Debug debug = Debug.getInstance("x509", "\t[AVA]");    // See CR 6391482: if enabled this flag preserves the old but incorrect    // PrintableString encoding for DomainComponent. It may need to be set to    // avoid breaking preexisting certificates generated with sun.security APIs.    private static final boolean PRESERVE_OLD_DC_ENCODING =        AccessController.doPrivileged(new GetBooleanAction            ("com.sun.security.preserveOldDCEncoding"));     /**     * DEFAULT format allows both RFC1779 and RFC2253 syntax and     * additional keywords.     */    final static int DEFAULT = 1;    /**     * RFC1779 specifies format according to RFC1779.     */    final static int RFC1779 = 2;    /**     * RFC2253 specifies format according to RFC2253.     */    final static int RFC2253 = 3;     // currently not private, accessed directly from RDN    final ObjectIdentifier oid;    final DerValue value;     /*     * If the value has any of these characters in it, it must be quoted.     * Backslash and quote characters must also be individually escaped.     * Leading and trailing spaces, also multiple internal spaces, also     * call for quoting the whole string.     */    private static final String specialChars = ",+=\n<>#;";     /*     * In RFC2253, if the value has any of these characters in it, it     * must be quoted by a preceding \.     */    private static final String specialChars2253 = ",+\"\\<>;";     /*     * includes special chars from RFC1779 and RFC2253, as well as ' '     */    private static final String specialCharsAll = ",=\n+<>#;\\\" ";     /*     * Values that aren't printable strings are emitted as BER-encoded     * hex data.     */    private static final String hexDigits = "0123456789ABCDEF";     public AVA(ObjectIdentifier type, DerValue val) {        if ((type == null) || (val == null)) {            throw new NullPointerException();        }        oid = type;        value = val;    }     /**     * Parse an RFC 1779 or RFC 2253 style AVA string:  CN=fee fie foe fum     * or perhaps with quotes.  Not all defined AVA tags are supported;     * of current note are X.400 related ones (PRMD, ADMD, etc).     *     * This terminates at unescaped AVA separators ("+") or RDN     * separators (",", ";"), or DN terminators (">"), and removes     * cosmetic whitespace at the end of values.     */    AVA(Reader in) throws IOException {        this(in, DEFAULT);    }     /**     * Parse an RFC 1779 or RFC 2253 style AVA string:  CN=fee fie foe fum     * or perhaps with quotes. Additional keywords can be specified in the     * keyword/OID map.     *     * This terminates at unescaped AVA separators ("+") or RDN     * separators (",", ";"), or DN terminators (">"), and removes     * cosmetic whitespace at the end of values.     */    AVA(Reader in, Map<String, String> keywordMap) throws IOException {        this(in, DEFAULT, keywordMap);    }     /**     * Parse an AVA string formatted according to format.     *     * XXX format RFC1779 should only allow RFC1779 syntax but is     * actually DEFAULT with RFC1779 keywords.     */    AVA(Reader in, int format) throws IOException {        this(in, format, Collections.<String, String>emptyMap());    }     /**     * Parse an AVA string formatted according to format.     *     * XXX format RFC1779 should only allow RFC1779 syntax but is     * actually DEFAULT with RFC1779 keywords.     *     * @param in Reader containing AVA String     * @param format parsing format     * @param keywordMap a Map where a keyword String maps to a corresponding     *   OID String. Each AVA keyword will be mapped to the corresponding OID.     *   If an entry does not exist, it will fallback to the builtin     *   keyword/OID mapping.     * @throws IOException if the AVA String is not valid in the specified     *   standard or an OID String from the keywordMap is improperly formatted     */    AVA(Reader in, int format, Map<String, String> keywordMap)        throws IOException {        // assume format is one of DEFAULT, RFC1779, RFC2253         StringBuilder   temp = new StringBuilder();        int             c;         /*         * First get the keyword indicating the attribute's type,         * and map it to the appropriate OID.         */        while (true) {            c = readChar(in, "Incorrect AVA format");            if (c == '=') {                break;            }            temp.append((char)c);        }         oid = AVAKeyword.getOID(temp.toString(), format, keywordMap);         /*         * Now parse the value.  "#hex", a quoted string, or a string         * terminated by "+", ",", ";", ">".  Whitespace before or after         * the value is stripped away unless format is RFC2253.         */        temp.setLength(0);        if (format == RFC2253) {            // read next character            c = in.read();            if (c == ' ') {                throw new IOException("Incorrect AVA RFC2253 format - " +                                        "leading space must be escaped");            }        } else {            // read next character skipping whitespace            do {                c = in.read();            } while ((c == ' ') || (c == '\n'));        }        if (c == -1) {            // empty value            value = new DerValue("");            return;        }         if (c == '#') {            value = parseHexString(in, format);        } else if ((c == '"') && (format != RFC2253)) {            value = parseQuotedString(in, temp);        } else {            value = parseString(in, c, format, temp);        }    }     /**     * Get the ObjectIdentifier of this AVA.     */    public ObjectIdentifier getObjectIdentifier() {        return oid;    }     /**     * Get the value of this AVA as a DerValue.     */    public DerValue getDerValue() {        return value;    }     /**     * Get the value of this AVA as a String.     *     * @exception RuntimeException if we could not obtain the string form     *    (should not occur)     */    public String getValueString() {        try {            String s = value.getAsString();            if (s == null) {                throw new RuntimeException("AVA string is null");            }            return s;        } catch (IOException e) {            // should not occur            throw new RuntimeException("AVA error: " + e, e);        }    }     private static DerValue parseHexString        (Reader in, int format) throws IOException {         int c;        ByteArrayOutputStream baos = new ByteArrayOutputStream();        byte b = 0;        int cNdx = 0;        while (true) {            c = in.read();             if (isTerminator(c, format)) {                break;            }             int cVal = hexDigits.indexOf(Character.toUpperCase((char)c));             if (cVal == -1) {                throw new IOException("AVA parse, invalid hex " +                                              "digit: "+ (char)c);            }             if ((cNdx % 2) == 1) {                b = (byte)((b * 16) + (byte)(cVal));                baos.write(b);            } else {                b = (byte)(cVal);            }            cNdx++;        }         // throw exception if no hex digits        if (cNdx == 0) {            throw new IOException("AVA parse, zero hex digits");        }         // throw exception if odd number of hex digits        if (cNdx % 2 == 1) {            throw new IOException("AVA parse, odd number of hex digits");        }         return new DerValue(baos.toByteArray());    }     private DerValue parseQuotedString        (Reader in, StringBuilder temp) throws IOException {         // RFC1779 specifies that an entire RDN may be enclosed in double        // quotes. In this case the syntax is any sequence of        // backslash-specialChar, backslash-backslash,        // backslash-doublequote, or character other than backslash or        // doublequote.        int c = readChar(in, "Quoted string did not end in quote");         List<Byte> embeddedHex = new ArrayList<Byte>();        boolean isPrintableString = true;        while (c != '"') {            if (c == '\\') {                c = readChar(in, "Quoted string did not end in quote");                 // check for embedded hex pairs                Byte hexByte = null;                if ((hexByte = getEmbeddedHexPair(c, in)) != null) {                     // always encode AVAs with embedded hex as UTF8                    isPrintableString = false;                     // append consecutive embedded hex                    // as single string later                    embeddedHex.add(hexByte);                    c = in.read();                    continue;                }                 if (c != '\\' && c != '"' &&                    specialChars.indexOf((char)c) < 0) {                    throw new IOException                        ("Invalid escaped character in AVA: " +                        (char)c);                }            }             // add embedded hex bytes before next char            if (embeddedHex.size() > 0) {                String hexString = getEmbeddedHexString(embeddedHex);                temp.append(hexString);                embeddedHex.clear();            }             // check for non-PrintableString chars            isPrintableString &= DerValue.isPrintableStringChar((char)c);            temp.append((char)c);            c = readChar(in, "Quoted string did not end in quote");        }         // add trailing embedded hex bytes        if (embeddedHex.size() > 0) {            String hexString = getEmbeddedHexString(embeddedHex);            temp.append(hexString);            embeddedHex.clear();        }         do {            c = in.read();        } while ((c == '\n') || (c == ' '));        if (c != -1) {            throw new IOException("AVA had characters other than "                    + "whitespace after terminating quote");        }         // encode as PrintableString unless value contains        // non-PrintableString chars        if (this.oid.equals(PKCS9Attribute.EMAIL_ADDRESS_OID) ||            (this.oid.equals(X500Name.DOMAIN_COMPONENT_OID) &&                PRESERVE_OLD_DC_ENCODING == false)) {            // EmailAddress and DomainComponent must be IA5String            return new DerValue(DerValue.tag_IA5String,                                        temp.toString().trim());        } else if (isPrintableString) {            return new DerValue(temp.toString().trim());        } else {            return new DerValue(DerValue.tag_UTF8String,                                        temp.toString().trim());        }    }     private DerValue parseString        (Reader in, int c, int format, StringBuilder temp) throws IOException {         List<Byte> embeddedHex = new ArrayList<Byte>();        boolean isPrintableString = true;        boolean escape = false;        boolean leadingChar = true;        int spaceCount = 0;        do {            escape = false;            if (c == '\\') {                escape = true;                c = readChar(in, "Invalid trailing backslash");                 // check for embedded hex pairs                Byte hexByte = null;                if ((hexByte = getEmbeddedHexPair(c, in)) != null) {                     // always encode AVAs with embedded hex as UTF8                    isPrintableString = false;                     // append consecutive embedded hex                    // as single string later                    embeddedHex.add(hexByte);                    c = in.read();                    leadingChar = false;                    continue;                }                 // check if character was improperly escaped                if ((format == DEFAULT &&                        specialCharsAll.indexOf((char)c) == -1) ||                    (format == RFC1779  &&                        specialChars.indexOf((char)c) == -1 &&                        c != '\\' && c != '\"')) {                     throw new IOException                        ("Invalid escaped character in AVA: '" +                        (char)c + "'");                 } else if (format == RFC2253) {                    if (c == ' ') {                        // only leading/trailing space can be escaped                        if (!leadingChar && !trailingSpace(in)) {                                throw new IOException                                        ("Invalid escaped space character " +                                        "in AVA.  Only a leading or trailing " +                                        "space character can be escaped.");                        }                    } else if (c == '#') {                        // only leading '#' can be escaped                        if (!leadingChar) {                            throw new IOException                                ("Invalid escaped '#' character in AVA.  " +                                "Only a leading '#' can be escaped.");                        }                    } else if (specialChars2253.indexOf((char)c) == -1) {                        throw new IOException                                ("Invalid escaped character in AVA: '" +                                (char)c + "'");                     }                }             } else {                // check if character should have been escaped                if (format == RFC2253) {                    if (specialChars2253.indexOf((char)c) != -1) {                        throw new IOException                                ("Character '" + (char)c +                                "' in AVA appears without escape");                    }                }            }             // add embedded hex bytes before next char            if (embeddedHex.size() > 0) {                // add space(s) before embedded hex bytes                for (int i = 0; i < spaceCount; i++) {                    temp.append(" ");                }                spaceCount = 0;                 String hexString = getEmbeddedHexString(embeddedHex);                temp.append(hexString);                embeddedHex.clear();            }             // check for non-PrintableString chars            isPrintableString &= DerValue.isPrintableStringChar((char)c);            if (c == ' ' && escape == false) {                // do not add non-escaped spaces yet                // (non-escaped trailing spaces are ignored)                spaceCount++;            } else {                // add space(s)                for (int i = 0; i < spaceCount; i++) {                    temp.append(" ");                }                spaceCount = 0;                temp.append((char)c);            }            c = in.read();            leadingChar = false;        } while (isTerminator(c, format) == false);         if (format == RFC2253 && spaceCount > 0) {            throw new IOException("Incorrect AVA RFC2253 format - " +                                        "trailing space must be escaped");        }         // add trailing embedded hex bytes        if (embeddedHex.size() > 0) {            String hexString = getEmbeddedHexString(embeddedHex);            temp.append(hexString);

Lines 1–500 of 1375.