1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package sun.security.x509; 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.io.OutputStream; 32 import java.io.Reader; 33 import java.security.AccessController; 34 import java.text.Normalizer; 35 import java.util.*; 36 37 import sun.security.action.GetBooleanAction; 38 import sun.security.util.*; 39 import sun.security.pkcs.PKCS9Attribute; 40 41 42 /** 43 * X.500 Attribute-Value-Assertion (AVA): an attribute, as identified by 44 * some attribute ID, has some particular value. Values are as a rule ASN.1 45 * printable strings. A conventional set of type IDs is recognized when 46 * parsing (and generating) RFC 1779 or RFC 2253 syntax strings. 47 * 48 * <P>AVAs are components of X.500 relative names. Think of them as being 49 * individual fields of a database record. The attribute ID is how you 50 * identify the field, and the value is part of a particular record. 51 * <p> 52 * Note that instances of this class are immutable. 53 * 54 * @see X500Name 55 * @see RDN 56 * 57 * 58 * @author David Brownell 59 * @author Amit Kapoor 60 * @author Hemma Prafullchandra 61 */ 62 public class AVA implements DerEncoder { 63 64 private static final Debug debug = Debug.getInstance("x509", "\t[AVA]"); 65 // See CR 6391482: if enabled this flag preserves the old but incorrect 66 // PrintableString encoding for DomainComponent. It may need to be set to 67 // avoid breaking preexisting certificates generated with sun.security APIs. 68 private static final boolean PRESERVE_OLD_DC_ENCODING = 69 AccessController.doPrivileged(new GetBooleanAction 70 ("com.sun.security.preserveOldDCEncoding")); 71 72 /** 73 * DEFAULT format allows both RFC1779 and RFC2253 syntax and 74 * additional keywords. 75 */ 76 final static int DEFAULT = 1; 77 /** 78 * RFC1779 specifies format according to RFC1779. 79 */ 80 final static int RFC1779 = 2; 81 /** 82 * RFC2253 specifies format according to RFC2253. 83 */ 84 final static int RFC2253 = 3; 85 86 // currently not private, accessed directly from RDN 87 final ObjectIdentifier oid; 88 final DerValue value; 89 90 /* 91 * If the value has any of these characters in it, it must be quoted. 92 * Backslash and quote characters must also be individually escaped. 93 * Leading and trailing spaces, also multiple internal spaces, also 94 * call for quoting the whole string. 95 */ 96 private static final String specialChars = ",+=\n<>#;"; 97 98 /* 99 * In RFC2253, if the value has any of these characters in it, it 100 * must be quoted by a preceding \. 101 */ 102 private static final String specialChars2253 = ",+\"\\<>;"; 103 104 /* 105 * includes special chars from RFC1779 and RFC2253, as well as ' ' 106 */ 107 private static final String specialCharsAll = ",=\n+<>#;\\\" "; 108 109 /* 110 * Values that aren't printable strings are emitted as BER-encoded 111 * hex data. 112 */ 113 private static final String hexDigits = "0123456789ABCDEF"; 114 AVA(ObjectIdentifier type, DerValue val)115 public AVA(ObjectIdentifier type, DerValue val) { 116 if ((type == null) || (val == null)) { 117 throw new NullPointerException(); 118 } 119 oid = type; 120 value = val; 121 } 122 123 /** 124 * Parse an RFC 1779 or RFC 2253 style AVA string: CN=fee fie foe fum 125 * or perhaps with quotes. Not all defined AVA tags are supported; 126 * of current note are X.400 related ones (PRMD, ADMD, etc). 127 * 128 * This terminates at unescaped AVA separators ("+") or RDN 129 * separators (",", ";"), or DN terminators (">"), and removes 130 * cosmetic whitespace at the end of values. 131 */ AVA(Reader in)132 AVA(Reader in) throws IOException { 133 this(in, DEFAULT); 134 } 135 136 /** 137 * Parse an RFC 1779 or RFC 2253 style AVA string: CN=fee fie foe fum 138 * or perhaps with quotes. Additional keywords can be specified in the 139 * keyword/OID map. 140 * 141 * This terminates at unescaped AVA separators ("+") or RDN 142 * separators (",", ";"), or DN terminators (">"), and removes 143 * cosmetic whitespace at the end of values. 144 */ AVA(Reader in, Map<String, String> keywordMap)145 AVA(Reader in, Map<String, String> keywordMap) throws IOException { 146 this(in, DEFAULT, keywordMap); 147 } 148 149 /** 150 * Parse an AVA string formatted according to format. 151 * 152 * XXX format RFC1779 should only allow RFC1779 syntax but is 153 * actually DEFAULT with RFC1779 keywords. 154 */ AVA(Reader in, int format)155 AVA(Reader in, int format) throws IOException { 156 this(in, format, Collections.<String, String>emptyMap()); 157 } 158 159 /** 160 * Parse an AVA string formatted according to format. 161 * 162 * XXX format RFC1779 should only allow RFC1779 syntax but is 163 * actually DEFAULT with RFC1779 keywords. 164 * 165 * @param in Reader containing AVA String 166 * @param format parsing format 167 * @param keywordMap a Map where a keyword String maps to a corresponding 168 * OID String. Each AVA keyword will be mapped to the corresponding OID. 169 * If an entry does not exist, it will fallback to the builtin 170 * keyword/OID mapping. 171 * @throws IOException if the AVA String is not valid in the specified 172 * standard or an OID String from the keywordMap is improperly formatted 173 */ AVA(Reader in, int format, Map<String, String> keywordMap)174 AVA(Reader in, int format, Map<String, String> keywordMap) 175 throws IOException { 176 // assume format is one of DEFAULT, RFC1779, RFC2253 177 178 StringBuilder temp = new StringBuilder(); 179 int c; 180 181 /* 182 * First get the keyword indicating the attribute's type, 183 * and map it to the appropriate OID. 184 */ 185 while (true) { 186 c = readChar(in, "Incorrect AVA format"); 187 if (c == '=') { 188 break; 189 } 190 temp.append((char)c); 191 } 192 193 oid = AVAKeyword.getOID(temp.toString(), format, keywordMap); 194 195 /* 196 * Now parse the value. "#hex", a quoted string, or a string 197 * terminated by "+", ",", ";", ">". Whitespace before or after 198 * the value is stripped away unless format is RFC2253. 199 */ 200 temp.setLength(0); 201 if (format == RFC2253) { 202 // read next character 203 c = in.read(); 204 if (c == ' ') { 205 throw new IOException("Incorrect AVA RFC2253 format - " + 206 "leading space must be escaped"); 207 } 208 } else { 209 // read next character skipping whitespace 210 do { 211 c = in.read(); 212 } while ((c == ' ') || (c == '\n')); 213 } 214 if (c == -1) { 215 // empty value 216 value = new DerValue(""); 217 return; 218 } 219 220 if (c == '#') { 221 value = parseHexString(in, format); 222 } else if ((c == '"') && (format != RFC2253)) { 223 value = parseQuotedString(in, temp); 224 } else { 225 value = parseString(in, c, format, temp); 226 } 227 } 228 229 /** 230 * Get the ObjectIdentifier of this AVA. 231 */ getObjectIdentifier()232 public ObjectIdentifier getObjectIdentifier() { 233 return oid; 234 } 235 236 /** 237 * Get the value of this AVA as a DerValue. 238 */ getDerValue()239 public DerValue getDerValue() { 240 return value; 241 } 242 243 /** 244 * Get the value of this AVA as a String. 245 * 246 * @exception RuntimeException if we could not obtain the string form 247 * (should not occur) 248 */ getValueString()249 public String getValueString() { 250 try { 251 String s = value.getAsString(); 252 if (s == null) { 253 throw new RuntimeException("AVA string is null"); 254 } 255 return s; 256 } catch (IOException e) { 257 // should not occur 258 throw new RuntimeException("AVA error: " + e, e); 259 } 260 } 261 parseHexString(Reader in, int format)262 private static DerValue parseHexString 263 (Reader in, int format) throws IOException { 264 265 int c; 266 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 267 byte b = 0; 268 int cNdx = 0; 269 while (true) { 270 c = in.read(); 271 272 if (isTerminator(c, format)) { 273 break; 274 } 275 276 // Android-changed: Skip trailing whitespace. 277 if (c == ' ' || c == '\n') { 278 do { 279 if (c != ' ' && c != '\n') { 280 throw new IOException("AVA parse, invalid hex " + "digit: "+ (char)c); 281 } 282 c = in.read(); 283 } while (!isTerminator(c, format)); 284 break; 285 } 286 287 int cVal = hexDigits.indexOf(Character.toUpperCase((char)c)); 288 289 if (cVal == -1) { 290 throw new IOException("AVA parse, invalid hex " + 291 "digit: "+ (char)c); 292 } 293 294 if ((cNdx % 2) == 1) { 295 b = (byte)((b * 16) + (byte)(cVal)); 296 baos.write(b); 297 } else { 298 b = (byte)(cVal); 299 } 300 cNdx++; 301 } 302 303 // throw exception if no hex digits 304 if (cNdx == 0) { 305 throw new IOException("AVA parse, zero hex digits"); 306 } 307 308 // throw exception if odd number of hex digits 309 if (cNdx % 2 == 1) { 310 throw new IOException("AVA parse, odd number of hex digits"); 311 } 312 313 return new DerValue(baos.toByteArray()); 314 } 315 parseQuotedString(Reader in, StringBuilder temp)316 private DerValue parseQuotedString 317 (Reader in, StringBuilder temp) throws IOException { 318 319 // RFC1779 specifies that an entire RDN may be enclosed in double 320 // quotes. In this case the syntax is any sequence of 321 // backslash-specialChar, backslash-backslash, 322 // backslash-doublequote, or character other than backslash or 323 // doublequote. 324 int c = readChar(in, "Quoted string did not end in quote"); 325 326 List<Byte> embeddedHex = new ArrayList<Byte>(); 327 boolean isPrintableString = true; 328 while (c != '"') { 329 if (c == '\\') { 330 c = readChar(in, "Quoted string did not end in quote"); 331 332 // check for embedded hex pairs 333 Byte hexByte = null; 334 if ((hexByte = getEmbeddedHexPair(c, in)) != null) { 335 336 // always encode AVAs with embedded hex as UTF8 337 isPrintableString = false; 338 339 // append consecutive embedded hex 340 // as single string later 341 embeddedHex.add(hexByte); 342 c = in.read(); 343 continue; 344 } 345 346 if (c != '\\' && c != '"' && 347 specialChars.indexOf((char)c) < 0) { 348 throw new IOException 349 ("Invalid escaped character in AVA: " + 350 (char)c); 351 } 352 } 353 354 // add embedded hex bytes before next char 355 if (embeddedHex.size() > 0) { 356 String hexString = getEmbeddedHexString(embeddedHex); 357 temp.append(hexString); 358 embeddedHex.clear(); 359 } 360 361 // check for non-PrintableString chars 362 isPrintableString &= DerValue.isPrintableStringChar((char)c); 363 temp.append((char)c); 364 c = readChar(in, "Quoted string did not end in quote"); 365 } 366 367 // add trailing embedded hex bytes 368 if (embeddedHex.size() > 0) { 369 String hexString = getEmbeddedHexString(embeddedHex); 370 temp.append(hexString); 371 embeddedHex.clear(); 372 } 373 374 do { 375 c = in.read(); 376 } while ((c == '\n') || (c == ' ')); 377 if (c != -1) { 378 throw new IOException("AVA had characters other than " 379 + "whitespace after terminating quote"); 380 } 381 382 // encode as PrintableString unless value contains 383 // non-PrintableString chars 384 if (this.oid.equals(PKCS9Attribute.EMAIL_ADDRESS_OID) || 385 (this.oid.equals(X500Name.DOMAIN_COMPONENT_OID) && 386 PRESERVE_OLD_DC_ENCODING == false)) { 387 // EmailAddress and DomainComponent must be IA5String 388 return new DerValue(DerValue.tag_IA5String, 389 temp.toString()); 390 } else if (isPrintableString) { 391 return new DerValue(temp.toString()); 392 } else { 393 return new DerValue(DerValue.tag_UTF8String, 394 temp.toString()); 395 } 396 } 397 parseString(Reader in, int c, int format, StringBuilder temp)398 private DerValue parseString 399 (Reader in, int c, int format, StringBuilder temp) throws IOException { 400 401 List<Byte> embeddedHex = new ArrayList<Byte>(); 402 boolean isPrintableString = true; 403 boolean escape = false; 404 boolean leadingChar = true; 405 int spaceCount = 0; 406 do { 407 escape = false; 408 if (c == '\\') { 409 escape = true; 410 c = readChar(in, "Invalid trailing backslash"); 411 412 // check for embedded hex pairs 413 Byte hexByte = null; 414 if ((hexByte = getEmbeddedHexPair(c, in)) != null) { 415 416 // always encode AVAs with embedded hex as UTF8 417 isPrintableString = false; 418 419 // append consecutive embedded hex 420 // as single string later 421 embeddedHex.add(hexByte); 422 c = in.read(); 423 leadingChar = false; 424 continue; 425 } 426 427 // check if character was improperly escaped 428 if ((format == DEFAULT && 429 specialCharsAll.indexOf((char)c) == -1) || 430 (format == RFC1779 && 431 specialChars.indexOf((char)c) == -1 && 432 c != '\\' && c != '\"')) { 433 434 throw new IOException 435 ("Invalid escaped character in AVA: '" + 436 (char)c + "'"); 437 438 } else if (format == RFC2253) { 439 if (c == ' ') { 440 // only leading/trailing space can be escaped 441 if (!leadingChar && !trailingSpace(in)) { 442 throw new IOException 443 ("Invalid escaped space character " + 444 "in AVA. Only a leading or trailing " + 445 "space character can be escaped."); 446 } 447 } else if (c == '#') { 448 // only leading '#' can be escaped 449 if (!leadingChar) { 450 throw new IOException 451 ("Invalid escaped '#' character in AVA. " + 452 "Only a leading '#' can be escaped."); 453 } 454 } else if (specialChars2253.indexOf((char)c) == -1) { 455 throw new IOException 456 ("Invalid escaped character in AVA: '" + 457 (char)c + "'"); 458 459 } 460 } 461 462 } else { 463 // check if character should have been escaped 464 if (format == RFC2253) { 465 if (specialChars2253.indexOf((char)c) != -1) { 466 throw new IOException 467 ("Character '" + (char)c + 468 "' in AVA appears without escape"); 469 } 470 } 471 } 472 473 // add embedded hex bytes before next char 474 if (embeddedHex.size() > 0) { 475 // add space(s) before embedded hex bytes 476 for (int i = 0; i < spaceCount; i++) { 477 temp.append(" "); 478 } 479 spaceCount = 0; 480 481 String hexString = getEmbeddedHexString(embeddedHex); 482 temp.append(hexString); 483 embeddedHex.clear(); 484 } 485 486 // check for non-PrintableString chars 487 isPrintableString &= DerValue.isPrintableStringChar((char)c); 488 if (c == ' ' && escape == false) { 489 // do not add non-escaped spaces yet 490 // (non-escaped trailing spaces are ignored) 491 spaceCount++; 492 } else { 493 // add space(s) 494 for (int i = 0; i < spaceCount; i++) { 495 temp.append(" "); 496 } 497 spaceCount = 0; 498 temp.append((char)c); 499 } 500 c = in.read(); 501 leadingChar = false; 502 } while (isTerminator(c, format) == false); 503 504 if (format == RFC2253 && spaceCount > 0) { 505 throw new IOException("Incorrect AVA RFC2253 format - " + 506 "trailing space must be escaped"); 507 } 508 509 // add trailing embedded hex bytes 510 if (embeddedHex.size() > 0) { 511 String hexString = getEmbeddedHexString(embeddedHex); 512 temp.append(hexString); 513 embeddedHex.clear(); 514 } 515 516 // encode as PrintableString unless value contains 517 // non-PrintableString chars 518 if (this.oid.equals(PKCS9Attribute.EMAIL_ADDRESS_OID) || 519 (this.oid.equals(X500Name.DOMAIN_COMPONENT_OID) && 520 PRESERVE_OLD_DC_ENCODING == false)) { 521 // EmailAddress and DomainComponent must be IA5String 522 return new DerValue(DerValue.tag_IA5String, temp.toString()); 523 } else if (isPrintableString) { 524 return new DerValue(temp.toString()); 525 } else { 526 return new DerValue(DerValue.tag_UTF8String, temp.toString()); 527 } 528 } 529 getEmbeddedHexPair(int c1, Reader in)530 private static Byte getEmbeddedHexPair(int c1, Reader in) 531 throws IOException { 532 533 if (hexDigits.indexOf(Character.toUpperCase((char)c1)) >= 0) { 534 int c2 = readChar(in, "unexpected EOF - " + 535 "escaped hex value must include two valid digits"); 536 537 if (hexDigits.indexOf(Character.toUpperCase((char)c2)) >= 0) { 538 int hi = Character.digit((char)c1, 16); 539 int lo = Character.digit((char)c2, 16); 540 return new Byte((byte)((hi<<4) + lo)); 541 } else { 542 throw new IOException 543 ("escaped hex value must include two valid digits"); 544 } 545 } 546 return null; 547 } 548 getEmbeddedHexString(List<Byte> hexList)549 private static String getEmbeddedHexString(List<Byte> hexList) 550 throws IOException { 551 int n = hexList.size(); 552 byte[] hexBytes = new byte[n]; 553 for (int i = 0; i < n; i++) { 554 hexBytes[i] = hexList.get(i).byteValue(); 555 } 556 return new String(hexBytes, "UTF8"); 557 } 558 isTerminator(int ch, int format)559 private static boolean isTerminator(int ch, int format) { 560 switch (ch) { 561 case -1: 562 case '+': 563 case ',': 564 return true; 565 case ';': 566 case '>': 567 return format != RFC2253; 568 default: 569 return false; 570 } 571 } 572 readChar(Reader in, String errMsg)573 private static int readChar(Reader in, String errMsg) throws IOException { 574 int c = in.read(); 575 if (c == -1) { 576 throw new IOException(errMsg); 577 } 578 return c; 579 } 580 trailingSpace(Reader in)581 private static boolean trailingSpace(Reader in) throws IOException { 582 583 boolean trailing = false; 584 585 if (!in.markSupported()) { 586 // oh well 587 return true; 588 } else { 589 // make readAheadLimit huge - 590 // in practice, AVA was passed a StringReader from X500Name, 591 // and StringReader ignores readAheadLimit anyways 592 in.mark(9999); 593 while (true) { 594 int nextChar = in.read(); 595 if (nextChar == -1) { 596 trailing = true; 597 break; 598 } else if (nextChar == ' ') { 599 continue; 600 } else if (nextChar == '\\') { 601 int followingChar = in.read(); 602 if (followingChar != ' ') { 603 trailing = false; 604 break; 605 } 606 } else { 607 trailing = false; 608 break; 609 } 610 } 611 612 in.reset(); 613 return trailing; 614 } 615 } 616 AVA(DerValue derval)617 AVA(DerValue derval) throws IOException { 618 // Individual attribute value assertions are SEQUENCE of two values. 619 // That'd be a "struct" outside of ASN.1. 620 if (derval.tag != DerValue.tag_Sequence) { 621 throw new IOException("AVA not a sequence"); 622 } 623 oid = X500Name.intern(derval.data.getOID()); 624 value = derval.data.getDerValue(); 625 626 if (derval.data.available() != 0) { 627 throw new IOException("AVA, extra bytes = " 628 + derval.data.available()); 629 } 630 } 631 AVA(DerInputStream in)632 AVA(DerInputStream in) throws IOException { 633 this(in.getDerValue()); 634 } 635 equals(Object obj)636 public boolean equals(Object obj) { 637 if (this == obj) { 638 return true; 639 } 640 if (obj instanceof AVA == false) { 641 return false; 642 } 643 AVA other = (AVA)obj; 644 return this.toRFC2253CanonicalString().equals 645 (other.toRFC2253CanonicalString()); 646 } 647 648 /** 649 * Returns a hashcode for this AVA. 650 * 651 * @return a hashcode for this AVA. 652 */ hashCode()653 public int hashCode() { 654 return toRFC2253CanonicalString().hashCode(); 655 } 656 657 /* 658 * AVAs are encoded as a SEQUENCE of two elements. 659 */ encode(DerOutputStream out)660 public void encode(DerOutputStream out) throws IOException { 661 derEncode(out); 662 } 663 664 /** 665 * DER encode this object onto an output stream. 666 * Implements the <code>DerEncoder</code> interface. 667 * 668 * @param out 669 * the output stream on which to write the DER encoding. 670 * 671 * @exception IOException on encoding error. 672 */ derEncode(OutputStream out)673 public void derEncode(OutputStream out) throws IOException { 674 DerOutputStream tmp = new DerOutputStream(); 675 DerOutputStream tmp2 = new DerOutputStream(); 676 677 tmp.putOID(oid); 678 value.encode(tmp); 679 tmp2.write(DerValue.tag_Sequence, tmp); 680 out.write(tmp2.toByteArray()); 681 } 682 toKeyword(int format, Map<String, String> oidMap)683 private String toKeyword(int format, Map<String, String> oidMap) { 684 return AVAKeyword.getKeyword(oid, format, oidMap); 685 } 686 687 /** 688 * Returns a printable form of this attribute, using RFC 1779 689 * syntax for individual attribute/value assertions. 690 */ toString()691 public String toString() { 692 return toKeywordValueString 693 (toKeyword(DEFAULT, Collections.<String, String>emptyMap())); 694 } 695 696 /** 697 * Returns a printable form of this attribute, using RFC 1779 698 * syntax for individual attribute/value assertions. It only 699 * emits standardised keywords. 700 */ toRFC1779String()701 public String toRFC1779String() { 702 return toRFC1779String(Collections.<String, String>emptyMap()); 703 } 704 705 /** 706 * Returns a printable form of this attribute, using RFC 1779 707 * syntax for individual attribute/value assertions. It 708 * emits standardised keywords, as well as keywords contained in the 709 * OID/keyword map. 710 */ toRFC1779String(Map<String, String> oidMap)711 public String toRFC1779String(Map<String, String> oidMap) { 712 return toKeywordValueString(toKeyword(RFC1779, oidMap)); 713 } 714 715 /** 716 * Returns a printable form of this attribute, using RFC 2253 717 * syntax for individual attribute/value assertions. It only 718 * emits standardised keywords. 719 */ toRFC2253String()720 public String toRFC2253String() { 721 return toRFC2253String(Collections.<String, String>emptyMap()); 722 } 723 724 /** 725 * Returns a printable form of this attribute, using RFC 2253 726 * syntax for individual attribute/value assertions. It 727 * emits standardised keywords, as well as keywords contained in the 728 * OID/keyword map. 729 */ toRFC2253String(Map<String, String> oidMap)730 public String toRFC2253String(Map<String, String> oidMap) { 731 /* 732 * Section 2.3: The AttributeTypeAndValue is encoded as the string 733 * representation of the AttributeType, followed by an equals character 734 * ('=' ASCII 61), followed by the string representation of the 735 * AttributeValue. The encoding of the AttributeValue is given in 736 * section 2.4. 737 */ 738 StringBuilder typeAndValue = new StringBuilder(100); 739 typeAndValue.append(toKeyword(RFC2253, oidMap)); 740 typeAndValue.append('='); 741 742 /* 743 * Section 2.4: Converting an AttributeValue from ASN.1 to a String. 744 * If the AttributeValue is of a type which does not have a string 745 * representation defined for it, then it is simply encoded as an 746 * octothorpe character ('#' ASCII 35) followed by the hexadecimal 747 * representation of each of the bytes of the BER encoding of the X.500 748 * AttributeValue. This form SHOULD be used if the AttributeType is of 749 * the dotted-decimal form. 750 */ 751 if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') || 752 !isDerString(value, false)) 753 { 754 byte[] data = null; 755 try { 756 data = value.toByteArray(); 757 } catch (IOException ie) { 758 throw new IllegalArgumentException("DER Value conversion"); 759 } 760 typeAndValue.append('#'); 761 for (int j = 0; j < data.length; j++) { 762 byte b = data[j]; 763 typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16)); 764 typeAndValue.append(Character.forDigit(0xF & b, 16)); 765 } 766 } else { 767 /* 768 * 2.4 (cont): Otherwise, if the AttributeValue is of a type which 769 * has a string representation, the value is converted first to a 770 * UTF-8 string according to its syntax specification. 771 * 772 * NOTE: this implementation only emits DirectoryStrings of the 773 * types returned by isDerString(). 774 */ 775 String valStr = null; 776 try { 777 valStr = new String(value.getDataBytes(), "UTF8"); 778 } catch (IOException ie) { 779 throw new IllegalArgumentException("DER Value conversion"); 780 } 781 782 /* 783 * 2.4 (cont): If the UTF-8 string does not have any of the 784 * following characters which need escaping, then that string can be 785 * used as the string representation of the value. 786 * 787 * o a space or "#" character occurring at the beginning of the 788 * string 789 * o a space character occurring at the end of the string 790 * o one of the characters ",", "+", """, "\", "<", ">" or ";" 791 * 792 * Implementations MAY escape other characters. 793 * 794 * NOTE: this implementation also recognizes "=" and "#" as 795 * characters which need escaping, and null which is escaped as 796 * '\00' (see RFC 4514). 797 * 798 * If a character to be escaped is one of the list shown above, then 799 * it is prefixed by a backslash ('\' ASCII 92). 800 * 801 * Otherwise the character to be escaped is replaced by a backslash 802 * and two hex digits, which form a single byte in the code of the 803 * character. 804 */ 805 final String escapees = ",=+<>#;\"\\"; 806 StringBuilder sbuffer = new StringBuilder(); 807 808 for (int i = 0; i < valStr.length(); i++) { 809 char c = valStr.charAt(i); 810 if (DerValue.isPrintableStringChar(c) || 811 escapees.indexOf(c) >= 0) { 812 813 // escape escapees 814 if (escapees.indexOf(c) >= 0) { 815 sbuffer.append('\\'); 816 } 817 818 // append printable/escaped char 819 sbuffer.append(c); 820 821 } else if (c == '\u0000') { 822 // escape null character 823 sbuffer.append("\\00"); 824 825 } else if (debug != null && Debug.isOn("ava")) { 826 827 // embed non-printable/non-escaped char 828 // as escaped hex pairs for debugging 829 byte[] valueBytes = null; 830 try { 831 valueBytes = Character.toString(c).getBytes("UTF8"); 832 } catch (IOException ie) { 833 throw new IllegalArgumentException 834 ("DER Value conversion"); 835 } 836 for (int j = 0; j < valueBytes.length; j++) { 837 sbuffer.append('\\'); 838 char hexChar = Character.forDigit 839 (0xF & (valueBytes[j] >>> 4), 16); 840 sbuffer.append(Character.toUpperCase(hexChar)); 841 hexChar = Character.forDigit 842 (0xF & (valueBytes[j]), 16); 843 sbuffer.append(Character.toUpperCase(hexChar)); 844 } 845 } else { 846 847 // append non-printable/non-escaped char 848 sbuffer.append(c); 849 } 850 } 851 852 char[] chars = sbuffer.toString().toCharArray(); 853 sbuffer = new StringBuilder(); 854 855 // Find leading and trailing whitespace. 856 int lead; // index of first char that is not leading whitespace 857 for (lead = 0; lead < chars.length; lead++) { 858 if (chars[lead] != ' ' && chars[lead] != '\r') { 859 break; 860 } 861 } 862 int trail; // index of last char that is not trailing whitespace 863 for (trail = chars.length - 1; trail >= 0; trail--) { 864 if (chars[trail] != ' ' && chars[trail] != '\r') { 865 break; 866 } 867 } 868 869 // escape leading and trailing whitespace 870 for (int i = 0; i < chars.length; i++) { 871 char c = chars[i]; 872 if (i < lead || i > trail) { 873 sbuffer.append('\\'); 874 } 875 sbuffer.append(c); 876 } 877 typeAndValue.append(sbuffer.toString()); 878 } 879 return typeAndValue.toString(); 880 } 881 toRFC2253CanonicalString()882 public String toRFC2253CanonicalString() { 883 /* 884 * Section 2.3: The AttributeTypeAndValue is encoded as the string 885 * representation of the AttributeType, followed by an equals character 886 * ('=' ASCII 61), followed by the string representation of the 887 * AttributeValue. The encoding of the AttributeValue is given in 888 * section 2.4. 889 */ 890 StringBuilder typeAndValue = new StringBuilder(40); 891 typeAndValue.append 892 (toKeyword(RFC2253, Collections.<String, String>emptyMap())); 893 typeAndValue.append('='); 894 895 /* 896 * Section 2.4: Converting an AttributeValue from ASN.1 to a String. 897 * If the AttributeValue is of a type which does not have a string 898 * representation defined for it, then it is simply encoded as an 899 * octothorpe character ('#' ASCII 35) followed by the hexadecimal 900 * representation of each of the bytes of the BER encoding of the X.500 901 * AttributeValue. This form SHOULD be used if the AttributeType is of 902 * the dotted-decimal form. 903 */ 904 if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') || 905 (!isDerString(value, true) && value.tag != DerValue.tag_T61String)) 906 { 907 byte[] data = null; 908 try { 909 data = value.toByteArray(); 910 } catch (IOException ie) { 911 throw new IllegalArgumentException("DER Value conversion"); 912 } 913 typeAndValue.append('#'); 914 for (int j = 0; j < data.length; j++) { 915 byte b = data[j]; 916 typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16)); 917 typeAndValue.append(Character.forDigit(0xF & b, 16)); 918 } 919 } else { 920 /* 921 * 2.4 (cont): Otherwise, if the AttributeValue is of a type which 922 * has a string representation, the value is converted first to a 923 * UTF-8 string according to its syntax specification. 924 * 925 * NOTE: this implementation only emits DirectoryStrings of the 926 * types returned by isDerString(). 927 */ 928 String valStr = null; 929 try { 930 valStr = new String(value.getDataBytes(), "UTF8"); 931 } catch (IOException ie) { 932 throw new IllegalArgumentException("DER Value conversion"); 933 } 934 935 /* 936 * 2.4 (cont): If the UTF-8 string does not have any of the 937 * following characters which need escaping, then that string can be 938 * used as the string representation of the value. 939 * 940 * o a space or "#" character occurring at the beginning of the 941 * string 942 * o a space character occurring at the end of the string 943 * 944 * o one of the characters ",", "+", """, "\", "<", ">" or ";" 945 * 946 * If a character to be escaped is one of the list shown above, then 947 * it is prefixed by a backslash ('\' ASCII 92). 948 * 949 * Otherwise the character to be escaped is replaced by a backslash 950 * and two hex digits, which form a single byte in the code of the 951 * character. 952 */ 953 final String escapees = ",+<>;\"\\"; 954 StringBuilder sbuffer = new StringBuilder(); 955 boolean previousWhite = false; 956 957 for (int i = 0; i < valStr.length(); i++) { 958 char c = valStr.charAt(i); 959 960 if (DerValue.isPrintableStringChar(c) || 961 escapees.indexOf(c) >= 0 || 962 (i == 0 && c == '#')) { 963 964 // escape leading '#' and escapees 965 if ((i == 0 && c == '#') || escapees.indexOf(c) >= 0) { 966 sbuffer.append('\\'); 967 } 968 969 // convert multiple whitespace to single whitespace 970 if (!Character.isWhitespace(c)) { 971 previousWhite = false; 972 sbuffer.append(c); 973 } else { 974 if (previousWhite == false) { 975 // add single whitespace 976 previousWhite = true; 977 sbuffer.append(c); 978 } else { 979 // ignore subsequent consecutive whitespace 980 continue; 981 } 982 } 983 984 } else if (debug != null && Debug.isOn("ava")) { 985 986 // embed non-printable/non-escaped char 987 // as escaped hex pairs for debugging 988 989 previousWhite = false; 990 991 byte valueBytes[] = null; 992 try { 993 valueBytes = Character.toString(c).getBytes("UTF8"); 994 } catch (IOException ie) { 995 throw new IllegalArgumentException 996 ("DER Value conversion"); 997 } 998 for (int j = 0; j < valueBytes.length; j++) { 999 sbuffer.append('\\'); 1000 sbuffer.append(Character.forDigit 1001 (0xF & (valueBytes[j] >>> 4), 16)); 1002 sbuffer.append(Character.forDigit 1003 (0xF & (valueBytes[j]), 16)); 1004 } 1005 } else { 1006 1007 // append non-printable/non-escaped char 1008 1009 previousWhite = false; 1010 sbuffer.append(c); 1011 } 1012 } 1013 1014 // remove leading and trailing whitespace from value 1015 typeAndValue.append(sbuffer.toString().trim()); 1016 } 1017 1018 String canon = typeAndValue.toString(); 1019 canon = canon.toUpperCase(Locale.US).toLowerCase(Locale.US); 1020 return Normalizer.normalize(canon, Normalizer.Form.NFKD); 1021 } 1022 1023 /* 1024 * Return true if DerValue can be represented as a String. 1025 */ isDerString(DerValue value, boolean canonical)1026 private static boolean isDerString(DerValue value, boolean canonical) { 1027 if (canonical) { 1028 switch (value.tag) { 1029 case DerValue.tag_PrintableString: 1030 case DerValue.tag_UTF8String: 1031 return true; 1032 default: 1033 return false; 1034 } 1035 } else { 1036 switch (value.tag) { 1037 case DerValue.tag_PrintableString: 1038 case DerValue.tag_T61String: 1039 case DerValue.tag_IA5String: 1040 case DerValue.tag_GeneralString: 1041 case DerValue.tag_BMPString: 1042 case DerValue.tag_UTF8String: 1043 return true; 1044 default: 1045 return false; 1046 } 1047 } 1048 } 1049 hasRFC2253Keyword()1050 boolean hasRFC2253Keyword() { 1051 return AVAKeyword.hasKeyword(oid, RFC2253); 1052 } 1053 toKeywordValueString(String keyword)1054 private String toKeywordValueString(String keyword) { 1055 /* 1056 * Construct the value with as little copying and garbage 1057 * production as practical. First the keyword (mandatory), 1058 * then the equals sign, finally the value. 1059 */ 1060 StringBuilder retval = new StringBuilder(40); 1061 1062 retval.append(keyword); 1063 retval.append("="); 1064 1065 try { 1066 String valStr = value.getAsString(); 1067 1068 if (valStr == null) { 1069 1070 // rfc1779 specifies that attribute values associated 1071 // with non-standard keyword attributes may be represented 1072 // using the hex format below. This will be used only 1073 // when the value is not a string type 1074 1075 byte data [] = value.toByteArray(); 1076 1077 retval.append('#'); 1078 for (int i = 0; i < data.length; i++) { 1079 retval.append(hexDigits.charAt((data [i] >> 4) & 0x0f)); 1080 retval.append(hexDigits.charAt(data [i] & 0x0f)); 1081 } 1082 1083 } else { 1084 1085 boolean quoteNeeded = false; 1086 StringBuilder sbuffer = new StringBuilder(); 1087 boolean previousWhite = false; 1088 final String escapees = ",+=\n<>#;\\\""; 1089 1090 /* 1091 * Special characters (e.g. AVA list separators) cause strings 1092 * to need quoting, or at least escaping. So do leading or 1093 * trailing spaces, and multiple internal spaces. 1094 */ 1095 int length = valStr.length(); 1096 boolean alreadyQuoted = 1097 (length > 1 && valStr.charAt(0) == '\"' 1098 && valStr.charAt(length - 1) == '\"'); 1099 1100 for (int i = 0; i < length; i++) { 1101 char c = valStr.charAt(i); 1102 if (alreadyQuoted && (i == 0 || i == length - 1)) { 1103 sbuffer.append(c); 1104 continue; 1105 } 1106 if (DerValue.isPrintableStringChar(c) || 1107 escapees.indexOf(c) >= 0) { 1108 1109 // quote if leading whitespace or special chars 1110 if (!quoteNeeded && 1111 ((i == 0 && (c == ' ' || c == '\n')) || 1112 escapees.indexOf(c) >= 0)) { 1113 quoteNeeded = true; 1114 } 1115 1116 // quote if multiple internal whitespace 1117 if (!(c == ' ' || c == '\n')) { 1118 // escape '"' and '\' 1119 if (c == '"' || c == '\\') { 1120 sbuffer.append('\\'); 1121 } 1122 previousWhite = false; 1123 } else { 1124 if (!quoteNeeded && previousWhite) { 1125 quoteNeeded = true; 1126 } 1127 previousWhite = true; 1128 } 1129 1130 sbuffer.append(c); 1131 1132 } else if (debug != null && Debug.isOn("ava")) { 1133 1134 // embed non-printable/non-escaped char 1135 // as escaped hex pairs for debugging 1136 1137 previousWhite = false; 1138 1139 // embed escaped hex pairs 1140 byte[] valueBytes = 1141 Character.toString(c).getBytes("UTF8"); 1142 for (int j = 0; j < valueBytes.length; j++) { 1143 sbuffer.append('\\'); 1144 char hexChar = Character.forDigit 1145 (0xF & (valueBytes[j] >>> 4), 16); 1146 sbuffer.append(Character.toUpperCase(hexChar)); 1147 hexChar = Character.forDigit 1148 (0xF & (valueBytes[j]), 16); 1149 sbuffer.append(Character.toUpperCase(hexChar)); 1150 } 1151 } else { 1152 1153 // append non-printable/non-escaped char 1154 1155 previousWhite = false; 1156 sbuffer.append(c); 1157 } 1158 } 1159 1160 // quote if trailing whitespace 1161 if (sbuffer.length() > 0) { 1162 char trailChar = sbuffer.charAt(sbuffer.length() - 1); 1163 if (trailChar == ' ' || trailChar == '\n') { 1164 quoteNeeded = true; 1165 } 1166 } 1167 1168 // Emit the string ... quote it if needed 1169 // if string is already quoted, don't re-quote 1170 if (!alreadyQuoted && quoteNeeded) { 1171 retval.append("\"" + sbuffer.toString() + "\""); 1172 } else { 1173 retval.append(sbuffer.toString()); 1174 } 1175 } 1176 } catch (IOException e) { 1177 throw new IllegalArgumentException("DER Value conversion"); 1178 } 1179 1180 return retval.toString(); 1181 } 1182 1183 } 1184 1185 /** 1186 * Helper class that allows conversion from String to ObjectIdentifier and 1187 * vice versa according to RFC1779, RFC2253, and an augmented version of 1188 * those standards. 1189 */ 1190 class AVAKeyword { 1191 1192 private static final Map<ObjectIdentifier,AVAKeyword> oidMap; 1193 private static final Map<String,AVAKeyword> keywordMap; 1194 1195 private String keyword; 1196 private ObjectIdentifier oid; 1197 private boolean rfc1779Compliant, rfc2253Compliant; 1198 AVAKeyword(String keyword, ObjectIdentifier oid, boolean rfc1779Compliant, boolean rfc2253Compliant)1199 private AVAKeyword(String keyword, ObjectIdentifier oid, 1200 boolean rfc1779Compliant, boolean rfc2253Compliant) { 1201 this.keyword = keyword; 1202 this.oid = oid; 1203 this.rfc1779Compliant = rfc1779Compliant; 1204 this.rfc2253Compliant = rfc2253Compliant; 1205 1206 // register it 1207 oidMap.put(oid, this); 1208 keywordMap.put(keyword, this); 1209 } 1210 isCompliant(int standard)1211 private boolean isCompliant(int standard) { 1212 switch (standard) { 1213 case AVA.RFC1779: 1214 return rfc1779Compliant; 1215 case AVA.RFC2253: 1216 return rfc2253Compliant; 1217 case AVA.DEFAULT: 1218 return true; 1219 default: 1220 // should not occur, internal error 1221 throw new IllegalArgumentException("Invalid standard " + standard); 1222 } 1223 } 1224 1225 /** 1226 * Get an object identifier representing the specified keyword (or 1227 * string encoded object identifier) in the given standard. 1228 * 1229 * @throws IOException If the keyword is not valid in the specified standard 1230 */ getOID(String keyword, int standard)1231 static ObjectIdentifier getOID(String keyword, int standard) 1232 throws IOException { 1233 return getOID 1234 (keyword, standard, Collections.<String, String>emptyMap()); 1235 } 1236 1237 /** 1238 * Get an object identifier representing the specified keyword (or 1239 * string encoded object identifier) in the given standard. 1240 * 1241 * @param keywordMap a Map where a keyword String maps to a corresponding 1242 * OID String. Each AVA keyword will be mapped to the corresponding OID. 1243 * If an entry does not exist, it will fallback to the builtin 1244 * keyword/OID mapping. 1245 * @throws IOException If the keyword is not valid in the specified standard 1246 * or the OID String to which a keyword maps to is improperly formatted. 1247 */ getOID(String keyword, int standard, Map<String, String> extraKeywordMap)1248 static ObjectIdentifier getOID 1249 (String keyword, int standard, Map<String, String> extraKeywordMap) 1250 throws IOException { 1251 1252 keyword = keyword.toUpperCase(Locale.ENGLISH); 1253 if (standard == AVA.RFC2253) { 1254 if (keyword.startsWith(" ") || keyword.endsWith(" ")) { 1255 throw new IOException("Invalid leading or trailing space " + 1256 "in keyword \"" + keyword + "\""); 1257 } 1258 } else { 1259 keyword = keyword.trim(); 1260 } 1261 1262 // check user-specified keyword map first, then fallback to built-in 1263 // map 1264 String oidString = extraKeywordMap.get(keyword); 1265 if (oidString == null) { 1266 AVAKeyword ak = keywordMap.get(keyword); 1267 if ((ak != null) && ak.isCompliant(standard)) { 1268 return ak.oid; 1269 } 1270 } else { 1271 return new ObjectIdentifier(oidString); 1272 } 1273 1274 // no keyword found or not standard compliant, check if OID string 1275 1276 // RFC1779 requires, DEFAULT allows OID. prefix 1277 if (standard == AVA.RFC1779) { 1278 if (keyword.startsWith("OID.") == false) { 1279 throw new IOException("Invalid RFC1779 keyword: " + keyword); 1280 } 1281 keyword = keyword.substring(4); 1282 } else if (standard == AVA.DEFAULT) { 1283 if (keyword.startsWith("OID.")) { 1284 keyword = keyword.substring(4); 1285 } 1286 } 1287 boolean number = false; 1288 if (keyword.length() != 0) { 1289 char ch = keyword.charAt(0); 1290 if ((ch >= '0') && (ch <= '9')) { 1291 number = true; 1292 } 1293 } 1294 if (number == false) { 1295 throw new IOException("Invalid keyword \"" + keyword + "\""); 1296 } 1297 return new ObjectIdentifier(keyword); 1298 } 1299 1300 /** 1301 * Get a keyword for the given ObjectIdentifier according to standard. 1302 * If no keyword is available, the ObjectIdentifier is encoded as a 1303 * String. 1304 */ getKeyword(ObjectIdentifier oid, int standard)1305 static String getKeyword(ObjectIdentifier oid, int standard) { 1306 return getKeyword 1307 (oid, standard, Collections.<String, String>emptyMap()); 1308 } 1309 1310 /** 1311 * Get a keyword for the given ObjectIdentifier according to standard. 1312 * Checks the extraOidMap for a keyword first, then falls back to the 1313 * builtin/default set. If no keyword is available, the ObjectIdentifier 1314 * is encoded as a String. 1315 */ getKeyword(ObjectIdentifier oid, int standard, Map<String, String> extraOidMap)1316 static String getKeyword 1317 (ObjectIdentifier oid, int standard, Map<String, String> extraOidMap) { 1318 1319 // check extraOidMap first, then fallback to built-in map 1320 String oidString = oid.toString(); 1321 String keywordString = extraOidMap.get(oidString); 1322 if (keywordString == null) { 1323 AVAKeyword ak = oidMap.get(oid); 1324 if ((ak != null) && ak.isCompliant(standard)) { 1325 return ak.keyword; 1326 } 1327 } else { 1328 if (keywordString.length() == 0) { 1329 throw new IllegalArgumentException("keyword cannot be empty"); 1330 } 1331 keywordString = keywordString.trim(); 1332 char c = keywordString.charAt(0); 1333 if (c < 65 || c > 122 || (c > 90 && c < 97)) { 1334 throw new IllegalArgumentException 1335 ("keyword does not start with letter"); 1336 } 1337 for (int i=1; i<keywordString.length(); i++) { 1338 c = keywordString.charAt(i); 1339 if ((c < 65 || c > 122 || (c > 90 && c < 97)) && 1340 (c < 48 || c > 57) && c != '_') { 1341 throw new IllegalArgumentException 1342 ("keyword character is not a letter, digit, or underscore"); 1343 } 1344 } 1345 return keywordString; 1346 } 1347 // no compliant keyword, use OID 1348 if (standard == AVA.RFC2253) { 1349 return oidString; 1350 } else { 1351 return "OID." + oidString; 1352 } 1353 } 1354 1355 /** 1356 * Test if oid has an associated keyword in standard. 1357 */ hasKeyword(ObjectIdentifier oid, int standard)1358 static boolean hasKeyword(ObjectIdentifier oid, int standard) { 1359 AVAKeyword ak = oidMap.get(oid); 1360 if (ak == null) { 1361 return false; 1362 } 1363 return ak.isCompliant(standard); 1364 } 1365 1366 static { 1367 oidMap = new HashMap<ObjectIdentifier,AVAKeyword>(); 1368 keywordMap = new HashMap<String,AVAKeyword>(); 1369 1370 // NOTE if multiple keywords are available for one OID, order 1371 // is significant!! Preferred *LAST*. 1372 new AVAKeyword("CN", X500Name.commonName_oid, true, true); 1373 new AVAKeyword("C", X500Name.countryName_oid, true, true); 1374 new AVAKeyword("L", X500Name.localityName_oid, true, true); 1375 new AVAKeyword("S", X500Name.stateName_oid, false, false); 1376 new AVAKeyword("ST", X500Name.stateName_oid, true, true); 1377 new AVAKeyword("O", X500Name.orgName_oid, true, true); 1378 new AVAKeyword("OU", X500Name.orgUnitName_oid, true, true); 1379 new AVAKeyword("T", X500Name.title_oid, false, false); 1380 new AVAKeyword("IP", X500Name.ipAddress_oid, false, false); 1381 new AVAKeyword("STREET", X500Name.streetAddress_oid,true, true); 1382 new AVAKeyword("DC", X500Name.DOMAIN_COMPONENT_OID, 1383 false, true); 1384 new AVAKeyword("DNQUALIFIER", X500Name.DNQUALIFIER_OID, false, false); 1385 new AVAKeyword("DNQ", X500Name.DNQUALIFIER_OID, false, false); 1386 new AVAKeyword("SURNAME", X500Name.SURNAME_OID, false, false); 1387 new AVAKeyword("GIVENNAME", X500Name.GIVENNAME_OID, false, false); 1388 new AVAKeyword("INITIALS", X500Name.INITIALS_OID, false, false); 1389 new AVAKeyword("GENERATION", X500Name.GENERATIONQUALIFIER_OID, 1390 false, false); 1391 new AVAKeyword("EMAIL", PKCS9Attribute.EMAIL_ADDRESS_OID, false, false); 1392 new AVAKeyword("EMAILADDRESS", PKCS9Attribute.EMAIL_ADDRESS_OID, 1393 false, false); 1394 new AVAKeyword("UID", X500Name.userid_oid, false, true); 1395 new AVAKeyword("SERIALNUMBER", X500Name.SERIALNUMBER_OID, false, false); 1396 } 1397 } 1398