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