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