7024771: "\\<>" in attribute value part of X500Principal constructor parameter makes strange effect
authormullan
Mon, 29 Aug 2011 12:22:06 -0400
changeset 10370 5db0cf452a50
parent 10346 916b87d13b0b
child 10371 7da2112e4236
7024771: "\\<>" in attribute value part of X500Principal constructor parameter makes strange effect Reviewed-by: vinnie
jdk/src/share/classes/sun/security/x509/AVA.java
jdk/src/share/classes/sun/security/x509/X500Name.java
jdk/test/javax/security/auth/x500/X500Principal/Parse.java
--- a/jdk/src/share/classes/sun/security/x509/AVA.java	Wed Aug 17 22:47:12 2011 -0700
+++ b/jdk/src/share/classes/sun/security/x509/AVA.java	Mon Aug 29 12:22:06 2011 -0400
@@ -42,7 +42,7 @@
  * 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.
+ * parsing (and generating) RFC 1779, 2253 or 4514 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
@@ -92,18 +92,20 @@
      * Leading and trailing spaces, also multiple internal spaces, also
      * call for quoting the whole string.
      */
-    private static final String specialChars = ",+=\n<>#;";
+    private static final String specialChars1779 = ",=\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 = ",+\"\\<>;";
+    private static final String specialChars2253 = ",=+<>#;\\\"";
 
     /*
-     * includes special chars from RFC1779 and RFC2253, as well as ' '
+     * includes special chars from RFC1779 and RFC2253, as well as ' ' from
+     * RFC 4514.
      */
-    private static final String specialCharsAll = ",=\n+<>#;\\\" ";
+    private static final String specialCharsDefault = ",=\n+<>#;\\\" ";
+    private static final String escapedDefault = ",+<>;\"";
 
     /*
      * Values that aren't printable strings are emitted as BER-encoded
@@ -120,26 +122,26 @@
     }
 
     /**
-     * Parse an RFC 1779 or RFC 2253 style AVA string:  CN=fee fie foe fum
+     * Parse an RFC 1779, 2253 or 4514 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.
+     * separators (",", ";"), 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
+     * Parse an RFC 1779, 2253 or 4514 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.
+     * separators (",", ";"), and removes cosmetic whitespace at the end of
+     * values.
      */
     AVA(Reader in, Map<String, String> keywordMap) throws IOException {
         this(in, DEFAULT, keywordMap);
@@ -147,9 +149,6 @@
 
     /**
      * 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());
@@ -158,9 +157,6 @@
     /**
      * 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
@@ -168,11 +164,11 @@
      *   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
+     *   format 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
+        // assume format is one of DEFAULT or RFC2253
 
         StringBuilder   temp = new StringBuilder();
         int             c;
@@ -193,7 +189,7 @@
 
         /*
          * Now parse the value.  "#hex", a quoted string, or a string
-         * terminated by "+", ",", ";", ">".  Whitespace before or after
+         * terminated by "+", ",", ";".  Whitespace before or after
          * the value is stripped away unless format is RFC2253.
          */
         temp.setLength(0);
@@ -202,7 +198,7 @@
             c = in.read();
             if (c == ' ') {
                 throw new IOException("Incorrect AVA RFC2253 format - " +
-                                        "leading space must be escaped");
+                                      "leading space must be escaped");
             }
         } else {
             // read next character skipping whitespace
@@ -331,8 +327,7 @@
                     continue;
                 }
 
-                if (c != '\\' && c != '"' &&
-                    specialChars.indexOf((char)c) < 0) {
+                if (specialChars1779.indexOf((char)c) < 0) {
                     throw new IOException
                         ("Invalid escaped character in AVA: " +
                         (char)c);
@@ -386,7 +381,7 @@
     private DerValue parseString
         (Reader in, int c, int format, StringBuilder temp) throws IOException {
 
-        List<Byte> embeddedHex = new ArrayList<Byte>();
+        List<Byte> embeddedHex = new ArrayList<>();
         boolean isPrintableString = true;
         boolean escape = false;
         boolean leadingChar = true;
@@ -413,24 +408,19 @@
                 }
 
                 // check if character was improperly escaped
-                if ((format == DEFAULT &&
-                        specialCharsAll.indexOf((char)c) == -1) ||
-                    (format == RFC1779  &&
-                        specialChars.indexOf((char)c) == -1 &&
-                        c != '\\' && c != '\"')) {
-
+                if (format == DEFAULT &&
+                       specialCharsDefault.indexOf((char)c) == -1) {
                     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.");
+                            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
@@ -443,18 +433,20 @@
                         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");
+                                 "' in AVA appears without escape");
                     }
+                } else if (escapedDefault.indexOf((char)c) != -1) {
+                    throw new IOException
+                            ("Character '" + (char)c +
+                            "' in AVA appears without escape");
                 }
             }
 
