1package org.bouncycastle.asn1.x500.style; 2 3import java.io.IOException; 4import java.util.Enumeration; 5import java.util.Hashtable; 6import java.util.Vector; 7 8import org.bouncycastle.asn1.ASN1Encodable; 9import org.bouncycastle.asn1.ASN1Encoding; 10import org.bouncycastle.asn1.ASN1ObjectIdentifier; 11import org.bouncycastle.asn1.ASN1Primitive; 12import org.bouncycastle.asn1.ASN1String; 13import org.bouncycastle.asn1.DERUniversalString; 14import org.bouncycastle.asn1.x500.AttributeTypeAndValue; 15import org.bouncycastle.asn1.x500.RDN; 16import org.bouncycastle.asn1.x500.X500NameBuilder; 17import org.bouncycastle.asn1.x500.X500NameStyle; 18import org.bouncycastle.util.Strings; 19import org.bouncycastle.util.encoders.Hex; 20 21public 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 IETFUtils.appendTypeAndValue(buf, rdn.getFirst(), oidSymbols); 326 } 327 } 328 329 public static void appendTypeAndValue( 330 StringBuffer buf, 331 AttributeTypeAndValue typeAndValue, 332 Hashtable oidSymbols) 333 { 334 String sym = (String)oidSymbols.get(typeAndValue.getType()); 335 336 if (sym != null) 337 { 338 buf.append(sym); 339 } 340 else 341 { 342 buf.append(typeAndValue.getType().getId()); 343 } 344 345 buf.append('='); 346 347 buf.append(valueToString(typeAndValue.getValue())); 348 } 349 350 public static String valueToString(ASN1Encodable value) 351 { 352 StringBuffer vBuf = new StringBuffer(); 353 354 if (value instanceof ASN1String && !(value instanceof DERUniversalString)) 355 { 356 String v = ((ASN1String)value).getString(); 357 if (v.length() > 0 && v.charAt(0) == '#') 358 { 359 vBuf.append("\\" + v); 360 } 361 else 362 { 363 vBuf.append(v); 364 } 365 } 366 else 367 { 368 try 369 { 370 vBuf.append("#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER)))); 371 } 372 catch (IOException e) 373 { 374 throw new IllegalArgumentException("Other value has no encoded form"); 375 } 376 } 377 378 int end = vBuf.length(); 379 int index = 0; 380 381 if (vBuf.length() >= 2 && vBuf.charAt(0) == '\\' && vBuf.charAt(1) == '#') 382 { 383 index += 2; 384 } 385 386 while (index != end) 387 { 388 if ((vBuf.charAt(index) == ',') 389 || (vBuf.charAt(index) == '"') 390 || (vBuf.charAt(index) == '\\') 391 || (vBuf.charAt(index) == '+') 392 || (vBuf.charAt(index) == '=') 393 || (vBuf.charAt(index) == '<') 394 || (vBuf.charAt(index) == '>') 395 || (vBuf.charAt(index) == ';')) 396 { 397 vBuf.insert(index, "\\"); 398 index++; 399 end++; 400 } 401 402 index++; 403 } 404 405 int start = 0; 406 if (vBuf.length() > 0) 407 { 408 while (vBuf.length() > start && vBuf.charAt(start) == ' ') 409 { 410 vBuf.insert(start, "\\"); 411 start += 2; 412 } 413 } 414 415 int endBuf = vBuf.length() - 1; 416 417 while (endBuf >= 0 && vBuf.charAt(endBuf) == ' ') 418 { 419 vBuf.insert(endBuf, '\\'); 420 endBuf--; 421 } 422 423 return vBuf.toString(); 424 } 425 426 private static String bytesToString( 427 byte[] data) 428 { 429 char[] cs = new char[data.length]; 430 431 for (int i = 0; i != cs.length; i++) 432 { 433 cs[i] = (char)(data[i] & 0xff); 434 } 435 436 return new String(cs); 437 } 438 439 public static String canonicalize(String s) 440 { 441 String value = Strings.toLowerCase(s.trim()); 442 443 if (value.length() > 0 && value.charAt(0) == '#') 444 { 445 ASN1Primitive obj = decodeObject(value); 446 447 if (obj instanceof ASN1String) 448 { 449 value = Strings.toLowerCase(((ASN1String)obj).getString().trim()); 450 } 451 } 452 453 value = stripInternalSpaces(value); 454 455 return value; 456 } 457 458 private static ASN1Primitive decodeObject(String oValue) 459 { 460 try 461 { 462 return ASN1Primitive.fromByteArray(Hex.decode(oValue.substring(1))); 463 } 464 catch (IOException e) 465 { 466 throw new IllegalStateException("unknown encoding in name: " + e); 467 } 468 } 469 470 public static String stripInternalSpaces( 471 String str) 472 { 473 StringBuffer res = new StringBuffer(); 474 475 if (str.length() != 0) 476 { 477 char c1 = str.charAt(0); 478 479 res.append(c1); 480 481 for (int k = 1; k < str.length(); k++) 482 { 483 char c2 = str.charAt(k); 484 if (!(c1 == ' ' && c2 == ' ')) 485 { 486 res.append(c2); 487 } 488 c1 = c2; 489 } 490 } 491 492 return res.toString(); 493 } 494 495 public static boolean rDNAreEqual(RDN rdn1, RDN rdn2) 496 { 497 if (rdn1.isMultiValued()) 498 { 499 if (rdn2.isMultiValued()) 500 { 501 AttributeTypeAndValue[] atvs1 = rdn1.getTypesAndValues(); 502 AttributeTypeAndValue[] atvs2 = rdn2.getTypesAndValues(); 503 504 if (atvs1.length != atvs2.length) 505 { 506 return false; 507 } 508 509 for (int i = 0; i != atvs1.length; i++) 510 { 511 if (!atvAreEqual(atvs1[i], atvs2[i])) 512 { 513 return false; 514 } 515 } 516 } 517 else 518 { 519 return false; 520 } 521 } 522 else 523 { 524 if (!rdn2.isMultiValued()) 525 { 526 return atvAreEqual(rdn1.getFirst(), rdn2.getFirst()); 527 } 528 else 529 { 530 return false; 531 } 532 } 533 534 return true; 535 } 536 537 private static boolean atvAreEqual(AttributeTypeAndValue atv1, AttributeTypeAndValue atv2) 538 { 539 if (atv1 == atv2) 540 { 541 return true; 542 } 543 544 if (atv1 == null) 545 { 546 return false; 547 } 548 549 if (atv2 == null) 550 { 551 return false; 552 } 553 554 ASN1ObjectIdentifier o1 = atv1.getType(); 555 ASN1ObjectIdentifier o2 = atv2.getType(); 556 557 if (!o1.equals(o2)) 558 { 559 return false; 560 } 561 562 String v1 = IETFUtils.canonicalize(IETFUtils.valueToString(atv1.getValue())); 563 String v2 = IETFUtils.canonicalize(IETFUtils.valueToString(atv2.getValue())); 564 565 if (!v1.equals(v2)) 566 { 567 return false; 568 } 569 570 return true; 571 } 572} 573