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