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 org.apache.http.conn.ssl; 19 20 import java.util.ArrayList; 21 import java.util.Collections; 22 import java.util.List; 23 import javax.security.auth.x500.X500Principal; 24 25 /** 26 * A distinguished name (DN) parser. This parser only supports extracting a 27 * string value from a DN. It doesn't support values in the hex-string style. 28 * 29 * @hide 30 */ 31 @Deprecated 32 final class AndroidDistinguishedNameParser { 33 private final String dn; 34 private final int length; 35 private int pos; 36 private int beg; 37 private int end; 38 39 /** tmp vars to store positions of the currently parsed item */ 40 private int cur; 41 42 /** distinguished name chars */ 43 private char[] chars; 44 AndroidDistinguishedNameParser(X500Principal principal)45 public AndroidDistinguishedNameParser(X500Principal principal) { 46 // RFC2253 is used to ensure we get attributes in the reverse 47 // order of the underlying ASN.1 encoding, so that the most 48 // significant values of repeated attributes occur first. 49 this.dn = principal.getName(X500Principal.RFC2253); 50 this.length = this.dn.length(); 51 } 52 53 // gets next attribute type: (ALPHA 1*keychar) / oid nextAT()54 private String nextAT() { 55 // skip preceding space chars, they can present after 56 // comma or semicolon (compatibility with RFC 1779) 57 for (; pos < length && chars[pos] == ' '; pos++) { 58 } 59 if (pos == length) { 60 return null; // reached the end of DN 61 } 62 63 // mark the beginning of attribute type 64 beg = pos; 65 66 // attribute type chars 67 pos++; 68 for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) { 69 // we don't follow exact BNF syntax here: 70 // accept any char except space and '=' 71 } 72 if (pos >= length) { 73 throw new IllegalStateException("Unexpected end of DN: " + dn); 74 } 75 76 // mark the end of attribute type 77 end = pos; 78 79 // skip trailing space chars between attribute type and '=' 80 // (compatibility with RFC 1779) 81 if (chars[pos] == ' ') { 82 for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) { 83 } 84 85 if (chars[pos] != '=' || pos == length) { 86 throw new IllegalStateException("Unexpected end of DN: " + dn); 87 } 88 } 89 90 pos++; //skip '=' char 91 92 // skip space chars between '=' and attribute value 93 // (compatibility with RFC 1779) 94 for (; pos < length && chars[pos] == ' '; pos++) { 95 } 96 97 // in case of oid attribute type skip its prefix: "oid." or "OID." 98 // (compatibility with RFC 1779) 99 if ((end - beg > 4) && (chars[beg + 3] == '.') 100 && (chars[beg] == 'O' || chars[beg] == 'o') 101 && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') 102 && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { 103 beg += 4; 104 } 105 106 return new String(chars, beg, end - beg); 107 } 108 109 // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION quotedAV()110 private String quotedAV() { 111 pos++; 112 beg = pos; 113 end = beg; 114 while (true) { 115 116 if (pos == length) { 117 throw new IllegalStateException("Unexpected end of DN: " + dn); 118 } 119 120 if (chars[pos] == '"') { 121 // enclosing quotation was found 122 pos++; 123 break; 124 } else if (chars[pos] == '\\') { 125 chars[end] = getEscaped(); 126 } else { 127 // shift char: required for string with escaped chars 128 chars[end] = chars[pos]; 129 } 130 pos++; 131 end++; 132 } 133 134 // skip trailing space chars before comma or semicolon. 135 // (compatibility with RFC 1779) 136 for (; pos < length && chars[pos] == ' '; pos++) { 137 } 138 139 return new String(chars, beg, end - beg); 140 } 141 142 // gets hex string attribute value: "#" hexstring hexAV()143 private String hexAV() { 144 if (pos + 4 >= length) { 145 // encoded byte array must be not less then 4 c 146 throw new IllegalStateException("Unexpected end of DN: " + dn); 147 } 148 149 beg = pos; // store '#' position 150 pos++; 151 while (true) { 152 153 // check for end of attribute value 154 // looks for space and component separators 155 if (pos == length || chars[pos] == '+' || chars[pos] == ',' 156 || chars[pos] == ';') { 157 end = pos; 158 break; 159 } 160 161 if (chars[pos] == ' ') { 162 end = pos; 163 pos++; 164 // skip trailing space chars before comma or semicolon. 165 // (compatibility with RFC 1779) 166 for (; pos < length && chars[pos] == ' '; pos++) { 167 } 168 break; 169 } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { 170 chars[pos] += 32; //to low case 171 } 172 173 pos++; 174 } 175 176 // verify length of hex string 177 // encoded byte array must be not less then 4 and must be even number 178 int hexLen = end - beg; // skip first '#' char 179 if (hexLen < 5 || (hexLen & 1) == 0) { 180 throw new IllegalStateException("Unexpected end of DN: " + dn); 181 } 182 183 // get byte encoding from string representation 184 byte[] encoded = new byte[hexLen / 2]; 185 for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) { 186 encoded[i] = (byte) getByte(p); 187 } 188 189 return new String(chars, beg, hexLen); 190 } 191 192 // gets string attribute value: *( stringchar / pair ) escapedAV()193 private String escapedAV() { 194 beg = pos; 195 end = pos; 196 while (true) { 197 if (pos >= length) { 198 // the end of DN has been found 199 return new String(chars, beg, end - beg); 200 } 201 202 switch (chars[pos]) { 203 case '+': 204 case ',': 205 case ';': 206 // separator char has been found 207 return new String(chars, beg, end - beg); 208 case '\\': 209 // escaped char 210 chars[end++] = getEscaped(); 211 pos++; 212 break; 213 case ' ': 214 // need to figure out whether space defines 215 // the end of attribute value or not 216 cur = end; 217 218 pos++; 219 chars[end++] = ' '; 220 221 for (; pos < length && chars[pos] == ' '; pos++) { 222 chars[end++] = ' '; 223 } 224 if (pos == length || chars[pos] == ',' || chars[pos] == '+' 225 || chars[pos] == ';') { 226 // separator char or the end of DN has been found 227 return new String(chars, beg, cur - beg); 228 } 229 break; 230 default: 231 chars[end++] = chars[pos]; 232 pos++; 233 } 234 } 235 } 236 237 // returns escaped char getEscaped()238 private char getEscaped() { 239 pos++; 240 if (pos == length) { 241 throw new IllegalStateException("Unexpected end of DN: " + dn); 242 } 243 244 switch (chars[pos]) { 245 case '"': 246 case '\\': 247 case ',': 248 case '=': 249 case '+': 250 case '<': 251 case '>': 252 case '#': 253 case ';': 254 case ' ': 255 case '*': 256 case '%': 257 case '_': 258 //FIXME: escaping is allowed only for leading or trailing space char 259 return chars[pos]; 260 default: 261 // RFC doesn't explicitly say that escaped hex pair is 262 // interpreted as UTF-8 char. It only contains an example of such DN. 263 return getUTF8(); 264 } 265 } 266 267 // decodes UTF-8 char 268 // see http://www.unicode.org for UTF-8 bit distribution table getUTF8()269 private char getUTF8() { 270 int res = getByte(pos); 271 pos++; //FIXME tmp 272 273 if (res < 128) { // one byte: 0-7F 274 return (char) res; 275 } else if (res >= 192 && res <= 247) { 276 277 int count; 278 if (res <= 223) { // two bytes: C0-DF 279 count = 1; 280 res = res & 0x1F; 281 } else if (res <= 239) { // three bytes: E0-EF 282 count = 2; 283 res = res & 0x0F; 284 } else { // four bytes: F0-F7 285 count = 3; 286 res = res & 0x07; 287 } 288 289 int b; 290 for (int i = 0; i < count; i++) { 291 pos++; 292 if (pos == length || chars[pos] != '\\') { 293 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 294 } 295 pos++; 296 297 b = getByte(pos); 298 pos++; //FIXME tmp 299 if ((b & 0xC0) != 0x80) { 300 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 301 } 302 303 res = (res << 6) + (b & 0x3F); 304 } 305 return (char) res; 306 } else { 307 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 308 } 309 } 310 311 // Returns byte representation of a char pair 312 // The char pair is composed of DN char in 313 // specified 'position' and the next char 314 // According to BNF syntax: 315 // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" 316 // / "a" / "b" / "c" / "d" / "e" / "f" getByte(int position)317 private int getByte(int position) { 318 if (position + 1 >= length) { 319 throw new IllegalStateException("Malformed DN: " + dn); 320 } 321 322 int b1, b2; 323 324 b1 = chars[position]; 325 if (b1 >= '0' && b1 <= '9') { 326 b1 = b1 - '0'; 327 } else if (b1 >= 'a' && b1 <= 'f') { 328 b1 = b1 - 87; // 87 = 'a' - 10 329 } else if (b1 >= 'A' && b1 <= 'F') { 330 b1 = b1 - 55; // 55 = 'A' - 10 331 } else { 332 throw new IllegalStateException("Malformed DN: " + dn); 333 } 334 335 b2 = chars[position + 1]; 336 if (b2 >= '0' && b2 <= '9') { 337 b2 = b2 - '0'; 338 } else if (b2 >= 'a' && b2 <= 'f') { 339 b2 = b2 - 87; // 87 = 'a' - 10 340 } else if (b2 >= 'A' && b2 <= 'F') { 341 b2 = b2 - 55; // 55 = 'A' - 10 342 } else { 343 throw new IllegalStateException("Malformed DN: " + dn); 344 } 345 346 return (b1 << 4) + b2; 347 } 348 349 /** 350 * Parses the DN and returns the most significant attribute value 351 * for an attribute type, or null if none found. 352 * 353 * @param attributeType attribute type to look for (e.g. "ca") 354 */ findMostSpecific(String attributeType)355 public String findMostSpecific(String attributeType) { 356 // Initialize internal state. 357 pos = 0; 358 beg = 0; 359 end = 0; 360 cur = 0; 361 chars = dn.toCharArray(); 362 363 String attType = nextAT(); 364 if (attType == null) { 365 return null; 366 } 367 while (true) { 368 String attValue = ""; 369 370 if (pos == length) { 371 return null; 372 } 373 374 switch (chars[pos]) { 375 case '"': 376 attValue = quotedAV(); 377 break; 378 case '#': 379 attValue = hexAV(); 380 break; 381 case '+': 382 case ',': 383 case ';': // compatibility with RFC 1779: semicolon can separate RDNs 384 //empty attribute value 385 break; 386 default: 387 attValue = escapedAV(); 388 } 389 390 // Values are ordered from most specific to least specific 391 // due to the RFC2253 formatting. So take the first match 392 // we see. 393 if (attributeType.equalsIgnoreCase(attType)) { 394 return attValue; 395 } 396 397 if (pos >= length) { 398 return null; 399 } 400 401 if (chars[pos] == ',' || chars[pos] == ';') { 402 } else if (chars[pos] != '+') { 403 throw new IllegalStateException("Malformed DN: " + dn); 404 } 405 406 pos++; 407 attType = nextAT(); 408 if (attType == null) { 409 throw new IllegalStateException("Malformed DN: " + dn); 410 } 411 } 412 } 413 414 /** 415 * Parses the DN and returns all values for an attribute type, in 416 * the order of decreasing significance (most significant first). 417 * 418 * @param attributeType attribute type to look for (e.g. "ca") 419 */ getAllMostSpecificFirst(String attributeType)420 public List<String> getAllMostSpecificFirst(String attributeType) { 421 // Initialize internal state. 422 pos = 0; 423 beg = 0; 424 end = 0; 425 cur = 0; 426 chars = dn.toCharArray(); 427 List<String> result = Collections.emptyList(); 428 429 String attType = nextAT(); 430 if (attType == null) { 431 return result; 432 } 433 while (pos < length) { 434 String attValue = ""; 435 436 switch (chars[pos]) { 437 case '"': 438 attValue = quotedAV(); 439 break; 440 case '#': 441 attValue = hexAV(); 442 break; 443 case '+': 444 case ',': 445 case ';': // compatibility with RFC 1779: semicolon can separate RDNs 446 //empty attribute value 447 break; 448 default: 449 attValue = escapedAV(); 450 } 451 452 // Values are ordered from most specific to least specific 453 // due to the RFC2253 formatting. So take the first match 454 // we see. 455 if (attributeType.equalsIgnoreCase(attType)) { 456 if (result.isEmpty()) { 457 result = new ArrayList<String>(); 458 } 459 result.add(attValue); 460 } 461 462 if (pos >= length) { 463 break; 464 } 465 466 if (chars[pos] == ',' || chars[pos] == ';') { 467 } else if (chars[pos] != '+') { 468 throw new IllegalStateException("Malformed DN: " + dn); 469 } 470 471 pos++; 472 attType = nextAT(); 473 if (attType == null) { 474 throw new IllegalStateException("Malformed DN: " + dn); 475 } 476 } 477 478 return result; 479 } 480 } 481