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 /** 19 * @author Alexander V. Esin, Stepan M. Mishura 20 * @version $Revision$ 21 */ 22 23 package org.apache.harmony.security.x509; 24 25 import java.io.IOException; 26 import java.util.ArrayList; 27 import java.util.List; 28 import org.apache.harmony.security.utils.ObjectIdentifier; 29 import org.apache.harmony.security.x501.AttributeTypeAndValue; 30 import org.apache.harmony.security.x501.AttributeValue; 31 32 /** 33 * Distinguished Name Parser. 34 * 35 * Parses a distinguished name(DN) string according 36 * BNF syntax specified in RFC 2253 and RFC 1779 37 * 38 * RFC 2253: Lightweight Directory Access Protocol (v3): 39 * UTF-8 String Representation of Distinguished Names 40 * http://www.ietf.org/rfc/rfc2253.txt 41 * 42 * RFC 1779: A String Representation of Distinguished Names 43 * http://www.ietf.org/rfc/rfc1779.txt 44 */ 45 public final class DNParser { 46 private int pos; 47 private int beg; 48 private int end; 49 50 /** distinguished name chars */ 51 private final char[] chars; 52 53 /** raw string contains '"' or '\' */ 54 private boolean hasQE; 55 56 /** DER encoding of currently parsed item */ 57 private byte[] encoded; 58 59 /** 60 * @param dn - distinguished name string to be parsed 61 */ 62 public DNParser(String dn) throws IOException { 63 chars = dn.toCharArray(); 64 } 65 66 /** 67 * Returns the next attribute type: (ALPHA 1*keychar) / oid 68 */ 69 private String nextAT() throws IOException { 70 hasQE = false; // reset 71 72 // skip preceding space chars, they can present after 73 // comma or semicolon (compatibility with RFC 1779) 74 for (; pos < chars.length && chars[pos] == ' '; pos++) { 75 } 76 if (pos == chars.length) { 77 return null; // reached the end of DN 78 } 79 80 // mark the beginning of attribute type 81 beg = pos; 82 83 // attribute type chars 84 pos++; 85 for (; pos < chars.length && chars[pos] != '=' && chars[pos] != ' '; pos++) { 86 // we don't follow exact BNF syntax here: 87 // accept any char except space and '=' 88 } 89 if (pos >= chars.length) { 90 // unexpected end of DN 91 throw new IOException("Invalid distinguished name string"); 92 } 93 94 // mark the end of attribute type 95 end = pos; 96 97 // skip trailing space chars between attribute type and '=' 98 // (compatibility with RFC 1779) 99 if (chars[pos] == ' ') { 100 for (; pos < chars.length && chars[pos] != '=' && chars[pos] == ' '; pos++) { 101 } 102 103 if (chars[pos] != '=' || pos == chars.length) { 104 // unexpected end of DN 105 throw new IOException("Invalid distinguished name string"); 106 } 107 } 108 109 pos++; //skip '=' char 110 111 // skip space chars between '=' and attribute value 112 // (compatibility with RFC 1779) 113 for (; pos < chars.length && chars[pos] == ' '; pos++) { 114 } 115 116 // in case of oid attribute type skip its prefix: "oid." or "OID." 117 // (compatibility with RFC 1779) 118 if ((end - beg > 4) && (chars[beg + 3] == '.') 119 && (chars[beg] == 'O' || chars[beg] == 'o') 120 && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i') 121 && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) { 122 beg += 4; 123 } 124 125 return new String(chars, beg, end - beg); 126 } 127 128 /** 129 * Returns a quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION 130 */ 131 private String quotedAV() throws IOException { 132 pos++; 133 beg = pos; 134 end = beg; 135 while (true) { 136 if (pos == chars.length) { 137 // unexpected end of DN 138 throw new IOException("Invalid distinguished name string"); 139 } 140 141 if (chars[pos] == '"') { 142 // enclosing quotation was found 143 pos++; 144 break; 145 } else if (chars[pos] == '\\') { 146 chars[end] = getEscaped(); 147 } else { 148 // shift char: required for string with escaped chars 149 chars[end] = chars[pos]; 150 } 151 pos++; 152 end++; 153 } 154 155 // skip trailing space chars before comma or semicolon. 156 // (compatibility with RFC 1779) 157 for (; pos < chars.length && chars[pos] == ' '; pos++) { 158 } 159 160 return new String(chars, beg, end - beg); 161 } 162 163 /** 164 * Returns a hex string attribute value: "#" hexstring 165 */ 166 private String hexAV() throws IOException { 167 if (pos + 4 >= chars.length) { 168 // encoded byte array must be not less then 4 c 169 throw new IOException("Invalid distinguished name string"); 170 } 171 172 beg = pos; // store '#' position 173 pos++; 174 while (true) { 175 // check for end of attribute value 176 // looks for space and component separators 177 if (pos == chars.length || chars[pos] == '+' || chars[pos] == ',' 178 || chars[pos] == ';') { 179 end = pos; 180 break; 181 } 182 183 if (chars[pos] == ' ') { 184 end = pos; 185 pos++; 186 // skip trailing space chars before comma or semicolon. 187 // (compatibility with RFC 1779) 188 for (; pos < chars.length && chars[pos] == ' '; pos++) { 189 } 190 break; 191 } else if (chars[pos] >= 'A' && chars[pos] <= 'F') { 192 chars[pos] += 32; //to low case 193 } 194 195 pos++; 196 } 197 198 // verify length of hex string 199 // encoded byte array must be not less then 4 and must be even number 200 int hexLen = end - beg; // skip first '#' char 201 if (hexLen < 5 || (hexLen & 1) == 0) { 202 throw new IOException("Invalid distinguished name string"); 203 } 204 205 // get byte encoding from string representation 206 encoded = new byte[hexLen / 2]; 207 for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) { 208 encoded[i] = (byte) getByte(p); 209 } 210 211 return new String(chars, beg, hexLen); 212 } 213 214 /** 215 * Returns a string attribute value: *( stringchar / pair ). 216 */ 217 private String escapedAV() throws IOException { 218 beg = pos; 219 end = pos; 220 while (true) { 221 if (pos >= chars.length) { 222 // the end of DN has been found 223 return new String(chars, beg, end - beg); 224 } 225 226 switch (chars[pos]) { 227 case '+': 228 case ',': 229 case ';': 230 // separator char has been found 231 return new String(chars, beg, end - beg); 232 case '\\': 233 // escaped char 234 chars[end++] = getEscaped(); 235 pos++; 236 break; 237 case ' ': 238 // need to figure out whether space defines 239 // the end of attribute value or not 240 int cur = end; 241 242 pos++; 243 chars[end++] = ' '; 244 245 for (; pos < chars.length && chars[pos] == ' '; pos++) { 246 chars[end++] = ' '; 247 } 248 if (pos == chars.length || chars[pos] == ',' || chars[pos] == '+' 249 || chars[pos] == ';') { 250 // separator char or the end of DN has been found 251 return new String(chars, beg, cur - beg); 252 } 253 break; 254 default: 255 chars[end++] = chars[pos]; 256 pos++; 257 } 258 } 259 } 260 261 /** 262 * Returns an escaped char 263 */ 264 private char getEscaped() throws IOException { 265 pos++; 266 if (pos == chars.length) { 267 throw new IOException("Invalid distinguished name string"); 268 } 269 270 char ch = chars[pos]; 271 switch (ch) { 272 case '"': 273 case '\\': 274 hasQE = true; 275 return ch; 276 case ',': 277 case '=': 278 case '+': 279 case '<': 280 case '>': 281 case '#': 282 case ';': 283 case ' ': 284 case '*': 285 case '%': 286 case '_': 287 //FIXME: escaping is allowed only for leading or trailing space char 288 return ch; 289 default: 290 // RFC doesn't explicitly say that escaped hex pair is 291 // interpreted as UTF-8 char. It only contains an example of such DN. 292 return getUTF8(); 293 } 294 } 295 296 /** 297 * Decodes a UTF-8 char. 298 */ 299 protected char getUTF8() throws IOException { 300 int res = getByte(pos); 301 pos++; //FIXME tmp 302 303 if (res < 128) { // one byte: 0-7F 304 return (char) res; 305 } else if (res >= 192 && res <= 247) { 306 307 int count; 308 if (res <= 223) { // two bytes: C0-DF 309 count = 1; 310 res = res & 0x1F; 311 } else if (res <= 239) { // three bytes: E0-EF 312 count = 2; 313 res = res & 0x0F; 314 } else { // four bytes: F0-F7 315 count = 3; 316 res = res & 0x07; 317 } 318 319 int b; 320 for (int i = 0; i < count; i++) { 321 pos++; 322 if (pos == chars.length || chars[pos] != '\\') { 323 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 324 } 325 pos++; 326 327 b = getByte(pos); 328 pos++; //FIXME tmp 329 if ((b & 0xC0) != 0x80) { 330 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 331 } 332 333 res = (res << 6) + (b & 0x3F); 334 } 335 return (char) res; 336 } else { 337 return 0x3F; //FIXME failed to decode UTF-8 char - return '?' 338 } 339 } 340 341 /** 342 * Returns byte representation of a char pair 343 * The char pair is composed of DN char in 344 * specified 'position' and the next char 345 * According to BNF syntax: 346 * hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" 347 * / "a" / "b" / "c" / "d" / "e" / "f" 348 */ 349 private int getByte(int position) throws IOException { 350 if ((position + 1) >= chars.length) { 351 // to avoid ArrayIndexOutOfBoundsException 352 throw new IOException("Invalid distinguished name string"); 353 } 354 355 int b1 = chars[position]; 356 if (b1 >= '0' && b1 <= '9') { 357 b1 = b1 - '0'; 358 } else if (b1 >= 'a' && b1 <= 'f') { 359 b1 = b1 - 87; // 87 = 'a' - 10 360 } else if (b1 >= 'A' && b1 <= 'F') { 361 b1 = b1 - 55; // 55 = 'A' - 10 362 } else { 363 throw new IOException("Invalid distinguished name string"); 364 } 365 366 int b2 = chars[position + 1]; 367 if (b2 >= '0' && b2 <= '9') { 368 b2 = b2 - '0'; 369 } else if (b2 >= 'a' && b2 <= 'f') { 370 b2 = b2 - 87; // 87 = 'a' - 10 371 } else if (b2 >= 'A' && b2 <= 'F') { 372 b2 = b2 - 55; // 55 = 'A' - 10 373 } else { 374 throw new IOException("Invalid distinguished name string"); 375 } 376 377 return (b1 << 4) + b2; 378 } 379 380 /** 381 * Parses DN 382 * 383 * @return a list of Relative Distinguished Names(RDN), 384 * each RDN is represented as a list of AttributeTypeAndValue objects 385 */ 386 public List<List<AttributeTypeAndValue>> parse() throws IOException { 387 List<List<AttributeTypeAndValue>> list = new ArrayList<List<AttributeTypeAndValue>>(); 388 389 String attType = nextAT(); 390 if (attType == null) { 391 return list; //empty list of RDNs 392 } 393 ObjectIdentifier oid = AttributeTypeAndValue.getObjectIdentifier(attType); 394 395 List<AttributeTypeAndValue> atav = new ArrayList<AttributeTypeAndValue>(); 396 while (true) { 397 if (pos == chars.length) { 398 //empty Attribute Value 399 atav.add(new AttributeTypeAndValue(oid, new AttributeValue("", false, oid))); 400 list.add(0, atav); 401 return list; 402 } 403 404 switch (chars[pos]) { 405 case '"': 406 atav.add(new AttributeTypeAndValue(oid, new AttributeValue(quotedAV(), hasQE, oid))); 407 break; 408 case '#': 409 atav.add(new AttributeTypeAndValue(oid, new AttributeValue(hexAV(), encoded))); 410 break; 411 case '+': 412 case ',': 413 case ';': // compatibility with RFC 1779: semicolon can separate RDNs 414 //empty attribute value 415 atav.add(new AttributeTypeAndValue(oid, new AttributeValue("", false, oid))); 416 break; 417 default: 418 atav.add(new AttributeTypeAndValue(oid, 419 new AttributeValue(escapedAV(), hasQE, oid))); 420 } 421 422 if (pos >= chars.length) { 423 list.add(0, atav); 424 return list; 425 } 426 427 if (chars[pos] == ',' || chars[pos] == ';') { 428 list.add(0, atav); 429 atav = new ArrayList<AttributeTypeAndValue>(); 430 } else if (chars[pos] != '+') { 431 throw new IOException("Invalid distinguished name string"); 432 } 433 434 pos++; 435 attType = nextAT(); 436 if (attType == null) { 437 throw new IOException("Invalid distinguished name string"); 438 } 439 oid = AttributeTypeAndValue.getObjectIdentifier(attType); 440 } 441 } 442 } 443