1 /* 2 * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * 28 * (C) Copyright IBM Corp. 1999 All Rights Reserved. 29 * Copyright 1997 The Open Group Research Institute. All rights reserved. 30 */ 31 32 package sun.security.krb5; 33 34 import sun.security.krb5.internal.*; 35 import sun.security.util.*; 36 import java.net.*; 37 import java.util.Vector; 38 import java.util.Locale; 39 import java.io.IOException; 40 import java.math.BigInteger; 41 import java.util.Arrays; 42 import sun.security.krb5.internal.ccache.CCacheOutputStream; 43 import sun.security.krb5.internal.util.KerberosString; 44 45 46 /** 47 * Implements the ASN.1 PrincipalName type and its realm in a single class. 48 * <pre>{@code 49 * Realm ::= KerberosString 50 * 51 * PrincipalName ::= SEQUENCE { 52 * name-type [0] Int32, 53 * name-string [1] SEQUENCE OF KerberosString 54 * } 55 * }</pre> 56 * This class is immutable. 57 * @see Realm 58 */ 59 public class PrincipalName implements Cloneable { 60 61 //name types 62 63 /** 64 * Name type not known 65 */ 66 public static final int KRB_NT_UNKNOWN = 0; 67 68 /** 69 * Just the name of the principal as in DCE, or for users 70 */ 71 public static final int KRB_NT_PRINCIPAL = 1; 72 73 /** 74 * Service and other unique instance (krbtgt) 75 */ 76 public static final int KRB_NT_SRV_INST = 2; 77 78 /** 79 * Service with host name as instance (telnet, rcommands) 80 */ 81 public static final int KRB_NT_SRV_HST = 3; 82 83 /** 84 * Service with host as remaining components 85 */ 86 public static final int KRB_NT_SRV_XHST = 4; 87 88 /** 89 * Unique ID 90 */ 91 public static final int KRB_NT_UID = 5; 92 93 /** 94 * Enterprise name (alias) 95 */ 96 public static final int KRB_NT_ENTERPRISE = 10; 97 98 /** 99 * TGS Name 100 */ 101 public static final String TGS_DEFAULT_SRV_NAME = "krbtgt"; 102 public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST; 103 104 public static final char NAME_COMPONENT_SEPARATOR = '/'; 105 public static final char NAME_REALM_SEPARATOR = '@'; 106 public static final char REALM_COMPONENT_SEPARATOR = '.'; 107 108 public static final String NAME_COMPONENT_SEPARATOR_STR = "/"; 109 public static final String NAME_REALM_SEPARATOR_STR = "@"; 110 public static final String REALM_COMPONENT_SEPARATOR_STR = "."; 111 112 // Instance fields. 113 114 /** 115 * The name type, from PrincipalName's name-type field. 116 */ 117 private final int nameType; 118 119 /** 120 * The name strings, from PrincipalName's name-strings field. This field 121 * must be neither null nor empty. Each entry of it must also be neither 122 * null nor empty. Make sure to clone the field when it's passed in or out. 123 */ 124 private final String[] nameStrings; 125 126 /** 127 * The realm this principal belongs to. 128 */ 129 private final Realm nameRealm; // not null 130 131 132 /** 133 * When constructing a PrincipalName, whether the realm is included in 134 * the input, or deduced from default realm or domain-realm mapping. 135 */ 136 private final boolean realmDeduced; 137 138 // cached default salt, not used in clone 139 private transient String salt = null; 140 141 // There are 3 basic constructors. All other constructors must call them. 142 // All basic constructors must call validateNameStrings. 143 // 1. From name components 144 // 2. From name 145 // 3. From DER encoding 146 147 /** 148 * Creates a PrincipalName. 149 */ PrincipalName(int nameType, String[] nameStrings, Realm nameRealm)150 public PrincipalName(int nameType, String[] nameStrings, Realm nameRealm) { 151 if (nameRealm == null) { 152 throw new IllegalArgumentException("Null realm not allowed"); 153 } 154 validateNameStrings(nameStrings); 155 this.nameType = nameType; 156 this.nameStrings = nameStrings.clone(); 157 this.nameRealm = nameRealm; 158 this.realmDeduced = false; 159 } 160 161 // Warning: called by NativeCreds.c PrincipalName(String[] nameParts, String realm)162 public PrincipalName(String[] nameParts, String realm) throws RealmException { 163 this(KRB_NT_UNKNOWN, nameParts, new Realm(realm)); 164 } 165 166 // Validate a nameStrings argument validateNameStrings(String[] ns)167 private static void validateNameStrings(String[] ns) { 168 if (ns == null) { 169 throw new IllegalArgumentException("Null nameStrings not allowed"); 170 } 171 if (ns.length == 0) { 172 throw new IllegalArgumentException("Empty nameStrings not allowed"); 173 } 174 for (String s: ns) { 175 if (s == null) { 176 throw new IllegalArgumentException("Null nameString not allowed"); 177 } 178 if (s.isEmpty()) { 179 throw new IllegalArgumentException("Empty nameString not allowed"); 180 } 181 } 182 } 183 clone()184 public Object clone() { 185 try { 186 PrincipalName pName = (PrincipalName) super.clone(); 187 UNSAFE.putReference(this, NAME_STRINGS_OFFSET, nameStrings.clone()); 188 return pName; 189 } catch (CloneNotSupportedException ex) { 190 throw new AssertionError("Should never happen"); 191 } 192 } 193 194 private static final long NAME_STRINGS_OFFSET; 195 private static final jdk.internal.misc.Unsafe UNSAFE; 196 static { 197 try { 198 jdk.internal.misc.Unsafe unsafe = jdk.internal.misc.Unsafe.getUnsafe(); 199 NAME_STRINGS_OFFSET = unsafe.objectFieldOffset( 200 PrincipalName.class.getDeclaredField("nameStrings")); 201 UNSAFE = unsafe; 202 } catch (ReflectiveOperationException e) { 203 throw new Error(e); 204 } 205 } 206 207 @Override equals(Object o)208 public boolean equals(Object o) { 209 if (this == o) { 210 return true; 211 } 212 if (o instanceof PrincipalName) { 213 PrincipalName other = (PrincipalName)o; 214 return nameRealm.equals(other.nameRealm) && 215 Arrays.equals(nameStrings, other.nameStrings); 216 } 217 return false; 218 } 219 220 /** 221 * Returns the ASN.1 encoding of the 222 * <pre>{@code 223 * PrincipalName ::= SEQUENCE { 224 * name-type [0] Int32, 225 * name-string [1] SEQUENCE OF KerberosString 226 * } 227 * 228 * KerberosString ::= GeneralString (IA5String) 229 * }</pre> 230 * 231 * <p> 232 * This definition reflects the Network Working Group RFC 4120 233 * specification available at 234 * <a href="http://www.ietf.org/rfc/rfc4120.txt"> 235 * http://www.ietf.org/rfc/rfc4120.txt</a>. 236 * 237 * @param encoding DER-encoded PrincipalName (without Realm) 238 * @param realm the realm for this name 239 * @exception Asn1Exception if an error occurs while decoding 240 * an ASN1 encoded data. 241 * @exception Asn1Exception if there is an ASN1 encoding error 242 * @exception IOException if an I/O error occurs 243 * @exception IllegalArgumentException if encoding is null 244 * reading encoded data. 245 */ PrincipalName(DerValue encoding, Realm realm)246 public PrincipalName(DerValue encoding, Realm realm) 247 throws Asn1Exception, IOException { 248 if (realm == null) { 249 throw new IllegalArgumentException("Null realm not allowed"); 250 } 251 realmDeduced = false; 252 nameRealm = realm; 253 DerValue der; 254 if (encoding == null) { 255 throw new IllegalArgumentException("Null encoding not allowed"); 256 } 257 if (encoding.getTag() != DerValue.tag_Sequence) { 258 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 259 } 260 der = encoding.getData().getDerValue(); 261 if ((der.getTag() & 0x1F) == 0x00) { 262 BigInteger bint = der.getData().getBigInteger(); 263 nameType = bint.intValue(); 264 } else { 265 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 266 } 267 der = encoding.getData().getDerValue(); 268 if ((der.getTag() & 0x01F) == 0x01) { 269 DerValue subDer = der.getData().getDerValue(); 270 if (subDer.getTag() != DerValue.tag_SequenceOf) { 271 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 272 } 273 Vector<String> v = new Vector<>(); 274 DerValue subSubDer; 275 while(subDer.getData().available() > 0) { 276 subSubDer = subDer.getData().getDerValue(); 277 String namePart = new KerberosString(subSubDer).toString(); 278 v.addElement(namePart); 279 } 280 nameStrings = new String[v.size()]; 281 v.copyInto(nameStrings); 282 validateNameStrings(nameStrings); 283 } else { 284 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 285 } 286 } 287 288 /** 289 * Parse (unmarshal) a <code>PrincipalName</code> from a DER 290 * input stream. This form 291 * parsing might be used when expanding a value which is part of 292 * a constructed sequence and uses explicitly tagged type. 293 * 294 * @exception Asn1Exception on error. 295 * @param data the Der input stream value, which contains one or 296 * more marshaled value. 297 * @param explicitTag tag number. 298 * @param optional indicate if this data field is optional 299 * @param realm the realm for the name 300 * @return an instance of <code>PrincipalName</code>, or null if the 301 * field is optional and missing. 302 */ parse(DerInputStream data, byte explicitTag, boolean optional, Realm realm)303 public static PrincipalName parse(DerInputStream data, 304 byte explicitTag, boolean 305 optional, 306 Realm realm) 307 throws Asn1Exception, IOException, RealmException { 308 309 if ((optional) && (((byte)data.peekByte() & (byte)0x1F) != 310 explicitTag)) 311 return null; 312 DerValue der = data.getDerValue(); 313 if (explicitTag != (der.getTag() & (byte)0x1F)) { 314 throw new Asn1Exception(Krb5.ASN1_BAD_ID); 315 } else { 316 DerValue subDer = der.getData().getDerValue(); 317 if (realm == null) { 318 realm = Realm.getDefault(); 319 } 320 return new PrincipalName(subDer, realm); 321 } 322 } 323 324 325 // XXX Error checkin consistent with MIT krb5_parse_name 326 // Code repetition, realm parsed again by class Realm parseName(String name)327 private static String[] parseName(String name) { 328 329 Vector<String> tempStrings = new Vector<>(); 330 String temp = name; 331 int i = 0; 332 int componentStart = 0; 333 String component; 334 335 while (i < temp.length()) { 336 if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) { 337 /* 338 * If this separator is escaped then don't treat it 339 * as a separator 340 */ 341 if (i > 0 && temp.charAt(i - 1) == '\\') { 342 temp = temp.substring(0, i - 1) + 343 temp.substring(i, temp.length()); 344 continue; 345 } 346 else { 347 if (componentStart <= i) { 348 component = temp.substring(componentStart, i); 349 tempStrings.addElement(component); 350 } 351 componentStart = i + 1; 352 } 353 } else { 354 if (temp.charAt(i) == NAME_REALM_SEPARATOR) { 355 /* 356 * If this separator is escaped then don't treat it 357 * as a separator 358 */ 359 if (i > 0 && temp.charAt(i - 1) == '\\') { 360 temp = temp.substring(0, i - 1) + 361 temp.substring(i, temp.length()); 362 continue; 363 } else { 364 if (componentStart < i) { 365 component = temp.substring(componentStart, i); 366 tempStrings.addElement(component); 367 } 368 componentStart = i + 1; 369 break; 370 } 371 } 372 } 373 i++; 374 } 375 376 if (i == temp.length()) { 377 component = temp.substring(componentStart, i); 378 tempStrings.addElement(component); 379 } 380 381 String[] result = new String[tempStrings.size()]; 382 tempStrings.copyInto(result); 383 return result; 384 } 385 386 /** 387 * Constructs a PrincipalName from a string. 388 * @param name the name 389 * @param type the type 390 * @param realm the realm, null if not known. Note that when realm is not 391 * null, it will be always used even if there is a realm part in name. When 392 * realm is null, will read realm part from name, or try to map a realm 393 * (for KRB_NT_SRV_HST), or use the default realm, or fail 394 * @throws RealmException 395 */ PrincipalName(String name, int type, String realm)396 public PrincipalName(String name, int type, String realm) 397 throws RealmException { 398 if (name == null) { 399 throw new IllegalArgumentException("Null name not allowed"); 400 } 401 String[] nameParts = parseName(name); 402 validateNameStrings(nameParts); 403 if (realm == null) { 404 realm = Realm.parseRealmAtSeparator(name); 405 } 406 407 // No realm info from parameter and string, must deduce later 408 realmDeduced = realm == null; 409 410 switch (type) { 411 case KRB_NT_SRV_HST: 412 if (nameParts.length >= 2) { 413 String hostName = nameParts[1]; 414 Boolean option; 415 try { 416 // If true, try canonicalizing and accept it if it starts 417 // with the short name. Otherwise, never. Default true. 418 option = Config.getInstance().getBooleanObject( 419 "libdefaults", "dns_canonicalize_hostname"); 420 } catch (KrbException e) { 421 option = null; 422 } 423 if (option != Boolean.FALSE) { 424 try { 425 // RFC4120 does not recommend canonicalizing a hostname. 426 // However, for compatibility reason, we will try 427 // canonicalizing it and see if the output looks better. 428 429 String canonicalized = (InetAddress.getByName(hostName)). 430 getCanonicalHostName(); 431 432 // Looks if canonicalized is a longer format of hostName, 433 // we accept cases like 434 // bunny -> bunny.rabbit.hole 435 if (canonicalized.toLowerCase(Locale.ENGLISH).startsWith( 436 hostName.toLowerCase(Locale.ENGLISH) + ".")) { 437 hostName = canonicalized; 438 } 439 } catch (UnknownHostException | SecurityException e) { 440 // not canonicalized or no permission to do so, use old 441 } 442 if (hostName.endsWith(".")) { 443 hostName = hostName.substring(0, hostName.length() - 1); 444 } 445 } 446 nameParts[1] = hostName.toLowerCase(Locale.ENGLISH); 447 } 448 nameStrings = nameParts; 449 nameType = type; 450 451 if (realm != null) { 452 nameRealm = new Realm(realm); 453 } else { 454 // We will try to get realm name from the mapping in 455 // the configuration. If it is not specified 456 // we will use the default realm. This nametype does 457 // not allow a realm to be specified. The name string must of 458 // the form service@host and this is internally changed into 459 // service/host by Kerberos 460 String mapRealm = mapHostToRealm(nameParts[1]); 461 if (mapRealm != null) { 462 nameRealm = new Realm(mapRealm); 463 } else { 464 nameRealm = Realm.getDefault(); 465 } 466 } 467 break; 468 case KRB_NT_UNKNOWN: 469 case KRB_NT_PRINCIPAL: 470 case KRB_NT_SRV_INST: 471 case KRB_NT_SRV_XHST: 472 case KRB_NT_UID: 473 case KRB_NT_ENTERPRISE: 474 nameStrings = nameParts; 475 nameType = type; 476 if (realm != null) { 477 nameRealm = new Realm(realm); 478 } else { 479 nameRealm = Realm.getDefault(); 480 } 481 break; 482 default: 483 throw new IllegalArgumentException("Illegal name type"); 484 } 485 } 486 487 // Warning: called by nativeccache.c PrincipalName(String name, int type)488 public PrincipalName(String name, int type) throws RealmException { 489 this(name, type, (String)null); 490 } 491 PrincipalName(String name)492 public PrincipalName(String name) throws RealmException { 493 this(name, KRB_NT_UNKNOWN); 494 } 495 PrincipalName(String name, String realm)496 public PrincipalName(String name, String realm) throws RealmException { 497 this(name, KRB_NT_UNKNOWN, realm); 498 } 499 tgsService(String r1, String r2)500 public static PrincipalName tgsService(String r1, String r2) 501 throws KrbException { 502 return new PrincipalName(PrincipalName.KRB_NT_SRV_INST, 503 new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1}, 504 new Realm(r2)); 505 } 506 getRealmAsString()507 public String getRealmAsString() { 508 return getRealmString(); 509 } 510 getPrincipalNameAsString()511 public String getPrincipalNameAsString() { 512 StringBuilder temp = new StringBuilder(nameStrings[0]); 513 for (int i = 1; i < nameStrings.length; i++) 514 temp.append(nameStrings[i]); 515 return temp.toString(); 516 } 517 hashCode()518 public int hashCode() { 519 return toString().hashCode(); 520 } 521 getName()522 public String getName() { 523 return toString(); 524 } 525 getNameType()526 public int getNameType() { 527 return nameType; 528 } 529 getNameStrings()530 public String[] getNameStrings() { 531 return nameStrings.clone(); 532 } 533 toByteArray()534 public byte[][] toByteArray() { 535 byte[][] result = new byte[nameStrings.length][]; 536 for (int i = 0; i < nameStrings.length; i++) { 537 result[i] = new byte[nameStrings[i].length()]; 538 result[i] = nameStrings[i].getBytes(); 539 } 540 return result; 541 } 542 getRealmString()543 public String getRealmString() { 544 return nameRealm.toString(); 545 } 546 getRealm()547 public Realm getRealm() { 548 return nameRealm; 549 } 550 getSalt()551 public String getSalt() { 552 if (salt == null) { 553 StringBuilder salt = new StringBuilder(); 554 salt.append(nameRealm.toString()); 555 for (int i = 0; i < nameStrings.length; i++) { 556 salt.append(nameStrings[i]); 557 } 558 return salt.toString(); 559 } 560 return salt; 561 } 562 toString()563 public String toString() { 564 StringBuilder str = new StringBuilder(); 565 for (int i = 0; i < nameStrings.length; i++) { 566 if (i > 0) 567 str.append("/"); 568 String n = nameStrings[i]; 569 n = n.replace("@", "\\@"); 570 str.append(n); 571 } 572 str.append("@"); 573 str.append(nameRealm.toString()); 574 return str.toString(); 575 } 576 getNameString()577 public String getNameString() { 578 StringBuilder str = new StringBuilder(); 579 for (int i = 0; i < nameStrings.length; i++) { 580 if (i > 0) 581 str.append("/"); 582 str.append(nameStrings[i]); 583 } 584 return str.toString(); 585 } 586 587 /** 588 * Encodes a <code>PrincipalName</code> object. Note that only the type and 589 * names are encoded. To encode the realm, call getRealm().asn1Encode(). 590 * @return the byte array of the encoded PrncipalName object. 591 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 592 * @exception IOException if an I/O error occurs while reading encoded data. 593 * 594 */ asn1Encode()595 public byte[] asn1Encode() throws Asn1Exception, IOException { 596 DerOutputStream bytes = new DerOutputStream(); 597 DerOutputStream temp = new DerOutputStream(); 598 BigInteger bint = BigInteger.valueOf(this.nameType); 599 temp.putInteger(bint); 600 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp); 601 temp = new DerOutputStream(); 602 DerValue[] der = new DerValue[nameStrings.length]; 603 for (int i = 0; i < nameStrings.length; i++) { 604 der[i] = new KerberosString(nameStrings[i]).toDerValue(); 605 } 606 temp.putSequence(der); 607 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp); 608 temp = new DerOutputStream(); 609 temp.write(DerValue.tag_Sequence, bytes); 610 return temp.toByteArray(); 611 } 612 613 614 /** 615 * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields. 616 * 617 * @param pname the other <code>PrincipalName</code> object. 618 * @return true if two have identical values, otherwise, return false. 619 */ 620 // It is used in <code>sun.security.krb5.internal.ccache</code> package. match(PrincipalName pname)621 public boolean match(PrincipalName pname) { 622 boolean matched = true; 623 //name type is just a hint, no two names can be the same ignoring name type. 624 // if (this.nameType != pname.nameType) { 625 // matched = false; 626 // } 627 if ((this.nameRealm != null) && (pname.nameRealm != null)) { 628 if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) { 629 matched = false; 630 } 631 } 632 if (this.nameStrings.length != pname.nameStrings.length) { 633 matched = false; 634 } else { 635 for (int i = 0; i < this.nameStrings.length; i++) { 636 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) { 637 matched = false; 638 } 639 } 640 } 641 return matched; 642 } 643 644 /** 645 * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream. 646 * 647 * @param cos a <code>CCacheOutputStream</code> for writing data. 648 * @exception IOException if an I/O exception occurs. 649 * @see sun.security.krb5.internal.ccache.CCacheOutputStream 650 */ writePrincipal(CCacheOutputStream cos)651 public void writePrincipal(CCacheOutputStream cos) throws IOException { 652 cos.write32(nameType); 653 cos.write32(nameStrings.length); 654 byte[] realmBytes = null; 655 realmBytes = nameRealm.toString().getBytes(); 656 cos.write32(realmBytes.length); 657 cos.write(realmBytes, 0, realmBytes.length); 658 byte[] bytes = null; 659 for (int i = 0; i < nameStrings.length; i++) { 660 bytes = nameStrings[i].getBytes(); 661 cos.write32(bytes.length); 662 cos.write(bytes, 0, bytes.length); 663 } 664 } 665 666 /** 667 * Returns the instance component of a name. 668 * In a multi-component name such as a KRB_NT_SRV_INST 669 * name, the second component is returned. 670 * Null is returned if there are not two or more 671 * components in the name. 672 * 673 * @return instance component of a multi-component name. 674 */ getInstanceComponent()675 public String getInstanceComponent() 676 { 677 if (nameStrings != null && nameStrings.length >= 2) 678 { 679 return new String(nameStrings[1]); 680 } 681 682 return null; 683 } 684 mapHostToRealm(String name)685 static String mapHostToRealm(String name) { 686 String result = null; 687 try { 688 String subname = null; 689 Config c = Config.getInstance(); 690 if ((result = c.get("domain_realm", name)) != null) 691 return result; 692 else { 693 for (int i = 1; i < name.length(); i++) { 694 if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM 695 subname = name.substring(i); 696 result = c.get("domain_realm", subname); 697 if (result != null) { 698 break; 699 } 700 else { 701 subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM 702 result = c.get("domain_realm", subname); 703 if (result != null) { 704 break; 705 } 706 } 707 } 708 } 709 } 710 } catch (KrbException e) { 711 } 712 return result; 713 } 714 isRealmDeduced()715 public boolean isRealmDeduced() { 716 return realmDeduced; 717 } 718 } 719