7024771: "\\<>" in attribute value part of X500Principal constructor parameter makes strange effect
Reviewed-by: vinnie
--- 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;
+ }
+ }
+}