@@ -551,7 +543,6 @@
         case ',':
             return true;
         case ';':
-        case '>':
             return format != RFC2253;
         default:
             return false;
@@ -1204,18 +1195,6 @@
      * Get an object identifier representing the specified keyword (or
      * string encoded object identifier) in the given standard.
      *
-     * @throws IOException If the keyword is not valid in the specified standard
-     */
-    static ObjectIdentifier getOID(String keyword, int standard)
-            throws IOException {
-        return getOID
-            (keyword, standard, Collections.<String, String>emptyMap());
-    }
-
-    /**
-     * Get an object identifier representing the specified keyword (or
-     * string encoded object identifier) in the given standard.
-     *
      * @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
@@ -1249,19 +1228,11 @@
             return new ObjectIdentifier(oidString);
         }
 
-        // no keyword found or not standard compliant, check if OID string
+        // no keyword found, check if OID string
+        if (standard == AVA.DEFAULT && keyword.startsWith("OID.")) {
+            keyword = keyword.substring(4);
+        }
 
-        // RFC1779 requires, DEFAULT allows OID. prefix
-        if (standard == AVA.RFC1779) {
-            if (keyword.startsWith("OID.") == false) {
-                throw new IOException("Invalid RFC1779 keyword: " + keyword);
-            }
-            keyword = keyword.substring(4);
-        } else if (standard == AVA.DEFAULT) {
-            if (keyword.startsWith("OID.")) {
-                keyword = keyword.substring(4);
-            }
-        }
         boolean number = false;
         if (keyword.length() != 0) {
             char ch = keyword.charAt(0);
--- a/jdk/src/share/classes/sun/security/x509/X500Name.java	Wed Aug 17 22:47:12 2011 -0700
+++ b/jdk/src/share/classes/sun/security/x509/X500Name.java	Mon Aug 29 12:22:06 2011 -0400
@@ -142,9 +142,9 @@
     /**
      * Constructs a name from a conventionally formatted string, such
      * as "CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US".
-     * (RFC 1779 or RFC 2253 style).
+     * (RFC 1779, 2253, or 4514 style).
      *
-     * @param DN X.500 Distinguished Name
+     * @param dname the X.500 Distinguished Name
      */
     public X500Name(String dname) throws IOException {
         this(dname, Collections.<String, String>emptyMap());
@@ -153,9 +153,9 @@
     /**
      * Constructs a name from a conventionally formatted string, such
      * as "CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US".
-     * (RFC 1779 or RFC 2253 style).
+     * (RFC 1779, 2253, or 4514 style).
      *
-     * @param DN X.500 Distinguished Name
+     * @param dname the X.500 Distinguished Name
      * @param keywordMap an additional keyword/OID map
      */
     public X500Name(String dname, Map<String, String> keywordMap)
@@ -167,10 +167,11 @@
      * Constructs a name from a string formatted according to format.
      * Currently, the formats DEFAULT and RFC2253 are supported.
      * DEFAULT is the default format used by the X500Name(String)
-     * constructor. RFC2253 is format strictly according to RFC2253
+     * constructor. RFC2253 is the format strictly according to RFC2253
      * without extensions.
      *
-     * @param DN X.500 Distinguished Name
+     * @param dname the X.500 Distinguished Name
+     * @param format the specified format of the String DN
      */
     public X500Name(String dname, String format) throws IOException {
         if (dname == null) {
@@ -865,8 +866,8 @@
      *     O="Sue, Grabbit and Runn" or
      *     O=Sue\, Grabbit and Runn
      *
-     * This method can parse 1779 or 2253 DNs and non-standard 3280 keywords.
-     * Additional keywords can be specified in the keyword/OID map.
+     * This method can parse RFC 1779, 2253 or 4514 DNs and non-standard 3280
+     * keywords. Additional keywords can be specified in the keyword/OID map.
      */
     private void parseDN(String input, Map<String, String> keywordMap)
         throws IOException {
@@ -875,7 +876,7 @@
             return;
         }
 
-        List<RDN> dnVector = new ArrayList<RDN>();
+        List<RDN> dnVector = new ArrayList<>();
         int dnOffset = 0;
         int rdnEnd;
         String rdnString;
@@ -945,52 +946,51 @@
         if (dnString.length() == 0) {
             names = new RDN[0];
             return;
-        }
-
-        List<RDN> dnVector = new ArrayList<RDN>();
-        int dnOffset = 0;
-        String rdnString;
+         }
 
-        int searchOffset = 0;
-        int rdnEnd = dnString.indexOf(',');
-        while (rdnEnd >=0) {
-            /*
-             * We have encountered an RDN delimiter (comma).
-             * If the comma in the RDN under consideration is
-             * preceded by a backslash (escape), it
-             * is part of the RDN. Otherwise, it is used as a separator, to
-             * delimit the RDN under consideration from any subsequent RDNs.
-             */
-            if (rdnEnd > 0 && !escaped(rdnEnd, searchOffset, dnString)) {
+         List<RDN> dnVector = new ArrayList<>();
+         int dnOffset = 0;
+         String rdnString;
+         int searchOffset = 0;
+         int rdnEnd = dnString.indexOf(',');
+         while (rdnEnd >=0) {
+             /*
+              * We have encountered an RDN delimiter (comma).
+              * If the comma in the RDN under consideration is
+              * preceded by a backslash (escape), it
+              * is part of the RDN. Otherwise, it is used as a separator, to
+              * delimit the RDN under consideration from any subsequent RDNs.
+              */
+             if (rdnEnd > 0 && !escaped(rdnEnd, searchOffset, dnString)) {
 
-                /*
-                 * Comma is a separator
-                 */
-                rdnString = dnString.substring(dnOffset, rdnEnd);
+                 /*
+                  * Comma is a separator
+                  */
+                 rdnString = dnString.substring(dnOffset, rdnEnd);
 
-                // Parse RDN, and store it in vector
-                RDN rdn = new RDN(rdnString, "RFC2253");
-                dnVector.add(rdn);
+                 // Parse RDN, and store it in vector
+                 RDN rdn = new RDN(rdnString, "RFC2253");
+                 dnVector.add(rdn);
 
-                // Increase the offset
-                dnOffset = rdnEnd + 1;
-            }
+                 // Increase the offset
+                 dnOffset = rdnEnd + 1;
+             }
 
-            searchOffset = rdnEnd + 1;
-            rdnEnd = dnString.indexOf(',', searchOffset);
-        }
+             searchOffset = rdnEnd + 1;
+             rdnEnd = dnString.indexOf(',', searchOffset);
+         }
 
-        // Parse last or only RDN, and store it in vector
-        rdnString = dnString.substring(dnOffset);
-        RDN rdn = new RDN(rdnString, "RFC2253");
-        dnVector.add(rdn);
+         // Parse last or only RDN, and store it in vector
+         rdnString = dnString.substring(dnOffset);
+         RDN rdn = new RDN(rdnString, "RFC2253");
+         dnVector.add(rdn);
 
-        /*
-         * Store the vector elements as an array of RDNs
-         * NOTE: It's only on output that little-endian ordering is used.
-         */
-        Collections.reverse(dnVector);
-        names = dnVector.toArray(new RDN[dnVector.size()]);
+         /*
+          * Store the vector elements as an array of RDNs
+          * NOTE: It's only on output that little-endian ordering is used.
+          */
+         Collections.reverse(dnVector);
+         names = dnVector.toArray(new RDN[dnVector.size()]);
     }
 
     /*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/x500/X500Principal/Parse.java	Mon Aug 29 12:22:06 2011 -0400
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 7024771
+ * @summary various X500Principal DN parsing tests
+ */
+
+import javax.security.auth.x500.X500Principal;
+
+public class Parse {
+
+    private static TestCase[] testCases = {
+        new TestCase("CN=prefix\\<>suffix", false)
+    };
+
+    public static void main(String args[]) throws Exception {
+        for (int i = 0; i < testCases.length; i++) {
+            testCases[i].run();
+        }
+        System.out.println("Test completed ok.");
+    }
+}
+
+class TestCase {
+
+     private String name;
+     private boolean expectedResult;
+
+     TestCase(String name, boolean expectedResult) {
+         this.name = name;
+         this.expectedResult = expectedResult;
+     }
+
+     void run() throws Exception {
+         Exception f = null;
+         try {
+             System.out.println("Parsing: \"" + name + "\"");
+             new X500Principal(name);
+             if (expectedResult == false) {
+                 f = new Exception("Successfully parsed invalid name");
+             }
+         } catch (IllegalArgumentException e) {
+             if (expectedResult == true) {
+                 throw e;
+             }
+         }
+         if (f != null) {
+             throw f;
+         }
+     }
+}