1 package org.bouncycastle.asn1.x500.style; 2 3 import java.io.IOException; 4 import java.util.Enumeration; 5 import java.util.Hashtable; 6 import java.util.Vector; 7 8 import org.bouncycastle.asn1.ASN1Encodable; 9 import org.bouncycastle.asn1.ASN1Encoding; 10 import org.bouncycastle.asn1.ASN1ObjectIdentifier; 11 import org.bouncycastle.asn1.ASN1Primitive; 12 import org.bouncycastle.asn1.ASN1String; 13 import org.bouncycastle.asn1.DERUniversalString; 14 import org.bouncycastle.asn1.x500.AttributeTypeAndValue; 15 import org.bouncycastle.asn1.x500.RDN; 16 import org.bouncycastle.asn1.x500.X500NameBuilder; 17 import org.bouncycastle.asn1.x500.X500NameStyle; 18 import org.bouncycastle.util.Strings; 19 import org.bouncycastle.util.encoders.Hex; 20 21 public class IETFUtils 22 { 23 private static String unescape(String elt) 24 { 25 if (elt.length() == 0 || (elt.indexOf('\\') < 0 && elt.indexOf('"') < 0)) 26 { 27 return elt.trim(); 28 } 29 30 char[] elts = elt.toCharArray(); 31 boolean escaped = false; 32 boolean quoted = false; 33 StringBuffer buf = new StringBuffer(elt.length()); 34 int start = 0; 35 36 // if it's an escaped hash string and not an actual encoding in string form 37 // we need to leave it escaped. 38 if (elts[0] == '\\') 39 { 40 if (elts[1] == '#') 41 { 42 start = 2; 43 buf.append("\\#"); 44 } 45 } 46 47 boolean nonWhiteSpaceEncountered = false; 48 int lastEscaped = 0; 49 char hex1 = 0; 50 51 for (int i = start; i != elts.length; i++) 52 { 53 char c = elts[i]; 54 55 if (c != ' ') 56 { 57 nonWhiteSpaceEncountered = true; 58 } 59 60 if (c == '"') 61 { 62 if (!escaped) 63 { 64 quoted = !quoted; 65 } 66 else 67 { 68 buf.append(c); 69 } 70 escaped = false; 71 } 72 else if (c == '\\' && !(escaped || quoted)) 73 { 74 escaped = true; 75 lastEscaped = buf.length(); 76 } 77 else 78 { 79 if (c == ' ' && !escaped && !nonWhiteSpaceEncountered) 80 { 81 continue; 82 } 83 if (escaped && isHexDigit(c)) 84 { 85 if (hex1 != 0) 86 { 87 buf.append((char)(convertHex(hex1) * 16 + convertHex(c))); 88 escaped = false; 89 hex1 = 0; 90 continue; 91 } 92 hex1 = c; 93 continue; 94 } 95 buf.append(c); 96 escaped = false; 97 } 98 } 99 100 if (buf.length() > 0) 101 { 102 while (buf.charAt(buf.length() - 1) == ' ' && lastEscaped != (buf.length() - 1)) 103 { 104 buf.setLength(buf.length() - 1); 105 } 106 } 107 108 return buf.toString(); 109 } 110 111 private static boolean isHexDigit(char c) 112 { 113 return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); 114 } 115 116 private static int convertHex(char c) 117 { 118 if ('0' <= c && c <= '9') 119 { 120 return c - '0'; 121 } 122 if ('a' <= c && c <= 'f') 123 { 124 return c - 'a' + 10; 125 } 126 return c - 'A' + 10; 127 } 128 129 public static RDN[] rDNsFromString(String name, X500NameStyle x500Style) 130 { 131 X500NameTokenizer nTok = new X500NameTokenizer(name); 132 X500NameBuilder builder = new X500NameBuilder(x500Style); 133 134 while (nTok.hasMoreTokens()) 135 { 136 String token = nTok.nextToken(); 137 138 if (token.indexOf('+') > 0) 139 { 140 X500NameTokenizer pTok = new X500NameTokenizer(token, '+'); 141 X500NameTokenizer vTok = new X500NameTokenizer(pTok.nextToken(), '='); 142 143 String attr = vTok.nextToken(); 144 145 if (!vTok.hasMoreTokens()) 146 { 147 throw new IllegalArgumentException("badly formatted directory string"); 148 } 149 150 String value = vTok.nextToken(); 151 ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim()); 152 153 if (pTok.hasMoreTokens()) 154 { 155 Vector oids = new Vector(); 156 Vector values = new Vector(); 157 158 oids.addElement(oid); 159 values.addElement(unescape(value)); 160 161 while (pTok.hasMoreTokens()) 162 { 163 vTok = new X500NameTokenizer(pTok.nextToken(), '='); 164 165 attr = vTok.nextToken(); 166 167 if (!vTok.hasMoreTokens()) 168 { 169 throw new IllegalArgumentException("badly formatted directory string"); 170 } 171 172 value = vTok.nextToken(); 173 oid = x500Style.attrNameToOID(attr.trim()); 174 175 176 oids.addElement(oid); 177 values.addElement(unescape(value)); 178 } 179 180 builder.addMultiValuedRDN(toOIDArray(oids), toValueArray(values)); 181 } 182 else 183 { 184 builder.addRDN(oid, unescape(value)); 185 } 186 } 187 else 188 { 189 X500NameTokenizer vTok = new X500NameTokenizer(token, '='); 190 191 String attr = vTok.nextToken(); 192 193 if (!vTok.hasMoreTokens()) 194 { 195 throw new IllegalArgumentException("badly formatted directory string"); 196 } 197 198 String value = vTok.nextToken(); 199 ASN1ObjectIdentifier oid = x500Style.attrNameToOID(attr.trim()); 200 201 builder.addRDN(oid, unescape(value)); 202 } 203 } 204 205 return builder.build().getRDNs(); 206 } 207 208 private static String[] toValueArray(Vector values) 209 { 210 String[] tmp = new String[values.size()]; 211 212 for (int i = 0; i != tmp.length; i++) 213 { 214 tmp[i] = (String)values.elementAt(i); 215 } 216 217 return tmp; 218 } 219 220 private static ASN1ObjectIdentifier[] toOIDArray(Vector oids) 221 { 222 ASN1ObjectIdentifier[] tmp = new ASN1ObjectIdentifier[oids.size()]; 223 224 for (int i = 0; i != tmp.length; i++) 225 { 226 tmp[i] = (ASN1ObjectIdentifier)oids.elementAt(i); 227 } 228 229 return tmp; 230 } 231 232 public static String[] findAttrNamesForOID( 233 ASN1ObjectIdentifier oid, 234 Hashtable lookup) 235 { 236 int count = 0; 237 for (Enumeration en = lookup.elements(); en.hasMoreElements();) 238 { 239 if (oid.equals(en.nextElement())) 240 { 241 count++; 242 } 243 } 244 245 String[] aliases = new String[count]; 246 count = 0; 247 248 for (Enumeration en = lookup.keys(); en.hasMoreElements();) 249 { 250 String key = (String)en.nextElement(); 251 if (oid.equals(lookup.get(key))) 252 { 253 aliases[count++] = key; 254 } 255 } 256 257 return aliases; 258 } 259 260 public static ASN1ObjectIdentifier decodeAttrName( 261 String name, 262 Hashtable lookUp) 263 { 264 if (Strings.toUpperCase(name).startsWith("OID.")) 265 { 266 return new ASN1ObjectIdentifier(name.substring(4)); 267 } 268 else if (name.charAt(0) >= '0' && name.charAt(0) <= '9') 269 { 270 return new ASN1ObjectIdentifier(name); 271 } 272 273 ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)lookUp.get(Strings.toLowerCase(name)); 274 if (oid == null) 275 { 276 throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name"); 277 } 278 279 return oid; 280 } 281 282 public static ASN1Encodable valueFromHexString( 283 String str, 284 int off) 285 throws IOException 286 { 287 byte[] data = new byte[(str.length() - off) / 2]; 288 for (int index = 0; index != data.length; index++) 289 { 290 char left = str.charAt((index * 2) + off); 291 char right = str.charAt((index * 2) + off + 1); 292 293 data[index] = (byte)((convertHex(left) << 4) | convertHex(right)); 294 } 295 296 return ASN1Primitive.fromByteArray(data); 297 } 298 299 public static void appendRDN( 300 StringBuffer buf, 301 RDN rdn, 302 Hashtable oidSymbols) 303 { 304 if (rdn.isMultiValued()) 305 { 306 AttributeTypeAndValue[] atv = rdn.getTypesAndValues(); 307 boolean firstAtv = true; 308 309 for (int j = 0; j != atv.length; j++) 310 { 311 if (firstAtv) 312 { 313 firstAtv = false; 314 } 315 else 316 { 317 buf.append('+'); 318 } 319 320 IETFUtils.appendTypeAndValue(buf, atv[j], oidSymbols); 321 } 322 } 323 else 324 { 325 if (rdn.getFirst() != null) 326 { 327 IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols); 328 } 329 } 330 } 331 332 public static void appendTypeAndValue( 333 StringBuffer buf, 334 AttributeTypeAndValue typeAndValue, 335 Hashtable oidSymbols) 336 { 337 String sym = (String)oidSymbols.get(typeAndValue.getType()); 338 339 if (sym != null) 340 { 341 buf.append(sym); 342 } 343 else 344 { 345 buf.append(typeAndValue.getType().getId()); 346 } 347 348 buf.append('='); 349 350 buf.append(valueToString(typeAndValue.getValue())); 351 } 352 353 public static String valueToString(ASN1Encodable value) 354 { 355 StringBuffer vBuf = new StringBuffer(); 356 357 if (value instanceof ASN1String && !(value instanceof DERUniversalString)) 358 { 359 String v = ((ASN1String)value).getString(); 360 if (v.length() > 0 && v.charAt(0) == '#') 361 { 362 vBuf.append("\\" + v); 363 } 364 else 365 { 366 vBuf.append(v); 367 } 368 } 369 else 370 { 371 try 372 { 373 vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER)))); 374 } 375 catch (IOException e) 376 { 377 throw new IllegalArgumentException("Other value has no encoded form"); 378 } 379 } 380 381 int end = vBuf.length(); 382 int index = 0; 383 384 if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#') 385 { 386 index += 2; 387 } 388 389 while (index != end) 390 { 391 if ((vBuf.charAt(index) == ',') 392 || (vBuf.charAt(index) == '"') 393 || (vBuf.charAt(index) == '\\') 394 || (vBuf.charAt(index) == '+') 395 || (vBuf.charAt(index) == '=') 396 || (vBuf.charAt(index) == '<') 397 || (vBuf.charAt(index) == '>') 398 || (vBuf.charAt(index) == ';')) 399 { 400 vBuf.insert(index, "\\"); 401 index++; 402 end++; 403 } 404 405 index++; 406 } 407 408 int start = 0; 409 if (vBuf.length() > 0) 410 { 411 while (vBuf.length() > start && vBuf.charAt(start) == ' ') 412 { 413 vBuf.insert(start, "\\"); 414 start += 2; 415 } 416 } 417 418 int endBuf = vBuf.length() - 1; 419 420 while (endBuf >= 0 && vBuf.charAt(endBuf) == ' ') 421 { 422 vBuf.insert(endBuf, '\\'); 423 endBuf--; 424 } 425 426 return vBuf.toString(); 427 } 428 429 private static String bytesToString( 430 byte[] data) 431 { 432 char[] cs = new char[data.length]; 433 434 for (int i = 0; i != cs.length; i++) 435 { 436 cs[i] = (char)(data[i] & 0xff); 437 } 438 439 return new String(cs); 440 } 441 442 public static String canonicalize(String s) 443 { 444 String value = Strings.toLowerCase(s); 445 446 if (value.length() > 0 && value.charAt(0) == '#') 447 { 448 ASN1Primitive obj = decodeObject(value); 449 450 if (obj instanceof ASN1String) 451 { 452 value = Strings.toLowerCase(((ASN1String)obj).getString()); 453 } 454 } 455 456 if (value.length() > 1) 457 { 458 int start = 0; 459 while (start + 1 < value.length() && value.charAt(start) == '\\' && value.charAt(start + 1) == ' ') 460 { 461 start += 2; 462 } 463 464 int end = value.length() - 1; 465 while (end - 1 > 0 && value.charAt(end - 1) == '\\' && value.charAt(end) == ' ') 466 { 467 end -= 2; 468 } 469 470 if (start > 0 || end < value.length() - 1) 471 { 472 value = value.substring(start, end + 1); 473 } 474 } 475 476 value = stripInternalSpaces(value); 477 478 return value; 479 } 480 481 private static ASN1Primitive decodeObject(String oValue) 482 { 483 try 484 { 485 return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1))); 486 } 487 catch (IOException e) 488 { 489 throw new IllegalStateException("unknown encoding in name: " + e); 490 } 491 } 492 493 public static String stripInternalSpaces( 494 String str) 495 { 496 StringBuffer res = new StringBuffer(); 497 498 if (str.length() != 0) 499 { 500 char c1 = str.charAt(0); 501 502 res.append(c1); 503 504 for (int k = 1; k < str.length(); k++) 505 { 506 char c2 = str.charAt(k); 507 if (!(c1 == ' ' && c2 == ' ')) 508 { 509 res.append(c2); 510 } 511 c1 = c2; 512 } 513 } 514 515 return res.toString(); 516 } 517 518 public static boolean rDNAreEqual(RDN rdn1, RDN rdn2) 519 { 520 if (rdn1.isMultiValued()) 521 { 522 if (rdn2.isMultiValued()) 523 { 524 AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues(); 525 AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues(); 526 527 if (atvs1.length != atvs2.length) 528 { 529 return false; 530 } 531 532 for (int i = 0; i != atvs1.length; i++) 533 { 534 if (!atvAreEqual(atvs1[i], atvs2[i])) 535 { 536 return false; 537 } 538 } 539 } 540 else 541 { 542 return false; 543 } 544 } 545 else 546 { 547 if (!rdn2.isMultiValued()) 548 { 549 return atvAreEqual(rdn1.getFirst(), rdn2.getFirst()); 550 } 551 else 552 { 553 return false; 554 } 555 } 556 557 return true; 558 } 559 560 private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2) 561 { 562 if (atv1 == atv2) 563 { 564 return true; 565 } 566 567 if (atv1 == null) 568 { 569 return false; 570 } 571 572 if (atv2 == null) 573 { 574 return false; 575 } 576 577 ASN1ObjectIdentifier o1 = atv1.getType(); 578 ASN1ObjectIdentifier o2 = atv2.getType(); 579 580 if (!o1.equals(o2)) 581 { 582 return false; 583 } 584 585 String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue())); 586 String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue())); 587 588 if (!v1.equals(v2)) 589 { 590 return false; 591 } 592 593 return true; 594 } 595 } 596