1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package javax.net.ssl; 19 20 import javax.security.auth.x500.X500Principal; 21 22 /** 23 * A distinguished name (DN) parser. This parser only supports extracting a 24 * string value from a DN. It doesn't support values in the hex-string style. 25 * 26 * @hide 27 */ 28 public final class DistinguishedNameParser { 29 private final String dn; 30 private final int length; 31 private int pos; 32 private int beg; 33 private int end; 34 35 /** tmp vars to store positions of the currently parsed item */ 36 private int cur; 37 38 /** distinguished name chars */ 39 private char[] chars; 40 41 public DistinguishedNameParser(X500Principal principal) { 42 this.dn = principal.getName(X500Principal.RFC2253); 43 this.length = this.dn.length(); 44 } 45 46 // gets next attribute type: (ALPHA 1*keychar) / oid 47 private String nextAT() { 48 // skip preceding space chars, they can present after 49 // comma or semicolon (compatibility with RFC 1779) 50 for (; pos < length && chars[pos] == ' '; pos++) { 51 } 52 if (pos == length) { 53 return null; // reached the end of DN 54 } 55 56 // mark the beginning of attribute type 57 beg = pos; 58 59 // attribute type chars 60 pos++; 61 for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) { 62 // we don't follow exact BNF syntax here: 63 // accept any char except space and '=' 64 } 65 if (pos >= length) { 66 throw new IllegalStateException("Unexpected end of DN: " + dn); 67 } 68 69 // mark the end of attribute type 70 end = pos; 71 72 // skip trailing space chars between attribute type and '=' 73 // (compatibility with RFC 1779) 74 if (chars[pos] == ' ') { 75 for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) { 76 } 77 78 if (chars[pos] != '=' || pos == length) { 79 throw new IllegalStateException("Unexpected end of DN: " + dn); 80 } 81 } 82 83 pos++; //skip '=' char 84 85 // skip space chars between '=' and attribute value 86 // (compatibility with RFC 1779) 87 for (; pos < length && chars[pos] == ' '; pos++) { 88 } 89 90 // in case of oid attribute type skip its prefix: "oid." or "OID." 91 // (compatibility with RFC 1779) 92 if ((end - beg > 4) && (chars[beg + 3] == '.') 93 && (chars[beg] == 'O' || chars[beg] == 'o') 94 && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') 95 && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { 96 beg += 4; 97 } 98 99 return new String(chars, beg, end - beg); 100 } 101 102 // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION 103 private String quotedAV() { 104 pos++; 105 beg = pos; 106 end = beg; 107 while (true) { 108 109 if (pos == length) { 110 throw new IllegalStateException("Unexpected end of DN: " + dn); 111 } 112 113 if (chars[pos] == '"') { 114 // enclosing quotation was found 115 pos++; 116 break; 117 } else if (chars[pos] == '\\') { 118 chars[end] = getEscaped(); 119 } else { 120 // shift char: required for string with escaped chars 121 chars[end] = chars[pos]; 122 } 123 pos++; 124 end++; 125 } 126 127 // skip trailing space chars before comma or semicolon. 128 // (compatibility with RFC 1779) 129 for (; pos < length && chars[pos] == ' '; pos++) { 130 } 131 132 return new String(chars, beg, end - beg); 133 } 134 135 // gets hex string attribute value: "#" hexstring 136 private String hexAV() { 137 if (pos + 4 >= length) { 138 // encoded byte array must be not less then 4 c 139 throw new IllegalStateException("Unexpected end of DN: " + dn); 140 } 141 142 beg = pos; // store '#' position 143 pos++; 144 while (true) { 145 146 // check for end of attribute value 147 // looks for space and component separators 148 if (pos == length || chars[pos] == '+' || chars[pos] == ',' 149 || chars[pos] == ';') { 150 end = pos; 151 break; 152 } 153 154 if (chars[pos] == ' ') { 155 end = pos; 156 pos++; 157 // skip trailing space chars before comma or semicolon. 158 // (compatibility with RFC 1779) 159 for (; pos < length && chars[pos] == ' '; pos++) { 160 } 161 break; 162 } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { 163 chars[pos] += 32; //to low case 164 } 165 166 pos++; 167 } 168 169 // verify length of hex string 170 // encoded byte array must be not less then 4 and must be even number 171 int hexLen = end - beg; // skip first '#' char 172 if (hexLen < 5 || (hexLen & 1) == 0) { 173 throw new IllegalStateException("Unexpected end of DN: " + dn); 174 } 175 176 // get byte encoding from string representation 177 byte[] encoded = new byte[hexLen / 2]; 178 for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) { 179 encoded[i] = (byte) getByte(p); 180 } 181 182 return new String(chars, beg, hexLen); 183 } 184 185 // gets string attribute value: *( stringchar / pair ) 186 private String escapedAV() { 187 beg = pos; 188 end = pos; 189 while (true) { 190 if (pos >= length) { 191 // the end of DN has been found 192 return new String(chars, beg, end - beg); 193 } 194 195 switch (chars[pos]) { 196 case '+': 197 case ',': 198 case ';': 199 // separator char has beed found 200 return new String(chars, beg, end - beg); 201 case '\\': 202 // escaped char 203 chars[end++] = getEscaped(); 204 pos++; 205 break; 206 case ' ': 207 // need to figure out whether space defines 208 // the end of attribute value or not 209 cur = end; 210 211 pos++; 212 chars[end++] = ' '; 213 214 for (; pos < length && chars[pos] == ' '; pos++) { 215 chars[end++] = ' '; 216 } 217 if (pos == length || chars[pos] == ',' || chars[pos] == '+' 218 || chars[pos] == ';') { 219 // separator char or the end of DN has beed found 220 return new String(chars, beg, cur - beg); 221 } 222 break; 223 default: 224 chars[end++] = chars[pos]; 225 pos++; 226 } 227 } 228 } 229 230 // returns escaped char 231 private char getEscaped() { 232 pos++; 233 if (pos == length) { 234 throw new IllegalStateException("Unexpected end of DN: " + dn); 235 } 236 237 switch (chars[pos]) { 238 case '"': 239 case '\\': 240 case ',': 241 case '=': 242 case '+': 243 case '<': 244 case '>': 245 case '#': 246 case ';': 247 case ' ': 248 case '*': 249 case '%': 250 case '_': 251 //FIXME: escaping is allowed only for leading or trailing space char 252 return chars[pos]; 253 default: 254 // RFC doesn't explicitly say that escaped hex pair is 255 // interpreted as UTF-8 char. It only contains an example of such DN. 256 return getUTF8(); 257 } 258 } 259 260 // decodes UTF-8 char 261 // see http://www.unicode.org for UTF-8 bit distribution table 262 private char getUTF8() { 263 int res = getByte(pos); 264 pos++; //FIXME tmp 265 266 if (res < 128) { // one byte: 0-7F 267 return (char) res; 268 } else if (res >= 192 && res <= 247) { 269 270 int count; 271 if (res <= 223) { // two bytes: C0-DF 272 count = 1; 273 res = res & 0x1F; 274 } else if (res <= 239) { // three bytes: E0-EF 275 count = 2; 276 res = res & 0x0F; 277 } else { // four bytes: F0-F7 278 count = 3; 279 res = res & 0x07; 280 } 281 282 int b; 283 for (int i = 0; i < count; i++) { 284 pos++; 285 if (pos == length || chars[pos] != '\\') { 286 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 287 } 288 pos++; 289 290 b = getByte(pos); 291 pos++; //FIXME tmp 292 if ((b & 0xC0) != 0x80) { 293 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 294 } 295 296 res = (res << 6) + (b & 0x3F); 297 } 298 return (char) res; 299 } else { 300 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 301 } 302 } 303 304 // Returns byte representation of a char pair 305 // The char pair is composed of DN char in 306 // specified 'position' and the next char 307 // According to BNF syntax: 308 // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" 309 // / "a" / "b" / "c" / "d" / "e" / "f" 310 private int getByte(int position) { 311 if (position + 1 >= length) { 312 throw new IllegalStateException("Malformed DN: " + dn); 313 } 314 315 int b1, b2; 316 317 b1 = chars[position]; 318 if (b1 >= '0' && b1 <= '9') { 319 b1 = b1 - '0'; 320 } else if (b1 >= 'a' && b1 <= 'f') { 321 b1 = b1 - 87; // 87 = 'a' - 10 322 } else if (b1 >= 'A' && b1 <= 'F') { 323 b1 = b1 - 55; // 55 = 'A' - 10 324 } else { 325 throw new IllegalStateException("Malformed DN: " + dn); 326 } 327 328 b2 = chars[position + 1]; 329 if (b2 >= '0' && b2 <= '9') { 330 b2 = b2 - '0'; 331 } else if (b2 >= 'a' && b2 <= 'f') { 332 b2 = b2 - 87; // 87 = 'a' - 10 333 } else if (b2 >= 'A' && b2 <= 'F') { 334 b2 = b2 - 55; // 55 = 'A' - 10 335 } else { 336 throw new IllegalStateException("Malformed DN: " + dn); 337 } 338 339 return (b1 << 4) + b2; 340 } 341 342 /** 343 * Parses the DN and returns the attribute value for an attribute type. 344 * 345 * @param attributeType attribute type to look for (e.g. "ca") 346 * @return value of the attribute that first found, or null if none found 347 */ 348 public String find(String attributeType) { 349 // Initialize internal state. 350 pos = 0; 351 beg = 0; 352 end = 0; 353 cur = 0; 354 chars = dn.toCharArray(); 355 356 String attType = nextAT(); 357 if (attType == null) { 358 return null; 359 } 360 while (true) { 361 String attValue = ""; 362 363 if (pos == length) { 364 return null; 365 } 366 367 switch (chars[pos]) { 368 case '"': 369 attValue = quotedAV(); 370 break; 371 case '#': 372 attValue = hexAV(); 373 break; 374 case '+': 375 case ',': 376 case ';': // compatibility with RFC 1779: semicolon can separate RDNs 377 //empty attribute value 378 break; 379 default: 380 attValue = escapedAV(); 381 } 382 383 if (attributeType.equalsIgnoreCase(attType)) { 384 return attValue; 385 } 386 387 if (pos >= length) { 388 return null; 389 } 390 391 if (chars[pos] == ',' || chars[pos] == ';') { 392 } else if (chars[pos] != '+') { 393 throw new IllegalStateException("Malformed DN: " + dn); 394 } 395 396 pos++; 397 attType = nextAT(); 398 if (attType == null) { 399 throw new IllegalStateException("Malformed DN: " + dn); 400 } 401 } 402 } 403 } 404