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 */ 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 // This method is called by Windows NativeCred.c 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 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 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 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 */ 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 */ 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 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 */ 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 public PrincipalName(String name, int type) throws RealmException { 488 this(name, type, (String)null); 489 } 490 491 public PrincipalName(String name) throws RealmException { 492 this(name, KRB_NT_UNKNOWN); 493 } 494 495 public PrincipalName(String name, String realm) throws RealmException { 496 this(name, KRB_NT_UNKNOWN, realm); 497 } 498 499 public static PrincipalName tgsService(String r1, String r2) 500 throws KrbException { 501 return new PrincipalName(PrincipalName.KRB_NT_SRV_INST, 502 new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1}, 503 new Realm(r2)); 504 } 505 506 public String getRealmAsString() { 507 return getRealmString(); 508 } 509 510 public String getPrincipalNameAsString() { 511 StringBuilder temp = new StringBuilder(nameStrings[0]); 512 for (int i = 1; i < nameStrings.length; i++) 513 temp.append(nameStrings[i]); 514 return temp.toString(); 515 } 516 517 public int hashCode() { 518 return toString().hashCode(); 519 } 520 521 public String getName() { 522 return toString(); 523 } 524 525 public int getNameType() { 526 return nameType; 527 } 528 529 public String[] getNameStrings() { 530 return nameStrings.clone(); 531 } 532 533 public byte[][] toByteArray() { 534 byte[][] result = new byte[nameStrings.length][]; 535 for (int i = 0; i < nameStrings.length; i++) { 536 result[i] = new byte[nameStrings[i].length()]; 537 result[i] = nameStrings[i].getBytes(); 538 } 539 return result; 540 } 541 542 public String getRealmString() { 543 return nameRealm.toString(); 544 } 545 546 public Realm getRealm() { 547 return nameRealm; 548 } 549 550 public String getSalt() { 551 if (salt == null) { 552 StringBuilder salt = new StringBuilder(); 553 salt.append(nameRealm.toString()); 554 for (int i = 0; i < nameStrings.length; i++) { 555 salt.append(nameStrings[i]); 556 } 557 return salt.toString(); 558 } 559 return salt; 560 } 561 562 public String toString() { 563 StringBuilder str = new StringBuilder(); 564 for (int i = 0; i < nameStrings.length; i++) { 565 if (i > 0) 566 str.append("/"); 567 str.append(nameStrings[i]); 568 } 569 str.append("@"); 570 str.append(nameRealm.toString()); 571 return str.toString(); 572 } 573 574 public String getNameString() { 575 StringBuilder str = new StringBuilder(); 576 for (int i = 0; i < nameStrings.length; i++) { 577 if (i > 0) 578 str.append("/"); 579 str.append(nameStrings[i]); 580 } 581 return str.toString(); 582 } 583 584 /** 585 * Encodes a <code>PrincipalName</code> object. Note that only the type and 586 * names are encoded. To encode the realm, call getRealm().asn1Encode(). 587 * @return the byte array of the encoded PrncipalName object. 588 * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data. 589 * @exception IOException if an I/O error occurs while reading encoded data. 590 * 591 */ 592 public byte[] asn1Encode() throws Asn1Exception, IOException { 593 DerOutputStream bytes = new DerOutputStream(); 594 DerOutputStream temp = new DerOutputStream(); 595 BigInteger bint = BigInteger.valueOf(this.nameType); 596 temp.putInteger(bint); 597 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp); 598 temp = new DerOutputStream(); 599 DerValue[] der = new DerValue[nameStrings.length]; 600 for (int i = 0; i < nameStrings.length; i++) { 601 der[i] = new KerberosString(nameStrings[i]).toDerValue(); 602 } 603 temp.putSequence(der); 604 bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp); 605 temp = new DerOutputStream(); 606 temp.write(DerValue.tag_Sequence, bytes); 607 return temp.toByteArray(); 608 } 609 610 611 /** 612 * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields. 613 * 614 * @param pname the other <code>PrincipalName</code> object. 615 * @return true if two have identical values, otherwise, return false. 616 */ 617 // It is used in <code>sun.security.krb5.internal.ccache</code> package. 618 public boolean match(PrincipalName pname) { 619 boolean matched = true; 620 //name type is just a hint, no two names can be the same ignoring name type. 621 // if (this.nameType != pname.nameType) { 622 // matched = false; 623 // } 624 if ((this.nameRealm != null) && (pname.nameRealm != null)) { 625 if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) { 626 matched = false; 627 } 628 } 629 if (this.nameStrings.length != pname.nameStrings.length) { 630 matched = false; 631 } else { 632 for (int i = 0; i < this.nameStrings.length; i++) { 633 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) { 634 matched = false; 635 } 636 } 637 } 638 return matched; 639 } 640 641 /** 642 * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream. 643 * 644 * @param cos a <code>CCacheOutputStream</code> for writing data. 645 * @exception IOException if an I/O exception occurs. 646 * @see sun.security.krb5.internal.ccache.CCacheOutputStream 647 */ 648 public void writePrincipal(CCacheOutputStream cos) throws IOException { 649 cos.write32(nameType); 650 cos.write32(nameStrings.length); 651 byte[] realmBytes = null; 652 realmBytes = nameRealm.toString().getBytes(); 653 cos.write32(realmBytes.length); 654 cos.write(realmBytes, 0, realmBytes.length); 655 byte[] bytes = null; 656 for (int i = 0; i < nameStrings.length; i++) { 657 bytes = nameStrings[i].getBytes(); 658 cos.write32(bytes.length); 659 cos.write(bytes, 0, bytes.length); 660 } 661 } 662 663 /** 664 * Returns the instance component of a name. 665 * In a multi-component name such as a KRB_NT_SRV_INST 666 * name, the second component is returned. 667 * Null is returned if there are not two or more 668 * components in the name. 669 * 670 * @return instance component of a multi-component name. 671 */ 672 public String getInstanceComponent() 673 { 674 if (nameStrings != null && nameStrings.length >= 2) 675 { 676 return new String(nameStrings[1]); 677 } 678 679 return null; 680 } 681 682 static String mapHostToRealm(String name) { 683 String result = null; 684 try { 685 String subname = null; 686 Config c = Config.getInstance(); 687 if ((result = c.get("domain_realm", name)) != null) 688 return result; 689 else { 690 for (int i = 1; i < name.length(); i++) { 691 if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM 692 subname = name.substring(i); 693 result = c.get("domain_realm", subname); 694 if (result != null) { 695 break; 696 } 697 else { 698 subname = name.substring(i + 1); //or mapping could be ibm.com = AUSTIN.IBM.COM 699 result = c.get("domain_realm", subname); 700 if (result != null) { 701 break; 702 } 703 } 704 } 705 } 706 } 707 } catch (KrbException e) { 708 } 709 return result; 710 } 711 712 public boolean isRealmDeduced() { 713 return realmDeduced; 714 } 715 }